summaryrefslogtreecommitdiffstats
path: root/telephony
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch)
tree35051494d2af230dce54d6b31c6af8fc24091316 /telephony
downloadframeworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip
frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz
frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2
Initial Contribution
Diffstat (limited to 'telephony')
-rw-r--r--telephony/java/android/telephony/CellLocation.java73
-rw-r--r--telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java98
-rw-r--r--telephony/java/android/telephony/PhoneNumberUtils.java1126
-rw-r--r--telephony/java/android/telephony/PhoneStateListener.java265
-rw-r--r--telephony/java/android/telephony/ServiceState.aidl21
-rw-r--r--telephony/java/android/telephony/ServiceState.java331
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java623
-rw-r--r--telephony/java/android/telephony/gsm/GsmCellLocation.java126
-rw-r--r--telephony/java/android/telephony/gsm/SmsManager.java432
-rw-r--r--telephony/java/android/telephony/gsm/SmsMessage.java1591
-rw-r--r--telephony/java/android/telephony/gsm/package.html6
-rw-r--r--telephony/java/android/telephony/package.html7
-rw-r--r--telephony/java/com/android/internal/telephony/ATParseEx.java35
-rw-r--r--telephony/java/com/android/internal/telephony/ATResponseParser.java186
-rw-r--r--telephony/java/com/android/internal/telephony/Call.java177
-rw-r--r--telephony/java/com/android/internal/telephony/CallStateException.java34
-rw-r--r--telephony/java/com/android/internal/telephony/CallerInfo.java271
-rw-r--r--telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java388
-rw-r--r--telephony/java/com/android/internal/telephony/Connection.java221
-rw-r--r--telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java224
-rw-r--r--telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl34
-rw-r--r--telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl65
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl163
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl37
-rw-r--r--telephony/java/com/android/internal/telephony/MmiCode.java62
-rw-r--r--telephony/java/com/android/internal/telephony/Phone.java1128
-rw-r--r--telephony/java/com/android/internal/telephony/PhoneBase.java310
-rw-r--r--telephony/java/com/android/internal/telephony/PhoneFactory.java162
-rw-r--r--telephony/java/com/android/internal/telephony/PhoneNotifier.java42
-rw-r--r--telephony/java/com/android/internal/telephony/PhoneStateIntentReceiver.java220
-rw-r--r--telephony/java/com/android/internal/telephony/PhoneSubInfo.java82
-rw-r--r--telephony/java/com/android/internal/telephony/SimCard.java208
-rw-r--r--telephony/java/com/android/internal/telephony/TelephonyIntents.java171
-rw-r--r--telephony/java/com/android/internal/telephony/TelephonyProperties.java101
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/AdnRecord.aidl19
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/AdnRecord.java570
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/AdnRecordCache.java346
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/ApnSetting.java75
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/BaseCommands.java359
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/CallFailCause.java50
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/CallForwardInfo.java44
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/CallTracker.java952
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/CommandException.java85
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/CommandsInterface.java915
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/DataConnectionTracker.java1512
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/DataLink.java41
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/DataLinkInterface.java77
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/DriverCall.java144
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/EncodeException.java39
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GSMCall.java221
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GSMConnection.java678
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GSMPhone.java1520
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GsmAlphabet.java813
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java1252
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GsmSimCard.java506
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/ISimPhoneBook.aidl101
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/ISms.aidl115
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/MccTable.java363
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/NetworkInfo.aidl26
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/NetworkInfo.java158
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/PDPContextState.java39
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/PdpConnection.java473
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/PppLink.java209
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/RIL.java2506
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/RILConstants.java150
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java521
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SIMRecords.java1523
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java942
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java1358
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimConstants.java55
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimException.java33
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimFileNotFound.java38
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimFileTypeMismatch.java33
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimIoResult.java75
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java278
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimProvider.java455
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java302
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimTlv.java129
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SimUtils.java329
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SmsHeader.java236
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SmsRawData.aidl19
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SmsRawData.java62
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SmsResponse.java34
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SuppServiceNotification.java70
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/VoiceMailConstants.java114
-rwxr-xr-xtelephony/java/com/android/internal/telephony/gsm/package.html6
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/AppInterface.java257
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/BerTlv.java122
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/CommandListener.java187
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/CommandParams.java119
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/ComprehensionTlv.java175
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/CtlvCommandDetails.java29
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/Duration.java52
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/FontSize.java50
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/IconLoader.java343
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/ImageDescriptor.java80
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/Item.java69
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/LaunchBrowserMode.java35
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/Menu.java99
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/PresentationType.java32
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/ResultCode.java177
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/ResultException.java76
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/Service.java2467
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/StkAppInstaller.java61
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/StkAppStateReceiver.java51
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/StkException.java31
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/TextAlignment.java52
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/TextAttribute.java49
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/TextColor.java63
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/Tone.java166
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/stk/package.html5
-rw-r--r--telephony/java/com/android/internal/telephony/package.html5
-rw-r--r--telephony/java/com/android/internal/telephony/test/ModelInterpreter.java750
-rw-r--r--telephony/java/com/android/internal/telephony/test/SimulatedCommands.java1364
-rw-r--r--telephony/java/com/android/internal/telephony/test/SimulatedGsmCallState.java837
-rw-r--r--telephony/java/com/android/internal/telephony/test/SimulatedRadioControl.java53
-rwxr-xr-xtelephony/java/com/android/internal/telephony/test/package.html5
117 files changed, 37851 insertions, 0 deletions
diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java
new file mode 100644
index 0000000..464085f
--- /dev/null
+++ b/telephony/java/android/telephony/CellLocation.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.telephony;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.gsm.GsmCellLocation;
+import com.android.internal.telephony.ITelephony;
+
+/**
+ * Abstract class that represents the location of the device. Currently the only
+ * subclass is {@link android.telephony.gsm.GsmCellLocation}. {@more}
+ */
+public abstract class CellLocation {
+
+ /**
+ * Request an update of the current location. If the location has changed,
+ * a broadcast will be sent to everyone registered with {@link
+ * PhoneStateListener#LISTEN_CELL_LOCATION}.
+ */
+ public static void requestLocationUpdate() {
+ try {
+ ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
+ if (phone != null) {
+ phone.updateServiceLocation();
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Create a new CellLocation from a intent notifier Bundle
+ *
+ * This method is used by PhoneStateIntentReceiver and maybe by
+ * external applications.
+ *
+ * @param bundle Bundle from intent notifier
+ * @return newly created CellLocation
+ *
+ * @hide
+ */
+ public static CellLocation newFromBundle(Bundle bundle) {
+ return new GsmCellLocation(bundle);
+ }
+
+ /**
+ * @hide
+ */
+ public abstract void fillInNotifierBundle(Bundle bundle);
+
+ /**
+ * Return a new CellLocation object representing an unknown location.
+ */
+ public static CellLocation getEmpty() {
+ return new GsmCellLocation();
+ }
+}
diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
new file mode 100644
index 0000000..0bc6c04
--- /dev/null
+++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2008 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 android.telephony;
+
+import android.text.Editable;
+import android.text.Selection;
+import android.text.TextWatcher;
+import android.widget.TextView;
+
+import java.util.Locale;
+
+/**
+ * Watches a {@link TextView} and if a phone number is entered will format it using
+ * {@link PhoneNumberUtils#formatNumber(Editable, int)}. The formatting is based on
+ * the current system locale when this object is created and future locale changes
+ * may not take effect on this instance.
+ */
+public class PhoneNumberFormattingTextWatcher implements TextWatcher {
+
+ static private int sFormatType;
+ static private Locale sCachedLocale;
+ private boolean mFormatting;
+ private boolean mDeletingHyphen;
+ private int mHyphenStart;
+ private boolean mDeletingBackward;
+
+ public PhoneNumberFormattingTextWatcher() {
+ if (sCachedLocale == null || sCachedLocale != Locale.getDefault()) {
+ sCachedLocale = Locale.getDefault();
+ sFormatType = PhoneNumberUtils.getFormatTypeForLocale(sCachedLocale);
+ }
+ }
+
+ public synchronized void afterTextChanged(Editable text) {
+ // Make sure to ignore calls to afterTextChanged caused by the work done below
+ if (!mFormatting) {
+ mFormatting = true;
+
+ // If deleting the hyphen, also delete the char before or after that
+ if (mDeletingHyphen && mHyphenStart > 0) {
+ if (mDeletingBackward) {
+ if (mHyphenStart - 1 < text.length()) {
+ text.delete(mHyphenStart - 1, mHyphenStart);
+ }
+ } else if (mHyphenStart < text.length()) {
+ text.delete(mHyphenStart, mHyphenStart + 1);
+ }
+ }
+
+ PhoneNumberUtils.formatNumber(text, sFormatType);
+
+ mFormatting = false;
+ }
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Check if the user is deleting a hyphen
+ if (!mFormatting) {
+ // Make sure user is deleting one char, without a selection
+ final int selStart = Selection.getSelectionStart(s);
+ final int selEnd = Selection.getSelectionEnd(s);
+ if (s.length() > 1 // Can delete another character
+ && count == 1 // Deleting only one character
+ && after == 0 // Deleting
+ && s.charAt(start) == '-' // a hyphen
+ && selStart == selEnd) { // no selection
+ mDeletingHyphen = true;
+ mHyphenStart = start;
+ // Check if the user is deleting forward or backward
+ if (selStart == start + 1) {
+ mDeletingBackward = true;
+ } else {
+ mDeletingBackward = false;
+ }
+ } else {
+ mDeletingHyphen = false;
+ }
+ }
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Does nothing
+ }
+}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
new file mode 100644
index 0000000..f2b63cc
--- /dev/null
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -0,0 +1,1126 @@
+/*
+ * 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 android.telephony;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemProperties;
+import android.provider.Contacts;
+import android.text.Editable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.util.SparseIntArray;
+
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Various utilities for dealing with phone number strings.
+ */
+public class PhoneNumberUtils
+{
+ /*
+ * Special characters
+ *
+ * (See "What is a phone number?" doc)
+ * 'p' --- GSM pause character, same as comma
+ * 'n' --- GSM wild character
+ * 'w' --- GSM wait character
+ */
+ public static final char PAUSE = ',';
+ public static final char WAIT = ';';
+ public static final char WILD = 'N';
+
+ /*
+ * TOA = TON + NPI
+ * See TS 24.008 section 10.5.4.7 for details.
+ * These are the only really useful TOA values
+ */
+ public static final int TOA_International = 0x91;
+ public static final int TOA_Unknown = 0x81;
+
+ /*
+ * global-phone-number = ["+"] 1*( DIGIT / written-sep )
+ * written-sep = ("-"/".")
+ */
+ private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN =
+ Pattern.compile("[\\+]?[0-9.-]+");
+
+ /** True if c is ISO-LATIN characters 0-9 */
+ public static boolean
+ isISODigit (char c) {
+ return c >= '0' && c <= '9';
+ }
+
+ /** True if c is ISO-LATIN characters 0-9, *, # */
+ public final static boolean
+ is12Key(char c) {
+ return (c >= '0' && c <= '9') || c == '*' || c == '#';
+ }
+
+ /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */
+ public final static boolean
+ isDialable(char c) {
+ return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD;
+ }
+
+ /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD) */
+ public final static boolean
+ isReallyDialable(char c) {
+ return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+';
+ }
+
+ /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE */
+ public final static boolean
+ isNonSeparator(char c) {
+ return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'
+ || c == WILD || c == WAIT || c == PAUSE;
+ }
+
+ /** This any anything to the right of this char is part of the
+ * post-dial string (eg this is PAUSE or WAIT)
+ */
+ public final static boolean
+ isStartsPostDial (char c) {
+ return c == PAUSE || c == WAIT;
+ }
+
+ /** Extracts the phone number from an Intent.
+ *
+ * @param intent the intent to get the number of
+ * @param context a context to use for database access
+ *
+ * @return the phone number that would be called by the intent, or
+ * <code>null</code> if the number cannot be found.
+ */
+ public static String getNumberFromIntent(Intent intent, Context context) {
+ String number = null;
+
+ Uri uri = intent.getData();
+ String scheme = uri.getScheme();
+
+ if (scheme.equals("tel")) {
+ return uri.getSchemeSpecificPart();
+ }
+
+ if (scheme.equals("voicemail")) {
+ return TelephonyManager.getDefault().getVoiceMailNumber();
+ }
+
+ if (context == null) {
+ return null;
+ }
+
+ String type = intent.resolveType(context);
+
+ Cursor c = context.getContentResolver().query(
+ uri, new String[]{ Contacts.People.Phones.NUMBER },
+ null, null, null);
+ if (c != null) {
+ try {
+ if (c.moveToFirst()) {
+ number = c.getString(
+ c.getColumnIndex(Contacts.People.Phones.NUMBER));
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+ return number;
+ }
+
+ /** Extracts the network address portion and canonicalizes
+ * (filters out separators.)
+ * Network address portion is everything up to DTMF control digit
+ * separators (pause or wait), but without non-dialable characters.
+ *
+ * Please note that the GSM wild character is allowed in the result.
+ * This must be resolved before dialing.
+ *
+ * Allows + only in the first position in the result string.
+ *
+ * Returns null if phoneNumber == null
+ */
+ public static String
+ extractNetworkPortion(String phoneNumber) {
+ if (phoneNumber == null) {
+ return null;
+ }
+
+ int len = phoneNumber.length();
+ StringBuilder ret = new StringBuilder(len);
+ boolean firstCharAdded = false;
+
+ for (int i = 0; i < len; i++) {
+ char c = phoneNumber.charAt(i);
+ if (isDialable(c) && (c != '+' || !firstCharAdded)) {
+ firstCharAdded = true;
+ ret.append(c);
+ } else if (isStartsPostDial (c)) {
+ break;
+ }
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * Strips separators from a phone number string.
+ * @param phoneNumber phone number to strip.
+ * @return phone string stripped of separators.
+ */
+ public static String stripSeparators(String phoneNumber) {
+ if (phoneNumber == null) {
+ return null;
+ }
+ int len = phoneNumber.length();
+ StringBuilder ret = new StringBuilder(len);
+
+ for (int i = 0; i < len; i++) {
+ char c = phoneNumber.charAt(i);
+ if (isNonSeparator(c)) {
+ ret.append(c);
+ }
+ }
+
+ return ret.toString();
+ }
+
+ /** or -1 if both are negative */
+ static private int
+ minPositive (int a, int b) {
+ if (a >= 0 && b >= 0) {
+ return (a < b) ? a : b;
+ } else if (a >= 0) { /* && b < 0 */
+ return a;
+ } else if (b >= 0) { /* && a < 0 */
+ return b;
+ } else { /* a < 0 && b < 0 */
+ return -1;
+ }
+ }
+
+ /** index of the last character of the network portion
+ * (eg anything after is a post-dial string)
+ */
+ static private int
+ indexOfLastNetworkChar(String a) {
+ int pIndex, wIndex;
+ int origLength;
+ int trimIndex;
+
+ origLength = a.length();
+
+ pIndex = a.indexOf(PAUSE);
+ wIndex = a.indexOf(WAIT);
+
+ trimIndex = minPositive(pIndex, wIndex);
+
+ if (trimIndex < 0) {
+ return origLength - 1;
+ } else {
+ return trimIndex - 1;
+ }
+ }
+
+ /**
+ * Extracts the post-dial sequence of DTMF control digits, pauses, and
+ * waits. Strips separators. This string may be empty, but will not be null
+ * unless phoneNumber == null.
+ *
+ * Returns null if phoneNumber == null
+ */
+
+ public static String
+ extractPostDialPortion(String phoneNumber) {
+ if (phoneNumber == null) return null;
+
+ int trimIndex;
+ StringBuilder ret = new StringBuilder();
+
+ trimIndex = indexOfLastNetworkChar (phoneNumber);
+
+ for (int i = trimIndex + 1, s = phoneNumber.length()
+ ; i < s; i++
+ ) {
+ char c = phoneNumber.charAt(i);
+ if (isNonSeparator(c)) {
+ ret.append(c);
+ }
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * Compare phone numbers a and b, return true if they're identical
+ * enough for caller ID purposes.
+ *
+ * - Compares from right to left
+ * - requires MIN_MATCH (5) characters to match
+ * - handles common trunk prefixes and international prefixes
+ * (basically, everything except the Russian trunk prefix)
+ *
+ * Tolerates nulls
+ */
+ public static boolean
+ compare(String a, String b) {
+ int ia, ib;
+ int matched;
+
+ if (a == null || b == null) return a == b;
+
+ if (a.length() == 0 || b.length() == 0) {
+ return false;
+ }
+
+ ia = indexOfLastNetworkChar (a);
+ ib = indexOfLastNetworkChar (b);
+ matched = 0;
+
+ while (ia >= 0 && ib >=0) {
+ char ca, cb;
+ boolean skipCmp = false;
+
+ ca = a.charAt(ia);
+
+ if (!isDialable(ca)) {
+ ia--;
+ skipCmp = true;
+ }
+
+ cb = b.charAt(ib);
+
+ if (!isDialable(cb)) {
+ ib--;
+ skipCmp = true;
+ }
+
+ if (!skipCmp) {
+ if (cb != ca && ca != WILD && cb != WILD) {
+ break;
+ }
+ ia--; ib--; matched++;
+ }
+ }
+
+ if (matched < MIN_MATCH) {
+ int aLen = a.length();
+
+ // if the input strings match, but their lengths < MIN_MATCH,
+ // treat them as equal.
+ if (aLen == b.length() && aLen == matched) {
+ return true;
+ }
+ return false;
+ }
+
+ // At least one string has matched completely;
+ if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) {
+ return true;
+ }
+
+ /*
+ * Now, what remains must be one of the following for a
+ * match:
+ *
+ * - a '+' on one and a '00' or a '011' on the other
+ * - a '0' on one and a (+,00)<country code> on the other
+ * (for this, a '0' and a '00' prefix would have succeeded above)
+ */
+
+ if (matchIntlPrefix(a, ia + 1)
+ && matchIntlPrefix (b, ib +1)
+ ) {
+ return true;
+ }
+
+ if (matchTrunkPrefix(a, ia + 1)
+ && matchIntlPrefixAndCC(b, ib +1)
+ ) {
+ return true;
+ }
+
+ if (matchTrunkPrefix(b, ib + 1)
+ && matchIntlPrefixAndCC(a, ia +1)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the rightmost MIN_MATCH (5) characters in the network portion
+ * in *reversed* order
+ *
+ * This can be used to do a database lookup against the column
+ * that stores getStrippedReversed()
+ *
+ * Returns null if phoneNumber == null
+ */
+ public static String
+ toCallerIDMinMatch(String phoneNumber) {
+ String np = extractNetworkPortion(phoneNumber);
+ return internalGetStrippedReversed(np, MIN_MATCH);
+ }
+
+ /**
+ * Returns the network portion reversed.
+ * This string is intended to go into an index column for a
+ * database lookup.
+ *
+ * Returns null if phoneNumber == null
+ */
+ public static String
+ getStrippedReversed(String phoneNumber) {
+ String np = extractNetworkPortion(phoneNumber);
+
+ if (np == null) return null;
+
+ return internalGetStrippedReversed(np, np.length());
+ }
+
+ /**
+ * Returns the last numDigits of the reversed phone number
+ * Returns null if np == null
+ */
+ private static String
+ internalGetStrippedReversed(String np, int numDigits) {
+ if (np == null) return null;
+
+ StringBuilder ret = new StringBuilder(numDigits);
+ int length = np.length();
+
+ for (int i = length - 1, s = length
+ ; i >= 0 && (s - i) <= numDigits ; i--
+ ) {
+ char c = np.charAt(i);
+
+ ret.append(c);
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * Basically: makes sure there's a + in front of a
+ * TOA_International number
+ *
+ * Returns null if s == null
+ */
+ public static String
+ stringFromStringAndTOA(String s, int TOA) {
+ if (s == null) return null;
+
+ if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') {
+ return "+" + s;
+ }
+
+ return s;
+ }
+
+ /**
+ * Returns the TOA for the given dial string
+ * Basically, returns TOA_International if there's a + prefix
+ */
+
+ public static int
+ toaFromString(String s) {
+ if (s != null && s.length() > 0 && s.charAt(0) == '+') {
+ return TOA_International;
+ }
+
+ return TOA_Unknown;
+ }
+
+ /**
+ * Phone numbers are stored in "lookup" form in the database
+ * as reversed strings to allow for caller ID lookup
+ *
+ * This method takes a phone number and makes a valid SQL "LIKE"
+ * string that will match the lookup form
+ *
+ */
+ /** all of a up to len must be an international prefix or
+ * separators/non-dialing digits
+ */
+ private static boolean
+ matchIntlPrefix(String a, int len) {
+ /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */
+ /* 0 1 2 3 45 */
+
+ int state = 0;
+ for (int i = 0 ; i < len ; i++) {
+ char c = a.charAt(i);
+
+ switch (state) {
+ case 0:
+ if (c == '+') state = 1;
+ else if (c == '0') state = 2;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 2:
+ if (c == '0') state = 3;
+ else if (c == '1') state = 4;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 4:
+ if (c == '1') state = 5;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ default:
+ if (isNonSeparator(c)) return false;
+ break;
+
+ }
+ }
+
+ return state == 1 || state == 3 || state == 5;
+ }
+
+ /**
+ * 3GPP TS 24.008 10.5.4.7
+ * Called Party BCD Number
+ *
+ * See Also TS 51.011 10.5.1 "dialing number/ssc string"
+ *
+ * @param bytes the data buffer
+ * @param offset should point to the TOI/NPI octet after the length byte
+ * @param length is the number of bytes including TOA byte
+ * and must be at least 2
+ *
+ * @return partial string on invalid decode
+ *
+ * FIXME(mkf) support alphanumeric address type
+ * currently implemented in SMSMessage.getAddress()
+ */
+ public static String
+ calledPartyBCDToString (byte[] bytes, int offset, int length) {
+ boolean prependedPlus = false;
+ StringBuilder ret = new StringBuilder(1 + length * 2);
+
+ if (length < 2) {
+ return "";
+ }
+
+ if ((bytes[offset] & 0xff) == TOA_International) {
+ ret.append("+");
+ prependedPlus = true;
+ }
+
+ internalCalledPartyBCDFragmentToString(
+ ret, bytes, offset + 1, length - 1);
+
+ if (prependedPlus && ret.length() == 1) {
+ // If the only thing there is a prepended plus, return ""
+ return "";
+ }
+
+ return ret.toString();
+ }
+
+ private static void
+ internalCalledPartyBCDFragmentToString(
+ StringBuilder sb, byte [] bytes, int offset, int length) {
+ for (int i = offset ; i < length + offset ; i++) {
+ byte b;
+ char c;
+
+ c = bcdToChar((byte)(bytes[i] & 0xf));
+
+ if (c == 0) {
+ return;
+ }
+ sb.append(c);
+
+ // FIXME(mkf) TS 23.040 9.1.2.3 says
+ // "if a mobile receives 1111 in a position prior to
+ // the last semi-octet then processing shall commense with
+ // the next semi-octet and the intervening
+ // semi-octet shall be ignored"
+ // How does this jive with 24,008 10.5.4.7
+
+ b = (byte)((bytes[i] >> 4) & 0xf);
+
+ if (b == 0xf && i + 1 == length + offset) {
+ //ignore final 0xf
+ break;
+ }
+
+ c = bcdToChar(b);
+ if (c == 0) {
+ return;
+ }
+
+ sb.append(c);
+ }
+
+ }
+
+ /**
+ * Like calledPartyBCDToString, but field does not start with a
+ * TOA byte. For example: SIM ADN extension fields
+ */
+
+ public static String
+ calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) {
+ StringBuilder ret = new StringBuilder(length * 2);
+
+ internalCalledPartyBCDFragmentToString(ret, bytes, offset, length);
+
+ return ret.toString();
+ }
+
+ /** returns 0 on invalid value */
+ private static char
+ bcdToChar(byte b) {
+ if (b < 0xa) {
+ return (char)('0' + b);
+ } else switch (b) {
+ case 0xa: return '*';
+ case 0xb: return '#';
+ case 0xc: return PAUSE;
+ case 0xd: return WILD;
+
+ default: return 0;
+ }
+ }
+
+ private static int
+ charToBCD(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c == '*') {
+ return 0xa;
+ } else if (c == '#') {
+ return 0xb;
+ } else if (c == PAUSE) {
+ return 0xc;
+ } else if (c == WILD) {
+ return 0xd;
+ } else {
+ throw new RuntimeException ("invalid char for BCD " + c);
+ }
+ }
+
+ /**
+ * Note: calls extractNetworkPortion(), so do not use for
+ * SIM EF[ADN] style records
+ *
+ * Exceptions thrown if extractNetworkPortion(s).length() == 0
+ */
+ public static byte[]
+ networkPortionToCalledPartyBCD(String s) {
+ return numberToCalledPartyBCD(extractNetworkPortion(s));
+ }
+
+ /**
+ * Return true iff the network portion of <code>address</code> is,
+ * as far as we can tell on the device, suitable for use as an SMS
+ * destination address.
+ */
+ public static boolean isWellFormedSmsAddress(String address) {
+ String networkPortion =
+ PhoneNumberUtils.extractNetworkPortion(address);
+
+ return (!(networkPortion.equals("+")
+ || TextUtils.isEmpty(networkPortion)))
+ && isDialable(networkPortion);
+ }
+
+ public static boolean isGlobalPhoneNumber(String phoneNumber) {
+ if (TextUtils.isEmpty(phoneNumber)) {
+ return false;
+ }
+
+ Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber);
+ return match.matches();
+ }
+
+ private static boolean isDialable(String address) {
+ for (int i = 0, count = address.length(); i < count; i++) {
+ if (!isDialable(address.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Same as {@link #networkPortionToCalledPartyBCD}, but includes a
+ * one-byte length prefix.
+ */
+ public static byte[]
+ networkPortionToCalledPartyBCDWithLength(String s) {
+ return numberToCalledPartyBCDWithLength(extractNetworkPortion(s));
+ }
+
+ /**
+ * Convert a dialing number to BCD byte array
+ *
+ * @param number dialing number string
+ * if the dialing number starts with '+', set to internationl TOA
+ * @return BCD byte array
+ */
+ public static byte[]
+ numberToCalledPartyBCD(String number) {
+ // The extra byte required for '+' is taken into consideration while calculating
+ // length of ret.
+ int size = ((number.charAt(0) == '+') ? number.length() - 1 : number.length());
+ byte[] ret = new byte[(size + 1) / 2 + 1];
+
+ return numberToCalledPartyBCDHelper(ret, 0, number);
+ }
+
+ /**
+ * Same as {@link #numberToCalledPartyBCD}, but includes a
+ * one-byte length prefix.
+ */
+ private static byte[]
+ numberToCalledPartyBCDWithLength(String number) {
+ // The extra byte required for '+' is taken into consideration while calculating
+ // length of ret.
+ int size = ((number.charAt(0) == '+') ? number.length() - 1 : number.length());
+ int length = (size + 1) / 2 + 1;
+ byte[] ret = new byte[length + 1];
+
+ ret[0] = (byte) (length & 0xff);
+ return numberToCalledPartyBCDHelper(ret, 1, number);
+ }
+
+ private static byte[]
+ numberToCalledPartyBCDHelper(byte[] ret, int offset, String number) {
+ int size;
+ int curChar;
+
+ size = number.length();
+
+ if (number.charAt(0) == '+') {
+ curChar = 1;
+ ret[offset] = (byte) TOA_International;
+ } else {
+ curChar = 0;
+ ret[offset] = (byte) TOA_Unknown;
+ }
+
+ int countFullBytes = ret.length - offset - 1 - ((size - curChar) & 1);
+ for (int i = 1; i < 1 + countFullBytes; i++) {
+ ret[offset + i]
+ = (byte) ((charToBCD(number.charAt(curChar++)))
+ | (charToBCD(number.charAt(curChar++))) << 4);
+ }
+
+ // The left-over octet for odd-length phone numbers should be
+ // filled with 0xf.
+ if (countFullBytes + offset < ret.length - 1) {
+ ret[ret.length - 1]
+ = (byte) (charToBCD(number.charAt(curChar))
+ | (0xf << 4));
+ }
+ return ret;
+ }
+
+ /** all of 'a' up to len must match non-US trunk prefix ('0') */
+ private static boolean
+ matchTrunkPrefix(String a, int len) {
+ boolean found;
+
+ found = false;
+
+ for (int i = 0 ; i < len ; i++) {
+ char c = a.charAt(i);
+
+ if (c == '0' && !found) {
+ found = true;
+ } else if (isNonSeparator(c)) {
+ return false;
+ }
+ }
+
+ return found;
+ }
+
+ /** all of 'a' up to len must be a (+|00|011)country code)
+ * We're fast and loose with the country code. Any \d{1,3} matches */
+ private static boolean
+ matchIntlPrefixAndCC(String a, int len) {
+ /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */
+ /* 0 1 2 3 45 6 7 8 */
+
+ int state = 0;
+ for (int i = 0 ; i < len ; i++ ) {
+ char c = a.charAt(i);
+
+ switch (state) {
+ case 0:
+ if (c == '+') state = 1;
+ else if (c == '0') state = 2;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 2:
+ if (c == '0') state = 3;
+ else if (c == '1') state = 4;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 4:
+ if (c == '1') state = 5;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 1:
+ case 3:
+ case 5:
+ if (isISODigit(c)) state = 6;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 6:
+ case 7:
+ if (isISODigit(c)) state++;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ default:
+ if (isNonSeparator(c)) return false;
+ }
+ }
+
+ return state == 6 || state == 7 || state == 8;
+ }
+
+ //================ Number formatting =========================
+
+ /** The current locale is unknown, look for a country code or don't format */
+ public static final int FORMAT_UNKNOWN = 0;
+ /** NANP formatting */
+ public static final int FORMAT_NANP = 1;
+
+ /** List of country codes for countries that use the NANP */
+ private static final String[] NANP_COUNTRIES = new String[] {
+ "US", // United States
+ "CA", // Canada
+ "AS", // American Samoa
+ "AI", // Anguilla
+ "AG", // Antigua and Barbuda
+ "BS", // Bahamas
+ "BB", // Barbados
+ "BM", // Bermuda
+ "VG", // British Virgin Islands
+ "KY", // Cayman Islands
+ "DM", // Dominica
+ "DO", // Dominican Republic
+ "GD", // Grenada
+ "GU", // Guam
+ "JM", // Jamaica
+ "PR", // Puerto Rico
+ "MS", // Montserrat
+ "NP", // Northern Mariana Islands
+ "KN", // Saint Kitts and Nevis
+ "LC", // Saint Lucia
+ "VC", // Saint Vincent and the Grenadines
+ "TT", // Trinidad and Tobago
+ "TC", // Turks and Caicos Islands
+ "VI", // U.S. Virgin Islands
+ };
+
+ /**
+ * Breaks the given number down and formats it according to the rules
+ * for the country the number is from.
+ *
+ * @param source the phone number to format
+ * @return a locally acceptable formatting of the input, or the raw input if
+ * formatting rules aren't known for the number
+ */
+ public static String formatNumber(String source) {
+ SpannableStringBuilder text = new SpannableStringBuilder(source);
+ formatNumber(text, getFormatTypeForLocale(Locale.getDefault()));
+ return text.toString();
+ }
+
+ /**
+ * Returns the phone number formatting type for the given locale.
+ *
+ * @param locale The locale of interest, usually {@link Locale#getDefault()}
+ * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
+ * rules are not known for the given locale
+ */
+ public static int getFormatTypeForLocale(Locale locale) {
+ String country = locale.getCountry();
+
+ // Check for the NANP countries
+ int length = NANP_COUNTRIES.length;
+ for (int i = 0; i < length; i++) {
+ if (NANP_COUNTRIES[i].equals(country)) {
+ return FORMAT_NANP;
+ }
+ }
+ return FORMAT_UNKNOWN;
+ }
+
+ /**
+ * Formats a phone number in-place. Currently only supports NANP formatting.
+ *
+ * @param text The number to be formatted, will be modified with the formatting
+ * @param defaultFormattingType The default formatting rules to apply if the number does
+ * not begin with +<country_code>
+ */
+ public static void formatNumber(Editable text, int defaultFormattingType) {
+ int formatType = defaultFormattingType;
+
+ // This only handles +1 for now
+ if (text.length() > 2 && text.charAt(0) == '+') {
+ if (text.charAt(1) == '1') {
+ formatType = FORMAT_NANP;
+ } else {
+ return;
+ }
+ }
+
+ switch (formatType) {
+ case FORMAT_NANP:
+ formatNanpNumber(text);
+ return;
+ }
+ }
+
+ private static final int NANP_STATE_DIGIT = 1;
+ private static final int NANP_STATE_PLUS = 2;
+ private static final int NANP_STATE_ONE = 3;
+ private static final int NANP_STATE_DASH = 4;
+
+ /**
+ * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted
+ * as:
+ *
+ * <p><code>
+ * xxx-xxxx
+ * xxx-xxx-xxxx
+ * 1-xxx-xxx-xxxx
+ * +1-xxx-xxx-xxxx
+ * </code></p>
+ *
+ * @param text the number to be formatted, will be modified with the formatting
+ */
+ public static void formatNanpNumber(Editable text) {
+ int length = text.length();
+ if (length > "+1-nnn-nnn-nnnn".length()) {
+ // The string is too long to be formatted
+ return;
+ }
+ CharSequence saved = text.subSequence(0, length);
+
+ // Strip the dashes first, as we're going to add them back
+ int p = 0;
+ while (p < text.length()) {
+ if (text.charAt(p) == '-') {
+ text.delete(p, p + 1);
+ } else {
+ p++;
+ }
+ }
+ length = text.length();
+
+ // When scanning the number we record where dashes need to be added,
+ // if they're non-0 at the end of the scan the dashes will be added in
+ // the proper places.
+ int dashPositions[] = new int[3];
+ int numDashes = 0;
+
+ int state = NANP_STATE_DIGIT;
+ int numDigits = 0;
+ for (int i = 0; i < length; i++) {
+ char c = text.charAt(i);
+ switch (c) {
+ case '1':
+ if (numDigits == 0 || state == NANP_STATE_PLUS) {
+ state = NANP_STATE_ONE;
+ break;
+ }
+ // fall through
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '0':
+ if (state == NANP_STATE_PLUS) {
+ // Only NANP number supported for now
+ text.replace(0, length, saved);
+ return;
+ } else if (state == NANP_STATE_ONE) {
+ // Found either +1 or 1, follow it up with a dash
+ dashPositions[numDashes++] = i;
+ } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) {
+ // Found a digit that should be after a dash that isn't
+ dashPositions[numDashes++] = i;
+ }
+ state = NANP_STATE_DIGIT;
+ numDigits++;
+ break;
+
+ case '-':
+ state = NANP_STATE_DASH;
+ break;
+
+ case '+':
+ if (i == 0) {
+ // Plus is only allowed as the first character
+ state = NANP_STATE_PLUS;
+ break;
+ }
+ // Fall through
+ default:
+ // Unknown character, bail on formatting
+ text.replace(0, length, saved);
+ return;
+ }
+ }
+
+ if (numDigits == 7) {
+ // With 7 digits we want xxx-xxxx, not xxx-xxx-x
+ numDashes--;
+ }
+
+ // Actually put the dashes in place
+ for (int i = 0; i < numDashes; i++) {
+ int pos = dashPositions[i];
+ text.replace(pos + i, pos + i, "-");
+ }
+
+ // Remove trailing dashes
+ int len = text.length();
+ while (len > 0) {
+ if (text.charAt(len - 1) == '-') {
+ text.delete(len - 1, len);
+ len--;
+ } else {
+ break;
+ }
+ }
+ }
+
+ // Three and four digit phone numbers for either special services
+ // or from the network (eg carrier-originated SMS messages) should
+ // not match
+ static final int MIN_MATCH = 5;
+
+ /**
+ * isEmergencyNumber: checks a given number against the list of
+ * emergency numbers provided by the RIL and SIM card.
+ *
+ * @param number the number to look up.
+ * @return if the number is in the list of emergency numbers
+ * listed in the ril / sim, then return true, otherwise false.
+ */
+ public static boolean isEmergencyNumber(String number) {
+ // Strip the separators from the number before comparing it
+ // to the list.
+ number = extractNetworkPortion(number);
+
+ // retrieve the list of emergency numbers
+ String numbers = SystemProperties.get("ro.ril.ecclist");
+
+ if (!TextUtils.isEmpty(numbers)) {
+ // searches through the comma-separated list for a match,
+ // return true if one is found.
+ for (String emergencyNum : numbers.split(",")) {
+ if (emergencyNum.equals(number)) {
+ return true;
+ }
+ }
+ // no matches found against the list!
+ return false;
+ }
+
+ //no ecclist system property, so use our own list.
+ return (number.equals("112") || number.equals("911"));
+ }
+
+ /**
+ * Translates any alphabetic letters (i.e. [A-Za-z]) in the
+ * specified phone number into the equivalent numeric digits,
+ * according to the phone keypad letter mapping described in
+ * ITU E.161 and ISO/IEC 9995-8.
+ *
+ * @return the input string, with alpha letters converted to numeric
+ * digits using the phone keypad letter mapping. For example,
+ * an input of "1-800-GOOG-411" will return "1-800-4664-411".
+ */
+ public static String convertKeypadLettersToDigits(String input) {
+ if (input == null) {
+ return input;
+ }
+ int len = input.length();
+ if (len == 0) {
+ return input;
+ }
+
+ char[] out = input.toCharArray();
+
+ for (int i = 0; i < len; i++) {
+ char c = out[i];
+ // If this char isn't in KEYPAD_MAP at all, just leave it alone.
+ out[i] = (char) KEYPAD_MAP.get(c, c);
+ }
+
+ return new String(out);
+ }
+
+ /**
+ * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.)
+ * TODO: This should come from a resource.
+ */
+ private static final SparseIntArray KEYPAD_MAP = new SparseIntArray();
+ static {
+ KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2');
+ KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2');
+
+ KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3');
+ KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3');
+
+ KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4');
+ KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4');
+
+ KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5');
+ KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5');
+
+ KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6');
+ KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6');
+
+ KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7');
+ KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7');
+
+ KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8');
+ KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8');
+
+ KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9');
+ KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9');
+ }
+}
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
new file mode 100644
index 0000000..8a8a675
--- /dev/null
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -0,0 +1,265 @@
+package android.telephony;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.ServiceState;
+import android.telephony.CellLocation;
+import android.util.Log;
+
+import com.android.internal.telephony.IPhoneStateListener;
+
+/**
+ * A listener class for monitoring changes in specific telephony states
+ * on the device, including service state, signal strength, message
+ * waiting indicator (voicemail), and others.
+ * <p>
+ * Override the methods for the state that you wish to receive updates for, and
+ * pass your PhoneStateListener object, along with bitwise-or of the LISTEN_
+ * flags to {@link TelephonyManager#listen TelephonyManager.listen()}.
+ * <p>
+ * Note that access to some telephony information is
+ * permission-protected. Your application won't receive updates for protected
+ * information unless it has the appropriate permissions declared in
+ * its manifest file. Where permissions apply, they are noted in the
+ * appropriate LISTEN_ flags.
+ */
+public class PhoneStateListener {
+
+ /**
+ * Stop listening for updates.
+ */
+ public static final int LISTEN_NONE = 0;
+
+ /**
+ * Listen for changes to the network service state (cellular).
+ *
+ * @see #onServiceStateChanged
+ * @see ServiceState
+ */
+ public static final int LISTEN_SERVICE_STATE = 0x00000001;
+
+ /**
+ * Listen for changes to the network signal strength (cellular).
+ * <p>
+ * Example: The status bar uses this to control the signal-strength
+ * icon.
+ *
+ * @see #onSignalStrengthChanged
+ */
+ public static final int LISTEN_SIGNAL_STRENGTH = 0x00000002;
+
+ /**
+ * Listen for changes to the message-waiting indicator.
+ * <p>
+ * Example: The status bar uses this to determine when to display the
+ * voicemail icon.
+ *
+ * @see #onMessageWaitingIndicatorChanged
+ */
+ public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 0x00000004;
+
+ /**
+ * Listen for changes to the call-forwarding indicator.
+ *
+ * @see #onCallForwardingIndicatorChanged
+ */
+ public static final int LISTEN_CALL_FORWARDING_INDICATOR = 0x00000008;
+
+ /**
+ * Listen for changes to the device's cell location. Note that
+ * this will result in frequent callbacks to the listener.
+ * {@more}
+ * Requires Permission: {@link android.Manifest.permission#ACCESS_COARSE_LOCATION
+ * ACCESS_COARSE_LOCATION}
+ * <p>
+ * If you need regular location updates but want more control over
+ * the update interval or location precision, you can set up a listener
+ * through the {@link android.location.LocationManager location manager}
+ * instead.
+ *
+ * @see #onCellLocationChanged
+ */
+ public static final int LISTEN_CELL_LOCATION = 0x00000010;
+
+ /**
+ * Listen for changes to the device call state.
+ *
+ * @see #onCallStateChanged
+ */
+ public static final int LISTEN_CALL_STATE = 0x00000020;
+
+ /**
+ * Listen for changes to the data connection state (cellular).
+ *
+ * @see #onDataConnectionStateChanged
+ */
+ public static final int LISTEN_DATA_CONNECTION_STATE = 0x00000040;
+
+ /**
+ * Listen for changes to the direction of data traffic on the data
+ * connection (cellular).
+ *
+ * Example: The status bar uses this to display the appropriate
+ * data-traffic icon.
+ *
+ * @see #onDataActivity
+ */
+ public static final int LISTEN_DATA_ACTIVITY = 0x00000080;
+
+ public PhoneStateListener() {
+ }
+
+ /**
+ * Callback invoked when device service state changes.
+ *
+ * @see ServiceState#STATE_EMERGENCY_ONLY
+ * @see ServiceState#STATE_IN_SERVICE
+ * @see ServiceState#STATE_OUT_OF_SERVICE
+ * @see ServiceState#STATE_POWER_OFF
+ */
+ public void onServiceStateChanged(ServiceState serviceState) {
+ // default implementation empty
+ }
+
+ /**
+ * Callback invoked when network signal strength changes.
+ *
+ * @see ServiceState#STATE_EMERGENCY_ONLY
+ * @see ServiceState#STATE_IN_SERVICE
+ * @see ServiceState#STATE_OUT_OF_SERVICE
+ * @see ServiceState#STATE_POWER_OFF
+ */
+ public void onSignalStrengthChanged(int asu) {
+ // default implementation empty
+ }
+
+ /**
+ * Callback invoked when the message-waiting indicator changes.
+ */
+ public void onMessageWaitingIndicatorChanged(boolean mwi) {
+ // default implementation empty
+ }
+
+ /**
+ * Callback invoked when the call-forwarding indicator changes.
+ */
+ public void onCallForwardingIndicatorChanged(boolean cfi) {
+ // default implementation empty
+ }
+
+ /**
+ * Callback invoked when device cell location changes.
+ */
+ public void onCellLocationChanged(CellLocation location) {
+ // default implementation empty
+ }
+
+ /**
+ * Callback invoked when device call state changes.
+ *
+ * @see TelephonyManager#CALL_STATE_IDLE
+ * @see TelephonyManager#CALL_STATE_RINGING
+ * @see TelephonyManager#CALL_STATE_OFFHOOK
+ */
+ public void onCallStateChanged(int state, String incomingNumber) {
+ // default implementation empty
+ }
+
+ /**
+ * Callback invoked when connection state changes.
+ *
+ * @see TelephonyManager#DATA_DISCONNECTED
+ * @see TelephonyManager#DATA_CONNECTING
+ * @see TelephonyManager#DATA_CONNECTED
+ * @see TelephonyManager#DATA_SUSPENDED
+ */
+ public void onDataConnectionStateChanged(int state) {
+ // default implementation empty
+ }
+
+ /**
+ * Callback invoked when data activity state changes.
+ *
+ * @see TelephonyManager#DATA_ACTIVITY_NONE
+ * @see TelephonyManager#DATA_ACTIVITY_IN
+ * @see TelephonyManager#DATA_ACTIVITY_OUT
+ * @see TelephonyManager#DATA_ACTIVITY_INOUT
+ */
+ public void onDataActivity(int direction) {
+ // default implementation empty
+ }
+
+ /**
+ * The callback methods need to be called on the handler thread where
+ * this object was created. If the binder did that for us it'd be nice.
+ */
+ IPhoneStateListener callback = new IPhoneStateListener.Stub() {
+ public void onServiceStateChanged(ServiceState serviceState) {
+ Message.obtain(mHandler, LISTEN_SERVICE_STATE, 0, 0, serviceState).sendToTarget();
+ }
+
+ public void onSignalStrengthChanged(int asu) {
+ Message.obtain(mHandler, LISTEN_SIGNAL_STRENGTH, asu, 0, null).sendToTarget();
+ }
+
+ public void onMessageWaitingIndicatorChanged(boolean mwi) {
+ Message.obtain(mHandler, LISTEN_MESSAGE_WAITING_INDICATOR, mwi ? 1 : 0, 0, null)
+ .sendToTarget();
+ }
+
+ public void onCallForwardingIndicatorChanged(boolean cfi) {
+ Message.obtain(mHandler, LISTEN_CALL_FORWARDING_INDICATOR, cfi ? 1 : 0, 0, null)
+ .sendToTarget();
+ }
+
+ public void onCellLocationChanged(Bundle bundle) {
+ CellLocation location = CellLocation.newFromBundle(bundle);
+ Message.obtain(mHandler, LISTEN_CELL_LOCATION, 0, 0, location).sendToTarget();
+ }
+
+ public void onCallStateChanged(int state, String incomingNumber) {
+ Message.obtain(mHandler, LISTEN_CALL_STATE, state, 0, incomingNumber).sendToTarget();
+ }
+
+ public void onDataConnectionStateChanged(int state) {
+ Message.obtain(mHandler, LISTEN_DATA_CONNECTION_STATE, state, 0, null).sendToTarget();
+ }
+
+ public void onDataActivity(int direction) {
+ Message.obtain(mHandler, LISTEN_DATA_ACTIVITY, direction, 0, null).sendToTarget();
+ }
+ };
+
+ Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ //Log.d("TelephonyRegistry", "what=0x" + Integer.toHexString(msg.what) + " msg=" + msg);
+ switch (msg.what) {
+ case LISTEN_SERVICE_STATE:
+ PhoneStateListener.this.onServiceStateChanged((ServiceState)msg.obj);
+ break;
+ case LISTEN_SIGNAL_STRENGTH:
+ PhoneStateListener.this.onSignalStrengthChanged(msg.arg1);
+ break;
+ case LISTEN_MESSAGE_WAITING_INDICATOR:
+ PhoneStateListener.this.onMessageWaitingIndicatorChanged(msg.arg1 != 0);
+ break;
+ case LISTEN_CALL_FORWARDING_INDICATOR:
+ PhoneStateListener.this.onCallForwardingIndicatorChanged(msg.arg1 != 0);
+ break;
+ case LISTEN_CELL_LOCATION:
+ PhoneStateListener.this.onCellLocationChanged((CellLocation)msg.obj);
+ break;
+ case LISTEN_CALL_STATE:
+ PhoneStateListener.this.onCallStateChanged(msg.arg1, (String)msg.obj);
+ break;
+ case LISTEN_DATA_CONNECTION_STATE:
+ PhoneStateListener.this.onDataConnectionStateChanged(msg.arg1);
+ break;
+ case LISTEN_DATA_ACTIVITY:
+ PhoneStateListener.this.onDataActivity(msg.arg1);
+ break;
+ }
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/ServiceState.aidl b/telephony/java/android/telephony/ServiceState.aidl
new file mode 100644
index 0000000..b1cf379
--- /dev/null
+++ b/telephony/java/android/telephony/ServiceState.aidl
@@ -0,0 +1,21 @@
+/* //device/java/android/android/content/Intent.aidl
+**
+** Copyright 2007, 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 android.telephony;
+
+parcelable ServiceState;
+
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
new file mode 100644
index 0000000..2c58051
--- /dev/null
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -0,0 +1,331 @@
+/*
+ * 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 android.telephony;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.android.internal.telephony.Phone;
+
+/**
+ * Contains phone state and service related information.
+ *
+ * The following phone information is included in returned ServiceState:
+ *
+ * <ul>
+ * <li>Service state: IN_SERVICE, OUT_OF_SERVICE, EMERGENCY_ONLY, POWER_OFF
+ * <li>Roaming indicator
+ * <li>Operator name, short name and numeric id
+ * <li>Network selection mode
+ * </ul>
+ */
+public class ServiceState implements Parcelable {
+
+ /**
+ * Normal operation condition, the phone is registered
+ * with an operator either in home network or in roaming.
+ */
+ public static final int STATE_IN_SERVICE = 0;
+
+ /**
+ * Phone is not registered with any operator, the phone
+ * can be currently searching a new operator to register to, or not
+ * searching to registration at all, or registration is denied, or radio
+ * signal is not available.
+ */
+ public static final int STATE_OUT_OF_SERVICE = 1;
+
+ /**
+ * The phone is registered and locked. Only emergency numbers are allowed. {@more}
+ */
+ public static final int STATE_EMERGENCY_ONLY = 2;
+
+ /**
+ * Radio of telephony is explictly powered off.
+ */
+ public static final int STATE_POWER_OFF = 3;
+
+ private int mState = STATE_OUT_OF_SERVICE;
+ private boolean mRoaming;
+ private String mOperatorAlphaLong;
+ private String mOperatorAlphaShort;
+ private String mOperatorNumeric;
+ private boolean mIsManualNetworkSelection;
+
+ /**
+ * Create a new ServiceState from a intent notifier Bundle
+ *
+ * This method is used by PhoneStateIntentReceiver and maybe by
+ * external applications.
+ *
+ * @param m Bundle from intent notifier
+ * @return newly created ServiceState
+ * @hide
+ */
+ public static ServiceState newFromBundle(Bundle m) {
+ ServiceState ret;
+ ret = new ServiceState();
+ ret.setFromNotifierBundle(m);
+ return ret;
+ }
+
+ /**
+ * Empty constructor
+ */
+ public ServiceState() {
+ }
+
+ /**
+ * Copy constructors
+ *
+ * @param s Source service state
+ */
+ public ServiceState(ServiceState s) {
+ copyFrom(s);
+ }
+
+ protected void copyFrom(ServiceState s) {
+ mState = s.mState;
+ mRoaming = s.mRoaming;
+ mOperatorAlphaLong = s.mOperatorAlphaLong;
+ mOperatorAlphaShort = s.mOperatorAlphaShort;
+ mOperatorNumeric = s.mOperatorNumeric;
+ mIsManualNetworkSelection = s.mIsManualNetworkSelection;
+ }
+
+ /**
+ * Construct a ServiceState object from the given parcel.
+ */
+ public ServiceState(Parcel in) {
+ mState = in.readInt();
+ mRoaming = in.readInt() != 0;
+ mOperatorAlphaLong = in.readString();
+ mOperatorAlphaShort = in.readString();
+ mOperatorNumeric = in.readString();
+ mIsManualNetworkSelection = in.readInt() != 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mState);
+ out.writeInt(mRoaming ? 1 : 0);
+ out.writeString(mOperatorAlphaLong);
+ out.writeString(mOperatorAlphaShort);
+ out.writeString(mOperatorNumeric);
+ out.writeInt(mIsManualNetworkSelection ? 1 : 0);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<ServiceState> CREATOR = new Parcelable.Creator() {
+ public ServiceState createFromParcel(Parcel in) {
+ return new ServiceState(in);
+ }
+
+ public ServiceState[] newArray(int size) {
+ return new ServiceState[size];
+ }
+ };
+
+ /**
+ * Get current servcie state of phone
+ *
+ * @see #STATE_IN_SERVICE
+ * @see #STATE_OUT_OF_SERVICE
+ * @see #STATE_EMERGENCY_ONLY
+ * @see #STATE_POWER_OFF
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Get current roaming indicator of phone
+ * (note: not just decoding from TS 27.007 7.2)
+ *
+ * @return true if TS 27.007 7.2 roaming is true
+ * and ONS is different from SPN
+ *
+ */
+ public boolean getRoaming() {
+ return mRoaming;
+ }
+
+ /**
+ * Get current registered operator name in long alphanumeric format
+ *
+ * In GSM/UMTS, long format can be upto 16 characters long
+ *
+ * @return long name of operator, null if unregistered or unknown
+ */
+ public String getOperatorAlphaLong() {
+ return mOperatorAlphaLong;
+ }
+
+ /**
+ * Get current registered operator name in short lphanumeric format
+ *
+ * In GSM/UMST, short format can be upto 8 characters long
+ *
+ * @return short name of operator, null if unregistered or unknown
+ */
+ public String getOperatorAlphaShort() {
+ return mOperatorAlphaShort;
+ }
+
+ /**
+ * Get current registered operator numeric id
+ *
+ * In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit
+ * network code
+ *
+ * The country code can be decoded using MccTable.countryCodeForMcc()
+ *
+ * @return numeric format of operator, null if unregistered or unknown
+ */
+ public String getOperatorNumeric() {
+ return mOperatorNumeric;
+ }
+
+ /**
+ * Get current network selection mode
+ *
+ * @return true if manual mode, false if automatic mode
+ */
+ public boolean getIsManualSelection() {
+ return mIsManualNetworkSelection;
+ }
+
+ @Override
+ public int hashCode() {
+ return (mState * 0x1234)
+ + (mRoaming ? 1 : 0)
+ + (mIsManualNetworkSelection ? 1 : 0)
+ + ((null == mOperatorAlphaLong) ? 0 : mOperatorAlphaLong.hashCode())
+ + ((null == mOperatorAlphaShort) ? 0 : mOperatorAlphaShort.hashCode())
+ + ((null == mOperatorNumeric) ? 0 : mOperatorNumeric.hashCode());
+ }
+
+ @Override
+ public boolean equals (Object o) {
+ ServiceState s;
+
+ try {
+ s = (ServiceState) o;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ return mState == s.mState
+ && mRoaming == s.mRoaming
+ && mIsManualNetworkSelection == s.mIsManualNetworkSelection
+ && equalsHandlesNulls(mOperatorAlphaLong, s.mOperatorAlphaLong)
+ && equalsHandlesNulls(mOperatorAlphaShort, s.mOperatorAlphaShort)
+ && equalsHandlesNulls(mOperatorNumeric, s.mOperatorNumeric);
+ }
+
+ @Override
+ public String toString() {
+ return mState + " " + (mRoaming ? "roaming" : "home")
+ + " " + mOperatorAlphaLong
+ + " " + mOperatorAlphaShort
+ + " " + mOperatorNumeric
+ + " " + (mIsManualNetworkSelection ? "(manual)" : "");
+ }
+
+ public void setStateOutOfService() {
+ mState = STATE_OUT_OF_SERVICE;
+ mRoaming = false;
+ mOperatorAlphaLong = null;
+ mOperatorAlphaShort = null;
+ mOperatorNumeric = null;
+ mIsManualNetworkSelection = false;
+ }
+
+ public void setStateOff() {
+ mState = STATE_POWER_OFF;
+ mRoaming = false;
+ mOperatorAlphaLong = null;
+ mOperatorAlphaShort = null;
+ mOperatorNumeric = null;
+ mIsManualNetworkSelection = false;
+ }
+
+ public void setState(int state) {
+ mState = state;
+ }
+
+ public void setRoaming(boolean roaming) {
+ mRoaming = roaming;
+ }
+
+ public void setOperatorName(String longName, String shortName, String numeric) {
+ mOperatorAlphaLong = longName;
+ mOperatorAlphaShort = shortName;
+ mOperatorNumeric = numeric;
+ }
+
+ public void setIsManualSelection(boolean isManual) {
+ mIsManualNetworkSelection = isManual;
+ }
+
+ /**
+ * Test whether two objects hold the same data values or both are null
+ *
+ * @param a first obj
+ * @param b second obj
+ * @return true if two objects equal or both are null
+ */
+ private static boolean equalsHandlesNulls (Object a, Object b) {
+ return (a == null) ? (b == null) : a.equals (b);
+ }
+
+ /**
+ * Set ServiceState based on intent notifier map
+ *
+ * @param m intent notifier map
+ * @hide
+ */
+ private void setFromNotifierBundle(Bundle m) {
+ mState = m.getInt("state");
+ mRoaming = m.getBoolean("roaming");
+ mOperatorAlphaLong = m.getString("operator-alpha-long");
+ mOperatorAlphaShort = m.getString("operator-alpha-short");
+ mOperatorNumeric = m.getString("operator-numeric");
+ mIsManualNetworkSelection = m.getBoolean("manual");
+ }
+
+ /**
+ * Set intent notifier Bundle based on service state
+ *
+ * @param m intent notifier Bundle
+ * @hide
+ */
+ public void fillInNotifierBundle(Bundle m) {
+ m.putInt("state", mState);
+ m.putBoolean("roaming", Boolean.valueOf(mRoaming));
+ m.putString("operator-alpha-long", mOperatorAlphaLong);
+ m.putString("operator-alpha-short", mOperatorAlphaShort);
+ m.putString("operator-numeric", mOperatorNumeric);
+ m.putBoolean("manual", Boolean.valueOf(mIsManualNetworkSelection));
+ }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
new file mode 100644
index 0000000..529ba31
--- /dev/null
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -0,0 +1,623 @@
+/*
+ * Copyright (C) 2008 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 android.telephony;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.ServiceManager;
+
+import com.android.internal.telephony.*;
+import android.telephony.CellLocation;
+import android.telephony.ServiceState;
+
+import java.util.ArrayList;
+
+/**
+ * Provides access to information about the telephony services on
+ * the device. Applications can use the methods in this class to
+ * determine telephony services and states, as well as to access some
+ * types of subscriber information. Applications can also register
+ * a listener to receive notification of telephony state changes.
+ * <p>
+ * You do not instantiate this class directly; instead, you retrieve
+ * a reference to an instance through
+ * {@link android.content.Context#getSystemService
+ * Context.getSystemService(Context.TELEPHONY_SERVICE)}.
+ * <p>
+ * Note that acess to some telephony information is
+ * permission-protected. Your application cannot access the protected
+ * information unless it has the appropriate permissions declared in
+ * its manifest file. Where permissions apply, they are noted in the
+ * the methods through which you access the protected information.
+ */
+public class TelephonyManager {
+ private static final String TAG = "TelephonyManager";
+
+ private Context mContext;
+ private ITelephonyRegistry mRegistry;
+
+ /** @hide */
+ public TelephonyManager(Context context) {
+ mContext = context;
+ mRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
+ "telephony.registry"));
+ }
+
+ /** @hide */
+ private TelephonyManager() {
+ }
+
+ private static TelephonyManager sInstance = new TelephonyManager();
+
+ /** @hide */
+ public static TelephonyManager getDefault() {
+ return sInstance;
+ }
+
+ //
+ //
+ // Device Info
+ //
+ //
+
+ /**
+ * Returns the software version number for the device, for example,
+ * the IMEI/SV for GSM phones.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ public String getDeviceSoftwareVersion() {
+ try {
+ return getSubscriberInfo().getDeviceSvn();
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Returns the unique device ID, for example,the IMEI for GSM
+ * phones.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ public String getDeviceId() {
+ try {
+ return getSubscriberInfo().getDeviceId();
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Returns the current location of the device.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#ACCESS_COARSE_LOCATION
+ * ACCESS_COARSE_LOCATION}.
+ */
+ public CellLocation getCellLocation() {
+ try {
+ Bundle bundle = getITelephony().getCellLocation();
+ return CellLocation.newFromBundle(bundle);
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Enables location update notifications. {@link PhoneStateListener#onCellLocationChanged
+ * PhoneStateListener.onCellLocationChanged} will be called on location updates.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#CONTROL_LOCATION_UPDATES
+ * CONTROL_LOCATION_UPDATES}
+ *
+ * @hide
+ */
+ public void enableLocationUpdates() {
+ try {
+ getITelephony().enableLocationUpdates();
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Disables location update notifications. {@link PhoneStateListener#onCellLocationChanged
+ * PhoneStateListener.onCellLocationChanged} will be called on location updates.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#CONTROL_LOCATION_UPDATES
+ * CONTROL_LOCATION_UPDATES}
+ *
+ * @hide
+ */
+ public void disableLocationUpdates() {
+ try {
+ getITelephony().disableLocationUpdates();
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * No phone module
+ */
+ public static final int PHONE_TYPE_NONE = 0;
+
+ /**
+ * GSM phone
+ */
+ public static final int PHONE_TYPE_GSM = 1;
+
+ /**
+ * Returns a constant indicating the device phone type.
+ *
+ * @see #PHONE_TYPE_NONE
+ * @see #PHONE_TYPE_GSM
+ */
+ public int getPhoneType() {
+ // in the future, we should really check this
+ return PHONE_TYPE_GSM;
+ }
+
+ //
+ //
+ // Current Network
+ //
+ //
+
+ /**
+ * Returns the alphabetic name of current registered operator.
+ * <p>
+ * Availability: Only when user is registered to a network
+ */
+ public String getNetworkOperatorName() {
+ return SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ALPHA);
+ }
+
+ /**
+ * Returns the numeric name (MCC+MNC) of current registered operator.
+ * <p>
+ * Availability: Only when user is registered to a network
+ */
+ public String getNetworkOperator() {
+ return SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
+ }
+
+ /**
+ * Returns true if the device is considered roaming on the current
+ * network, for GSM purposes.
+ * <p>
+ * Availability: Only when user registered to a network
+ */
+ public boolean isNetworkRoaming() {
+ return "true".equals(SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING));
+ }
+
+ /**
+ * Returns the ISO country code equivilent of the current registered
+ * operator's MCC (Mobile Country Code).
+ * <p>
+ * Availability: Only when user is registered to a network
+ */
+ public String getNetworkCountryIso() {
+ return SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
+ }
+
+ /** Network type is unknown */
+ public static final int NETWORK_TYPE_UNKNOWN = 0;
+ /** Current network is GPRS */
+ public static final int NETWORK_TYPE_GPRS = 1;
+ /** Current network is EDGE */
+ public static final int NETWORK_TYPE_EDGE = 2;
+ /** Current network is UMTS */
+ public static final int NETWORK_TYPE_UMTS = 3;
+
+ /**
+ * Returns a constant indicating the radio technology (network type)
+ * currently in use on the device.
+ *
+ * @see #NETWORK_TYPE_UNKNOWN
+ * @see #NETWORK_TYPE_GPRS
+ * @see #NETWORK_TYPE_EDGE
+ * @see #NETWORK_TYPE_UMTS
+ */
+ public int getNetworkType() {
+ String prop = SystemProperties.get(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE);
+ if ("GPRS".equals(prop)) {
+ return NETWORK_TYPE_GPRS;
+ }
+ else if ("EDGE".equals(prop)) {
+ return NETWORK_TYPE_EDGE;
+ }
+ else if ("UMTS".equals(prop)) {
+ return NETWORK_TYPE_UMTS;
+ }
+ else {
+ return NETWORK_TYPE_UNKNOWN;
+ }
+ }
+
+ //
+ //
+ // SIM Card
+ //
+ //
+
+ /** SIM card state: Unknown. Signifies that the SIM is in transition
+ * between states. For example, when the user inputs the SIM pin
+ * under PIN_REQUIRED state, a query for sim status returns
+ * this state before turning to SIM_STATE_READY. */
+ public static final int SIM_STATE_UNKNOWN = 0;
+ /** SIM card state: no SIM card is available in the device */
+ public static final int SIM_STATE_ABSENT = 1;
+ /** SIM card state: Locked: requires the user's SIM PIN to unlock */
+ public static final int SIM_STATE_PIN_REQUIRED = 2;
+ /** SIM card state: Locked: requires the user's SIM PUK to unlock */
+ public static final int SIM_STATE_PUK_REQUIRED = 3;
+ /** SIM card state: Locked: requries a network PIN to unlock */
+ public static final int SIM_STATE_NETWORK_LOCKED = 4;
+ /** SIM card state: Ready */
+ public static final int SIM_STATE_READY = 5;
+
+ /**
+ * Returns a constant indicating the state of the
+ * device SIM card.
+ *
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_ABSENT
+ * @see #SIM_STATE_PIN_REQUIRED
+ * @see #SIM_STATE_PUK_REQUIRED
+ * @see #SIM_STATE_NETWORK_LOCKED
+ * @see #SIM_STATE_READY
+ */
+ public int getSimState() {
+ String prop = SystemProperties.get(TelephonyProperties.PROPERTY_SIM_STATE);
+ if ("ABSENT".equals(prop)) {
+ return SIM_STATE_ABSENT;
+ }
+ else if ("PIN_REQUIRED".equals(prop)) {
+ return SIM_STATE_PIN_REQUIRED;
+ }
+ else if ("PUK_REQUIRED".equals(prop)) {
+ return SIM_STATE_PUK_REQUIRED;
+ }
+ else if ("NETWORK_LOCKED".equals(prop)) {
+ return SIM_STATE_NETWORK_LOCKED;
+ }
+ else if ("READY".equals(prop)) {
+ return SIM_STATE_READY;
+ }
+ else {
+ return SIM_STATE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the MCC+MNC (mobile country code + mobile network code) of the
+ * provider of the SIM. 5 or 6 decimal digits.
+ * <p>
+ * Availability: SIM state must be {@link #SIM_STATE_READY}
+ *
+ * @see #getSimState
+ */
+ public String getSimOperator() {
+ return SystemProperties.get(TelephonyProperties.PROPERTY_SIM_OPERATOR_NUMERIC);
+ }
+
+ /**
+ * Returns the Service Provider Name (SPN).
+ * <p>
+ * Availability: SIM state must be {@link #SIM_STATE_READY}
+ *
+ * @see #getSimState
+ */
+ public String getSimOperatorName() {
+ return SystemProperties.get(TelephonyProperties.PROPERTY_SIM_OPERATOR_ALPHA);
+ }
+
+ /**
+ * Returns the ISO country code equivalent for the SIM provider's country code.
+ */
+ public String getSimCountryIso() {
+ return SystemProperties.get(TelephonyProperties.PROPERTY_SIM_OPERATOR_ISO_COUNTRY);
+ }
+
+ /**
+ * Returns the serial number of the SIM, if applicable.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ public String getSimSerialNumber() {
+ try {
+ return getSubscriberInfo().getSimSerialNumber();
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+ //
+ //
+ // Subscriber Info
+ //
+ //
+
+ /**
+ * Returns the unique subscriber ID, for example, the IMSI for a GSM phone.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ public String getSubscriberId() {
+ try {
+ return getSubscriberInfo().getSubscriberId();
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Returns the phone number string for line 1, for example, the MSISDN
+ * for a GSM phone.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ public String getLine1Number() {
+ try {
+ return getSubscriberInfo().getLine1Number();
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Returns the alphabetic identifier associated with the line 1 number.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * @hide
+ * nobody seems to call this.
+ */
+ public String getLine1AlphaTag() {
+ try {
+ return getSubscriberInfo().getLine1AlphaTag();
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Returns the voice mail number.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ public String getVoiceMailNumber() {
+ try {
+ return getSubscriberInfo().getVoiceMailNumber();
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the alphabetic identifier associated with the voice
+ * mail number.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ public String getVoiceMailAlphaTag() {
+ try {
+ return getSubscriberInfo().getVoiceMailAlphaTag();
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+
+ private IPhoneSubInfo getSubscriberInfo() {
+ // get it each time because that process crashes a lot
+ return IPhoneSubInfo.Stub.asInterface(ServiceManager.getService("iphonesubinfo"));
+ }
+
+
+ /** Device call state: No activity. */
+ public static final int CALL_STATE_IDLE = 0;
+ /** Device call state: Ringing. A new call arrived and is
+ * ringing or waiting. In the latter case, another call is
+ * already active. */
+ public static final int CALL_STATE_RINGING = 1;
+ /** Device call state: Off-hook. At least one call exists
+ * that is dialing, active, or on hold, and no calls are ringing
+ * or waiting. */
+ public static final int CALL_STATE_OFFHOOK = 2;
+
+ /**
+ * Returns a constant indicating the call state (cellular) on the device.
+ */
+ public int getCallState() {
+ try {
+ return getITelephony().getCallState();
+ } catch (RemoteException ex) {
+ // the phone process is restarting.
+ return CALL_STATE_IDLE;
+ }
+ }
+
+ /** Data connection activity: No traffic. */
+ public static final int DATA_ACTIVITY_NONE = 0x00000000;
+ /** Data connection activity: Currently receiving IP PPP traffic. */
+ public static final int DATA_ACTIVITY_IN = 0x00000001;
+ /** Data connection activity: Currently sending IP PPP traffic. */
+ public static final int DATA_ACTIVITY_OUT = 0x00000002;
+ /** Data connection activity: Currently both sending and receiving
+ * IP PPP traffic. */
+ public static final int DATA_ACTIVITY_INOUT = DATA_ACTIVITY_IN | DATA_ACTIVITY_OUT;
+
+ /**
+ * Returns a constant indicating the type of activity on a data connection
+ * (cellular).
+ *
+ * @see #DATA_ACTIVITY_NONE
+ * @see #DATA_ACTIVITY_IN
+ * @see #DATA_ACTIVITY_OUT
+ * @see #DATA_ACTIVITY_INOUT
+ */
+ public int getDataActivity() {
+ try {
+ return getITelephony().getDataActivity();
+ } catch (RemoteException ex) {
+ // the phone process is restarting.
+ return DATA_ACTIVITY_NONE;
+ }
+ }
+
+ /** Data connection state: Disconnected. IP traffic not available. */
+ public static final int DATA_DISCONNECTED = 0;
+ /** Data connection state: Currently setting up a data connection. */
+ public static final int DATA_CONNECTING = 1;
+ /** Data connection state: Connected. IP traffic should be available. */
+ public static final int DATA_CONNECTED = 2;
+ /** Data connection state: Suspended. The connection is up, but IP
+ * traffic is temporarily unavailable. For example, in a 2G network,
+ * data activity may be suspended when a voice call arrives. */
+ public static final int DATA_SUSPENDED = 3;
+
+ /**
+ * Returns a constant indicating the current data connection state
+ * (cellular).
+ *
+ * @see #DATA_DISCONNECTED
+ * @see #DATA_CONNECTING
+ * @see #DATA_CONNECTED
+ * @see #DATA_SUSPENDED
+ */
+ public int getDataState() {
+ try {
+ return getITelephony().getDataState();
+ } catch (RemoteException ex) {
+ // the phone process is restarting.
+ return DATA_DISCONNECTED;
+ }
+ }
+
+ private ITelephony getITelephony() {
+ return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ }
+
+ //
+ //
+ // PhoneStateListener
+ //
+ //
+
+ /**
+ * Registers a listener object to receive notification of changes
+ * in specified telephony states.
+ * <p>
+ * To register a listener, pass a {@link PhoneStateListener}
+ * and specify at least one telephony state of interest in
+ * the events argument.
+ *
+ * At registration, and when a specified telephony state
+ * changes, the telephony manager invokes the appropriate
+ * callback method on the listener object and passes the
+ * current (udpated) values.
+ * <p>
+ * To unregister a listener, pass the listener object and set the
+ * events argument to
+ * {@link PhoneStateListener#LISTEN_NONE LISTEN_NONE} (0).
+ *
+ * @param listener The {@link PhoneStateListener} object to register
+ * (or unregister)
+ * @param events The telephony state(s) of interest to the listener,
+ * as a bitwise-OR combination of {@link PhoneStateListener}
+ * LISTEN_ flags.
+ */
+ public void listen(PhoneStateListener listener, int events) {
+ String pkgForDebug = mContext != null ? mContext.getPackageName() : "<unknown>";
+ try {
+ mRegistry.listen(pkgForDebug, listener.callback, events, true);
+ } catch (RemoteException ex) {
+ // system process dead
+ }
+ }
+}
+
+/*
+ * These have not been implemented since they're not used anywhere in the system
+ * and it's impossible to tell if they're working or not. The comments in SystemProperties
+ * were wrong in at least one case, so I don't trust them. They claimed that
+ * PROPERTY_OPERATOR_ISROAMING was "1" if it was true, but it's actually set to "true."
+ */
+
+
+ /* Set to '1' if voice mail is waiting, otherwise false */
+ /*
+ public boolean isVoiceMailWaiting() {
+ return "1".equals(
+ SystemProperties.get(TelephonyProperties.PROPERTY_LINE1_VOICE_MAIL_WAITING));
+ }
+ */
+
+ /* Set to 'true' if unconditional voice call forwarding is enabled
+ * Availablity: only if configured in SIM; SIM state must be "READY"
+ */
+ /*
+ public boolean isCallForwarding() {
+ SystemProperties.get(TelephonyProperties.PROPERTY_LINE1_VOICE_CALL_FORWARDING);
+ }
+ */
+
+ /* '1' if the current network is the result of a manual network selection.
+ * Availability: when registered to a network
+ */
+ /*
+ public boolean isNetworkManual() {
+ return SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISMANUAL);
+ }
+ */
+
+
+/*
+ * These have not been implemented because they're not public IMHO. -joeo
+ */
+
+ /*
+ * Baseband version
+ * Availability: property is available any time radio is on
+ */
+ /*
+ public String getBasebandVersion() {
+ return SystemProperties.get(TelephonyProperties.PROPERTY_BASEBAND_VERSION);
+ }
+ */
+
+ /*
+ * Radio Interface Layer (RIL) library implementation.
+ */
+ /*
+ public String getRilLibrary() {
+ return SystemProperties.get(TelephonyProperties.PROPERTY_RIL_IMPL);
+ }
+ */
+
diff --git a/telephony/java/android/telephony/gsm/GsmCellLocation.java b/telephony/java/android/telephony/gsm/GsmCellLocation.java
new file mode 100644
index 0000000..fb9b73a
--- /dev/null
+++ b/telephony/java/android/telephony/gsm/GsmCellLocation.java
@@ -0,0 +1,126 @@
+/*
+ * 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 android.telephony.gsm;
+
+import android.os.Bundle;
+import com.android.internal.telephony.Phone;
+import android.telephony.CellLocation;
+
+/**
+ * Represents the cell location on a GSM phone.
+ */
+public class GsmCellLocation extends CellLocation
+{
+ private int mLac;
+ private int mCid;
+
+ /**
+ * Empty constructor. Initializes the LAC and CID to -1.
+ */
+ public GsmCellLocation() {
+ mLac = -1;
+ mCid = -1;
+ }
+
+ /**
+ * Initialize the object from a bundle.
+ */
+ public GsmCellLocation(Bundle bundle) {
+ mLac = bundle.getInt("lac");
+ mCid = bundle.getInt("cid");
+ }
+
+ /**
+ * @return gsm location area code, -1 if unknown, 0xffff max legal value
+ */
+ public int getLac() {
+ return mLac;
+ }
+
+ /**
+ * @return gsm cell id, -1 if unknown, 0xffff max legal value
+ */
+ public int getCid() {
+ return mCid;
+ }
+
+ /**
+ * Invalidate this object. The location area code and the cell id are set to -1.
+ */
+ public void setStateInvalid() {
+ mLac = -1;
+ mCid = -1;
+ }
+
+ /**
+ * Set the location area code and the cell id.
+ */
+ public void setLacAndCid(int lac, int cid) {
+ mLac = lac;
+ mCid = cid;
+ }
+
+ @Override
+ public int hashCode() {
+ return mLac ^ mCid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ GsmCellLocation s;
+
+ try {
+ s = (GsmCellLocation)o;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ return equalsHandlesNulls(mLac, s.mLac) && equalsHandlesNulls(mCid, s.mCid);
+ }
+
+ @Override
+ public String toString() {
+ return "["+ mLac + "," + mCid + "]";
+ }
+
+ /**
+ * Test whether two objects hold the same data values or both are null
+ *
+ * @param a first obj
+ * @param b second obj
+ * @return true if two objects equal or both are null
+ */
+ private static boolean equalsHandlesNulls(Object a, Object b) {
+ return (a == null) ? (b == null) : a.equals (b);
+ }
+
+ /**
+ * Set intent notifier Bundle based on service state
+ *
+ * @param m intent notifier Bundle
+ */
+ public void fillInNotifierBundle(Bundle m) {
+ m.putInt("lac", mLac);
+ m.putInt("cid", mCid);
+ }
+}
+
+
diff --git a/telephony/java/android/telephony/gsm/SmsManager.java b/telephony/java/android/telephony/gsm/SmsManager.java
new file mode 100644
index 0000000..274c461
--- /dev/null
+++ b/telephony/java/android/telephony/gsm/SmsManager.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2007 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 android.telephony.gsm;
+
+import android.app.PendingIntent;
+import android.os.RemoteException;
+import android.os.IServiceManager;
+import android.os.ServiceManager;
+import android.os.ServiceManagerNative;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.gsm.EncodeException;
+import com.android.internal.telephony.gsm.GsmAlphabet;
+import com.android.internal.telephony.gsm.ISms;
+import com.android.internal.telephony.gsm.SimConstants;
+import com.android.internal.telephony.gsm.SmsRawData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Manages SMS operations such as sending data, text, and pdu SMS messages.
+ * Get this object by calling the static method SmsManager.getDefault().
+ */
+public final class SmsManager {
+ private static SmsManager sInstance;
+
+ /**
+ * Send a text based SMS.
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applicaitons,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or text are empty
+ */
+ public void sendTextMessage(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(
+ scAddress, destinationAddress, text, (deliveryIntent != null));
+ sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
+ }
+
+ /**
+ * Divide a text message into several messages, none bigger than
+ * the maximum SMS message size.
+ *
+ * @param text the original message. Must not be null.
+ * @return an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ */
+ public ArrayList<String> divideMessage(String text) {
+ int size = text.length();
+ int[] params = SmsMessage.calculateLength(text, false);
+ /* SmsMessage.calculateLength returns an int[4] with:
+ * int[0] being the number of SMS's required,
+ * int[1] the number of code units used,
+ * int[2] is the number of code units remaining until the next message.
+ * int[3] is the encoding type that should be used for the message.
+ */
+ int messageCount = params[0];
+ int encodingType = params[3];
+ ArrayList<String> result = new ArrayList<String>(messageCount);
+
+ int start = 0;
+ int limit;
+
+ if (messageCount > 1) {
+ limit = (encodingType == SmsMessage.ENCODING_7BIT)?
+ SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER: SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
+ } else {
+ limit = (encodingType == SmsMessage.ENCODING_7BIT)?
+ SmsMessage.MAX_USER_DATA_SEPTETS: SmsMessage.MAX_USER_DATA_BYTES;
+ }
+
+ try {
+ while (start < size) {
+ int end = GsmAlphabet.findLimitIndex(text, start, limit, encodingType);
+ result.add(text.substring(start, end));
+ start = end;
+ }
+ }
+ catch (EncodeException e) {
+ // ignore it.
+ }
+ return result;
+ }
+
+ /**
+ * Send a multi-part text based SMS. The callee should have already
+ * divided the message into correctly sized parts by calling
+ * <code>divideMessage</code>.
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applicaitons,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ */
+ public void sendMultipartTextMessage(
+ String destinationAddress, String scAddress, ArrayList<String> parts,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+ if (parts == null || parts.size() < 1) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ if (parts.size() > 1) {
+ try {
+ ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (simISms != null) {
+ simISms.sendMultipartText(destinationAddress, scAddress, parts,
+ sentIntents, deliveryIntents);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ } else {
+ PendingIntent sentIntent = null;
+ PendingIntent deliveryIntent = null;
+ if (sentIntents != null && sentIntents.size() > 0) {
+ sentIntent = sentIntents.get(0);
+ }
+ if (deliveryIntents != null && deliveryIntents.size() > 0) {
+ deliveryIntent = deliveryIntents.get(0);
+ }
+ sendTextMessage(destinationAddress, scAddress, parts.get(0),
+ sentIntent, deliveryIntent);
+ }
+ }
+
+ /**
+ * Send a data based SMS to a specific application port.
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param destinationPort the port to deliver the message to
+ * @param data the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applicaitons,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ */
+ public void sendDataMessage(
+ String destinationAddress, String scAddress, short destinationPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (data == null || data.length == 0) {
+ throw new IllegalArgumentException("Invalid message data");
+ }
+
+ SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
+ destinationPort, data, (deliveryIntent != null));
+ sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
+ }
+
+ /**
+ * Send a raw SMS PDU.
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * default SMSC
+ * @param pdu the raw PDU to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applicaitons,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ */
+ private void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
+ PendingIntent deliveryIntent) {
+ try {
+ ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (simISms != null) {
+ simISms.sendRawPdu(smsc, pdu, sentIntent, deliveryIntent);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Get the default instance of the SmsManager
+ *
+ * @return the default instance of the SmsManager
+ */
+ public static SmsManager getDefault() {
+ if (sInstance == null) {
+ sInstance = new SmsManager();
+ }
+ return sInstance;
+ }
+
+ private SmsManager() {
+ // nothing to see here
+ }
+
+ /**
+ * Copy a raw SMS PDU to the SIM.
+ *
+ * @param smsc the SMSC for this message, or NULL for the default SMSC
+ * @param pdu the raw PDU to store
+ * @param status message status (STATUS_ON_SIM_READ, STATUS_ON_SIM_UNREAD,
+ * STATUS_ON_SIM_SENT, STATUS_ON_SIM_UNSENT)
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean copyMessageToSim(byte[] smsc, byte[] pdu, int status) {
+ boolean success = false;
+
+ try {
+ ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (simISms != null) {
+ success = simISms.copyMessageToSimEf(status, pdu, smsc);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Delete the specified message from the SIM.
+ *
+ * @param messageIndex is the record index of the message on SIM
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean
+ deleteMessageFromSim(int messageIndex) {
+ boolean success = false;
+ byte[] pdu = new byte[SimConstants.SMS_RECORD_LENGTH-1];
+ Arrays.fill(pdu, (byte)0xff);
+
+ try {
+ ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (simISms != null) {
+ success = simISms.updateMessageOnSimEf(messageIndex,
+ STATUS_ON_SIM_FREE, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Update the specified message on the SIM.
+ *
+ * @param messageIndex record index of message to update
+ * @param newStatus new message status (STATUS_ON_SIM_READ,
+ * STATUS_ON_SIM_UNREAD, STATUS_ON_SIM_SENT,
+ * STATUS_ON_SIM_UNSENT, STATUS_ON_SIM_FREE)
+ * @param pdu the raw PDU to store
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean updateMessageOnSim(int messageIndex, int newStatus,
+ byte[] pdu) {
+ boolean success = false;
+
+ try {
+ ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (simISms != null) {
+ success = simISms.updateMessageOnSimEf(messageIndex, newStatus, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+
+ /**
+ * Retrieves all messages currently stored on SIM.
+ *
+ * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
+ *
+ * {@hide}
+ */
+ public ArrayList<SmsMessage> getAllMessagesFromSim() {
+ List<SmsRawData> records = null;
+
+ try {
+ ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (simISms != null) {
+ records = simISms.getAllMessagesFromSimEf();
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return createMessageListFromRawRecords(records);
+ }
+
+ /**
+ * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
+ * records returned by <code>getAllMessagesFromSim()</code>
+ *
+ * @param records SMS EF records, returned by
+ * <code>getAllMessagesFromSim</code>
+ * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
+ */
+ private ArrayList<SmsMessage> createMessageListFromRawRecords(List records) {
+ ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
+ if (records != null) {
+ int count = records.size();
+ for (int i = 0; i < count; i++) {
+ SmsRawData data = (SmsRawData)records.get(i);
+ // List contains all records, including "free" records (null)
+ if (data != null) {
+ SmsMessage sms =
+ SmsMessage.createFromEfRecord(i+1, data.getBytes());
+ messages.add(sms);
+ }
+ }
+ }
+ return messages;
+ }
+
+ /** Free space (TS 51.011 10.5.3). */
+ static public final int STATUS_ON_SIM_FREE = 0;
+
+ /** Received and read (TS 51.011 10.5.3). */
+ static public final int STATUS_ON_SIM_READ = 1;
+
+ /** Received and unread (TS 51.011 10.5.3). */
+ static public final int STATUS_ON_SIM_UNREAD = 3;
+
+ /** Stored and sent (TS 51.011 10.5.3). */
+ static public final int STATUS_ON_SIM_SENT = 5;
+
+ /** Stored and unsent (TS 51.011 10.5.3). */
+ static public final int STATUS_ON_SIM_UNSENT = 7;
+
+
+ // SMS send failure result codes
+
+ /** Generic failure cause */
+ static public final int RESULT_ERROR_GENERIC_FAILURE = 1;
+ /** Failed because radio was explicitly turned off */
+ static public final int RESULT_ERROR_RADIO_OFF = 2;
+ /** Failed because no pdu provided */
+ static public final int RESULT_ERROR_NULL_PDU = 3;
+ /** Failed because service is currently unavailable */
+ static public final int RESULT_ERROR_NO_SERVICE = 4;
+}
diff --git a/telephony/java/android/telephony/gsm/SmsMessage.java b/telephony/java/android/telephony/gsm/SmsMessage.java
new file mode 100644
index 0000000..836a013
--- /dev/null
+++ b/telephony/java/android/telephony/gsm/SmsMessage.java
@@ -0,0 +1,1591 @@
+/*
+ * 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 android.telephony.gsm;
+
+import android.pim.Time;
+import android.telephony.PhoneNumberUtils;
+import android.util.Config;
+import android.util.Log;
+import android.telephony.PhoneNumberUtils;
+import com.android.internal.telephony.gsm.EncodeException;
+import com.android.internal.telephony.gsm.GsmAlphabet;
+import com.android.internal.telephony.gsm.SimUtils;
+import com.android.internal.telephony.gsm.SmsHeader;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+class SmsAddress {
+ // From TS 23.040 9.1.2.5 and TS 24.008 table 10.5.118
+ static final int TON_UNKNOWN = 0;
+
+ static final int TON_INTERNATIONAL = 1;
+
+ static final int TON_NATIONAL = 2;
+
+ static final int TON_NETWORK = 3;
+
+ static final int TON_SUBSCRIBER = 4;
+
+ static final int TON_ALPHANUMERIC = 5;
+
+ static final int TON_APPREVIATED = 6;
+
+ static final int OFFSET_ADDRESS_LENGTH = 0;
+
+ static final int OFFSET_TOA = 1;
+
+ static final int OFFSET_ADDRESS_VALUE = 2;
+
+ int ton;
+
+ String address;
+
+ byte[] origBytes;
+
+ /**
+ * New SmsAddress from TS 23.040 9.1.2.5 Address Field
+ *
+ * @param offset the offset of the Address-Length byte
+ * @param length the length in bytes rounded up, e.g. "2 +
+ * (addressLength + 1) / 2"
+ */
+
+ SmsAddress(byte[] data, int offset, int length) {
+ origBytes = new byte[length];
+ System.arraycopy(data, offset, origBytes, 0, length);
+
+ // addressLength is the count of semi-octets, not bytes
+ int addressLength = origBytes[OFFSET_ADDRESS_LENGTH] & 0xff;
+
+ int toa = origBytes[OFFSET_TOA] & 0xff;
+ ton = 0x7 & (toa >> 4);
+
+ // TOA must have its high bit set
+ if ((toa & 0x80) != 0x80) {
+ throw new RuntimeException("Invalid TOA - high bit must be set");
+ }
+
+ if (isAlphanumeric()) {
+ // An alphanumeric address
+ int countSeptets = addressLength * 4 / 7;
+
+ address = GsmAlphabet.gsm7BitPackedToString(origBytes,
+ OFFSET_ADDRESS_VALUE, countSeptets);
+ } else {
+ // TS 23.040 9.1.2.5 says
+ // that "the MS shall interpret reserved values as 'Unknown'
+ // but shall store them exactly as received"
+
+ byte lastByte = origBytes[length - 1];
+
+ if ((addressLength & 1) == 1) {
+ // Make sure the final unused BCD digit is 0xf
+ origBytes[length - 1] |= 0xf0;
+ }
+ address = PhoneNumberUtils.calledPartyBCDToString(origBytes,
+ OFFSET_TOA, length - OFFSET_TOA);
+
+ // And restore origBytes
+ origBytes[length - 1] = lastByte;
+ }
+ }
+
+ public String getAddressString() {
+ return address;
+ }
+
+ /**
+ * Returns true if this is an alphanumeric addres
+ */
+ public boolean isAlphanumeric() {
+ return ton == TON_ALPHANUMERIC;
+ }
+
+ public boolean isNetworkSpecific() {
+ return ton == TON_NETWORK;
+ }
+
+ /**
+ * Returns true of this is a valid CPHS voice message waiting indicator
+ * address
+ */
+ public boolean isCphsVoiceMessageIndicatorAddress() {
+ // CPHS-style MWI message
+ // See CPHS 4.7 B.4.2.1
+ //
+ // Basically:
+ //
+ // - Originating address should be 4 bytes long and alphanumeric
+ // - Decode will result with two chars:
+ // - Char 1
+ // 76543210
+ // ^ set/clear indicator (0 = clear)
+ // ^^^ type of indicator (000 = voice)
+ // ^^^^ must be equal to 0001
+ // - Char 2:
+ // 76543210
+ // ^ line number (0 = line 1)
+ // ^^^^^^^ set to 0
+ //
+ // Remember, since the alpha address is stored in 7-bit compact form,
+ // the "line number" is really the top bit of the first address value
+ // byte
+
+ return (origBytes[OFFSET_ADDRESS_LENGTH] & 0xff) == 4
+ && isAlphanumeric() && (origBytes[OFFSET_TOA] & 0x0f) == 0;
+ }
+
+ /**
+ * Returns true if this is a valid CPHS voice message waiting indicator
+ * address indicating a "set" of "indicator 1" of type "voice message
+ * waiting"
+ */
+ public boolean isCphsVoiceMessageSet() {
+ // 0x11 means "set" "voice message waiting" "indicator 1"
+ return isCphsVoiceMessageIndicatorAddress()
+ && (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x11;
+
+ }
+
+ /**
+ * Returns true if this is a valid CPHS voice message waiting indicator
+ * address indicationg a "clear" of "indicator 1" of type "voice message
+ * waiting"
+ */
+ public boolean isCphsVoiceMessageClear() {
+ // 0x10 means "clear" "voice message waiting" "indicator 1"
+ return isCphsVoiceMessageIndicatorAddress()
+ && (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x10;
+
+ }
+
+ public boolean couldBeEmailGateway() {
+ // Some carriers seems to send email gateway messages in this form:
+ // from: an UNKNOWN TON, 3 or 4 digits long, beginning with a 5
+ // PID: 0x00, Data coding scheme 0x03
+ // So we just attempt to treat any message from an address length <= 4
+ // as an email gateway
+
+ return address.length() <= 4;
+ }
+
+}
+
+/**
+ * A Short Message Service message.
+ *
+ */
+public class SmsMessage {
+ static final String LOG_TAG = "GSM";
+
+ /**
+ * SMS Class enumeration.
+ * See TS 23.038.
+ *
+ */
+ public enum MessageClass {
+ UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
+ }
+
+ /** Unknown encoding scheme (see TS 23.038) */
+ public static final int ENCODING_UNKNOWN = 0;
+ /** 7-bit encoding scheme (see TS 23.038) */
+ public static final int ENCODING_7BIT = 1;
+ /** 8-bit encoding scheme (see TS 23.038) */
+ public static final int ENCODING_8BIT = 2;
+ /** 16-bit encoding scheme (see TS 23.038) */
+ public static final int ENCODING_16BIT = 3;
+
+ /** The maximum number of payload bytes per message */
+ public static final int MAX_USER_DATA_BYTES = 140;
+
+ /**
+ * The maximum number of payload bytes per message if a user data header
+ * is present. This assumes the header only contains the
+ * CONCATENATED_8_BIT_REFERENCE element.
+ *
+ * @hide pending API Council approval to extend the public API
+ */
+ public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134;
+
+ /** The maximum number of payload septets per message */
+ public static final int MAX_USER_DATA_SEPTETS = 160;
+
+ /**
+ * The maximum number of payload septets per message if a user data header
+ * is present. This assumes the header only contains the
+ * CONCATENATED_8_BIT_REFERENCE element.
+ */
+ public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
+
+ /** The address of the SMSC. May be null */
+ String scAddress;
+
+ /** The address of the sender */
+ SmsAddress originatingAddress;
+
+ /** The message body as a string. May be null if the message isn't text */
+ String messageBody;
+
+ String pseudoSubject;
+
+ /** Non-null this is an email gateway message */
+ String emailFrom;
+
+ /** Non-null if this is an email gateway message */
+ String emailBody;
+
+ boolean isEmail;
+
+ long scTimeMillis;
+
+ /** The raw PDU of the message */
+ byte[] mPdu;
+
+ /** The raw bytes for the user data section of the message */
+ byte[] userData;
+
+ SmsHeader userDataHeader;
+
+ /**
+ * TP-Message-Type-Indicator
+ * 9.2.3
+ */
+ int mti;
+
+ /** TP-Protocol-Identifier (TP-PID) */
+ int protocolIdentifier;
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ int dataCodingScheme;
+
+ // TP-Reply-Path
+ // e.g. 23.040 9.2.2.1
+ boolean replyPathPresent = false;
+
+ // "Message Marked for Automatic Deletion Group"
+ // 23.038 Section 4
+ boolean automaticDeletion;
+
+ // "Message Waiting Indication Group"
+ // 23.038 Section 4
+ private boolean isMwi;
+
+ private boolean mwiSense;
+
+ private boolean mwiDontStore;
+
+ MessageClass messageClass;
+
+ /**
+ * Indicates status for messages stored on the SIM.
+ */
+ int statusOnSim = -1;
+
+ /**
+ * Record index of message in the EF.
+ */
+ int indexOnSim = -1;
+
+ /** TP-Message-Reference - Message Reference of sent message. @hide */
+ public int messageRef;
+
+ /** True if Status Report is for SMS-SUBMIT; false for SMS-COMMAND. */
+ boolean forSubmit;
+
+ /** The address of the receiver. */
+ SmsAddress recipientAddress;
+
+ /** Time when SMS-SUBMIT was delivered from SC to MSE. */
+ long dischargeTimeMillis;
+
+ /**
+ * TP-Status - status of a previously submitted SMS.
+ * This field applies to SMS-STATUS-REPORT messages. 0 indicates success;
+ * see TS 23.040, 9.2.3.15 for description of other possible values.
+ */
+ int status;
+
+ /**
+ * TP-Status - status of a previously submitted SMS.
+ * This field is true iff the message is a SMS-STATUS-REPORT message.
+ */
+ boolean isStatusReportMessage = false;
+
+ public static class SubmitPdu {
+ public byte[] encodedScAddress; // Null if not applicable.
+ public byte[] encodedMessage;
+
+ public String toString() {
+ return "SubmitPdu: encodedScAddress = "
+ + Arrays.toString(encodedScAddress)
+ + ", encodedMessage = "
+ + Arrays.toString(encodedMessage);
+ }
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU.
+ */
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /**
+ * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+ * +CMT unsolicited response (PDU mode, of course)
+ * +CMT: [&lt;alpha>],<length><CR><LF><pdu>
+ *
+ * Only public for debugging
+ *
+ * {@hide}
+ */
+ /* package */ public static SmsMessage newFromCMT(String[] lines) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(SimUtils.hexStringToBytes(lines[1]));
+ return msg;
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /* pacakge */ static SmsMessage newFromCMTI(String line) {
+ // the thinking here is not to read the message immediately
+ // FTA test case
+ Log.e(LOG_TAG, "newFromCMTI: not yet supported");
+ return null;
+ }
+
+ /** @hide */
+ /* package */ public static SmsMessage newFromCDS(String line) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(SimUtils.hexStringToBytes(line));
+ return msg;
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by SmsManager.getAllMessagesFromSim + 1.
+ * @param data Record data.
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data) {
+ try {
+ SmsMessage msg = new SmsMessage();
+
+ msg.indexOnSim = index;
+
+ // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
+ // or STORED_UNSENT
+ // See TS 51.011 10.5.3
+ if ((data[0] & 1) == 0) {
+ Log.w(LOG_TAG,
+ "SMS parsing failed: Trying to parse a free record");
+ return null;
+ } else {
+ msg.statusOnSim = data[0] & 0x07;
+ }
+
+ int size = data.length - 1;
+
+ // Note: Data may include trailing FF's. That's OK; message
+ // should still parse correctly.
+ byte[] pdu = new byte[size];
+ System.arraycopy(data, 1, pdu, 0, size);
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
+ * length in bytes (not hex chars) less the SMSC header
+ */
+ public static int getTPLayerLengthForPDU(String pdu) {
+ int len = pdu.length() / 2;
+ int smscLen = 0;
+
+ smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
+
+ return len - smscLen - 1;
+ }
+
+ /**
+ * Calculates the number of SMS's required to encode the message body and
+ * the number of characters remaining until the next message, given the
+ * current encoding.
+ *
+ * @param messageBody the message to encode
+ * @param use7bitOnly if true, characters that are not part of the GSM
+ * alphabet are counted as a single space char. If false, a
+ * messageBody containing non-GSM alphabet characters is calculated
+ * for 16-bit encoding.
+ * @return an int[4] with int[0] being the number of SMS's required, int[1]
+ * the number of code units used, and int[2] is the number of code
+ * units remaining until the next message. int[3] is the encoding
+ * type that should be used for the message.
+ */
+ public static int[] calculateLength(String messageBody, boolean use7bitOnly) {
+ int ret[] = new int[4];
+
+ try {
+ // Try GSM alphabet
+ int septets = GsmAlphabet.countGsmSeptets(messageBody, !use7bitOnly);
+ ret[1] = septets;
+ if (septets > MAX_USER_DATA_SEPTETS) {
+ ret[0] = (septets / MAX_USER_DATA_SEPTETS_WITH_HEADER) + 1;
+ ret[2] = septets % MAX_USER_DATA_SEPTETS_WITH_HEADER;
+ } else {
+ ret[0] = 1;
+ ret[2] = MAX_USER_DATA_SEPTETS - septets;
+ }
+ ret[3] = ENCODING_7BIT;
+ } catch (EncodeException ex) {
+ // fall back to USC-2
+ int octets = messageBody.length() * 2;
+ ret[1] = octets;
+ if (octets > MAX_USER_DATA_BYTES) {
+ // 6 is the size of the user data header
+ ret[0] = (octets / MAX_USER_DATA_BYTES_WITH_HEADER) + 1;
+ ret[2] = octets % MAX_USER_DATA_BYTES_WITH_HEADER;
+ } else {
+ ret[0] = 1;
+ ret[2] = MAX_USER_DATA_BYTES - octets;
+ }
+ ret[3] = ENCODING_16BIT;
+ }
+
+ return ret;
+ }
+
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header) {
+
+ // Perform null parameter checks.
+ if (message == null || destinationAddress == null) {
+ return null;
+ }
+
+ SubmitPdu ret = new SubmitPdu();
+ // MTI = SMS-SUBMIT, UDHI = header != null
+ byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+ ByteArrayOutputStream bo = getSubmitPduHead(
+ scAddress, destinationAddress, mtiByte,
+ statusReportRequested, ret);
+
+ try {
+ // First, try encoding it with the GSM alphabet
+
+ // User Data (and length)
+ byte[] userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header);
+
+ if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
+ // Message too long
+ return null;
+ }
+
+ // TP-Data-Coding-Scheme
+ // Default encoding, uncompressed
+ bo.write(0x00);
+
+ // (no TP-Validity-Period)
+
+ bo.write(userData, 0, userData.length);
+ } catch (EncodeException ex) {
+ byte[] userData, textPart;
+ // Encoding to the 7-bit alphabet failed. Let's see if we can
+ // send it as a UCS-2 encoded message
+
+ try {
+ textPart = message.getBytes("utf-16be");
+ } catch (UnsupportedEncodingException uex) {
+ Log.e(LOG_TAG,
+ "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+
+ if (header != null) {
+ userData = new byte[header.length + textPart.length];
+
+ System.arraycopy(header, 0, userData, 0, header.length);
+ System.arraycopy(textPart, 0, userData, header.length, textPart.length);
+ }
+ else {
+ userData = textPart;
+ }
+
+ if (userData.length > MAX_USER_DATA_BYTES) {
+ // Message too long
+ return null;
+ }
+
+ // TP-Data-Coding-Scheme
+ // Class 3, UCS-2 encoding, uncompressed
+ bo.write(0x0b);
+
+ // (no TP-Validity-Period)
+
+ // TP-UDL
+ bo.write(userData.length);
+
+ bo.write(userData, 0, userData.length);
+ }
+
+ ret.encodedMessage = bo.toByteArray();
+ return ret;
+ }
+
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested) {
+
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param destinationPort the port to deliver the message to at the
+ * destination
+ * @param data the dat for the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, short destinationPort, byte[] data,
+ boolean statusReportRequested) {
+ if (data.length > (MAX_USER_DATA_BYTES - 7 /* UDH size */)) {
+ Log.e(LOG_TAG, "SMS data message may only contain "
+ + (MAX_USER_DATA_BYTES - 7) + " bytes");
+ return null;
+ }
+
+ SubmitPdu ret = new SubmitPdu();
+ ByteArrayOutputStream bo = getSubmitPduHead(
+ scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
+ // TP-UDHI = true
+ statusReportRequested, ret);
+
+ // TP-Data-Coding-Scheme
+ // No class, 8 bit data
+ bo.write(0x04);
+
+ // (no TP-Validity-Period)
+
+ // User data size
+ bo.write(data.length + 7);
+
+ // User data header size
+ bo.write(0x06); // header is 6 octets
+
+ // User data header, indicating the destination port
+ bo.write(SmsHeader.APPLICATION_PORT_ADDRESSING_16_BIT); // port
+ // addressing
+ // header
+ bo.write(0x04); // each port is 2 octets
+ bo.write((destinationPort >> 8) & 0xFF); // MSB of destination port
+ bo.write(destinationPort & 0xFF); // LSB of destination port
+ bo.write(0x00); // MSB of originating port
+ bo.write(0x00); // LSB of originating port
+
+ // User data
+ bo.write(data, 0, data.length);
+
+ ret.encodedMessage = bo.toByteArray();
+ return ret;
+ }
+
+ /**
+ * Create the beginning of a SUBMIT PDU. This is the part of the
+ * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
+ * one of which takes a byte array and the other of which takes a
+ * <code>String</code>.
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param mtiByte
+ * @param ret <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message
+ */
+ private static ByteArrayOutputStream getSubmitPduHead(
+ String scAddress, String destinationAddress, byte mtiByte,
+ boolean statusReportRequested, SubmitPdu ret) {
+ ByteArrayOutputStream bo = new ByteArrayOutputStream(
+ MAX_USER_DATA_BYTES + 40);
+
+ // SMSC address with length octet, or 0
+ if (scAddress == null) {
+ ret.encodedScAddress = null;
+ } else {
+ ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
+ scAddress);
+ }
+
+ // TP-Message-Type-Indicator (and friends)
+ if (statusReportRequested) {
+ // Set TP-Status-Report-Request bit.
+ mtiByte |= 0x20;
+ if (Config.LOGD) Log.d(LOG_TAG, "SMS status report requested");
+ }
+ bo.write(mtiByte);
+
+ // space for TP-Message-Reference
+ bo.write(0);
+
+ byte[] daBytes;
+
+ daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
+
+ // destination address length in BCD digits, ignoring TON byte and pad
+ // TODO Should be better.
+ bo.write((daBytes.length - 1) * 2
+ - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
+
+ // destination address
+ bo.write(daBytes, 0, daBytes.length);
+
+ // TP-Protocol-Identifier
+ bo.write(0);
+ return bo;
+ }
+
+ static class PduParser {
+ byte pdu[];
+
+ int cur;
+
+ SmsHeader userDataHeader;
+
+ byte[] userData;
+
+ int mUserDataSeptetPadding;
+
+ int mUserDataSize;
+
+ PduParser(String s) {
+ this(SimUtils.hexStringToBytes(s));
+ }
+
+ PduParser(byte[] pdu) {
+ this.pdu = pdu;
+ cur = 0;
+ mUserDataSeptetPadding = 0;
+ }
+
+ /**
+ * Parse and return the SC address prepended to SMS messages coming via
+ * the TS 27.005 / AT interface. Returns null on invalid address
+ */
+ String getSCAddress() {
+ int len;
+ String ret;
+
+ // length of SC Address
+ len = getByte();
+
+ if (len == 0) {
+ // no SC address
+ ret = null;
+ } else {
+ // SC address
+ try {
+ ret = PhoneNumberUtils
+ .calledPartyBCDToString(pdu, cur, len);
+ } catch (RuntimeException tr) {
+ Log.d(LOG_TAG, "invalid SC address: ", tr);
+ ret = null;
+ }
+ }
+
+ cur += len;
+
+ return ret;
+ }
+
+ /**
+ * returns non-sign-extended byte value
+ */
+ int getByte() {
+ return pdu[cur++] & 0xff;
+ }
+
+ /**
+ * Any address except the SC address (eg, originating address) See TS
+ * 23.040 9.1.2.5
+ */
+ SmsAddress getAddress() {
+ SmsAddress ret;
+
+ // "The Address-Length field is an integer representation of
+ // the number field, i.e. excludes any semi octet containing only
+ // fill bits."
+ // The TOA field is not included as part of this
+ int addressLength = pdu[cur] & 0xff;
+ int lengthBytes = 2 + (addressLength + 1) / 2;
+
+ ret = new SmsAddress(pdu, cur, lengthBytes);
+
+ cur += lengthBytes;
+
+ return ret;
+ }
+
+ /**
+ * Parses an SC timestamp and returns a currentTimeMillis()-style
+ * timestamp
+ */
+
+ long getSCTimestampMillis() {
+ // TP-Service-Centre-Time-Stamp
+ int year = SimUtils.bcdByteToInt(pdu[cur++]);
+ int month = SimUtils.bcdByteToInt(pdu[cur++]);
+ int day = SimUtils.bcdByteToInt(pdu[cur++]);
+ int hour = SimUtils.bcdByteToInt(pdu[cur++]);
+ int minute = SimUtils.bcdByteToInt(pdu[cur++]);
+ int second = SimUtils.bcdByteToInt(pdu[cur++]);
+
+ // For the timezone, the most significant bit of the
+ // least signficant nibble is the sign byte
+ // (meaning the max range of this field is 79 quarter-hours,
+ // which is more than enough)
+
+ byte tzByte = pdu[cur++];
+
+ // Mask out sign bit.
+ int timezoneOffset = SimUtils
+ .bcdByteToInt((byte) (tzByte & (~0x08)));
+
+ timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset
+ : -timezoneOffset;
+
+ Time time = new Time(Time.TIMEZONE_UTC);
+
+ // It's 2006. Should I really support years < 2000?
+ time.year = year >= 90 ? year + 1900 : year + 2000;
+ time.month = month - 1;
+ time.monthDay = day;
+ time.hour = hour;
+ time.minute = minute;
+ time.second = second;
+
+ // Timezone offset is in quarter hours.
+ return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
+ }
+
+ /**
+ * Pulls the user data out of the PDU, and separates the payload from
+ * the header if there is one.
+ *
+ * @param hasUserDataHeader true if there is a user data header
+ * @param dataInSeptets true if the data payload is in septets instead
+ * of octets
+ * @return the number of septets or octets in the user data payload
+ */
+ int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets)
+ {
+ int offset = cur;
+ int userDataLength = pdu[offset++] & 0xff;
+ int headerSeptets = 0;
+
+ if (hasUserDataHeader) {
+ int userDataHeaderLength = pdu[offset++] & 0xff;
+
+ byte[] udh = new byte[userDataHeaderLength];
+ System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
+ userDataHeader = SmsHeader.parse(udh);
+ offset += userDataHeaderLength;
+
+ int headerBits = (userDataHeaderLength + 1) * 8;
+ headerSeptets = headerBits / 7;
+ headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
+ mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
+ }
+
+ /*
+ * Here we just create the user data length to be the remainder of
+ * the pdu minus the user data hearder. This is because the count
+ * could mean the number of uncompressed sepets if the userdata is
+ * encoded in 7-bit.
+ */
+ userData = new byte[pdu.length - offset];
+ System.arraycopy(pdu, offset, userData, 0, userData.length);
+ cur = offset;
+
+ if (dataInSeptets) {
+ // Return the number of septets
+ return userDataLength - headerSeptets;
+ } else {
+ // Return the number of octets
+ return userData.length;
+ }
+ }
+
+ /**
+ * Returns the user data payload, not including the headers
+ *
+ * @return the user data payload, not including the headers
+ */
+ byte[] getUserData() {
+ return userData;
+ }
+
+ /**
+ * Returns the number of padding bits at the begining of the user data
+ * array before the start of the septets.
+ *
+ * @return the number of padding bits at the begining of the user data
+ * array before the start of the septets
+ */
+ int getUserDataSeptetPadding() {
+ return mUserDataSeptetPadding;
+ }
+
+ /**
+ * Returns an object representing the user data headers
+ *
+ * @return an object representing the user data headers
+ *
+ * {@hide}
+ */
+ SmsHeader getUserDataHeader() {
+ return userDataHeader;
+ }
+
+/*
+ XXX Not sure what this one is supposed to be doing, and no one is using
+ it.
+ String getUserDataGSM8bit() {
+ // System.out.println("remainder of pud:" +
+ // HexDump.dumpHexString(pdu, cur, pdu.length - cur));
+ int count = pdu[cur++] & 0xff;
+ int size = pdu[cur++];
+
+ // skip over header for now
+ cur += size;
+
+ if (pdu[cur - 1] == 0x01) {
+ int tid = pdu[cur++] & 0xff;
+ int type = pdu[cur++] & 0xff;
+
+ size = pdu[cur++] & 0xff;
+
+ int i = cur;
+
+ while (pdu[i++] != '\0') {
+ }
+
+ int length = i - cur;
+ String mimeType = new String(pdu, cur, length);
+
+ cur += length;
+
+ if (false) {
+ System.out.println("tid = 0x" + HexDump.toHexString(tid));
+ System.out.println("type = 0x" + HexDump.toHexString(type));
+ System.out.println("header size = " + size);
+ System.out.println("mimeType = " + mimeType);
+ System.out.println("remainder of header:" +
+ HexDump.dumpHexString(pdu, cur, (size - mimeType.length())));
+ }
+
+ cur += size - mimeType.length();
+
+ // System.out.println("data count = " + count + " cur = " + cur
+ // + " :" + HexDump.dumpHexString(pdu, cur, pdu.length - cur));
+
+ MMSMessage msg = MMSMessage.parseEncoding(mContext, pdu, cur,
+ pdu.length - cur);
+ } else {
+ System.out.println(new String(pdu, cur, pdu.length - cur - 1));
+ }
+
+ return SimUtils.bytesToHexString(pdu);
+ }
+*/
+
+ /**
+ * Interprets the user data payload as pack GSM 7bit characters, and
+ * decodes them into a String.
+ *
+ * @param septetCount the number of septets in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataGSM7Bit(int septetCount) {
+ String ret;
+
+ ret = GsmAlphabet.gsm7BitPackedToString(pdu, cur, septetCount,
+ mUserDataSeptetPadding);
+
+ cur += (septetCount * 7) / 8;
+
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as UCS2 characters, and
+ * decodes them into a String.
+ *
+ * @param byteCount the number of bytes in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataUCS2(int byteCount) {
+ String ret;
+
+ try {
+ ret = new String(pdu, cur, byteCount, "utf-16");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+ }
+
+ cur += byteCount;
+ return ret;
+ }
+
+ boolean moreDataPresent() {
+ return (pdu.length > cur);
+ }
+ }
+
+ /**
+ * Returns the address of the SMS service center that relayed this message
+ * or null if there is none.
+ */
+ public String getServiceCenterAddress() {
+ return scAddress;
+ }
+
+ /**
+ * Returns the originating address (sender) of this SMS message in String
+ * form or null if unavailable
+ */
+ public String getOriginatingAddress() {
+ if (originatingAddress == null) {
+ return null;
+ }
+
+ return originatingAddress.getAddressString();
+ }
+
+ /**
+ * Returns the originating address, or email from address if this message
+ * was from an email gateway. Returns null if originating address
+ * unavailable.
+ */
+ public String getDisplayOriginatingAddress() {
+ if (isEmail) {
+ return emailFrom;
+ } else {
+ return getOriginatingAddress();
+ }
+ }
+
+ /**
+ * Returns the message body as a String, if it exists and is text based.
+ * @return message body is there is one, otherwise null
+ */
+ public String getMessageBody() {
+ return messageBody;
+ }
+
+ /**
+ * Returns the class of this message.
+ */
+ public MessageClass getMessageClass() {
+ return messageClass;
+ }
+
+ /**
+ * Returns the message body, or email message body if this message was from
+ * an email gateway. Returns null if message body unavailable.
+ */
+ public String getDisplayMessageBody() {
+ if (isEmail) {
+ return emailBody;
+ } else {
+ return getMessageBody();
+ }
+ }
+
+ /**
+ * Unofficial convention of a subject line enclosed in parens empty string
+ * if not present
+ */
+ public String getPseudoSubject() {
+ return pseudoSubject == null ? "" : pseudoSubject;
+ }
+
+ /**
+ * Returns the service centre timestamp in currentTimeMillis() format
+ */
+ public long getTimestampMillis() {
+ return scTimeMillis;
+ }
+
+ /**
+ * Returns true if message is an email.
+ *
+ * @return true if this message came through an email gateway and email
+ * sender / subject / parsed body are available
+ */
+ public boolean isEmail() {
+ return isEmail;
+ }
+
+ /**
+ * @return if isEmail() is true, body of the email sent through the gateway.
+ * null otherwise
+ */
+ public String getEmailBody() {
+ return emailBody;
+ }
+
+ /**
+ * @return if isEmail() is true, email from address of email sent through
+ * the gateway. null otherwise
+ */
+ public String getEmailFrom() {
+ return emailFrom;
+ }
+
+ /**
+ * Get protocol identifier.
+ */
+ public int getProtocolIdentifier() {
+ return protocolIdentifier;
+ }
+
+ /**
+ * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+ * SMS
+ */
+ public boolean isReplace() {
+ return (protocolIdentifier & 0xc0) == 0x40
+ && (protocolIdentifier & 0x3f) > 0
+ && (protocolIdentifier & 0x3f) < 8;
+ }
+
+ /**
+ * Returns true for CPHS MWI toggle message.
+ *
+ * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
+ * B.4.2
+ */
+ public boolean isCphsMwiMessage() {
+ return originatingAddress.isCphsVoiceMessageClear()
+ || originatingAddress.isCphsVoiceMessageSet();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) clear message
+ */
+ public boolean isMWIClearMessage() {
+ if (isMwi && (mwiSense == false)) {
+ return true;
+ }
+
+ return originatingAddress != null
+ && originatingAddress.isCphsVoiceMessageClear();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) set message
+ */
+ public boolean isMWISetMessage() {
+ if (isMwi && (mwiSense == true)) {
+ return true;
+ }
+
+ return originatingAddress != null
+ && originatingAddress.isCphsVoiceMessageSet();
+ }
+
+ /**
+ * returns true if this message is a "Message Waiting Indication Group:
+ * Discard Message" notification and should not be stored.
+ */
+ public boolean isMwiDontStore() {
+ if (isMwi && mwiDontStore) {
+ return true;
+ }
+
+ if (isCphsMwiMessage()) {
+ // See CPHS 4.2 Section B.4.2.1
+ // If the user data is a single space char, do not store
+ // the message. Otherwise, store and display as usual
+ if (" ".equals(getMessageBody())) {
+ ;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * returns the user data section minus the user data header if one was
+ * present.
+ */
+ public byte[] getUserData() {
+ return userData;
+ }
+
+ /**
+ * Returns an object representing the user data header
+ *
+ * @return an object representing the user data header
+ *
+ * {@hide}
+ */
+ public SmsHeader getUserDataHeader() {
+ return userDataHeader;
+ }
+
+ /**
+ * Returns the raw PDU for the message.
+ *
+ * @return the raw PDU for the message.
+ */
+ public byte[] getPdu() {
+ return mPdu;
+ }
+
+ /**
+ * Returns the status of the message on the SIM (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the SIM. These are:
+ * SmsManager.STATUS_ON_SIM_FREE
+ * SmsManager.STATUS_ON_SIM_READ
+ * SmsManager.STATUS_ON_SIM_UNREAD
+ * SmsManager.STATUS_ON_SIM_SEND
+ * SmsManager.STATUS_ON_SIM_UNSENT
+ */
+ public int getStatusOnSim() {
+ return statusOnSim;
+ }
+
+ /**
+ * Returns the record index of the message on the SIM (1-based index).
+ * @return the record index of the message on the SIM, or -1 if this
+ * SmsMessage was not created from a SIM SMS EF record.
+ */
+ public int getIndexOnSim() {
+ return indexOnSim;
+ }
+
+ /**
+ * For an SMS-STATUS-REPORT message, this returns the status field from
+ * the status report. This field indicates the status of a previousely
+ * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
+ * description of values.
+ *
+ * @return 0 indicates the previously sent message was received.
+ * See TS 23.040, 9.9.2.3.15 for a description of other possible
+ * values.
+ */
+ public int getStatus() {
+ return status;
+ }
+
+ /**
+ * Return true iff the message is a SMS-STATUS-REPORT message.
+ */
+ public boolean isStatusReportMessage() {
+ return isStatusReportMessage;
+ }
+
+ /**
+ * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+ * this message.
+ */
+ public boolean isReplyPathPresent() {
+ return replyPathPresent;
+ }
+
+ /**
+ * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
+ * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
+ * ME/TA converts each octet of TP data unit into two IRA character long
+ * hexad number (e.g. octet with integer value 42 is presented to TE as two
+ * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
+ * something else...
+ */
+ private void parsePdu(byte[] pdu) {
+ mPdu = pdu;
+ // Log.d(LOG_TAG, "raw sms mesage:");
+ // Log.d(LOG_TAG, s);
+
+ PduParser p = new PduParser(pdu);
+
+ scAddress = p.getSCAddress();
+
+ if (scAddress != null) {
+ if (Config.LOGD) Log.d(LOG_TAG, "SMS SC address: " + scAddress);
+ }
+
+ // TODO(mkf) support reply path, user data header indicator
+
+ // TP-Message-Type-Indicator
+ // 9.2.3
+ int firstByte = p.getByte();
+
+ mti = firstByte & 0x3;
+ switch (mti) {
+ // TP-Message-Type-Indicator
+ // 9.2.3
+ case 0:
+ parseSmsDeliver(p, firstByte);
+ break;
+ case 2:
+ parseSmsStatusReport(p, firstByte);
+ break;
+ default:
+ // TODO(mkf) the rest of these
+ throw new RuntimeException("Unsupported message type");
+ }
+ }
+
+ /**
+ * Parses a SMS-STATUS-REPORT message.
+ *
+ * @param p A PduParser, cued past the first byte.
+ * @param firstByte The first byte of the PDU, which contains MTI, etc.
+ */
+ private void parseSmsStatusReport(PduParser p, int firstByte) {
+ isStatusReportMessage = true;
+
+ // TP-Status-Report-Qualifier bit == 0 for SUBMIT
+ forSubmit = (firstByte & 0x20) == 0x00;
+ // TP-Message-Reference
+ messageRef = p.getByte();
+ // TP-Recipient-Address
+ recipientAddress = p.getAddress();
+ // TP-Service-Centre-Time-Stamp
+ scTimeMillis = p.getSCTimestampMillis();
+ // TP-Discharge-Time
+ dischargeTimeMillis = p.getSCTimestampMillis();
+ // TP-Status
+ status = p.getByte();
+
+ // The following are optional fields that may or may not be present.
+ if (p.moreDataPresent()) {
+ // TP-Parameter-Indicator
+ int extraParams = p.getByte();
+ int moreExtraParams = extraParams;
+ while ((moreExtraParams & 0x80) != 0) {
+ // We only know how to parse a few extra parameters, all
+ // indicated in the first TP-PI octet, so skip over any
+ // additional TP-PI octets.
+ moreExtraParams = p.getByte();
+ }
+ // TP-Protocol-Identifier
+ if ((extraParams & 0x01) != 0) {
+ protocolIdentifier = p.getByte();
+ }
+ // TP-Data-Coding-Scheme
+ if ((extraParams & 0x02) != 0) {
+ dataCodingScheme = p.getByte();
+ }
+ // TP-User-Data-Length (implies existence of TP-User-Data)
+ if ((extraParams & 0x04) != 0) {
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+ parseUserData(p, hasUserDataHeader);
+ }
+ }
+ }
+
+ private void parseSmsDeliver(PduParser p, int firstByte) {
+ replyPathPresent = (firstByte & 0x80) == 0x80;
+
+ originatingAddress = p.getAddress();
+
+ if (originatingAddress != null) {
+ if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
+ + originatingAddress.address);
+ }
+
+ // TP-Protocol-Identifier (TP-PID)
+ // TS 23.040 9.2.3.9
+ protocolIdentifier = p.getByte();
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ dataCodingScheme = p.getByte();
+
+ if (Config.LOGV) {
+ Log.v(LOG_TAG, "SMS TP-PID:" + protocolIdentifier
+ + " data coding scheme: " + dataCodingScheme);
+ }
+
+ scTimeMillis = p.getSCTimestampMillis();
+
+ if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
+
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+
+ parseUserData(p, hasUserDataHeader);
+ }
+
+ /**
+ * Parses the User Data of an SMS.
+ *
+ * @param p The current PduParser.
+ * @param hasUserDataHeader Indicates whether a header is present in the
+ * User Data.
+ */
+ private void parseUserData(PduParser p, boolean hasUserDataHeader) {
+ boolean hasMessageClass = false;
+ boolean userDataCompressed = false;
+
+ int encodingType = ENCODING_UNKNOWN;
+
+ // Look up the data encoding scheme
+ if ((dataCodingScheme & 0x80) == 0) {
+ // Bits 7..4 == 0xxx
+ automaticDeletion = (0 != (dataCodingScheme & 0x40));
+ userDataCompressed = (0 != (dataCodingScheme & 0x20));
+ hasMessageClass = (0 != (dataCodingScheme & 0x10));
+
+ if (userDataCompressed) {
+ Log.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
+ + "(compression) " + (dataCodingScheme & 0xff));
+ } else {
+ switch ((dataCodingScheme >> 2) & 0x3) {
+ case 0: // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ break;
+
+ case 2: // UCS 2 (16bit)
+ encodingType = ENCODING_16BIT;
+ break;
+
+ case 1: // 8 bit data
+ case 3: // reserved
+ Log.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+ + (dataCodingScheme & 0xff));
+ encodingType = ENCODING_8BIT;
+ break;
+ }
+ }
+ } else if ((dataCodingScheme & 0xf0) == 0xf0) {
+ automaticDeletion = false;
+ hasMessageClass = true;
+ userDataCompressed = false;
+
+ if (0 == (dataCodingScheme & 0x04)) {
+ // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ } else {
+ // 8 bit data
+ encodingType = ENCODING_8BIT;
+ }
+ } else if ((dataCodingScheme & 0xF0) == 0xC0
+ || (dataCodingScheme & 0xF0) == 0xD0
+ || (dataCodingScheme & 0xF0) == 0xE0) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+
+ // 0xC0 == 7 bit, don't store
+ // 0xD0 == 7 bit, store
+ // 0xE0 == UCS-2, store
+
+ if ((dataCodingScheme & 0xF0) == 0xE0) {
+ encodingType = ENCODING_16BIT;
+ } else {
+ encodingType = ENCODING_7BIT;
+ }
+
+ userDataCompressed = false;
+ boolean active = ((dataCodingScheme & 0x08) == 0x08);
+
+ // bit 0x04 reserved
+
+ if ((dataCodingScheme & 0x03) == 0x00) {
+ isMwi = true;
+ mwiSense = active;
+ mwiDontStore = ((dataCodingScheme & 0xF0) == 0xC0);
+ } else {
+ isMwi = false;
+
+ Log.w(LOG_TAG, "MWI for fax, email, or other "
+ + (dataCodingScheme & 0xff));
+ }
+ } else {
+ Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
+ + (dataCodingScheme & 0xff));
+ }
+
+ // set both the user data and the user data header.
+ int count = p.constructUserData(hasUserDataHeader,
+ encodingType == ENCODING_7BIT);
+ this.userData = p.getUserData();
+ this.userDataHeader = p.getUserDataHeader();
+
+ switch (encodingType) {
+ case ENCODING_UNKNOWN:
+ case ENCODING_8BIT:
+ messageBody = null;
+ break;
+
+ case ENCODING_7BIT:
+ messageBody = p.getUserDataGSM7Bit(count);
+ break;
+
+ case ENCODING_16BIT:
+ messageBody = p.getUserDataUCS2(count);
+ break;
+ }
+
+ if (Config.LOGV) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'");
+
+ if (messageBody != null) {
+ parseMessageBody();
+ }
+
+ if (!hasMessageClass) {
+ messageClass = MessageClass.UNKNOWN;
+ } else {
+ switch (dataCodingScheme & 0x3) {
+ case 0:
+ messageClass = MessageClass.CLASS_0;
+ break;
+ case 1:
+ messageClass = MessageClass.CLASS_1;
+ break;
+ case 2:
+ messageClass = MessageClass.CLASS_2;
+ break;
+ case 3:
+ messageClass = MessageClass.CLASS_3;
+ break;
+ }
+ }
+ }
+
+ private void parseMessageBody() {
+ if (originatingAddress.couldBeEmailGateway()) {
+ extractEmailAddressFromMessageBody();
+ }
+ }
+
+ /**
+ * Try to parse this message as an email gateway message -> Neither
+ * of the standard ways are currently supported: There are two ways
+ * specified in TS 23.040 Section 3.8 (not supported via this mechanism) -
+ * SMS message "may have its TP-PID set for internet electronic mail - MT
+ * SMS format: [<from-address><space>]<message> - "Depending on the
+ * nature of the gateway, the destination/origination address is either
+ * derived from the content of the SMS TP-OA or TP-DA field, or the
+ * TP-OA/TP-DA field contains a generic gateway address and the to/from
+ * address is added at the beginning as shown above." - multiple addreses
+ * separated by commas, no spaces - subject field delimited by '()' or '##'
+ * and '#' Section 9.2.3.24.11
+ */
+ private void extractEmailAddressFromMessageBody() {
+
+ /*
+ * a little guesswork here. I haven't found doc for this.
+ * the format could be either
+ *
+ * 1. [x@y][ ]/[subject][ ]/[body]
+ * -or-
+ * 2. [x@y][ ]/[body]
+ */
+ int slash = 0, slash2 = 0, atSymbol = 0;
+
+ try {
+ slash = messageBody.indexOf(" /");
+ if (slash == -1) {
+ return;
+ }
+
+ atSymbol = messageBody.indexOf('@');
+ if (atSymbol == -1 || atSymbol > slash) {
+ return;
+ }
+
+ emailFrom = messageBody.substring(0, slash);
+
+ slash2 = messageBody.indexOf(" /", slash + 2);
+
+ if (slash2 == -1) {
+ pseudoSubject = null;
+ emailBody = messageBody.substring(slash + 2);
+ } else {
+ pseudoSubject = messageBody.substring(slash + 2, slash2);
+ emailBody = messageBody.substring(slash2 + 2);
+ }
+
+ isEmail = true;
+ } catch (Exception ex) {
+ Log.w(LOG_TAG,
+ "extractEmailAddressFromMessageBody: exception slash="
+ + slash + ", atSymbol=" + atSymbol + ", slash2="
+ + slash2, ex);
+ }
+ }
+
+}
diff --git a/telephony/java/android/telephony/gsm/package.html b/telephony/java/android/telephony/gsm/package.html
new file mode 100644
index 0000000..9860417
--- /dev/null
+++ b/telephony/java/android/telephony/gsm/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides APIs for utilizing GSM-specific telephony features, such as
+text/data/PDU SMS messages.
+</BODY>
+</HTML> \ No newline at end of file
diff --git a/telephony/java/android/telephony/package.html b/telephony/java/android/telephony/package.html
new file mode 100644
index 0000000..aee4a6f
--- /dev/null
+++ b/telephony/java/android/telephony/package.html
@@ -0,0 +1,7 @@
+<HTML>
+<BODY>
+Provides APIs for monitoring the basic phone information, such as
+the network type and connection state, plus utilities
+for manipulating phone number strings.
+</BODY>
+</HTML> \ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ATParseEx.java b/telephony/java/com/android/internal/telephony/ATParseEx.java
new file mode 100644
index 0000000..c93b875
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ATParseEx.java
@@ -0,0 +1,35 @@
+/*
+ * 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.internal.telephony;
+
+/**
+ * {@hide}
+ */
+public class ATParseEx extends RuntimeException
+{
+ public
+ ATParseEx()
+ {
+ super();
+ }
+
+ public
+ ATParseEx(String s)
+ {
+ super(s);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/ATResponseParser.java b/telephony/java/com/android/internal/telephony/ATResponseParser.java
new file mode 100644
index 0000000..93ec455
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ATResponseParser.java
@@ -0,0 +1,186 @@
+/*
+ * 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.internal.telephony;
+
+/**
+ * {@hide}
+ */
+public class ATResponseParser
+{
+ /*************************** Instance Variables **************************/
+
+ private String line;
+ private int next = 0;
+ private int tokStart, tokEnd;
+
+ /***************************** Class Methods *****************************/
+
+ public
+ ATResponseParser (String line)
+ {
+ this.line = line;
+ }
+
+ public boolean
+ nextBoolean()
+ {
+ // "\s*(\d)(,|$)"
+ // \d is '0' or '1'
+
+ nextTok();
+
+ if (tokEnd - tokStart > 1) {
+ throw new ATParseEx();
+ }
+ char c = line.charAt(tokStart);
+
+ if (c == '0') return false;
+ if (c == '1') return true;
+ throw new ATParseEx();
+ }
+
+
+ /** positive int only */
+ public int
+ nextInt()
+ {
+ // "\s*(\d+)(,|$)"
+ int ret = 0;
+
+ nextTok();
+
+ for (int i = tokStart ; i < tokEnd ; i++) {
+ char c = line.charAt(i);
+
+ // Yes, ASCII decimal digits only
+ if (c < '0' || c > '9') {
+ throw new ATParseEx();
+ }
+
+ ret *= 10;
+ ret += c - '0';
+ }
+
+ return ret;
+ }
+
+ public String
+ nextString()
+ {
+ nextTok();
+
+ return line.substring(tokStart, tokEnd);
+ }
+
+ public boolean
+ hasMore()
+ {
+ return next < line.length();
+ }
+
+ private void
+ nextTok()
+ {
+ int len = line.length();
+
+ if (next == 0) {
+ skipPrefix();
+ }
+
+ if (next >= len) {
+ throw new ATParseEx();
+ }
+
+ try {
+ // \s*("([^"]*)"|(.*)\s*)(,|$)
+
+ char c = line.charAt(next++);
+ boolean hasQuote = false;
+
+ c = skipWhiteSpace(c);
+
+ if (c == '"') {
+ if (next >= len) {
+ throw new ATParseEx();
+ }
+ c = line.charAt(next++);
+ tokStart = next - 1;
+ while (c != '"' && next < len) {
+ c = line.charAt(next++);
+ }
+ if (c != '"') {
+ throw new ATParseEx();
+ }
+ tokEnd = next - 1;
+ if (next < len && line.charAt(next++) != ',') {
+ throw new ATParseEx();
+ }
+ } else {
+ tokStart = next - 1;
+ tokEnd = tokStart;
+ while (c != ',') {
+ if (!Character.isWhitespace(c)) {
+ tokEnd = next;
+ }
+ if (next == len) {
+ break;
+ }
+ c = line.charAt(next++);
+ }
+ }
+ } catch (StringIndexOutOfBoundsException ex) {
+ throw new ATParseEx();
+ }
+ }
+
+
+ /** Throws ATParseEx if whitespace extends to the end of string */
+ private char
+ skipWhiteSpace (char c)
+ {
+ int len;
+ len = line.length();
+ while (next < len && Character.isWhitespace(c)) {
+ c = line.charAt(next++);
+ }
+
+ if (Character.isWhitespace(c)) {
+ throw new ATParseEx();
+ }
+ return c;
+ }
+
+
+ private void
+ skipPrefix()
+ {
+ // consume "^[^:]:"
+
+ next = 0;
+ int s = line.length();
+ while (next < s){
+ char c = line.charAt(next++);
+
+ if (c == ':') {
+ return;
+ }
+ }
+
+ throw new ATParseEx("missing prefix");
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/Call.java b/telephony/java/com/android/internal/telephony/Call.java
new file mode 100644
index 0000000..82aeb25
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/Call.java
@@ -0,0 +1,177 @@
+/*
+ * 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.internal.telephony;
+
+import java.util.List;
+/**
+ * {@hide}
+ */
+public abstract class Call {
+ /* Enums */
+
+ public enum State {
+ IDLE, ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING, DISCONNECTED;
+
+ public boolean isAlive() {
+ return !(this == IDLE || this == DISCONNECTED);
+ }
+
+ public boolean isRinging() {
+ return this == INCOMING || this == WAITING;
+ }
+
+ public boolean isDialing() {
+ return this == DIALING || this == ALERTING;
+ }
+ }
+
+ /* Instance Methods */
+
+ /** Do not modify the List result!!! This list is not yours to keep
+ * It will change across event loop iterations top
+ */
+
+ public abstract List<Connection> getConnections();
+ public abstract State getState();
+ public abstract Phone getPhone();
+
+ /**
+ * hasConnection
+ *
+ * @param c a Connection object
+ * @return true if the call contains the connection object passed in
+ */
+ public boolean hasConnection(Connection c) {
+ return c.getCall() == this;
+ }
+
+ /**
+ * hasConnections
+ * @return true if the call contains one or more connections
+ */
+ public boolean hasConnections() {
+ List connections = getConnections();
+
+ if (connections == null) {
+ return false;
+ }
+
+ return connections.size() > 0;
+ }
+
+ /**
+ * isIdle
+ *
+ * FIXME rename
+ * @return true if the call contains only disconnected connections (if any)
+ */
+ public boolean isIdle() {
+ return !getState().isAlive();
+ }
+
+ /**
+ * Returns the Connection associated with this Call that was created
+ * first, or null if there are no Connections in this Call
+ */
+ public Connection
+ getEarliestConnection() {
+ List l;
+ long time = Long.MAX_VALUE;
+ Connection c;
+ Connection earliest = null;
+
+ l = getConnections();
+
+ if (l.size() == 0) {
+ return null;
+ }
+
+ for (int i = 0, s = l.size() ; i < s ; i++) {
+ c = (Connection) l.get(i);
+ long t;
+
+ t = c.getCreateTime();
+
+ if (t < time) {
+ earliest = c;
+ }
+ }
+
+ return earliest;
+ }
+
+ public long
+ getEarliestCreateTime() {
+ List l;
+ long time = Long.MAX_VALUE;
+
+ l = getConnections();
+
+ if (l.size() == 0) {
+ return 0;
+ }
+
+ for (int i = 0, s = l.size() ; i < s ; i++) {
+ Connection c = (Connection) l.get(i);
+ long t;
+
+ t = c.getCreateTime();
+
+ time = t < time ? t : time;
+ }
+
+ return time;
+ }
+
+ public long
+ getEarliestConnectTime() {
+ List l;
+ long time = Long.MAX_VALUE;
+
+ l = getConnections();
+
+ if (l.size() == 0) {
+ return 0;
+ }
+
+ for (int i = 0, s = l.size() ; i < s ; i++) {
+ Connection c = (Connection) l.get(i);
+ long t;
+
+ t = c.getConnectTime();
+
+ time = t < time ? t : time;
+ }
+
+ return time;
+ }
+
+ public abstract boolean isMultiparty();
+
+ public abstract void hangup() throws CallStateException;
+
+ public boolean
+ isDialingOrAlerting() {
+ return getState().isDialing();
+ }
+
+ public boolean
+ isRinging() {
+ return getState().isRinging();
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/CallStateException.java b/telephony/java/com/android/internal/telephony/CallStateException.java
new file mode 100644
index 0000000..6087124
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/CallStateException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.internal.telephony;
+
+/**
+ * {@hide}
+ */
+public class CallStateException extends Exception
+{
+ public
+ CallStateException()
+ {
+ }
+
+ public
+ CallStateException(String string)
+ {
+ super(string);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
new file mode 100644
index 0000000..5f630f8
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -0,0 +1,271 @@
+/*
+ * 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.internal.telephony;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.Contacts;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.text.TextUtils;
+import android.telephony.TelephonyManager;
+import android.telephony.PhoneNumberUtils;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * Looks up caller information for the given phone number.
+ *
+ * {@hide}
+ */
+public class CallerInfo {
+ private static final String TAG = "CallerInfo";
+
+ public static final String UNKNOWN_NUMBER = "-1";
+ public static final String PRIVATE_NUMBER = "-2";
+
+ public String name;
+ public String phoneNumber;
+ public String phoneLabel;
+ /* Split up the phoneLabel into number type and label name */
+ public int numberType;
+ public String numberLabel;
+
+ public int photoResource;
+ public long person_id;
+ public boolean needUpdate;
+ public Uri contactRefUri;
+
+ // fields to hold individual contact preference data,
+ // including the send to voicemail flag and the ringtone
+ // uri reference.
+ public Uri contactRingtoneUri;
+ public boolean shouldSendToVoicemail;
+
+ /**
+ * Drawable representing the caller image. This is essentially
+ * a cache for the image data tied into the connection /
+ * callerinfo object. The isCachedPhotoCurrent flag indicates
+ * if the image data needs to be reloaded.
+ */
+ public Drawable cachedPhoto;
+ public boolean isCachedPhotoCurrent;
+
+ // Don't keep checking VM if it's going to throw an exception for this proc.
+ private static boolean sSkipVmCheck = false;
+
+ public CallerInfo() {
+ }
+
+ /**
+ * getCallerInfo given a Cursor.
+ * @param context the context used to retrieve string constants
+ * @param contactRef the URI to attach to this CallerInfo object
+ * @param cursor the first object in the cursor is used to build the CallerInfo object.
+ * @return the CallerInfo which contains the caller id for the given
+ * number. The returned CallerInfo is null if no number is supplied.
+ */
+ public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
+
+ CallerInfo info = new CallerInfo();
+ info.photoResource = 0;
+ info.phoneLabel = null;
+ info.numberType = 0;
+ info.numberLabel = null;
+ info.cachedPhoto = null;
+ info.isCachedPhotoCurrent = false;
+
+ if (Config.LOGV) Log.v(TAG, "construct callerInfo from cursor");
+
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+
+ int columnIndex;
+
+ // Look for the name
+ columnIndex = cursor.getColumnIndex(People.NAME);
+ if (columnIndex != -1) {
+ info.name = cursor.getString(columnIndex);
+ }
+
+ // Look for the number
+ columnIndex = cursor.getColumnIndex(Phones.NUMBER);
+ if (columnIndex != -1) {
+ info.phoneNumber = cursor.getString(columnIndex);
+ }
+
+ // Look for the label/type combo
+ columnIndex = cursor.getColumnIndex(Phones.LABEL);
+ if (columnIndex != -1) {
+ int typeColumnIndex = cursor.getColumnIndex(Phones.TYPE);
+ if (typeColumnIndex != -1) {
+ info.numberType = cursor.getInt(typeColumnIndex);
+ info.numberLabel = cursor.getString(columnIndex);
+ info.phoneLabel = Contacts.Phones.getDisplayLabel(context,
+ info.numberType, info.numberLabel)
+ .toString();
+ }
+ }
+
+ // Look for the person ID
+ columnIndex = cursor.getColumnIndex(Phones.PERSON_ID);
+ if (columnIndex != -1) {
+ info.person_id = cursor.getLong(columnIndex);
+ } else {
+ columnIndex = cursor.getColumnIndex(People._ID);
+ if (columnIndex != -1) {
+ info.person_id = cursor.getLong(columnIndex);
+ }
+ }
+
+ // look for the custom ringtone, create from the string stored
+ // in the database.
+ columnIndex = cursor.getColumnIndex(People.CUSTOM_RINGTONE);
+ if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
+ info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
+ } else {
+ info.contactRingtoneUri = null;
+ }
+
+ // look for the send to voicemail flag, set it to true only
+ // under certain circumstances.
+ columnIndex = cursor.getColumnIndex(People.SEND_TO_VOICEMAIL);
+ info.shouldSendToVoicemail = (columnIndex != -1) &&
+ ((cursor.getInt(columnIndex)) == 1);
+ }
+ cursor.close();
+ }
+
+ info.needUpdate = false;
+ info.name = normalize(info.name);
+ info.contactRefUri = contactRef;
+
+ return info;
+ }
+
+ /**
+ * getCallerInfo given a URI, look up in the call-log database
+ * for the uri unique key.
+ * @param context the context used to get the ContentResolver
+ * @param contactRef the URI used to lookup caller id
+ * @return the CallerInfo which contains the caller id for the given
+ * number. The returned CallerInfo is null if no number is supplied.
+ */
+ public static CallerInfo getCallerInfo(Context context, Uri contactRef) {
+
+ return getCallerInfo(context, contactRef,
+ context.getContentResolver().query(contactRef, null, null, null, null));
+ }
+
+ /**
+ * getCallerInfo given a phone number, look up in the call-log database
+ * for the matching caller id info.
+ * @param context the context used to get the ContentResolver
+ * @param number the phone number used to lookup caller id
+ * @return the CallerInfo which contains the caller id for the given
+ * number. The returned CallerInfo is null if no number is supplied. If
+ * a matching number is not found, then a generic caller info is returned,
+ * with all relevant fields empty or null.
+ */
+ public static CallerInfo getCallerInfo(Context context, String number) {
+ if (TextUtils.isEmpty(number)) {
+ return null;
+ } else {
+ // Change the callerInfo number ONLY if it is an emergency number
+ // or if it is the voicemail number. If it is either, take a
+ // shortcut and skip the query.
+ if (PhoneNumberUtils.isEmergencyNumber(number)) {
+ CallerInfo ci = new CallerInfo();
+ ci.phoneNumber = context.getString(
+ com.android.internal.R.string.emergency_call_dialog_number_for_display);
+ return ci;
+ } else {
+ try {
+ if (!sSkipVmCheck && PhoneNumberUtils.compare(number,
+ TelephonyManager.getDefault().getVoiceMailNumber())) {
+ CallerInfo ci = new CallerInfo();
+ ci.name = TelephonyManager.getDefault().getVoiceMailAlphaTag();
+ // TODO: FIND ANOTHER ICON
+ //info.photoResource = android.R.drawable.badge_voicemail;
+ return ci;
+ }
+ } catch (SecurityException ex) {
+ // Don't crash if this process doesn't have permission to
+ // retrieve VM number. It's still allowed to look up caller info.
+ // But don't try it again.
+ sSkipVmCheck = true;
+ }
+ }
+ }
+
+ Uri contactUri = Uri.withAppendedPath(Contacts.Phones.CONTENT_FILTER_URL, number);
+
+ CallerInfo info = getCallerInfo(context, contactUri);
+
+ // if no query results were returned with a viable number,
+ // fill in the original number value we used to query with.
+ if (TextUtils.isEmpty(info.phoneNumber)) {
+ info.phoneNumber = number;
+ }
+
+ return info;
+ }
+
+ /**
+ * getCallerId: a convenience method to get the caller id for a given
+ * number.
+ *
+ * @param context the context used to get the ContentResolver.
+ * @param number a phone number.
+ * @return if the number belongs to a contact, the contact's name is
+ * returned; otherwise, the number itself is returned.
+ *
+ * TODO NOTE: This MAY need to refer to the Asynchronous Query API
+ * [startQuery()], instead of getCallerInfo, but since it looks like
+ * it is only being used by the provider calls in the messaging app:
+ * 1. android.provider.Telephony.Mms.getDisplayAddress()
+ * 2. android.provider.Telephony.Sms.getDisplayAddress()
+ * We may not need to make the change.
+ */
+ public static String getCallerId(Context context, String number) {
+ CallerInfo info = getCallerInfo(context, number);
+ String callerID = null;
+
+ if (info != null) {
+ String name = info.name;
+
+ if (!TextUtils.isEmpty(name)) {
+ callerID = name;
+ } else {
+ callerID = number;
+ }
+ }
+
+ return callerID;
+ }
+
+ private static String normalize(String s) {
+ if (s == null || s.length() > 0) {
+ return s;
+ } else {
+ return null;
+ }
+ }
+}
+
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
new file mode 100644
index 0000000..6cb829d
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -0,0 +1,388 @@
+/*
+ * 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.internal.telephony;
+
+import android.content.AsyncQueryHandler;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Contacts;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * ASYNCHRONOUS QUERY API
+ */
+
+public class CallerInfoAsyncQuery {
+
+ private static final boolean DBG = false;
+ private static final String LOG_TAG = "CallerInfoAsyncQuery";
+
+ private static final int EVENT_NEW_QUERY = 1;
+ private static final int EVENT_ADD_LISTENER = 2;
+ private static final int EVENT_END_OF_QUEUE = 3;
+ private static final int EVENT_EMERGENCY_NUMBER = 4;
+ private static final int EVENT_VOICEMAIL_NUMBER = 5;
+
+ private CallerInfoAsyncQueryHandler mHandler;
+
+ // Don't keep checking VM if it's going to throw an exception for this proc.
+ private static boolean sSkipVmCheck = false;
+
+ /**
+ * Interface for a CallerInfoAsyncQueryHandler result return.
+ */
+ public interface OnQueryCompleteListener {
+ /**
+ * Called when the query is complete.
+ */
+ public void onQueryComplete(int token, Object cookie, CallerInfo ci);
+ }
+
+
+ /**
+ * Wrap the cookie from the WorkerArgs with additional information needed by our
+ * classes.
+ */
+ private static final class CookieWrapper {
+ public OnQueryCompleteListener listener;
+ public Object cookie;
+ public int event;
+ public String number;
+ }
+
+
+ /**
+ * Simple exception used to communicate problems with the query pool.
+ */
+ public static class QueryPoolException extends SQLException {
+ public QueryPoolException(String error) {
+ super(error);
+ }
+ }
+
+ /**
+ * Our own implementation of the AsyncQueryHandler.
+ */
+ private class CallerInfoAsyncQueryHandler extends AsyncQueryHandler {
+
+ /**
+ * The information relevant to each CallerInfo query. Each query may have multiple
+ * listeners, so each AsyncCursorInfo is associated with 2 or more CookieWrapper
+ * objects in the queue (one with a new query event, and one with a end event, with
+ * 0 or more additional listeners in between).
+ */
+ private Context mQueryContext;
+ private Uri mQueryUri;
+ private CallerInfo mCallerInfo;
+
+ /**
+ * Our own query worker thread.
+ *
+ * This thread handles the messages enqueued in the looper. The normal sequence
+ * of events is that a new query shows up in the looper queue, followed by 0 or
+ * more add listener requests, and then an end request. Of course, these requests
+ * can be interlaced with requests from other tokens, but is irrelevant to this
+ * handler since the handler has no state.
+ *
+ * Note that we depend on the queue to keep things in order; in other words, the
+ * looper queue must be FIFO with respect to input from the synchronous startQuery
+ * calls and output to this handleMessage call.
+ *
+ * This use of the queue is required because CallerInfo objects may be accessed
+ * multiple times before the query is complete. All accesses (listeners) must be
+ * queued up and informed in order when the query is complete.
+ */
+ protected class CallerInfoWorkerHandler extends WorkerHandler {
+ public CallerInfoWorkerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ WorkerArgs args = (WorkerArgs) msg.obj;
+ CookieWrapper cw = (CookieWrapper) args.cookie;
+
+ if (cw == null) {
+ // Normally, this should never be the case for calls originating
+ // from within this code.
+ // However, if there is any code that this Handler calls (such as in
+ // super.handleMessage) that DOES place unexpected messages on the
+ // queue, then we need pass these messages on.
+ if (DBG) log("Unexpected command (CookieWrapper is null): " + msg.what +
+ " ignored by CallerInfoWorkerHandler, passing onto parent.");
+
+ super.handleMessage(msg);
+ } else {
+
+ if (DBG) log("Processing event: " + cw.event + " token (arg1): " + msg.arg1 +
+ " command: " + msg.what + " query URI: " + args.uri);
+
+ switch (cw.event) {
+ case EVENT_NEW_QUERY:
+ //start the sql command.
+ super.handleMessage(msg);
+ break;
+
+ // shortcuts to avoid query for recognized numbers.
+ case EVENT_EMERGENCY_NUMBER:
+ case EVENT_VOICEMAIL_NUMBER:
+
+ case EVENT_ADD_LISTENER:
+ case EVENT_END_OF_QUEUE:
+ // query was already completed, so just send the reply.
+ // passing the original token value back to the caller
+ // on top of the event values in arg1.
+ Message reply = args.handler.obtainMessage(msg.what);
+ reply.obj = args;
+ reply.arg1 = msg.arg1;
+
+ reply.sendToTarget();
+
+ break;
+ default:
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Asynchronous query handler class for the contact / callerinfo object.
+ */
+ private CallerInfoAsyncQueryHandler(Context context) {
+ super(context.getContentResolver());
+ }
+
+ @Override
+ protected Handler createHandler(Looper looper) {
+ return new CallerInfoWorkerHandler(looper);
+ }
+
+ /**
+ * Overrides onQueryComplete from AsyncQueryHandler.
+ *
+ * This method takes into account the state of this class; we construct the CallerInfo
+ * object only once for each set of listeners. When the query thread has done its work
+ * and calls this method, we inform the remaining listeners in the queue, until we're
+ * out of listeners. Once we get the message indicating that we should expect no new
+ * listeners for this CallerInfo object, we release the AsyncCursorInfo back into the
+ * pool.
+ */
+ @Override
+ protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ if (DBG) log("query complete for token: " + token);
+
+ //get the cookie and notify the listener.
+ CookieWrapper cw = (CookieWrapper) cookie;
+ if (cw == null) {
+ // Normally, this should never be the case for calls originating
+ // from within this code.
+ // However, if there is any code that calls this method, we should
+ // check the parameters to make sure they're viable.
+ if (DBG) log("Cookie is null, ignoring onQueryComplete() request.");
+ return;
+ }
+
+ if (cw.event == EVENT_END_OF_QUEUE) {
+ release();
+ return;
+ }
+
+ // check the token and if needed, create the callerinfo object.
+ if (mCallerInfo == null) {
+ if ((mQueryContext == null) || (mQueryUri == null)) {
+ throw new QueryPoolException
+ ("Bad context or query uri, or CallerInfoAsyncQuery already released.");
+ }
+
+ // adjust the callerInfo data as needed, and only if it was set from the
+ // initial query request.
+ // Change the callerInfo number ONLY if it is an emergency number or the
+ // voicemail number, and adjust other data (including photoResource)
+ // accordingly.
+ if (cw.event == EVENT_EMERGENCY_NUMBER) {
+ mCallerInfo = new CallerInfo();
+ mCallerInfo.phoneNumber = mQueryContext.getString(com.android.internal
+ .R.string.emergency_call_dialog_number_for_display);
+ mCallerInfo.photoResource = com.android.internal.R.drawable.picture_emergency;
+
+ } else if (cw.event == EVENT_VOICEMAIL_NUMBER) {
+ mCallerInfo = new CallerInfo();
+ try {
+ mCallerInfo.name = TelephonyManager.getDefault().getVoiceMailAlphaTag();
+ } catch (SecurityException ex) {
+ // Should never happen: if this process does not have
+ // permission to retrieve VM tag, it should not have
+ // permission to retrieve VM number and would not generate
+ // an EVENT_VOICEMAIL_NUMBER. But if it happens, don't crash.
+ }
+ } else {
+ mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);
+ // Use the number entered by the user for display.
+ if (!TextUtils.isEmpty(cw.number)) {
+ mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number);
+ }
+ }
+
+ if (DBG) log("constructing CallerInfo object for token: " + token);
+
+ //notify that we can clean up the queue after this.
+ CookieWrapper endMarker = new CookieWrapper();
+ endMarker.event = EVENT_END_OF_QUEUE;
+ startQuery (token, endMarker, null, null, null, null, null);
+ }
+
+ //notify the listener that the query is complete.
+ if (cw.listener != null) {
+ if (DBG) log("notifying listener: " + cw.listener.getClass().toString() +
+ " for token: " + token);
+ cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
+ }
+ }
+ }
+
+ /**
+ * Private constructor for factory methods.
+ */
+ private CallerInfoAsyncQuery() {
+ }
+
+
+ /**
+ * Factory method to start query with a Uri query spec
+ */
+ public static CallerInfoAsyncQuery startQuery(int token, Context context, Uri contactRef,
+ OnQueryCompleteListener listener, Object cookie) {
+
+ CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
+ c.allocate(context, contactRef);
+
+ if (DBG) log("starting query for URI: " + contactRef + " handler: " + c.toString());
+
+ //create cookieWrapper, start query
+ CookieWrapper cw = new CookieWrapper();
+ cw.listener = listener;
+ cw.cookie = cookie;
+ cw.event = EVENT_NEW_QUERY;
+
+ c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
+
+ return c;
+ }
+
+ /**
+ * Factory method to start query with a number
+ */
+ public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
+ OnQueryCompleteListener listener, Object cookie) {
+ //contruct the URI object and start Query.
+ Uri contactRef = Uri.withAppendedPath(Contacts.Phones.CONTENT_FILTER_URL, number);
+
+ CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
+ c.allocate(context, contactRef);
+
+ if (DBG) log("starting query for number: " + number + " handler: " + c.toString());
+
+ //create cookieWrapper, start query
+ CookieWrapper cw = new CookieWrapper();
+ cw.listener = listener;
+ cw.cookie = cookie;
+ cw.number = number;
+
+ // check to see if these are recognized numbers, and use shortcuts if we can.
+ if (PhoneNumberUtils.isEmergencyNumber(number)) {
+ cw.event = EVENT_EMERGENCY_NUMBER;
+ } else {
+ String vmNumber = null;
+ if (!sSkipVmCheck){
+ try {
+ vmNumber = TelephonyManager.getDefault().getVoiceMailNumber();
+ } catch (SecurityException ex) {
+ // Don't crash if this process doesn't have permission to
+ // retrieve VM number. It's still allowed to look up caller info.
+ // But don't try it again.
+ sSkipVmCheck = true;
+ }
+ }
+ if (PhoneNumberUtils.compare(number, vmNumber)) {
+ cw.event = EVENT_VOICEMAIL_NUMBER;
+ } else {
+ cw.event = EVENT_NEW_QUERY;
+ }
+ }
+
+ c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
+
+ return c;
+ }
+
+ /**
+ * Method to add listeners to a currently running query
+ */
+ public void addQueryListener(int token, OnQueryCompleteListener listener, Object cookie) {
+
+ if (DBG) log("adding listener to query: " + mHandler.mQueryUri + " handler: " +
+ mHandler.toString());
+
+ //create cookieWrapper, add query request to end of queue.
+ CookieWrapper cw = new CookieWrapper();
+ cw.listener = listener;
+ cw.cookie = cookie;
+ cw.event = EVENT_ADD_LISTENER;
+
+ mHandler.startQuery (token, cw, null, null, null, null, null);
+ }
+
+ /**
+ * Method to create a new CallerInfoAsyncQueryHandler object, ensuring correct
+ * state of context and uri.
+ */
+ private void allocate (Context context, Uri contactRef) {
+ if ((context == null) || (contactRef == null)){
+ throw new QueryPoolException("Bad context or query uri.");
+ }
+ mHandler = new CallerInfoAsyncQueryHandler(context);
+ mHandler.mQueryContext = context;
+ mHandler.mQueryUri = contactRef;
+ }
+
+ /**
+ * Releases the relevant data.
+ */
+ private void release () {
+ mHandler.mQueryContext = null;
+ mHandler.mQueryUri = null;
+ mHandler.mCallerInfo = null;
+ mHandler = null;
+ }
+
+ /**
+ * static logging method
+ */
+ private static void log(String msg) {
+ Log.d(LOG_TAG, msg);
+ }
+}
+
diff --git a/telephony/java/com/android/internal/telephony/Connection.java b/telephony/java/com/android/internal/telephony/Connection.java
new file mode 100644
index 0000000..d53d4ab
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/Connection.java
@@ -0,0 +1,221 @@
+/*
+ * 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.internal.telephony;
+
+/**
+ * {@hide}
+ */
+public abstract class Connection
+{
+ public enum DisconnectCause {
+ NOT_DISCONNECTED, /* has not yet disconnected */
+ INCOMING_MISSED, /* an incoming call that was missed and never answered */
+ NORMAL, /* normal; remote */
+ LOCAL, /* normal; local hangup */
+ BUSY, /* outgoing call to busy line */
+ CONGESTION, /* outgoing call to congested network */
+ MMI, /* not presently used; dial() returns null */
+ INVALID_NUMBER, /* invalid dial string */
+ LOST_SIGNAL,
+ LIMIT_EXCEEDED, /* eg GSM ACM limit exceeded */
+ INCOMING_REJECTED, /* an incoming call that was rejected */
+ POWER_OFF, /* radio is turned off explicitly */
+ OUT_OF_SERVICE, /* out of service */
+ SIM_ERROR, /* No SIM, SIM locked, or other SIM error */
+ CALL_BARRED, /* call was blocked by call barrring */
+ FDN_BLOCKED /* call was blocked by fixed dial number */
+ }
+
+ Object userData;
+
+ /* Instance Methods */
+
+ /**
+ * Gets address (e.g., phone number) associated with connection
+ * TODO: distinguish reasons for unavailablity
+ *
+ * @return address or null if unavailable
+ */
+
+ public abstract String getAddress();
+
+ /**
+ * @return Call that owns this Connection, or null if none
+ */
+ public abstract Call getCall();
+
+ /**
+ * Connection create time in currentTimeMillis() format
+ * Basically, set when object is created.
+ * Effectively, when an incoming call starts ringing or an
+ * outgoing call starts dialing
+ */
+ public abstract long getCreateTime();
+
+ /**
+ * Connection connect time in currentTimeMillis() format
+ * For outgoing calls: Begins at (DIALING|ALERTING) -> ACTIVE transition
+ * For incoming calls: Begins at (INCOMING|WAITING) -> ACTIVE transition
+ * Returns 0 before then
+ */
+ public abstract long getConnectTime();
+
+ /**
+ * Disconnect time in currentTimeMillis() format
+ * The time when this Connection makes a transition into ENDED or FAIL
+ * Returns 0 before then
+ */
+ public abstract long getDisconnectTime();
+
+ /**
+ * returns the number of milliseconds the call has been connected,
+ * or 0 if the call has never connected.
+ * If the call is still connected, then returns the elapsed
+ * time since connect
+ */
+ public abstract long getDurationMillis();
+
+ /**
+ * If this connection is HOLDING, return the number of milliseconds
+ * that it has been on hold for (approximently)
+ * If this connection is in any other state, return 0
+ */
+
+ public abstract long getHoldDurationMillis();
+
+ /**
+ * Returns "NOT_DISCONNECTED" if not yet disconnected
+ */
+ public abstract DisconnectCause getDisconnectCause();
+
+ /**
+ * Returns true of this connection originated elsewhere
+ * ("MT" or mobile terminated; another party called this terminal)
+ * or false if this call originated here (MO or mobile originated)
+ */
+ public abstract boolean isIncoming();
+
+ /**
+ * If this Connection is connected, then it is associated with
+ * a Call.
+ *
+ * Returns getCall().getState() or Call.State.IDLE if not
+ * connected
+ */
+ public Call.State getState()
+ {
+ Call c;
+
+ c = getCall();
+
+ if (c == null) {
+ return Call.State.IDLE;
+ } else {
+ return c.getState();
+ }
+ }
+
+ /**
+ * isAlive()
+ *
+ * @return true if the connection isn't disconnected
+ * (could be active, holding, ringing, dialing, etc)
+ */
+ public boolean
+ isAlive()
+ {
+ return getState().isAlive();
+ }
+
+ /**
+ * Returns true if Connection is connected and is INCOMING or WAITING
+ */
+ public boolean
+ isRinging()
+ {
+ return getState().isRinging();
+ }
+
+ /**
+ *
+ * @return the userdata set in setUserData()
+ */
+ public Object getUserData()
+ {
+ return userData;
+ }
+
+ /**
+ *
+ * @param userdata user can store an any userdata in the Connection object.
+ */
+ public void setUserData(Object userdata)
+ {
+ this.userData = userdata;
+ }
+
+ /**
+ * Hangup individual Connection
+ */
+ public abstract void hangup() throws CallStateException;
+
+ /**
+ * Separate this call from its owner Call and assigns it to a new Call
+ * (eg if it is currently part of a Conference call
+ * TODO: Throw exception? Does GSM require error display on failure here?
+ */
+ public abstract void separate() throws CallStateException;
+
+ public enum PostDialState {
+ NOT_STARTED, /* The post dial string playback hasn't
+ been started, or this call is not yet
+ connected, or this is an incoming call */
+ STARTED, /* The post dial string playback has begun */
+ WAIT, /* The post dial string playback is waiting for a
+ call to proceedAfterWaitChar() */
+ WILD, /* The post dial string playback is waiting for a
+ call to proceedAfterWildChar() */
+ COMPLETE, /* The post dial string playback is complete */
+ CANCELLED /* The post dial string playback was cancelled
+ with cancelPostDial() */
+ }
+
+ public abstract PostDialState getPostDialState();
+
+ /**
+ * Returns the portion of the post dial string that has not
+ * yet been dialed, or "" if none
+ */
+ public abstract String getRemainingPostDialString();
+
+ /**
+ * See Phone.setOnPostDialWaitCharacter()
+ */
+
+ public abstract void proceedAfterWaitChar();
+
+ /**
+ * See Phone.setOnPostDialWildCharacter()
+ */
+ public abstract void proceedAfterWildChar(String str);
+ /**
+ * Cancel any post
+ */
+ public abstract void cancelPostDial();
+
+
+}
diff --git a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
new file mode 100644
index 0000000..81ef623
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -0,0 +1,224 @@
+/*
+ * 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.internal.telephony;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephonyRegistry;
+
+/**
+ * broadcast intents
+ */
+public class DefaultPhoneNotifier implements PhoneNotifier {
+
+ static final String LOG_TAG = "GSM";
+ private static final boolean DBG = true;
+ private ITelephonyRegistry mRegistry;
+
+ /*package*/
+ DefaultPhoneNotifier() {
+ mRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
+ "telephony.registry"));
+ }
+
+ public void notifyPhoneState(Phone sender) {
+ Call ringingCall = sender.getRingingCall();
+ String incomingNumber = "";
+ if (ringingCall != null && ringingCall.getEarliestConnection() != null){
+ incomingNumber = ringingCall.getEarliestConnection().getAddress();
+ }
+ try {
+ mRegistry.notifyCallState(convertCallState(sender.getState()), incomingNumber);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyServiceState(Phone sender) {
+ try {
+ mRegistry.notifyServiceState(sender.getServiceState());
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifySignalStrength(Phone sender) {
+ try {
+ mRegistry.notifySignalStrength(sender.getSignalStrengthASU());
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyMessageWaitingChanged(Phone sender) {
+ try {
+ mRegistry.notifyMessageWaitingChanged(sender.getMessageWaitingIndicator());
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyCallForwardingChanged(Phone sender) {
+ try {
+ mRegistry.notifyCallForwardingChanged(sender.getCallForwardingIndicator());
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyDataActivity(Phone sender) {
+ try {
+ mRegistry.notifyDataActivity(convertDataActivityState(sender.getDataActivityState()));
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyDataConnection(Phone sender, String reason) {
+ try {
+ mRegistry.notifyDataConnection(convertDataState(sender.getDataConnectionState()),
+ sender.isDataConnectivityPossible(), reason, sender.getActiveApn(),
+ sender.getInterfaceName(null));
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyDataConnectionFailed(Phone sender, String reason) {
+ try {
+ mRegistry.notifyDataConnectionFailed(reason);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyCellLocation(Phone sender) {
+ Bundle data = new Bundle();
+ sender.getCellLocation().fillInNotifierBundle(data);
+ try {
+ mRegistry.notifyCellLocation(data);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ private void log(String s) {
+ Log.d(LOG_TAG, "[PhoneNotifier] " + s);
+ }
+
+ /**
+ * Convert the {@link State} enum into the TelephonyManager.CALL_STATE_* constants
+ * for the public API.
+ */
+ public static int convertCallState(Phone.State state) {
+ switch (state) {
+ case RINGING:
+ return TelephonyManager.CALL_STATE_RINGING;
+ case OFFHOOK:
+ return TelephonyManager.CALL_STATE_OFFHOOK;
+ default:
+ return TelephonyManager.CALL_STATE_IDLE;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.CALL_STATE_* constants into the {@link State} enum
+ * for the public API.
+ */
+ public static Phone.State convertCallState(int state) {
+ switch (state) {
+ case TelephonyManager.CALL_STATE_RINGING:
+ return Phone.State.RINGING;
+ case TelephonyManager.CALL_STATE_OFFHOOK:
+ return Phone.State.OFFHOOK;
+ default:
+ return Phone.State.IDLE;
+ }
+ }
+
+ /**
+ * Convert the {@link DataState} enum into the TelephonyManager.DATA_* constants
+ * for the public API.
+ */
+ public static int convertDataState(Phone.DataState state) {
+ switch (state) {
+ case CONNECTING:
+ return TelephonyManager.DATA_CONNECTING;
+ case CONNECTED:
+ return TelephonyManager.DATA_CONNECTED;
+ case SUSPENDED:
+ return TelephonyManager.DATA_SUSPENDED;
+ default:
+ return TelephonyManager.DATA_DISCONNECTED;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.DATA_* constants into {@link DataState} enum
+ * for the public API.
+ */
+ public static Phone.DataState convertDataState(int state) {
+ switch (state) {
+ case TelephonyManager.DATA_CONNECTING:
+ return Phone.DataState.CONNECTING;
+ case TelephonyManager.DATA_CONNECTED:
+ return Phone.DataState.CONNECTED;
+ case TelephonyManager.DATA_SUSPENDED:
+ return Phone.DataState.SUSPENDED;
+ default:
+ return Phone.DataState.DISCONNECTED;
+ }
+ }
+
+ /**
+ * Convert the {@link DataState} enum into the TelephonyManager.DATA_* constants
+ * for the public API.
+ */
+ public static int convertDataActivityState(Phone.DataActivityState state) {
+ switch (state) {
+ case DATAIN:
+ return TelephonyManager.DATA_ACTIVITY_IN;
+ case DATAOUT:
+ return TelephonyManager.DATA_ACTIVITY_OUT;
+ case DATAINANDOUT:
+ return TelephonyManager.DATA_ACTIVITY_INOUT;
+ default:
+ return TelephonyManager.DATA_ACTIVITY_NONE;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.DATA_* constants into the {@link DataState} enum
+ * for the public API.
+ */
+ public static Phone.DataActivityState convertDataActivityState(int state) {
+ switch (state) {
+ case TelephonyManager.DATA_ACTIVITY_IN:
+ return Phone.DataActivityState.DATAIN;
+ case TelephonyManager.DATA_ACTIVITY_OUT:
+ return Phone.DataActivityState.DATAOUT;
+ case TelephonyManager.DATA_ACTIVITY_INOUT:
+ return Phone.DataActivityState.DATAINANDOUT;
+ default:
+ return Phone.DataActivityState.NONE;
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
new file mode 100644
index 0000000..e0884b3
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony;
+
+import android.os.Bundle;
+import android.telephony.ServiceState;
+
+oneway interface IPhoneStateListener {
+ void onServiceStateChanged(in ServiceState serviceState);
+ void onSignalStrengthChanged(int asu);
+ void onMessageWaitingIndicatorChanged(boolean mwi);
+ void onCallForwardingIndicatorChanged(boolean cfi);
+
+ // we use bundle here instead of CellLocation so it can get the right subclass
+ void onCellLocationChanged(in Bundle location);
+ void onCallStateChanged(int state, String incomingNumber);
+ void onDataConnectionStateChanged(int state);
+ void onDataActivity(int direction);
+}
+
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
new file mode 100644
index 0000000..00cbaf9
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony;
+
+/**
+ * Interface used to retrieve various phone-related subscriber information.
+ *
+ */
+interface IPhoneSubInfo {
+
+ /**
+ * Retrieves the unique device ID, e.g., IMEI for GSM phones.
+ */
+ String getDeviceId();
+
+ /**
+ * Retrieves the software version number for the device, e.g., IMEI/SV
+ * for GSM phones.
+ */
+ String getDeviceSvn();
+
+ /**
+ * Retrieves the unique sbuscriber ID, e.g., IMSI for GSM phones.
+ */
+ String getSubscriberId();
+
+ /**
+ * Retrieves the serial number of the SIM, if applicable.
+ */
+ String getSimSerialNumber();
+
+ /**
+ * Retrieves the phone number string for line 1.
+ */
+ String getLine1Number();
+
+ /**
+ * Retrieves the alpha identifier for line 1.
+ */
+ String getLine1AlphaTag();
+
+ /**
+ * Retrieves the voice mail number.
+ */
+ String getVoiceMailNumber();
+
+ /**
+ * Retrieves the alpha identifier associated with the voice mail number.
+ */
+ String getVoiceMailAlphaTag();
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
new file mode 100644
index 0000000..663fc03
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony;
+
+import android.os.Bundle;
+
+/**
+ * Interface used to interact with the phone. Mostly this is used by the
+ * TelephonyManager class. A few places are still using this directly.
+ * Please clean them up if possible and use TelephonyManager insteadl.
+ *
+ * {@hide}
+ */
+interface ITelephony {
+
+ /**
+ * Dial a number. This doesn't place the call. It displays
+ * the Dialer screen.
+ * @param number the number to be dialed. If null, this
+ * would display the Dialer screen with no number pre-filled.
+ */
+ void dial(String number);
+
+ /**
+ * Place a call to the numer.
+ * @param number the number to be called.
+ */
+ void call(String number);
+
+ /**
+ * If there is currently a call in progress, show the call screen.
+ * Returns true if the call screen was shown.
+ */
+ boolean showCallScreen();
+
+ /**
+ * End call or go to the Home screen
+ *
+ * @return whether it hung up
+ */
+ boolean endCall();
+
+ /**
+ * Check if we are in either an active or holding call
+ * @return true if the phone state is OFFHOOK.
+ */
+ boolean isOffhook();
+
+ /**
+ * Check if an incoming phone call is ringing or call waiting.
+ * @return true if the phone state is RINGING.
+ */
+ boolean isRinging();
+
+ /**
+ * Check if the phone is idle.
+ * @return true if the phone state is IDLE.
+ */
+ boolean isIdle();
+
+ /**
+ * Check to see if the radio is on or not.
+ * @return returns true if the radio is on.
+ */
+ boolean isRadioOn();
+
+ /**
+ * Check if the SIM pin lock is enabled.
+ * @return true if the SIM pin lock is enabled.
+ */
+ boolean isSimPinEnabled();
+
+ /**
+ * Cancels the missed calls notification.
+ */
+ void cancelMissedCallsNotification();
+
+ /**
+ * Supply a pin to unlock the SIM. Blocks until a result is determined.
+ * @param pin The pin to check.
+ * @return whether the operation was a success.
+ */
+ boolean supplyPin(String pin);
+
+ /**
+ * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated
+ * without SEND (so <code>dial</code> is not appropriate).
+ *
+ * @param dialString the MMI command to be executed.
+ * @return true if MMI command is executed.
+ */
+ boolean handlePinMmi(String dialString);
+
+ /**
+ * Toggles the radio on or off.
+ */
+ void toggleRadioOnOff();
+
+ /**
+ * Set the radio to on or off
+ */
+ boolean setRadio(boolean turnOn);
+
+ /**
+ * Request to update location information in service state
+ */
+ void updateServiceLocation();
+
+ /**
+ * Enable location update notifications.
+ */
+ void enableLocationUpdates();
+
+ /**
+ * Disable location update notifications.
+ */
+ void disableLocationUpdates();
+
+ /**
+ * Enable a specific APN type.
+ */
+ int enableApnType(String type);
+
+ /**
+ * Disable a specific APN type.
+ */
+ int disableApnType(String type);
+
+ /**
+ * Allow mobile data connections.
+ */
+ boolean enableDataConnectivity();
+
+ /**
+ * Disallow mobile data connections.
+ */
+ boolean disableDataConnectivity();
+
+ /**
+ * Report whether data connectivity is possible.
+ */
+ boolean isDataConnectivityPossible();
+
+ Bundle getCellLocation();
+
+ int getCallState();
+ int getDataActivity();
+ int getDataState();
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
new file mode 100644
index 0000000..1b011fe
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.ServiceState;
+import com.android.internal.telephony.IPhoneStateListener;
+
+interface ITelephonyRegistry {
+ void listen(String pkg, IPhoneStateListener callback, int events, boolean notifyNow);
+
+ void notifyCallState(int state, String incomingNumber);
+ void notifyServiceState(in ServiceState state);
+ void notifySignalStrength(int signalStrengthASU);
+ void notifyMessageWaitingChanged(boolean mwi);
+ void notifyCallForwardingChanged(boolean cfi);
+ void notifyDataActivity(int state);
+ void notifyDataConnection(int state, boolean isDataConnectivityPossible,
+ String reason, String apn, String interfaceName);
+ void notifyDataConnectionFailed(String reason);
+ void notifyCellLocation(in Bundle cellLocation);
+}
diff --git a/telephony/java/com/android/internal/telephony/MmiCode.java b/telephony/java/com/android/internal/telephony/MmiCode.java
new file mode 100644
index 0000000..925b06f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/MmiCode.java
@@ -0,0 +1,62 @@
+/*
+ * 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.internal.telephony;
+
+/**
+ * {@hide}
+ */
+public interface MmiCode
+{
+ /**
+ * {@hide}
+ */
+ public enum State {
+ PENDING,
+ CANCELLED,
+ COMPLETE,
+ FAILED
+ }
+
+
+ /**
+ * @return Current state of MmiCode request
+ */
+ public State getState();
+
+ /**
+ * @return Localized message for UI display, valid only in COMPLETE
+ * or FAILED states. null otherwise
+ */
+
+ public CharSequence getMessage();
+
+ /**
+ * Cancels pending MMI request.
+ * State becomes CANCELLED unless already COMPLETE or FAILED
+ */
+ public void cancel();
+
+ /**
+ * @return true if the network response is a REQUEST for more user input.
+ */
+ public boolean isUssdRequest();
+
+ /**
+ * @return true if an outstanding request can be canceled.
+ */
+ public boolean isCancelable();
+}
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
new file mode 100644
index 0000000..9a36bc1
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -0,0 +1,1128 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.CellLocation;
+import android.telephony.ServiceState;
+import com.android.internal.telephony.gsm.NetworkInfo;
+import com.android.internal.telephony.gsm.PdpConnection;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+
+import java.util.List;
+
+/**
+ * Internal interface used to control the phone; SDK developers cannot
+ * obtain this interface.
+ *
+ * {@hide}
+ *
+ */
+public interface Phone {
+
+ /** used to enable additional debug messages */
+ static final boolean DEBUG_PHONE = true;
+
+
+ /**
+ * The phone state. One of the following:<p>
+ * <ul>
+ * <li>IDLE = no phone activity</li>
+ * <li>RINGING = a phone call is ringing or call waiting.
+ * In the latter case, another call is active as well</li>
+ * <li>OFFHOOK = The phone is off hook. At least one call
+ * exists that is dialing, active or holding and no calls are
+ * ringing or waiting.</li>
+ * </ul>
+ */
+ enum State {
+ IDLE, RINGING, OFFHOOK;
+ };
+
+ /**
+ * The state of a data connection.
+ * <ul>
+ * <li>CONNECTED = IP traffic should be available</li>
+ * <li>CONNECTING = Currently setting up data connection</li>
+ * <li>DISCONNECTED = IP not available</li>
+ * <li>SUSPENDED = connection is created but IP traffic is
+ * temperately not available. i.e. voice call is in place
+ * in 2G network</li>
+ * </ul>
+ */
+ enum DataState {
+ CONNECTED, CONNECTING, DISCONNECTED, SUSPENDED;
+ };
+
+ enum DataActivityState {
+ /**
+ * The state of a data activity.
+ * <ul>
+ * <li>NONE = No traffic</li>
+ * <li>DATAIN = Receiving IP ppp traffic</li>
+ * <li>DATAOUT = Sending IP ppp traffic</li>
+ * <li>DATAINANDOUT = Both receiving and sending IP ppp traffic</li>
+ * </ul>
+ */
+ NONE, DATAIN, DATAOUT, DATAINANDOUT;
+ };
+
+ enum SuppService {
+ UNKNOWN, SWITCH, SEPARATE, TRANSFER, CONFERENCE, REJECT, HANGUP;
+ };
+
+ static final String STATE_KEY = "state";
+ static final String PHONE_NAME_KEY = "phoneName";
+ static final String FAILURE_REASON_KEY = "reason";
+ static final String STATE_CHANGE_REASON_KEY = "reason";
+ static final String DATA_APN_TYPE_KEY = "apnType";
+ static final String DATA_APN_KEY = "apn";
+ static final String DATA_IFACE_NAME_KEY = "iface";
+ static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
+
+ /**
+ * APN types for data connections. These are usage categories for an APN
+ * entry. One APN entry may support multiple APN types, eg, a single APN
+ * may service regular internet traffic ("default") as well as MMS-specific
+ * connections.<br/>
+ * APN_TYPE_ALL is a special type to indicate that this APN entry can
+ * service all data connections.
+ */
+ static final String APN_TYPE_ALL = "*";
+ /** APN type for default data traffic */
+ static final String APN_TYPE_DEFAULT = "default";
+ /** APN type for MMS traffic */
+ static final String APN_TYPE_MMS = "mms";
+
+ // "Features" accessible through the connectivity manager
+ static final String FEATURE_ENABLE_MMS = "enableMMS";
+
+ /**
+ * Return codes for <code>enableApnType()</code>
+ */
+ static final int APN_ALREADY_ACTIVE = 0;
+ static final int APN_REQUEST_STARTED = 1;
+ static final int APN_TYPE_NOT_AVAILABLE = 2;
+ static final int APN_REQUEST_FAILED = 3;
+
+
+ /**
+ * Optional reasons for disconnect and connect
+ */
+ static final String REASON_ROAMING_ON = "roamingOn";
+ static final String REASON_ROAMING_OFF = "roamingOff";
+ static final String REASON_DATA_DISABLED = "dataDisabled";
+ static final String REASON_DATA_ENABLED = "dataEnabled";
+ static final String REASON_GPRS_ATTACHED = "gprsAttached";
+ static final String REASON_GPRS_DETACHED = "gprsDetached";
+ static final String REASON_APN_CHANGED = "apnChanged";
+ static final String REASON_APN_SWITCHED = "apnSwitched";
+ static final String REASON_RESTORE_DEFAULT_APN = "restoreDefaultApn";
+ static final String REASON_RADIO_TURNED_OFF = "radioTurnedOff";
+ static final String REASON_PDP_RESET = "pdpReset";
+ static final String REASON_VOICE_CALL_ENDED = "2GVoiceCallEnded";
+ static final String REASON_VOICE_CALL_STARTED = "2GVoiceCallStarted";
+
+ // Used for band mode selction methods
+ static final int BM_UNSPECIFIED = 0; // selected by baseband automatically
+ static final int BM_EURO_BAND = 1; // GSM-900 / DCS-1800 / WCDMA-IMT-2000
+ static final int BM_US_BAND = 2; // GSM-850 / PCS-1900 / WCDMA-850 / WCDMA-PCS-1900
+ static final int BM_JPN_BAND = 3; // WCDMA-800 / WCDMA-IMT-2000
+ static final int BM_AUS_BAND = 4; // GSM-900 / DCS-1800 / WCDMA-850 / WCDMA-IMT-2000
+ static final int BM_AUS2_BAND = 5; // GSM-900 / DCS-1800 / WCDMA-850
+ static final int BM_BOUNDARY = 6; // upper band boundary
+
+ // Used for preferred network type
+ static final int NT_AUTO_TYPE = 0; // WCDMA preferred (auto mode)
+ static final int NT_GSM_TYPE = 1; // GSM only
+ static final int NT_WCDMA_TYPE = 2; // WCDMA only
+
+ /**
+ * Get the current ServiceState. Use
+ * <code>registerForServiceStateChanged</code> to be informed of
+ * updates.
+ */
+ ServiceState getServiceState();
+
+ /**
+ * Get the current CellLocation.
+ */
+ CellLocation getCellLocation();
+
+ /**
+ * Get the current DataState. No change notification exists at this
+ * interface -- use
+ * {@link com.android.internal.telephony.PhoneStateIntentReceiver PhoneStateIntentReceiver} instead.
+ */
+ DataState getDataConnectionState();
+
+ /**
+ * Get the current DataActivityState. No change notification exists at this
+ * interface -- use
+ * {@link TelephonyManager} instead.
+ */
+ DataActivityState getDataActivityState();
+
+ /**
+ * Gets the context for the phone, as set at initialization time.
+ */
+ Context getContext();
+
+ /**
+ * Get current coarse-grained voice call state.
+ * Use {@link #registerForPhoneStateChanged(Handler, int, Object)
+ * registerForPhoneStateChanged()} for change notification. <p>
+ * If the phone has an active call and call waiting occurs,
+ * then the phone state is RINGING not OFFHOOK
+ * <strong>Note:</strong>
+ * This registration point provides notification of finer-grained
+ * changes.<p>
+ *
+ */
+ State getState();
+
+ /**
+ * Returns a string identifier for this phone interface for parties
+ * outside the phone app process.
+ * @return The string name.
+ */
+ String getPhoneName();
+
+ /**
+ * Returns an array of string identifiers for the APN types serviced by the
+ * currently active or last connected APN.
+ * @return The string array.
+ */
+ String[] getActiveApnTypes();
+
+ /**
+ * Returns a string identifier for currently active or last connected APN.
+ * @return The string name.
+ */
+ String getActiveApn();
+
+ /**
+ * Get current signal strength. No change notification available on this
+ * interface. Use <code>PhoneStateNotifier</code> or an equivalent.
+ * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
+ * The following special values are defined:</p>
+ * <ul><li>0 means "-113 dBm or less".</li>
+ * <li>31 means "-51 dBm or greater".</li></ul>
+ *
+ * @return Current signal strength in ASU's.
+ */
+ int getSignalStrengthASU();
+
+ /**
+ * Notifies when a previously untracked non-ringing/waiting connection has appeared.
+ * This is likely due to some other entity (eg, SIM card application) initiating a call.
+ */
+ void registerForUnknownConnection(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for unknown connection notifications.
+ */
+ void unregisterForUnknownConnection(Handler h);
+
+ /**
+ * Notifies when any aspect of the voice call state changes.
+ * Resulting events will have an AsyncResult in <code>Message.obj</code>.
+ * AsyncResult.userData will be set to the obj argument here.
+ * The <em>h</em> parameter is held only by a weak reference.
+ */
+ void registerForPhoneStateChanged(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for voice call state change notifications.
+ * Extraneous calls are tolerated silently.
+ */
+ void unregisterForPhoneStateChanged(Handler h);
+
+
+ /**
+ * Notifies when a new ringing or waiting connection has appeared.<p>
+ *
+ * Messages received from this:
+ * Message.obj will be an AsyncResult
+ * AsyncResult.userObj = obj
+ * AsyncResult.result = a Connection. <p>
+ * Please check Connection.isRinging() to make sure the Connection
+ * has not dropped since this message was posted.
+ * If Connection.isRinging() is true, then
+ * Connection.getCall() == Phone.getRingingCall()
+ */
+ void registerForNewRingingConnection(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for new ringing connection notification.
+ * Extraneous calls are tolerated silently
+ */
+
+ void unregisterForNewRingingConnection(Handler h);
+
+ /**
+ * Notifies when an incoming call rings.<p>
+ *
+ * Messages received from this:
+ * Message.obj will be an AsyncResult
+ * AsyncResult.userObj = obj
+ * AsyncResult.result = a Connection. <p>
+ */
+ void registerForIncomingRing(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for ring notification.
+ * Extraneous calls are tolerated silently
+ */
+
+ void unregisterForIncomingRing(Handler h);
+
+
+ /**
+ * Notifies when a voice connection has disconnected, either due to local
+ * or remote hangup or error.
+ *
+ * Messages received from this will have the following members:<p>
+ * <ul><li>Message.obj will be an AsyncResult</li>
+ * <li>AsyncResult.userObj = obj</li>
+ * <li>AsyncResult.result = a Connection object that is
+ * no longer connected.</li></ul>
+ */
+ void registerForDisconnect(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for voice disconnection notification.
+ * Extraneous calls are tolerated silently
+ */
+ void unregisterForDisconnect(Handler h);
+
+
+ /**
+ * Register for notifications of initiation of a new MMI code request.
+ * MMI codes for GSM are discussed in 3GPP TS 22.030.<p>
+ *
+ * Example: If Phone.dial is called with "*#31#", then the app will
+ * be notified here.<p>
+ *
+ * The returned <code>Message.obj</code> will contain an AsyncResult.
+ *
+ * <code>obj.result</code> will be an "MmiCode" object.
+ */
+ void registerForMmiInitiate(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for new MMI initiate notification.
+ * Extraneous calls are tolerated silently
+ */
+ void unregisterForMmiInitiate(Handler h);
+
+ /**
+ * Register for notifications that an MMI request has completed
+ * its network activity and is in its final state. This may mean a state
+ * of COMPLETE, FAILED, or CANCELLED.
+ *
+ * <code>Message.obj</code> will contain an AsyncResult.
+ * <code>obj.result</code> will be an "MmiCode" object
+ */
+ void registerForMmiComplete(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for MMI complete notification.
+ * Extraneous calls are tolerated silently
+ */
+ void unregisterForMmiComplete(Handler h);
+
+ /**
+ * Returns a list of MMI codes that are pending. (They have initiated
+ * but have not yet completed).
+ * Presently there is only ever one.
+ * Use <code>registerForMmiInitiate</code>
+ * and <code>registerForMmiComplete</code> for change notification.
+ */
+ public List<? extends MmiCode> getPendingMmiCodes();
+
+ /**
+ * Sends user response to a USSD REQUEST message. An MmiCode instance
+ * representing this response is sent to handlers registered with
+ * registerForMmiInitiate.
+ *
+ * @param ussdMessge Message to send in the response.
+ */
+ public void sendUssdResponse(String ussdMessge);
+
+ /**
+ * Register for ServiceState changed.
+ * Message.obj will contain an AsyncResult.
+ * AsyncResult.result will be a ServiceState instance
+ */
+ void registerForServiceStateChanged(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for ServiceStateChange notification.
+ * Extraneous calls are tolerated silently
+ */
+ void unregisterForServiceStateChanged(Handler h);
+
+ /**
+ * Register for Supplementary Service notifications from the network.
+ * Message.obj will contain an AsyncResult.
+ * AsyncResult.result will be a SuppServiceNotification instance.
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForSuppServiceNotification(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for Supplementary Service notifications.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForSuppServiceNotification(Handler h);
+
+ /**
+ * Register for notifications when a supplementary service attempt fails.
+ * Message.obj will contain an AsyncResult.
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForSuppServiceFailed(Handler h, int what, Object obj);
+
+ /**
+ * Unregister for notifications when a supplementary service attempt fails.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForSuppServiceFailed(Handler h);
+
+ /**
+ * Returns SIM record load state. Use
+ * <code>getSimCard().registerForReady()</code> for change notification.
+ *
+ * @return true if records from the SIM have been loaded and are
+ * available (if applicable). If not applicable to the underlying
+ * technology, returns true as well.
+ */
+ boolean getSimRecordsLoaded();
+
+ /**
+ * Returns the SIM card interface for this phone, or null
+ * if not applicable to underlying technology.
+ */
+ SimCard getSimCard();
+
+ /**
+ * Answers a ringing or waiting call. Active calls, if any, go on hold.
+ * Answering occurs asynchronously, and final notification occurs via
+ * {@link #registerForPhoneStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPhoneStateChanged()}.
+ *
+ * @exception CallStateException when no call is ringing or waiting
+ */
+ void acceptCall() throws CallStateException;
+
+ /**
+ * Reject (ignore) a ringing call. In GSM, this means UDUB
+ * (User Determined User Busy). Reject occurs asynchronously,
+ * and final notification occurs via
+ * {@link #registerForPhoneStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPhoneStateChanged()}.
+ *
+ * @exception CallStateException when no call is ringing or waiting
+ */
+ void rejectCall() throws CallStateException;
+
+ /**
+ * Places any active calls on hold, and makes any held calls
+ * active. Switch occurs asynchronously and may fail.
+ * Final notification occurs via
+ * {@link #registerForPhoneStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPhoneStateChanged()}.
+ *
+ * @exception CallStateException if a call is ringing, waiting, or
+ * dialing/alerting. In these cases, this operation may not be performed.
+ */
+ void switchHoldingAndActive() throws CallStateException;
+
+ /**
+ * Whether or not the phone can conference in the current phone
+ * state--that is, one call holding and one call active.
+ * @return true if the phone can conference; false otherwise.
+ */
+ boolean canConference();
+
+ /**
+ * Conferences holding and active. Conference occurs asynchronously
+ * and may fail. Final notification occurs via
+ * {@link #registerForPhoneStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPhoneStateChanged()}.
+ *
+ * @exception CallStateException if canConference() would return false.
+ * In these cases, this operation may not be performed.
+ */
+ void conference() throws CallStateException;
+
+ /**
+ * Whether or not the phone can do explicit call transfer in the current
+ * phone state--that is, one call holding and one call active.
+ * @return true if the phone can do explicit call transfer; false otherwise.
+ */
+ boolean canTransfer();
+
+ /**
+ * Connects the two calls and disconnects the subscriber from both calls
+ * Explicit Call Transfer occurs asynchronously
+ * and may fail. Final notification occurs via
+ * {@link #registerForPhoneStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPhoneStateChanged()}.
+ *
+ * @exception CallStateException if canTransfer() would return false.
+ * In these cases, this operation may not be performed.
+ */
+ void explicitCallTransfer() throws CallStateException;
+
+ /**
+ * Clears all DISCONNECTED connections from Call connection lists.
+ * Calls that were in the DISCONNECTED state become idle. This occurs
+ * synchronously.
+ */
+ void clearDisconnected();
+
+
+ /**
+ * Gets the foreground call object, which represents all connections that
+ * are dialing or active (all connections
+ * that have their audio path connected).<p>
+ *
+ * The foreground call is a singleton object. It is constant for the life
+ * of this phone. It is never null.<p>
+ *
+ * The foreground call will only ever be in one of these states:
+ * IDLE, ACTIVE, DIALING, ALERTING, or DISCONNECTED.
+ *
+ * State change notification is available via
+ * {@link #registerForPhoneStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPhoneStateChanged()}.
+ */
+ Call getForegroundCall();
+
+ /**
+ * Gets the background call object, which represents all connections that
+ * are holding (all connections that have been accepted or connected, but
+ * do not have their audio path connected). <p>
+ *
+ * The background call is a singleton object. It is constant for the life
+ * of this phone object . It is never null.<p>
+ *
+ * The background call will only ever be in one of these states:
+ * IDLE, HOLDING or DISCONNECTED.
+ *
+ * State change notification is available via
+ * {@link #registerForPhoneStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPhoneStateChanged()}.
+ */
+ Call getBackgroundCall();
+
+ /**
+ * Gets the ringing call object, which represents an incoming
+ * connection (if present) that is pending answer/accept. (This connection
+ * may be RINGING or WAITING, and there may be only one.)<p>
+
+ * The ringing call is a singleton object. It is constant for the life
+ * of this phone. It is never null.<p>
+ *
+ * The ringing call will only ever be in one of these states:
+ * IDLE, INCOMING, WAITING or DISCONNECTED.
+ *
+ * State change notification is available via
+ * {@link #registerForPhoneStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPhoneStateChanged()}.
+ */
+ Call getRingingCall();
+
+ /**
+ * Initiate a new voice connection. This happens asynchronously, so you
+ * cannot assume the audio path is connected (or a call index has been
+ * assigned) until PhoneStateChanged notification has occurred.
+ *
+ * @exception CallStateException if a new outgoing call is not currently
+ * possible because no more call slots exist or a call exists that is
+ * dialing, alerting, ringing, or waiting. Other errors are
+ * handled asynchronously.
+ */
+ Connection dial(String dialString) throws CallStateException;
+
+ /**
+ * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated
+ * without SEND (so <code>dial</code> is not appropriate).
+ *
+ * @param dialString the MMI command to be executed.
+ * @return true if MMI command is executed.
+ */
+ boolean handlePinMmi(String dialString);
+
+ /**
+ * Handles in-call MMI commands. While in a call, or while receiving a
+ * call, use this to execute MMI commands.
+ * see 3GPP 20.030, section 6.5.5.1 for specs on the allowed MMI commands.
+ *
+ * @param command the MMI command to be executed.
+ * @return true if the MMI command is executed.
+ * @throws CallStateException
+ */
+ boolean handleInCallMmiCommands(String command) throws CallStateException;
+
+ /**
+ * Play a DTMF tone on the active call. Ignored if there is no active call.
+ * @param c should be one of 0-9, '*' or '#'. Other values will be
+ * silently ignored.
+ */
+ void sendDtmf(char c);
+
+ /**
+ * Start to paly a DTMF tone on the active call. Ignored if there is no active call
+ * or there is a playing DTMF tone.
+ * @param c should be one of 0-9, '*' or '#'. Other values will be
+ * silently ignored.
+ */
+ void startDtmf(char c);
+
+ /**
+ * Stop the playing DTMF tone. Ignored if there is no playing DTMF
+ * tone or no active call.
+ */
+ void stopDtmf();
+
+
+ /**
+ * Sets the radio power on/off state (off is sometimes
+ * called "airplane mode"). Current state can be gotten via
+ * {@link #getServiceState()}.{@link
+ * android.telephony.ServiceState#getState() getState()}.
+ * <strong>Note: </strong>This request is asynchronous.
+ * getServiceState().getState() will not change immediately after this call.
+ * registerForServiceStateChanged() to find out when the
+ * request is complete.
+ *
+ * @param power true means "on", false means "off".
+ */
+ void setRadioPower(boolean power);
+
+ /**
+ * Get voice message waiting indicator status. No change notification
+ * available on this interface. Use PhoneStateNotifier or similar instead.
+ *
+ * @return true if there is a voice message waiting
+ */
+ boolean getMessageWaitingIndicator();
+
+ /**
+ * Get voice call forwarding indicator status. No change notification
+ * available on this interface. Use PhoneStateNotifier or similar instead.
+ *
+ * @return true if there is a voice call forwarding
+ */
+ boolean getCallForwardingIndicator();
+
+ /**
+ * Get the line 1 phone number (MSISDN).<p>
+ *
+ * @return phone number. May return null if not
+ * available or the SIM is not ready
+ */
+ String getLine1Number();
+
+ /**
+ * Returns the alpha tag associated with the msisdn number.
+ * If there is no alpha tag associated or the record is not yet available,
+ * returns a default localized string. <p>
+ */
+ String getLine1AlphaTag();
+
+ /**
+ * Sets the MSISDN phone number in the SIM card.
+ *
+ * @param alphaTag the alpha tag associated with the MSISDN phone number
+ * (see getMsisdnAlphaTag)
+ * @param number the new MSISDN phone number to be set on the SIM.
+ * @param onComplete a callback message when the action is completed.
+ */
+ void setLine1Number(String alphaTag, String number, Message onComplete);
+
+ /**
+ * Get the voice mail access phone number. Typically dialed when the
+ * user holds the "1" key in the phone app. May return null if not
+ * available or the SIM is not ready.<p>
+ */
+ String getVoiceMailNumber();
+
+ /**
+ * Returns the alpha tag associated with the voice mail number.
+ * If there is no alpha tag associated or the record is not yet available,
+ * returns a default localized string. <p>
+ *
+ * Please use this value instead of some other localized string when
+ * showing a name for this number in the UI. For example, call log
+ * entries should show this alpha tag. <p>
+ *
+ * Usage of this alpha tag in the UI is a common carrier requirement.
+ */
+ String getVoiceMailAlphaTag();
+
+ /**
+ * setVoiceMailNumber
+ * sets the voicemail number in the SIM card.
+ *
+ * @param alphaTag the alpha tag associated with the voice mail number
+ * (see getVoiceMailAlphaTag)
+ * @param voiceMailNumber the new voicemail number to be set on the SIM.
+ * @param onComplete a callback message when the action is completed.
+ */
+ void setVoiceMailNumber(String alphaTag,
+ String voiceMailNumber,
+ Message onComplete);
+
+ /**
+ * getCallForwardingOptions
+ * gets a call forwarding option. The return value of
+ * ((AsyncResult)onComplete.obj) is an array of CallForwardInfo.
+ *
+ * @param commandInterfaceCFReason is one of the valid call forwarding
+ * CF_REASONS, as defined in
+ * <code>com.android.internal.telephony.gsm.CommandsInterface</code>
+ * @param onComplete a callback message when the action is completed.
+ * @see com.android.internal.telephony.gsm.CallForwardInfo for details.
+ */
+ void getCallForwardingOption(int commandInterfaceCFReason,
+ Message onComplete);
+
+ /**
+ * setCallForwardingOptions
+ * sets a call forwarding option.
+ *
+ * @param commandInterfaceCFReason is one of the valid call forwarding
+ * CF_REASONS, as defined in
+ * <code>com.android.internal.telephony.gsm.CommandsInterface</code>
+ * @param commandInterfaceCFAction is one of the valid call forwarding
+ * CF_ACTIONS, as defined in
+ * <code>com.android.internal.telephony.gsm.CommandsInterface</code>
+ * @param dialingNumber is the target phone number to forward calls to
+ * @param timerSeconds is used by CFNRy to indicate the timeout before
+ * forwarding is attempted.
+ * @param onComplete a callback message when the action is completed.
+ */
+ void setCallForwardingOption(int commandInterfaceCFReason,
+ int commandInterfaceCFAction,
+ String dialingNumber,
+ int timerSeconds,
+ Message onComplete);
+
+ /**
+ * getOutgoingCallerIdDisplay
+ * gets outgoing caller id display. The return value of
+ * ((AsyncResult)onComplete.obj) is an array of int, with a length of 2.
+ *
+ * @param onComplete a callback message when the action is completed.
+ * @see com.android.internal.telephony.gsm.CommandsInterface.getCLIR for details.
+ */
+ void getOutgoingCallerIdDisplay(Message onComplete);
+
+ /**
+ * setOutgoingCallerIdDisplay
+ * sets a call forwarding option.
+ *
+ * @param commandInterfaceCLIRMode is one of the valid call CLIR
+ * modes, as defined in
+ * <code>com.android.internal.telephony.gsm.CommandsInterface</code>
+ * @param onComplete a callback message when the action is completed.
+ */
+ void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
+ Message onComplete);
+
+ /**
+ * getCallWaiting
+ * gets call waiting activation state. The return value of
+ * ((AsyncResult)onComplete.obj) is an array of int, with a length of 1.
+ *
+ * @param onComplete a callback message when the action is completed.
+ * @see com.android.internal.telephony.gsm.CommandsInterface.queryCallWaiting for details.
+ */
+ void getCallWaiting(Message onComplete);
+
+ /**
+ * setCallWaiting
+ * sets a call forwarding option.
+ *
+ * @param enable is a boolean representing the state that you are
+ * requesting, true for enabled, false for disabled.
+ * @param onComplete a callback message when the action is completed.
+ */
+ void setCallWaiting(boolean enable, Message onComplete);
+
+ /**
+ * Scan available networks. This method is asynchronous; .
+ * On completion, <code>response.obj</code> is set to an AsyncResult with
+ * one of the following members:.<p>
+ *<ul>
+ * <li><code>response.obj.result</code> will be a <code>List</code> of
+ * <code>com.android.internal.telephony.gsm.NetworkInfo</code> objects, or</li>
+ * <li><code>response.obj.exception</code> will be set with an exception
+ * on failure.</li>
+ * </ul>
+ */
+ void getAvailableNetworks(Message response);
+
+ /**
+ * Switches network selection mode to "automatic", re-scanning and
+ * re-selecting a network if appropriate.
+ *
+ * @param response The message to dispatch when the network selection
+ * is complete.
+ *
+ * @see #selectNetworkManually(com.android.internal.telephony.gsm.NetworkInfo,
+ * android.os.Message )
+ */
+ void setNetworkSelectionModeAutomatic(Message response);
+
+ /**
+ * Manually selects a network. <code>response</code> is
+ * dispatched when this is complete. <code>response.obj</code> will be
+ * an AsyncResult, and <code>response.obj.exception</code> will be non-null
+ * on failure.
+ *
+ * @see #setNetworkSelectionModeAutomatic(Message)
+ */
+ void selectNetworkManually(NetworkInfo network,
+ Message response);
+
+ /**
+ * Requests to set the preferred network type for searching and registering
+ * (CS/PS domain, RAT, and operation mode)
+ * @param networkType one of NT_*_TYPE
+ * @param response is callback message
+ */
+ void setPreferredNetworkType(int networkType, Message response);
+
+ /**
+ * Query the preferred network type setting
+ *
+ * @param response is callback message to report one of NT_*_TYPE
+ */
+ void getPreferredNetworkType(Message response);
+
+ /**
+ * Query neighboring cell IDs. <code>response</code> is dispatched when
+ * this is complete. <code>response.obj</code> will be an AsyncResult,
+ * and <code>response.obj.exception</code> will be non-null on failure.
+ * On success, <code>AsyncResult.result</code> will be a <code>String[]</code>
+ * containing the neighboring cell IDs. Index 0 will contain the count
+ * of available cell IDs. Cell IDs are in hexadecimal format.
+ *
+ * @param response callback message that is dispatched when the query
+ * completes.
+ */
+ void getNeighboringCids(Message response);
+
+ /**
+ * Sets an event to be fired when the telephony system processes
+ * a post-dial character on an outgoing call.<p>
+ *
+ * Messages of type <code>what</code> will be sent to <code>h</code>.
+ * The <code>obj</code> field of these Message's will be instances of
+ * <code>AsyncResult</code>. <code>Message.obj.result</code> will be
+ * a Connection object.<p>
+ *
+ * Message.arg1 will be the post dial character being processed,
+ * or 0 ('\0') if end of string.<p>
+ *
+ * If Connection.getPostDialState() == WAIT,
+ * the application must call
+ * {@link com.android.internal.telephony.Connection#proceedAfterWaitChar()
+ * Connection.proceedAfterWaitChar()} or
+ * {@link com.android.internal.telephony.Connection#cancelPostDial()
+ * Connection.cancelPostDial()}
+ * for the telephony system to continue playing the post-dial
+ * DTMF sequence.<p>
+ *
+ * If Connection.getPostDialState() == WILD,
+ * the application must call
+ * {@link com.android.internal.telephony.Connection#proceedAfterWildChar
+ * Connection.proceedAfterWildChar()}
+ * or
+ * {@link com.android.internal.telephony.Connection#cancelPostDial()
+ * Connection.cancelPostDial()}
+ * for the telephony system to continue playing the
+ * post-dial DTMF sequence.<p>
+ *
+ * Only one post dial character handler may be set. <p>
+ * Calling this method with "h" equal to null unsets this handler.<p>
+ */
+ void setOnPostDialCharacter(Handler h, int what, Object obj);
+
+
+ /**
+ * Mutes or unmutes the microphone for the active call. The microphone
+ * is automatically unmuted if a call is answered, dialed, or resumed
+ * from a holding state.
+ *
+ * @param muted true to mute the microphone,
+ * false to activate the microphone.
+ */
+
+ void setMute(boolean muted);
+
+ /**
+ * Gets current mute status. Use
+ * {@link #registerForPhoneStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPhoneStateChanged()}
+ * as a change notifcation, although presently phone state changed is not
+ * fired when setMute() is called.
+ *
+ * @return true is muting, false is unmuting
+ */
+ boolean getMute();
+
+ /**
+ * Invokes RIL_REQUEST_OEM_HOOK_RAW on RIL implementation.
+ *
+ * @param data The data for the request.
+ * @param response <strong>On success</strong>,
+ * (byte[])(((AsyncResult)response.obj).result)
+ * <strong>On failure</strong>,
+ * (((AsyncResult)response.obj).result) == null and
+ * (((AsyncResult)response.obj).exception) being an instance of
+ * com.android.internal.telephony.gsm.CommandException
+ *
+ * @see #invokeOemRilRequestRaw(byte[], android.os.Message)
+ */
+ void invokeOemRilRequestRaw(byte[] data, Message response);
+
+ /**
+ * Invokes RIL_REQUEST_OEM_HOOK_Strings on RIL implementation.
+ *
+ * @param strings The strings to make available as the request data.
+ * @param response <strong>On success</strong>, "response" bytes is
+ * made available as:
+ * (String[])(((AsyncResult)response.obj).result).
+ * <strong>On failure</strong>,
+ * (((AsyncResult)response.obj).result) == null and
+ * (((AsyncResult)response.obj).exception) being an instance of
+ * com.android.internal.telephony.gsm.CommandException
+ *
+ * @see #invokeOemRilRequestStrings(java.lang.String[], android.os.Message)
+ */
+ void invokeOemRilRequestStrings(String[] strings, Message response);
+
+ /**
+ * Get the current active PDP context list
+ *
+ * @param response <strong>On success</strong>, "response" bytes is
+ * made available as:
+ * (String[])(((AsyncResult)response.obj).result).
+ * <strong>On failure</strong>,
+ * (((AsyncResult)response.obj).result) == null and
+ * (((AsyncResult)response.obj).exception) being an instance of
+ * com.android.internal.telephony.gsm.CommandException
+ */
+ void getPdpContextList(Message response);
+
+ /**
+ * Get current mutiple PDP link status
+ *
+ * @return list of pdp link connections
+ */
+ List<PdpConnection> getCurrentPdpList ();
+
+ /**
+ * Udpate LAC and CID in service state for currnet GSM netowrk registration
+ *
+ * If get different LAC and/or CID, notifyServiceState will be sent
+ *
+ * @param
+ * <strong>On failure</strong>,
+ * (((AsyncResult)response.obj).result) == null and
+ * (((AsyncResult)response.obj).exception) being an instance of
+ * com.android.internal.telephony.gsm.CommandException
+ */
+ void updateServiceLocation(Message response);
+
+ /**
+ * Enable location update notifications.
+ */
+ void enableLocationUpdates();
+
+ /**
+ * Disable location update notifications.
+ */
+ void disableLocationUpdates();
+
+ /**
+ * For unit tests; don't send notifications to "Phone"
+ * mailbox registrants if true.
+ */
+ void setUnitTestMode(boolean f);
+
+ /**
+ * @return true If unit test mode is enabled
+ */
+ boolean getUnitTestMode();
+
+ /**
+ * Assign a specified band for RF configuration.
+ *
+ * @param bandMode one of BM_*_BAND
+ * @param response is callback message
+ */
+ void setBandMode(int bandMode, Message response);
+
+ /**
+ * Query the list of band mode supported by RF.
+ *
+ * @param response is callback message
+ * ((AsyncResult)response.obj).result is an int[] with every
+ * element representing one avialable BM_*_BAND
+ */
+ void queryAvailableBandMode(Message response);
+
+ /**
+ * @return true if enable data connection on roaming
+ */
+ boolean getDataRoamingEnabled();
+
+ /**
+ * @param enable set true if enable data connection on roaming
+ */
+ void setDataRoamingEnabled(boolean enable);
+
+ /**
+ * If this is a simulated phone interface, returns a SimulatedRadioControl.
+ * @ return A SimulatedRadioControl if this is a simulated interface;
+ * otherwise, null.
+ */
+ SimulatedRadioControl getSimulatedRadioControl();
+
+ /**
+ * Allow mobile data connections.
+ * @return {@code true} if the operation started successfully
+ * <br/>{@code false} if it
+ * failed immediately.<br/>
+ * Even in the {@code true} case, it may still fail later
+ * during setup, in which case an asynchronous indication will
+ * be supplied.
+ */
+ boolean enableDataConnectivity();
+
+ /**
+ * Disallow mobile data connections, and terminate any that
+ * are in progress.
+ * @return {@code true} if the operation started successfully
+ * <br/>{@code false} if it
+ * failed immediately.<br/>
+ * Even in the {@code true} case, it may still fail later
+ * during setup, in which case an asynchronous indication will
+ * be supplied.
+ */
+ boolean disableDataConnectivity();
+
+ /**
+ * Enables the specified APN type. Only works for "special" APN types,
+ * i.e., not the default APN.
+ * @param type The desired APN type. Cannot be {@link #APN_TYPE_DEFAULT}.
+ * @return <code>APN_ALREADY_ACTIVE</code> if the current APN
+ * services the requested type.<br/>
+ * <code>APN_TYPE_NOT_AVAILABLE</code> if the carrier does not
+ * support the requested APN.<br/>
+ * <code>APN_REQUEST_STARTED</code> if the request has been initiated.<br/>
+ * <code>APN_REQUEST_FAILED</code> if the request was invalid.<br/>
+ * A <code>ACTION_ANY_DATA_CONNECTION_STATE_CHANGED</code> broadcast will
+ * indicate connection state progress.
+ */
+ int enableApnType(String type);
+
+ /**
+ * Disables the specified APN type, and switches back to the default APN,
+ * if necessary. Switching to the default APN will not happen if default
+ * data traffic has been explicitly disabled via a call to {@link #disableDataConnectivity}.
+ * <p/>Only works for "special" APN types,
+ * i.e., not the default APN.
+ * @param type The desired APN type. Cannot be {@link #APN_TYPE_DEFAULT}.
+ * @return <code>APN_ALREADY_ACTIVE</code> if the default APN
+ * is already active.<br/>
+ * <code>APN_REQUEST_STARTED</code> if the request to switch to the default
+ * APN has been initiated.<br/>
+ * <code>APN_REQUEST_FAILED</code> if the request was invalid.<br/>
+ * A <code>ACTION_ANY_DATA_CONNECTION_STATE_CHANGED</code> broadcast will
+ * indicate connection state progress.
+ */
+ int disableApnType(String type);
+
+ /**
+ * Report on whether data connectivity is allowed.
+ */
+ boolean isDataConnectivityPossible();
+
+ /**
+ * Returns the name of the network interface used by the specified APN type.
+ */
+ String getInterfaceName(String apnType);
+
+ /**
+ * Returns the IP address of the network interface used by the specified
+ * APN type.
+ */
+ String getIpAddress(String apnType);
+
+ /**
+ * Returns the gateway for the network interface used by the specified APN
+ * type.
+ */
+ String getGateway(String apnType);
+
+ /**
+ * Returns the DNS servers for the network interface used by the specified
+ * APN type.
+ */
+ public String[] getDnsServers(String apnType);
+
+ /**
+ * Retrieves the unique device ID, e.g., IMEI for GSM phones.
+ */
+ String getDeviceId();
+
+ /**
+ * Retrieves the software version number for the device, e.g., IMEI/SV
+ * for GSM phones.
+ */
+ String getDeviceSvn();
+
+ /**
+ * Retrieves the unique sbuscriber ID, e.g., IMSI for GSM phones.
+ */
+ String getSubscriberId();
+
+ /**
+ * Retrieves the serial number of the SIM, if applicable.
+ */
+ String getSimSerialNumber();
+}
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
new file mode 100644
index 0000000..580814f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RegistrantList;
+import android.telephony.ServiceState;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+
+import java.util.List;
+
+/**
+ * (<em>Not for SDK use</em>)
+ * A base implementation for the com.android.internal.telephony.Phone interface.
+ *
+ * Note that implementations of Phone.java are expected to be used
+ * from a single application thread. This should be the same thread that
+ * originally called PhoneFactory to obtain the interface.
+ *
+ * {@hide}
+ *
+ */
+
+public abstract class PhoneBase implements Phone {
+ private static final String LOG_TAG = "GSM";
+
+ protected final RegistrantList mPhoneStateRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mNewRingingConnectionRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mIncomingRingRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mDisconnectRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mServiceStateRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mMmiCompleteRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mMmiRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mUnknownConnectionRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mSuppServiceFailedRegistrants
+ = new RegistrantList();
+
+ protected Looper mLooper; /* to insure registrants are in correct thread*/
+
+ protected Context mContext;
+
+ /**
+ * PhoneNotifier is an abstraction for all system-wide
+ * state change notification. DefaultPhoneNotifier is
+ * used here unless running we're inside a unit test.
+ */
+ protected PhoneNotifier mNotifier;
+
+ protected SimulatedRadioControl mSimulatedRadioControl;
+
+ boolean mUnitTestMode;
+
+ /**
+ * Constructs a PhoneBase in normal (non-unit test) mode.
+ *
+ * @param context Context object from hosting application
+ * @param notifier An instance of DefaultPhoneNotifier,
+ * unless unit testing.
+ */
+ protected PhoneBase(PhoneNotifier notifier, Context context) {
+ this(notifier, context, false);
+ }
+
+ /**
+ * Constructs a PhoneBase in normal (non-unit test) mode.
+ *
+ * @param context Context object from hosting application
+ * @param notifier An instance of DefaultPhoneNotifier,
+ * unless unit testing.
+ * @param unitTestMode when true, prevents notifications
+ * of state change events
+ */
+ protected PhoneBase(PhoneNotifier notifier, Context context,
+ boolean unitTestMode) {
+ this.mNotifier = notifier;
+ this.mContext = context;
+ mLooper = Looper.myLooper();
+
+ setUnitTestMode(unitTestMode);
+ }
+
+ // Inherited documentation suffices.
+ public Context getContext() {
+ return mContext;
+ }
+
+ // Inherited documentation suffices.
+ public void registerForPhoneStateChanged(Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mPhoneStateRegistrants.addUnique(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForPhoneStateChanged(Handler h) {
+ mPhoneStateRegistrants.remove(h);
+ }
+
+ /**
+ * Notify registrants of a PhoneStateChanged.
+ * Subclasses of Phone probably want to replace this with a
+ * version scoped to their packages
+ */
+ protected void notifyCallStateChangedP() {
+ AsyncResult ar = new AsyncResult(null, this, null);
+ mPhoneStateRegistrants.notifyRegistrants(ar);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForUnknownConnection(Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mUnknownConnectionRegistrants.addUnique(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForUnknownConnection(Handler h) {
+ mUnknownConnectionRegistrants.remove(h);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForNewRingingConnection(
+ Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mNewRingingConnectionRegistrants.addUnique(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForNewRingingConnection(Handler h) {
+ mNewRingingConnectionRegistrants.remove(h);
+ }
+
+ /**
+ * Notifiy registrants of a new ringing Connection.
+ * Subclasses of Phone probably want to replace this with a
+ * version scoped to their packages
+ */
+ protected void notifyNewRingingConnectionP(Connection cn) {
+ AsyncResult ar = new AsyncResult(null, cn, null);
+ mNewRingingConnectionRegistrants.notifyRegistrants(ar);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForIncomingRing(
+ Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mIncomingRingRegistrants.addUnique(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForIncomingRing(Handler h) {
+ mIncomingRingRegistrants.remove(h);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForDisconnect(Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mDisconnectRegistrants.addUnique(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForDisconnect(Handler h) {
+ mDisconnectRegistrants.remove(h);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForSuppServiceFailed(Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mSuppServiceFailedRegistrants.addUnique(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForSuppServiceFailed(Handler h) {
+ mSuppServiceFailedRegistrants.remove(h);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForMmiInitiate(Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mMmiRegistrants.addUnique(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForMmiInitiate(Handler h) {
+ mMmiRegistrants.remove(h);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForMmiComplete(Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mMmiCompleteRegistrants.addUnique(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForMmiComplete(Handler h) {
+ checkCorrectThread(h);
+
+ mMmiCompleteRegistrants.remove(h);
+ }
+
+ /**
+ * Subclasses should override this. See documentation in superclass.
+ */
+ public abstract List getPendingMmiCodes();
+
+ // Inherited documentation suffices.
+ public void setUnitTestMode(boolean f) {
+ mUnitTestMode = f;
+ }
+
+ // Inherited documentation suffices.
+ public boolean getUnitTestMode() {
+ return mUnitTestMode;
+ }
+
+ /**
+ * To be invoked when a voice call Connection disconnects.
+ *
+ * Subclasses of Phone probably want to replace this with a
+ * version scoped to their packages
+ */
+ protected void notifyDisconnectP(Connection cn) {
+ AsyncResult ar = new AsyncResult(null, cn, null);
+ mDisconnectRegistrants.notifyRegistrants(ar);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForServiceStateChanged(
+ Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mServiceStateRegistrants.add(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForServiceStateChanged(Handler h) {
+ mServiceStateRegistrants.remove(h);
+ }
+
+ /**
+ * Subclasses of Phone probably want to replace this with a
+ * version scoped to their packages
+ */
+ protected void notifyServiceStateChangedP(ServiceState ss) {
+ AsyncResult ar = new AsyncResult(null, ss, null);
+ mServiceStateRegistrants.notifyRegistrants(ar);
+
+ mNotifier.notifyServiceState(this);
+ }
+
+ // Inherited documentation suffices.
+ public SimulatedRadioControl getSimulatedRadioControl() {
+ return mSimulatedRadioControl;
+ }
+
+ /**
+ * Verifies the current thread is the same as the thread originally
+ * used in the initialization of this instance. Throws RuntimeException
+ * if not.
+ *
+ * @exception RuntimeException if the current thread is not
+ * the thread that originally obtained this PhoneBase instance.
+ */
+ private void checkCorrectThread(Handler h) {
+ if (h.getLooper() != mLooper) {
+ throw new RuntimeException(
+ "com.android.internal.telephony.Phone must be used from within one thread");
+ }
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/PhoneFactory.java b/telephony/java/com/android/internal/telephony/PhoneFactory.java
new file mode 100644
index 0000000..0ca5f45
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/PhoneFactory.java
@@ -0,0 +1,162 @@
+/*
+ * 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.internal.telephony;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import java.util.Collections;
+
+import android.util.Log;
+import com.android.internal.telephony.gsm.GSMPhone;
+import com.android.internal.telephony.gsm.RIL;
+import com.android.internal.telephony.test.ModelInterpreter;
+import com.android.internal.telephony.test.SimulatedCommands;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.content.Context;
+import android.content.Intent;
+import android.net.LocalServerSocket;
+import android.app.ActivityManagerNative;
+
+/**
+ * {@hide}
+ */
+public class PhoneFactory
+{
+ static final String LOG_TAG="GSM";
+
+ static final int SOCKET_OPEN_RETRY_MILLIS = 2 * 1000;
+ static final int SOCKET_OPEN_MAX_RETRY = 3;
+ //***** Class Variables
+
+ static private ArrayList<Phone> sPhones = new ArrayList<Phone>();
+
+ static private boolean sMadeDefaults = false;
+ static private PhoneNotifier sPhoneNotifier;
+ static private Looper sLooper;
+
+ static private Object testMailbox;
+
+ //***** Class Methods
+
+ private static void
+ useNewRIL(Context context)
+ {
+ ModelInterpreter mi = null;
+ GSMPhone phone;
+
+ try {
+ if (false) {
+ mi = new ModelInterpreter(new InetSocketAddress("127.0.0.1", 6502));
+ }
+
+ phone = new GSMPhone(context, new RIL(context), sPhoneNotifier);
+
+ registerPhone (phone);
+ } catch (IOException ex) {
+ Log.e(LOG_TAG, "Error creating ModelInterpreter", ex);
+ }
+ }
+
+
+ /**
+ * FIXME replace this with some other way of making these
+ * instances
+ */
+ public static void
+ makeDefaultPhones(Context context)
+ {
+ synchronized(Phone.class) {
+ if (!sMadeDefaults) {
+ sLooper = Looper.myLooper();
+
+ if (sLooper == null) {
+ throw new RuntimeException(
+ "PhoneFactory.makeDefaultPhones must be called from Looper thread");
+ }
+
+ int retryCount = 0;
+ for(;;) {
+ boolean hasException = false;
+ retryCount ++;
+
+ try {
+ // use UNIX domain socket to
+ // prevent subsequent initialization
+ new LocalServerSocket("com.android.internal.telephony");
+ } catch (java.io.IOException ex) {
+ hasException = true;
+ }
+
+ if ( !hasException ) {
+ break;
+ } else if (retryCount > SOCKET_OPEN_MAX_RETRY) {
+ throw new RuntimeException("PhoneFactory probably already running");
+ }else {
+ try {
+ Thread.sleep(SOCKET_OPEN_RETRY_MILLIS);
+ } catch (InterruptedException er) {
+ }
+ }
+ }
+
+ sPhoneNotifier = new DefaultPhoneNotifier();
+
+ if ((SystemProperties.get("ro.radio.noril","")).equals("")) {
+ useNewRIL(context);
+ } else {
+ GSMPhone phone;
+ phone = new GSMPhone(context, new SimulatedCommands(), sPhoneNotifier);
+ registerPhone (phone);
+ }
+
+ sMadeDefaults = true;
+ }
+ }
+ }
+
+ public static Phone getDefaultPhone()
+ {
+ if (!sMadeDefaults) {
+ throw new IllegalStateException("Default phones haven't been made yet!");
+ }
+
+ if (sLooper != Looper.myLooper()) {
+ throw new RuntimeException(
+ "PhoneFactory.getDefaultPhone must be called from Looper thread");
+ }
+
+ synchronized (sPhones) {
+ return sPhones.isEmpty() ? null : sPhones.get(0);
+ }
+ }
+
+ public static void registerPhone(Phone p)
+ {
+ if (sLooper != Looper.myLooper()) {
+ throw new RuntimeException(
+ "PhoneFactory.getDefaultPhone must be called from Looper thread");
+ }
+ synchronized (sPhones) {
+ sPhones.add(p);
+ }
+ }
+}
+
diff --git a/telephony/java/com/android/internal/telephony/PhoneNotifier.java b/telephony/java/com/android/internal/telephony/PhoneNotifier.java
new file mode 100644
index 0000000..e96eeae
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/PhoneNotifier.java
@@ -0,0 +1,42 @@
+/*
+ * 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.internal.telephony;
+
+/**
+ * {@hide}
+ */
+public interface PhoneNotifier {
+
+ public void notifyPhoneState(Phone sender);
+
+ public void notifyServiceState(Phone sender);
+
+ public void notifyCellLocation(Phone sender);
+
+ public void notifySignalStrength(Phone sender);
+
+ public void notifyMessageWaitingChanged(Phone sender);
+
+ public void notifyCallForwardingChanged(Phone sender);
+
+ public void notifyDataConnection(Phone sender, String reason);
+
+ public void notifyDataConnectionFailed(Phone sender, String reason);
+
+ public void notifyDataActivity(Phone sender);
+
+}
diff --git a/telephony/java/com/android/internal/telephony/PhoneStateIntentReceiver.java b/telephony/java/com/android/internal/telephony/PhoneStateIntentReceiver.java
new file mode 100644
index 0000000..c558cd1
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/PhoneStateIntentReceiver.java
@@ -0,0 +1,220 @@
+/*
+ * 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.internal.telephony;
+
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.ServiceState;
+
+/**
+ *
+ * DO NOT USE THIS CLASS:
+ *
+ * Use android.telephony.TelephonyManager and PhoneStateListener instead.
+ *
+ *
+ */
+@Deprecated
+public final class PhoneStateIntentReceiver extends BroadcastReceiver {
+ private static final String LOG_TAG = "PhoneStateIntRecv";
+ private static final boolean DBG = false;
+
+ public static final String INTENT_KEY_ASU = "asu";
+ public static final String INTENT_KEY_NUM = "incoming_number";
+
+ private static final int NOTIF_PHONE = 1 << 0;
+ private static final int NOTIF_SERVICE = 1 << 1;
+ private static final int NOTIF_SIGNAL = 1 << 2;
+
+ private static final int NOTIF_MAX = 1 << 5;
+
+ Phone.State mPhoneState = Phone.State.IDLE;
+ String mIncomingNumber;
+ ServiceState mServiceState = new ServiceState();
+ int mAsu = -1;
+ private Context mContext;
+ private Handler mTarget;
+ private IntentFilter mFilter;
+ private int mWants;
+ private int mPhoneStateEventWhat;
+ private int mServiceStateEventWhat;
+ private int mLocationEventWhat;
+ private int mAsuEventWhat;
+
+ public PhoneStateIntentReceiver() {
+ super();
+ mFilter = new IntentFilter();
+ }
+
+ public PhoneStateIntentReceiver(Context context, Handler target) {
+ this();
+ setContext(context);
+ setTarget(target);
+ }
+
+ public void setContext(Context c) {
+ mContext = c;
+ }
+
+ public void setTarget(Handler h) {
+ mTarget = h;
+ }
+
+ public Phone.State getPhoneState() {
+ if ((mWants & NOTIF_PHONE) == 0) {
+ throw new RuntimeException
+ ("client must call notifyPhoneCallState(int)");
+ }
+ return mPhoneState;
+ }
+
+ public ServiceState getServiceState() {
+ if ((mWants & NOTIF_SERVICE) == 0) {
+ throw new RuntimeException
+ ("client must call notifyServiceState(int)");
+ }
+ return mServiceState;
+ }
+
+ /**
+ * Returns current signal strength in "asu", ranging from 0-31
+ * or -1 if unknown
+ *
+ * For GSM, dBm = -113 + 2*asu
+ * 0 means "-113 dBm or less"
+ * 31 means "-51 dBm or greater"
+ *
+ * @return signal strength in asu, -1 if not yet updated
+ * Throws RuntimeException if client has not called notifySignalStrength()
+ */
+ public int getSignalStrength() {
+ if ((mWants & NOTIF_SIGNAL) == 0) {
+ throw new RuntimeException
+ ("client must call notifySignalStrength(int)");
+ }
+
+ return mAsu;
+ }
+
+ /**
+ * Return current signal strength in "dBm", ranging from -113 - -51dBm
+ * or -1 if unknown
+ *
+ * @return signal strength in dBm, -1 if not yet updated
+ * Throws RuntimeException if client has not called notifySignalStrength()
+ */
+ public int getSignalStrengthDbm() {
+ if ((mWants & NOTIF_SIGNAL) == 0) {
+ throw new RuntimeException
+ ("client must call notifySignalStrength(int)");
+ }
+
+ int dBm = -1;
+
+ if (mAsu != -1) {
+ dBm = -113 + 2*mAsu;
+ }
+
+ return dBm;
+ }
+
+ public void notifyPhoneCallState(int eventWhat) {
+ mWants |= NOTIF_PHONE;
+ mPhoneStateEventWhat = eventWhat;
+ mFilter.addAction(TelephonyIntents.ACTION_PHONE_STATE_CHANGED);
+ }
+
+ public boolean getNotifyPhoneCallState() {
+ return ((mWants & NOTIF_PHONE) != 0);
+ }
+
+ public void notifyServiceState(int eventWhat) {
+ mWants |= NOTIF_SERVICE;
+ mServiceStateEventWhat = eventWhat;
+ mFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ }
+
+ public boolean getNotifyServiceState() {
+ return ((mWants & NOTIF_SERVICE) != 0);
+ }
+
+ public void notifySignalStrength (int eventWhat) {
+ mWants |= NOTIF_SIGNAL;
+ mAsuEventWhat = eventWhat;
+ mFilter.addAction(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
+ }
+
+ public boolean getNotifySignalStrength() {
+ return ((mWants & NOTIF_SIGNAL) != 0);
+ }
+
+ public void registerIntent() {
+ mContext.registerReceiver(this, mFilter);
+ }
+
+ public void unregisterIntent() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ try {
+ if (TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED.equals(action)) {
+ mAsu = intent.getIntExtra(INTENT_KEY_ASU, mAsu);
+ if (DBG) Log.d(LOG_TAG, "onReceiveIntent: set asu=" + mAsu);
+
+ if (mTarget != null && getNotifySignalStrength()) {
+ Message message = Message.obtain(mTarget, mAsuEventWhat);
+ mTarget.sendMessage(message);
+ }
+ } else if (TelephonyIntents.ACTION_PHONE_STATE_CHANGED.equals(action)) {
+ if (DBG) Log.d(LOG_TAG, "onReceiveIntent: ACTION_PHONE_STATE_CHANGED, state="
+ + intent.getStringExtra(Phone.STATE_KEY));
+ String phoneState = intent.getStringExtra(Phone.STATE_KEY);
+ mIncomingNumber = intent.getStringExtra(INTENT_KEY_NUM);
+ mPhoneState = (Phone.State) Enum.valueOf(
+ Phone.State.class, phoneState);
+
+ if (mTarget != null && getNotifyPhoneCallState()) {
+ Message message = Message.obtain(mTarget,
+ mPhoneStateEventWhat);
+ mTarget.sendMessage(message);
+ }
+ } else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
+ mServiceState = ServiceState.newFromBundle(intent.getExtras());
+
+ if (mTarget != null && getNotifyServiceState()) {
+ Message message = Message.obtain(mTarget,
+ mServiceStateEventWhat);
+ mTarget.sendMessage(message);
+ }
+ }
+ } catch (Exception ex) {
+ Log.e(LOG_TAG, "[PhoneStateIntentRecv] caught " + ex);
+ ex.printStackTrace();
+ }
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/PhoneSubInfo.java b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
new file mode 100644
index 0000000..644d1f4
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
@@ -0,0 +1,82 @@
+package com.android.internal.telephony;
+
+import android.content.Context;
+import android.os.ServiceManager;
+import com.android.internal.telephony.*;
+
+public class PhoneSubInfo extends IPhoneSubInfo.Stub {
+ private Phone mPhone;
+ private Context mContext;
+ private static final String READ_PHONE_STATE =
+ android.Manifest.permission.READ_PHONE_STATE;
+
+ public PhoneSubInfo(Phone phone) {
+ mPhone = phone;
+ mContext = phone.getContext();
+ ServiceManager.addService("iphonesubinfo", this);
+ }
+ /**
+ * Retrieves the unique device ID, e.g., IMEI for GSM phones.
+ */
+ public String getDeviceId() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return mPhone.getDeviceId();
+ }
+
+ /**
+ * Retrieves the software version number for the device, e.g., IMEI/SV
+ * for GSM phones.
+ */
+ public String getDeviceSvn() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return mPhone.getDeviceSvn();
+ }
+
+ /**
+ * Retrieves the unique sbuscriber ID, e.g., IMSI for GSM phones.
+ */
+ public String getSubscriberId() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return mPhone.getSubscriberId();
+ }
+
+ /**
+ * Retrieves the serial number of the SIM, if applicable.
+ */
+ public String getSimSerialNumber() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return mPhone.getSimSerialNumber();
+ }
+
+ /**
+ * Retrieves the phone number string for line 1.
+ */
+ public String getLine1Number() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return mPhone.getLine1Number();
+ }
+
+ /**
+ * Retrieves the alpha identifier for line 1.
+ */
+ public String getLine1AlphaTag() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return (String) mPhone.getLine1AlphaTag();
+ }
+
+ /**
+ * Retrieves the voice mail number.
+ */
+ public String getVoiceMailNumber() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return (String) mPhone.getVoiceMailNumber();
+ }
+
+ /**
+ * Retrieves the alpha identifier associated with the voice mail number.
+ */
+ public String getVoiceMailAlphaTag() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return (String) mPhone.getVoiceMailAlphaTag();
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/SimCard.java b/telephony/java/com/android/internal/telephony/SimCard.java
new file mode 100644
index 0000000..03b366f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SimCard.java
@@ -0,0 +1,208 @@
+/*
+ * 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.internal.telephony;
+
+import android.os.Message;
+import android.os.Handler;
+
+/**
+ * {@hide}
+ */
+public interface SimCard
+{
+ /* The extra data for broacasting intent INTENT_SIM_STATE_CHANGE */
+ static public final String INTENT_KEY_SIM_STATE = "ss";
+ /* NOT_READY means the SIM interface is not ready (eg, radio is off or powering on) */
+ static public final String INTENT_VALUE_SIM_NOT_READY = "NOT_READY";
+ /* ABSENT means SIM is missing */
+ static public final String INTENT_VALUE_SIM_ABSENT = "ABSENT";
+ /* LOCKED means SIM is locked by pin or by network */
+ static public final String INTENT_VALUE_SIM_LOCKED = "LOCKED";
+ /* READY means SIM is ready to access */
+ static public final String INTENT_VALUE_SIM_READY = "READY";
+ /* IMSI means SIM IMSI is ready in property */
+ static public final String INTENT_VALUE_SIM_IMSI = "IMSI";
+ /* LOADED means all SIM records, including IMSI, are loaded */
+ static public final String INTENT_VALUE_SIM_LOADED = "LOADED";
+ /* The extra data for broacasting intent INTENT_SIM_STATE_CHANGE */
+ static public final String INTENT_KEY_LOCKED_REASON = "reason";
+ /* PIN means SIM is locked on PIN1 */
+ static public final String INTENT_VALUE_LOCKED_ON_PIN = "PIN";
+ /* PUK means SIM is locked on PUK1 */
+ static public final String INTENT_VALUE_LOCKED_ON_PUK = "PUK";
+ /* NETWORK means SIM is locked on NETWORK PERSONALIZATION */
+ static public final String INTENT_VALUE_LOCKED_NETWORK = "NETWORK";
+
+
+ /*
+ UNKNOWN is a transient state, for example, after uesr inputs sim pin under
+ PIN_REQUIRED state, the query for sim status returns UNKNOWN before it
+ turns to READY
+ */
+ public enum State {
+ UNKNOWN,
+ ABSENT,
+ PIN_REQUIRED,
+ PUK_REQUIRED,
+ NETWORK_LOCKED,
+ READY;
+
+ public boolean isPinLocked() {
+ return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED));
+ }
+ }
+
+ State getState();
+
+
+ /**
+ * Notifies handler of any transition into State.ABSENT
+ */
+ void registerForAbsent(Handler h, int what, Object obj);
+ void unregisterForAbsent(Handler h);
+
+ /**
+ * Notifies handler of any transition into State.isPinLocked()
+ */
+ void registerForLocked(Handler h, int what, Object obj);
+ void unregisterForLocked(Handler h);
+
+ /**
+ * Notifies handler of any transition into State.NETWORK_LOCKED
+ */
+ void registerForNetworkLocked(Handler h, int what, Object obj);
+ void unregisterForNetworkLocked(Handler h);
+
+ /**
+ * Supply the SIM PIN to the SIM
+ *
+ * When the operation is complete, onComplete will be sent to it's
+ * Handler.
+ *
+ * onComplete.obj will be an AsyncResult
+ *
+ * ((AsyncResult)onComplete.obj).exception == null on success
+ * ((AsyncResult)onComplete.obj).exception != null on fail
+ *
+ * If the supplied PIN is incorrect:
+ * ((AsyncResult)onComplete.obj).exception != null
+ * && ((AsyncResult)onComplete.obj).exception
+ * instanceof com.android.internal.telephony.gsm.CommandException)
+ * && ((CommandException)(((AsyncResult)onComplete.obj).exception))
+ * .getCommandError() == CommandException.Error.PASSWORD_INCORRECT
+ *
+ *
+ */
+
+ void supplyPin (String pin, Message onComplete);
+ void supplyPuk (String puk, String newPin, Message onComplete);
+ void supplyPin2 (String pin2, Message onComplete);
+ void supplyPuk2 (String puk2, String newPin2, Message onComplete);
+
+ /**
+ * Check whether sim pin lock is enabled
+ * This is a sync call which returns the cached pin enabled state
+ *
+ * @return true for sim locked enabled
+ * false for sim locked disabled
+ */
+ boolean getSimLockEnabled ();
+
+ /**
+ * Set the sim pin lock enabled or disabled
+ * When the operation is complete, onComplete will be sent to its handler
+ *
+ * @param enabled "true" for locked "false" for unlocked.
+ * @param password needed to change the sim pin state, aka. Pin1
+ * @param onComplete
+ * onComplete.obj will be an AsyncResult
+ * ((AsyncResult)onComplete.obj).exception == null on success
+ * ((AsyncResult)onComplete.obj).exception != null on fail
+ */
+ void setSimLockEnabled(boolean enabled, String password, Message onComplete);
+
+
+ /**
+ * Change the sim password used in sim pin lock
+ * When the operation is complete, onComplete will be sent to its handler
+ *
+ * @param oldPassword is the old password
+ * @param newPassword is the new password
+ * @param onComplete
+ * onComplete.obj will be an AsyncResult
+ * ((AsyncResult)onComplete.obj).exception == null on success
+ * ((AsyncResult)onComplete.obj).exception != null on fail
+ */
+ void changeSimLockPassword(String oldPassword, String newPassword,
+ Message onComplete);
+
+ /**
+ * Check whether sim fdn (fixed dialing number) is enabled
+ * This is a sync call which returns the cached pin enabled state
+ *
+ * @return true for sim fdn enabled
+ * false for sim fdn disabled
+ */
+ boolean getSimFdnEnabled ();
+
+ /**
+ * Set the sim fdn enabled or disabled
+ * When the operation is complete, onComplete will be sent to its handler
+ *
+ * @param enabled "true" for locked "false" for unlocked.
+ * @param password needed to change the sim fdn enable, aka Pin2
+ * @param onComplete
+ * onComplete.obj will be an AsyncResult
+ * ((AsyncResult)onComplete.obj).exception == null on success
+ * ((AsyncResult)onComplete.obj).exception != null on fail
+ */
+ void setSimFdnEnabled(boolean enabled, String password, Message onComplete);
+
+ /**
+ * Change the sim password used in sim fdn enable
+ * When the operation is complete, onComplete will be sent to its handler
+ *
+ * @param oldPassword is the old password
+ * @param newPassword is the new password
+ * @param onComplete
+ * onComplete.obj will be an AsyncResult
+ * ((AsyncResult)onComplete.obj).exception == null on success
+ * ((AsyncResult)onComplete.obj).exception != null on fail
+ */
+ void changeSimFdnPassword(String oldPassword, String newPassword,
+ Message onComplete);
+
+ void supplyNetworkDepersonalization (String pin, Message onComplete);
+
+ /**
+ * Returns service provider name stored in SIM card.
+ * If there is no service provider name associated or the record is not
+ * yet available, null will be returned <p>
+ *
+ * Please use this value when display Service Provider Name in idle mode <p>
+ *
+ * Usage of this provider name in the UI is a common carrier requirement.
+ *
+ * Also available via Android property "gsm.sim.operator.alpha"
+ *
+ * @return Service Provider Name stored in SIM card
+ * null if no service provider name associated or the record is not
+ * yet available
+ *
+ */
+ String getServiceProviderName();
+}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
new file mode 100644
index 0000000..8519796
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -0,0 +1,171 @@
+package com.android.internal.telephony;
+
+/**
+ * The intents that the telephony services broadcast.
+ *
+ * <p class="warning">
+ * THESE ARE NOT THE API! Use the {@link android.telephony.TelephonyManager} class.
+ * DON'T LISTEN TO THESE DIRECTLY.
+ */
+public class TelephonyIntents {
+
+ /**
+ * <p>Broadcast Action: The phone state has changed. The intent will have the following
+ * extra values:</p>
+ * <ul>
+ * <li><em>phoneName</em> - A string version of the phone name.</li>
+ * <li><em>state</em> - A string version of the new phone state.</li>
+ * </ul>
+ *
+ * <p class="note">
+ * You can <em>not</em> receive this through components declared
+ * in manifests, only by exlicitly registering for it with
+ * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
+ * android.content.IntentFilter) Context.registerReceiver()}.
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ */
+ public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
+
+
+ /**
+ * Broadcast Action: The phone service state has changed. The intent will have the following
+ * extra values:</p>
+ * <ul>
+ * <li><em>state</em> - An int with one of the following values:
+ * {@link android.telephony.ServiceState#STATE_IN_SERVICE},
+ * {@link android.telephony.ServiceState#STATE_OUT_OF_SERVICE},
+ * {@link android.telephony.ServiceState#STATE_EMERGENCY_ONLY}
+ * or {@link android.telephony.ServiceState#STATE_POWER_OFF}
+ * <li><em>roaming</em> - A boolean value indicating whether the phone is roaming.</li>
+ * <li><em>operator-alpha-long</em> - The carrier name as a string.</li>
+ * <li><em>operator-alpha-short</em> - A potentially shortened version of the carrier name,
+ * as a string.</li>
+ * <li><em>operator-numeric</em> - A number representing the carrier, as a string. This is
+ * a five or six digit number consisting of the MCC (Mobile Country Code, 3 digits)
+ * and MNC (Mobile Network code, 2-3 digits).</li>
+ * <li><em>manual</em> - A boolean, where true indicates that the user has chosen to select
+ * the network manually, and false indicates that network selection is handled by the
+ * phone.</li>
+ * </ul>
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ */
+ public static final String ACTION_SERVICE_STATE_CHANGED = "android.intent.action.SERVICE_STATE";
+
+
+ /**
+ * Broadcast Action: The phone's signal strength has changed. The intent will have the
+ * following extra values:</p>
+ * <ul>
+ * <li><em>phoneName</em> - A string version of the phone name.</li>
+ * <li><em>asu</em> - A numeric value for the signal strength.
+ * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
+ * The following special values are defined:
+ * <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul>
+ * </li>
+ * </ul>
+ *
+ * <p class="note">
+ * You can <em>not</em> receive this through components declared
+ * in manifests, only by exlicitly registering for it with
+ * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
+ * android.content.IntentFilter) Context.registerReceiver()}.
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ */
+ public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
+
+
+ /**
+ * Broadcast Action: The data connection state has changed for any one of the
+ * phone's mobile data connections (eg, default, MMS or GPS specific connection).
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li><em>phoneName</em> - A string version of the phone name.</li>
+ * <li><em>state</em> - One of <code>"CONNECTED"</code>
+ * <code>"CONNECTING"</code> or <code>"DISCONNNECTED"</code></li>
+ * <li><em>apn</em> - A string that is the APN associated with this
+ * connection.</li>
+ * <li><em>apnType</em> - A string array of APN types associated with
+ * this connection. The APN type <code>"*"</code> is a special
+ * type that means this APN services all types.</li>
+ * </ul>
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ */
+ public static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED
+ = "android.intent.action.ANY_DATA_STATE";
+
+
+ /**
+ * Broadcast Action: An attempt to establish a data connection has failed.
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li><em>phoneName</em> &mdash A string version of the phone name.</li>
+ * <li><em>state</em> &mdash; One of <code>"CONNECTED"</code>
+ * <code>"CONNECTING"</code> or <code>"DISCONNNECTED"</code></li>
+ * <li><em>reason</em> &mdash; A string indicating the reason for the failure, if available</li>
+ * </ul>
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ */
+ public static final String ACTION_DATA_CONNECTION_FAILED
+ = "android.intent.action.DATA_CONNECTION_FAILED";
+
+
+ /**
+ * Broadcast Action: The sim card state has changed.
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li><em>phoneName</em> - A string version of the phone name.</li>
+ * <li><em>ss</em> - The sim state. One of
+ * <code>"ABSENT"</code> <code>"LOCKED"</code>
+ * <code>"READY"</code> <code>"ISMI"</code> <code>"LOADED"</code> </li>
+ * <li><em>reason</em> - The reason while ss is LOCKED, otherwise is null
+ * <code>"PIN"</code> locked on PIN1
+ * <code>"PUK"</code> locked on PUK1
+ * <code>"NETWORK"</code> locked on Network Personalization </li>
+ * </ul>
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ */
+ public static final String ACTION_SIM_STATE_CHANGED
+ = "android.intent.action.SIM_STATE_CHANGED";
+
+
+ /**
+ * Broadcast Action: The time was set by the carrier (typically by the NITZ string).
+ * This is a sticky broadcast.
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li><em>time</em> - The time as a long in UTC milliseconds.</li>
+ * </ul>
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ */
+ public static final String ACTION_NETWORK_SET_TIME = "android.intent.action.NETWORK_SET_TIME";
+
+
+ /**
+ * Broadcast Action: The timezone was set by the carrier (typically by the NITZ string).
+ * This is a sticky broadcast.
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li><em>time-zone</em> - The java.util.TimeZone.getID() value identifying the new time
+ * zone.</li>
+ * </ul>
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ */
+ public static final String ACTION_NETWORK_SET_TIMEZONE
+ = "android.intent.action.NETWORK_SET_TIMEZONE";
+}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
new file mode 100644
index 0000000..c197734
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -0,0 +1,101 @@
+/*
+ * 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.internal.telephony;
+
+/**
+ * Contains a list of string constants used to get or set telephone properties
+ * in the system. You can use {@link android.os.SystemProperties os.SystemProperties}
+ * to get and set these values.
+ * @hide
+ */
+public interface TelephonyProperties
+{
+ //****** Baseband and Radio Interface version
+
+ /**
+ * Baseband version
+ * Availability: property is available any time radio is on
+ */
+ static final String PROPERTY_BASEBAND_VERSION = "gsm.version.baseband";
+
+ /** Radio Interface Layer (RIL) library implementation. */
+ static final String PROPERTY_RIL_IMPL = "gsm.version.ril-impl";
+
+ //****** Current Network
+
+ /** Alpha name of current registered operator.
+ * Availability: when registered to a network
+ */
+ static final String PROPERTY_OPERATOR_ALPHA = "gsm.operator.alpha";
+
+ /** Numeric name (MCC+MNC) of current registered operator.
+ * Availability: when registered to a network
+ */
+ static final String PROPERTY_OPERATOR_NUMERIC = "gsm.operator.numeric";
+
+ /** 'true' if the device is considered roaming on this network for GSM
+ * purposes.
+ * Availability: when registered to a network
+ */
+ static final String PROPERTY_OPERATOR_ISROAMING = "gsm.operator.isroaming";
+
+ /** '1' if the current network is the result of a manual network selection.
+ * Availability: when registered to a network
+ */
+ static final String PROPERTY_OPERATOR_ISMANUAL = "gsm.operator.ismanual";
+ /** The ISO country code equivilent of the current registered operator's
+ * MCC (Mobile Country Code)
+ * Availability: when registered to a network
+ */
+ static final String PROPERTY_OPERATOR_ISO_COUNTRY = "gsm.operator.iso-country";
+
+ //****** SIM Card
+ /**
+ * One of <code>"UNKNOWN"</code> <code>"ABSENT"</code> <code>"PIN_REQUIRED"</code>
+ * <code>"PUK_REQUIRED"</code> <code>"NETWORK_LOCKED"</code> or <code>"READY"</code>
+ */
+ static String PROPERTY_SIM_STATE = "gsm.sim.state";
+
+ /** Set to '1' if voice mail is waiting, otherwise false */
+ static String PROPERTY_LINE1_VOICE_MAIL_WAITING = "gsm.sim.voice-mail.waiting";
+
+ /** Set to 'true' if unconditional voice call forwarding is enabled
+ * Availablity: only if configured in SIM; SIM state must be "READY"
+ */
+ static String PROPERTY_LINE1_VOICE_CALL_FORWARDING = "gsm.sim.line1.cff";
+
+ /** The MCC+MNC (mobile country code+mobile network code) of the
+ * provider of the SIM. 5 or 6 decimal digits.
+ * Availablity: SIM state must be "READY"
+ */
+ static String PROPERTY_SIM_OPERATOR_NUMERIC = "gsm.sim.operator.numeric";
+
+ /** PROPERTY_SIM_OPERATOR_ALPHA is also known as the SPN, or Service Provider Name.
+ * Availablity: SIM state must be "READY"
+ */
+ static String PROPERTY_SIM_OPERATOR_ALPHA = "gsm.sim.operator.alpha";
+
+ /** ISO country code equivalent for the SIM provider's country code*/
+ static String PROPERTY_SIM_OPERATOR_ISO_COUNTRY = "gsm.sim.operator.iso-country";
+
+ /**
+ * Indicates the available radio technology. Values include: <code>"unknown"</code>,
+ * <code>"GPRS"</code>, <code>"EDGE"</code> and <code>"UMTS"</code>.
+ */
+ static String PROPERTY_DATA_NETWORK_TYPE = "gsm.network.type";
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/AdnRecord.aidl b/telephony/java/com/android/internal/telephony/gsm/AdnRecord.aidl
new file mode 100644
index 0000000..68d9a7c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/AdnRecord.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2007, 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.internal.telephony.gsm;
+
+parcelable AdnRecord;
diff --git a/telephony/java/com/android/internal/telephony/gsm/AdnRecord.java b/telephony/java/com/android/internal/telephony/gsm/AdnRecord.java
new file mode 100644
index 0000000..30df699
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/AdnRecord.java
@@ -0,0 +1,570 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import com.android.internal.telephony.*;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Message;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.AsyncResult;
+import android.util.Log;
+import android.telephony.PhoneNumberUtils;
+import java.util.ArrayList;
+
+class AdnRecordLoader extends Handler
+{
+ static final String LOG_TAG = "GSM";
+
+ //***** Instance Variables
+
+ GSMPhone phone;
+ int ef;
+ int extensionEF;
+ int pendingExtLoads;
+ Message userResponse;
+ String pin2;
+
+ // For "load one"
+ int recordNumber;
+
+ // for "load all"
+ ArrayList<AdnRecord> adns; // only valid after EVENT_ADN_LOAD_ALL_DONE
+
+ // Either an AdnRecord or a reference to adns depending
+ // if this is a load one or load all operation
+ Object result;
+
+ //***** Event Constants
+
+ static final int EVENT_ADN_LOAD_DONE = 1;
+ static final int EVENT_EXT_RECORD_LOAD_DONE = 2;
+ static final int EVENT_ADN_LOAD_ALL_DONE = 3;
+ static final int EVENT_EF_LINEAR_RECORD_SIZE_DONE = 4;
+ static final int EVENT_UPDATE_RECORD_DONE = 5;
+
+ //***** Constructor
+
+ AdnRecordLoader(GSMPhone phone)
+ {
+ // The telephony unit-test cases may create AdnRecords
+ // in secondary threads
+ super(phone.h.getLooper());
+
+ this.phone = phone;
+ }
+
+ /**
+ * Resulting AdnRecord is placed in response.obj.result
+ * or response.obj.exception is set
+ */
+ void
+ loadFromEF(int ef, int extensionEF, int recordNumber,
+ Message response)
+ {
+ this.ef = ef;
+ this.extensionEF = extensionEF;
+ this.recordNumber = recordNumber;
+ this.userResponse = response;
+
+ phone.mSIMFileHandler.loadEFLinearFixed(
+ ef, recordNumber,
+ obtainMessage(EVENT_ADN_LOAD_DONE));
+
+ }
+
+
+ /**
+ * Resulting ArrayList&lt;adnRecord> is placed in response.obj.result
+ * or response.obj.exception is set
+ */
+ void
+ loadAllFromEF(int ef, int extensionEF,
+ Message response)
+ {
+ this.ef = ef;
+ this.extensionEF = extensionEF;
+ this.userResponse = response;
+
+ phone.mSIMFileHandler.loadEFLinearFixedAll(
+ ef,
+ obtainMessage(EVENT_ADN_LOAD_ALL_DONE));
+
+ }
+
+ /**
+ * Write adn to a EF SIM record
+ * It will get the record size of EF record and compose hex adn array
+ * then write the hex array to EF record
+ *
+ * @param adn is set with alphaTag and phoneNubmer
+ * @param ef EF fileid
+ * @param extensionEF extension EF fileid
+ * @param recordNumber 1-based record index
+ * @param pin2 for CHV2 operations, must be null if pin2 is not needed
+ * @param response will be sent to its handler when completed
+ */
+ void
+ updateEF(AdnRecord adn, int ef, int extensionEF, int recordNumber,
+ String pin2, Message response)
+ {
+ this.ef = ef;
+ this.extensionEF = extensionEF;
+ this.recordNumber = recordNumber;
+ this.userResponse = response;
+ this.pin2 = pin2;
+
+ phone.mSIMFileHandler.getEFLinearRecordSize( ef,
+ obtainMessage(EVENT_EF_LINEAR_RECORD_SIZE_DONE, adn));
+ }
+
+ //***** Overridden from Handler
+
+ public void
+ handleMessage(Message msg)
+ {
+ AsyncResult ar;
+ byte data[];
+ AdnRecord adn;
+
+ try {
+ switch (msg.what) {
+ case EVENT_EF_LINEAR_RECORD_SIZE_DONE:
+ ar = (AsyncResult)(msg.obj);
+ adn = (AdnRecord)(ar.userObj);
+
+ if (ar.exception != null) {
+ throw new RuntimeException("get EF record size failed",
+ ar.exception);
+ }
+
+ int[] recordSize = (int[])ar.result;
+ // recordSize is int[3] array
+ // int[0] is the record length
+ // int[1] is the total length of the EF file
+ // int[2] is the number of records in the EF file
+ // So int[0] * int[2] = int[1]
+ if (recordSize.length != 3 || recordNumber > recordSize[2]) {
+ throw new RuntimeException("get wrong EF record size format",
+ ar.exception);
+ }
+
+ data = adn.buildAdnString(recordSize[0]);
+
+ if(data == null) {
+ throw new RuntimeException("worong ADN format",
+ ar.exception);
+ }
+
+ phone.mSIMFileHandler.updateEFLinearFixed(ef, recordNumber,
+ data, pin2, obtainMessage(EVENT_UPDATE_RECORD_DONE));
+
+ pendingExtLoads = 1;
+
+ break;
+ case EVENT_UPDATE_RECORD_DONE:
+ ar = (AsyncResult)(msg.obj);
+ if (ar.exception != null) {
+ throw new RuntimeException("update EF adn record failed",
+ ar.exception);
+ }
+ pendingExtLoads = 0;
+ result = null;
+ break;
+ case EVENT_ADN_LOAD_DONE:
+ ar = (AsyncResult)(msg.obj);
+ data = (byte[])(ar.result);
+
+ if (ar.exception != null) {
+ throw new RuntimeException("load failed", ar.exception);
+ }
+
+ if (false) {
+ Log.d(LOG_TAG,"ADN EF: 0x"
+ + Integer.toHexString(ef)
+ + ":" + recordNumber
+ + "\n" + SimUtils.bytesToHexString(data));
+ }
+
+ adn = new AdnRecord(ef, recordNumber, data);
+ result = adn;
+
+ if (adn.hasExtendedRecord()) {
+ // If we have a valid value in the ext record field,
+ // we're not done yet: we need to read the corresponding
+ // ext record and append it
+
+ pendingExtLoads = 1;
+
+ phone.mSIMFileHandler.loadEFLinearFixed(
+ extensionEF, adn.extRecord,
+ obtainMessage(EVENT_EXT_RECORD_LOAD_DONE, adn));
+ }
+ break;
+
+ case EVENT_EXT_RECORD_LOAD_DONE:
+ ar = (AsyncResult)(msg.obj);
+ data = (byte[])(ar.result);
+ adn = (AdnRecord)(ar.userObj);
+
+ if (ar.exception != null) {
+ throw new RuntimeException("load failed", ar.exception);
+ }
+
+ Log.d(LOG_TAG,"ADN extention EF: 0x"
+ + Integer.toHexString(extensionEF)
+ + ":" + adn.extRecord
+ + "\n" + SimUtils.bytesToHexString(data));
+
+ adn.appendExtRecord(data);
+
+ pendingExtLoads--;
+ // result should have been set in
+ // EVENT_ADN_LOAD_DONE or EVENT_ADN_LOAD_ALL_DONE
+ break;
+
+ case EVENT_ADN_LOAD_ALL_DONE:
+ ar = (AsyncResult)(msg.obj);
+ ArrayList<byte[]> datas = (ArrayList<byte[]>)(ar.result);
+
+ if (ar.exception != null) {
+ throw new RuntimeException("load failed", ar.exception);
+ }
+
+ adns = new ArrayList<AdnRecord>(datas.size());
+ result = adns;
+ pendingExtLoads = 0;
+
+ for(int i = 0, s = datas.size() ; i < s ; i++) {
+ adn = new AdnRecord(ef, 1 + i, datas.get(i));
+ adns.add(adn);
+
+ if (adn.hasExtendedRecord()) {
+ // If we have a valid value in the ext record field,
+ // we're not done yet: we need to read the corresponding
+ // ext record and append it
+
+ pendingExtLoads++;
+
+ phone.mSIMFileHandler.loadEFLinearFixed(
+ extensionEF, adn.extRecord,
+ obtainMessage(EVENT_EXT_RECORD_LOAD_DONE, adn));
+ }
+ }
+ break;
+ }
+ } catch (RuntimeException exc) {
+ if (userResponse != null) {
+ AsyncResult.forMessage(userResponse)
+ .exception = exc;
+ userResponse.sendToTarget();
+ // Loading is all or nothing--either every load succeeds
+ // or we fail the whole thing.
+ userResponse = null;
+ }
+ return;
+ }
+
+ if (userResponse != null && pendingExtLoads == 0) {
+ AsyncResult.forMessage(userResponse).result
+ = result;
+
+ userResponse.sendToTarget();
+ userResponse = null;
+ }
+ }
+
+
+}
+
+/**
+ *
+ * Used to load or store ADNs (Abbreviated Dialing Numbers).
+ *
+ * {@hide}
+ *
+ */
+public class AdnRecord implements Parcelable
+{
+ static final String LOG_TAG = "GSM";
+
+ //***** Instance Variables
+
+ String alphaTag = "";
+ String number = "";
+ int extRecord = 0xff;
+ int efid; // or 0 if none
+ int recordNumber; // or 0 if none
+
+
+ //***** Constants
+
+ // In an ADN record, everything but the alpha identifier
+ // is in a footer that's 14 bytes
+ static final int FOOTER_SIZE_BYTES = 14;
+
+ // Maximum size of the un-extended number field
+ static final int MAX_NUMBER_SIZE_BYTES = 11;
+
+ static final int EXT_RECORD_LENGTH_BYTES = 13;
+ static final int EXT_RECORD_TYPE_ADDITIONAL_DATA = 2;
+ static final int EXT_RECORD_TYPE_MASK = 3;
+ static final int MAX_EXT_CALLED_PARTY_LENGTH = 0xa;
+
+ // ADN offset
+ static final int ADN_BCD_NUMBER_LENGTH = 0;
+ static final int ADN_TON_AND_NPI = 1;
+ static final int ADN_DAILING_NUMBER_START = 2;
+ static final int ADN_DAILING_NUMBER_END = 11;
+ static final int ADN_CAPABILITY_ID = 12;
+ static final int ADN_EXTENSION_ID = 13;
+
+ //***** Static Methods
+
+ public static final Parcelable.Creator<AdnRecord> CREATOR
+ = new Parcelable.Creator<AdnRecord>()
+ {
+ public AdnRecord createFromParcel(Parcel source)
+ {
+ int efid;
+ int recordNumber;
+ String alphaTag;
+ String number;
+
+ efid = source.readInt();
+ recordNumber = source.readInt();
+ alphaTag = source.readString();
+ number = source.readString();
+
+ return new AdnRecord(efid, recordNumber, alphaTag, number);
+ }
+
+ public AdnRecord[] newArray(int size)
+ {
+ return new AdnRecord[size];
+ }
+ };
+
+
+ //***** Constructor
+ public
+ AdnRecord (byte[] record)
+ {
+ this(0, 0, record);
+ }
+
+ public
+ AdnRecord (int efid, int recordNumber, byte[] record)
+ {
+ this.efid = efid;
+ this.recordNumber = recordNumber;
+ parseRecord(record);
+ }
+
+ public
+ AdnRecord (String alphaTag, String number)
+ {
+ this(0, 0, alphaTag, number);
+ }
+
+ public
+ AdnRecord (int efid, int recordNumber, String alphaTag, String number)
+ {
+ this.efid = efid;
+ this.recordNumber = recordNumber;
+ this.alphaTag = alphaTag;
+ this.number = number;
+ }
+
+ //***** Instance Methods
+
+ public String getAlphaTag()
+ {
+ return alphaTag;
+ }
+
+ public String getNumber()
+ {
+ return number;
+ }
+
+ public String toString()
+ {
+ return "ADN Record '" + alphaTag + "' '" + number + "'";
+ }
+
+ public boolean isEmpty()
+ {
+ return alphaTag.equals("") && number.equals("");
+ }
+
+ public boolean hasExtendedRecord()
+ {
+ return extRecord != 0 && extRecord != 0xff;
+ }
+
+ public boolean isEqual(AdnRecord adn) {
+ return ( alphaTag.equals(adn.getAlphaTag()) &&
+ number.equals(adn.getNumber()) );
+ }
+ //***** Parcelable Implementation
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags)
+ {
+ dest.writeInt(efid);
+ dest.writeInt(recordNumber);
+ dest.writeString(alphaTag);
+ dest.writeString(number);
+ }
+
+ /**
+ * Build adn hex byte array based on record size
+ * The format of byte array is defined in 51.011 10.5.1
+ *
+ * @param recordSize is the size X of EF record
+ * @return hex byte[recordSize] to be written to EF record
+ * return nulll for wrong format of dialing nubmer or tag
+ */
+ public byte[] buildAdnString(int recordSize) {
+ byte[] bcdNumber;
+ byte[] byteTag;
+ byte[] adnString = null;
+ int footerOffset = recordSize - FOOTER_SIZE_BYTES;
+
+ if (number == null || number.equals("") ||
+ alphaTag == null || alphaTag.equals("")) {
+
+ Log.w(LOG_TAG, "[buildAdnString] Empty alpha tag or number");
+ adnString = new byte[recordSize];
+ for (int i = 0; i < recordSize; i++) {
+ adnString[i] = (byte) 0xFF;
+ }
+ } else if (number.length()
+ > (ADN_DAILING_NUMBER_END - ADN_DAILING_NUMBER_START + 1) * 2) {
+ Log.w(LOG_TAG,
+ "[buildAdnString] Max length of dailing number is 20");
+ } else if (alphaTag.length() > footerOffset) {
+ Log.w(LOG_TAG,
+ "[buildAdnString] Max length of tag is " + footerOffset);
+ } else {
+
+ adnString = new byte[recordSize];
+ for (int i = 0; i < recordSize; i++) {
+ adnString[i] = (byte) 0xFF;
+ }
+
+ bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(number);
+
+ System.arraycopy(bcdNumber, 0, adnString,
+ footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
+
+ adnString[footerOffset + ADN_BCD_NUMBER_LENGTH]
+ = (byte) (bcdNumber.length);
+ adnString[footerOffset + ADN_CAPABILITY_ID]
+ = (byte) 0xFF; // Capacility Id
+ adnString[footerOffset + ADN_EXTENSION_ID]
+ = (byte) 0xFF; // Extension Record Id
+
+ byteTag = GsmAlphabet.stringToGsm8BitPacked(alphaTag);
+ System.arraycopy(byteTag, 0, adnString, 0, byteTag.length);
+
+ }
+
+ return adnString;
+ }
+
+ /**
+ * See TS 51.011 10.5.10
+ */
+ public void
+ appendExtRecord (byte[] extRecord) {
+ try {
+ if (extRecord.length != EXT_RECORD_LENGTH_BYTES) {
+ return;
+ }
+
+ if ((extRecord[0] & EXT_RECORD_TYPE_MASK)
+ != EXT_RECORD_TYPE_ADDITIONAL_DATA
+ ) {
+ return;
+ }
+
+ if ((0xff & extRecord[1]) > MAX_EXT_CALLED_PARTY_LENGTH) {
+ // invalid or empty record
+ return;
+ }
+
+ number += PhoneNumberUtils.calledPartyBCDFragmentToString(
+ extRecord, 2, 0xff & extRecord[1]);
+
+ // We don't support ext record chaining.
+
+ } catch (RuntimeException ex) {
+
+
+
+
+ Log.w(LOG_TAG, "Error parsing AdnRecord ext record", ex);
+ }
+ }
+
+ //***** Private Methods
+
+ /**
+ * alphaTag and number are set to null on invalid format
+ */
+ private void
+ parseRecord(byte[] record) {
+ try {
+ alphaTag = SimUtils.adnStringFieldToString(
+ record, 0, record.length - FOOTER_SIZE_BYTES);
+
+ int footerOffset = record.length - FOOTER_SIZE_BYTES;
+
+ int numberLength = 0xff & record[footerOffset];
+
+ if (numberLength > MAX_NUMBER_SIZE_BYTES) {
+ // Invalid number length
+ number = "";
+ return;
+ }
+
+ // Please note 51.011 10.5.1:
+ //
+ // "If the Dialling Number/SSC String does not contain
+ // a dialling number, e.g. a control string deactivating
+ // a service, the TON/NPI byte shall be set to 'FF' by
+ // the ME (see note 2)."
+
+ number = PhoneNumberUtils.calledPartyBCDToString(
+ record, footerOffset + 1, numberLength);
+
+
+ extRecord = 0xff & record[record.length - 1];
+
+ } catch (RuntimeException ex) {
+ Log.w(LOG_TAG, "Error parsing AdnRecord", ex);
+ number = "";
+ alphaTag = "";
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/AdnRecordCache.java b/telephony/java/com/android/internal/telephony/gsm/AdnRecordCache.java
new file mode 100644
index 0000000..9da18e3
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/AdnRecordCache.java
@@ -0,0 +1,346 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.util.SparseArray;
+import android.util.Log;
+import android.os.Message;
+import android.os.Handler;
+import android.os.AsyncResult;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * {@hide}
+ */
+public final class AdnRecordCache extends Handler implements SimConstants
+{
+ //***** Instance Variables
+
+ GSMPhone phone;
+
+ // Indexed by EF ID
+ SparseArray<ArrayList<AdnRecord>> adnLikeFiles
+ = new SparseArray<ArrayList<AdnRecord>>();
+
+ // People waiting for ADN-like files to be loaded
+ SparseArray<ArrayList<Message>> adnLikeWaiters
+ = new SparseArray<ArrayList<Message>>();
+
+ // People waiting for adn record to be updated
+ SparseArray<Message> userWriteResponse = new SparseArray<Message>();
+
+ //***** Event Constants
+
+ static final int EVENT_LOAD_ALL_ADN_LIKE_DONE = 1;
+ static final int EVENT_UPDATE_ADN_DONE = 2;
+
+ //***** Constructor
+
+
+ /*package*/
+ AdnRecordCache(GSMPhone phone)
+ {
+ this.phone = phone;
+ }
+
+ //***** Called from SIMRecords
+
+ /**
+ * Called from SIMRecords.onRadioNotAvailable and SIMRecords.handleSimRefresh.
+ */
+ /*package*/ void
+ reset()
+ {
+ adnLikeFiles.clear();
+
+ clearWaiters();
+ clearUserWriters();
+
+ }
+
+ private void clearWaiters() {
+ int size = adnLikeWaiters.size();
+ for (int i = 0; i < size; i++) {
+ ArrayList<Message> waiters = adnLikeWaiters.valueAt(i);
+ AsyncResult ar = new AsyncResult(null, null, new RuntimeException("AdnCache reset"));
+ notifyWaiters(waiters, ar);
+ }
+ adnLikeWaiters.clear();
+ }
+
+ private void clearUserWriters() {
+ int size = userWriteResponse.size();
+ for (int i = 0; i < size; i++) {
+ sendErrorResponse(userWriteResponse.valueAt(i), "AdnCace reset");
+ }
+ userWriteResponse.clear();
+ }
+
+ /**
+ * @return List of AdnRecords for efid if we've already loaded them this
+ * radio session, or null if we haven't
+ */
+ /*package*/ ArrayList<AdnRecord>
+ getRecordsIfLoaded(int efid)
+ {
+ return adnLikeFiles.get(efid);
+ }
+
+ /**
+ * Returns extension ef associated with ADN-like EF or -1 if
+ * we don't know.
+ *
+ * See 3GPP TS 51.011 for this mapping
+ */
+ private int
+ extensionEfForEf(int efid)
+ {
+ switch (efid) {
+ case EF_MBDN: return EF_EXT6;
+ case EF_ADN: return EF_EXT1;
+ case EF_SDN: return EF_EXT3;
+ case EF_FDN: return EF_EXT2;
+ case EF_MSISDN: return EF_EXT1;
+ default: return -1;
+ }
+ }
+
+ private void sendErrorResponse(Message response, String errString) {
+ if (response != null) {
+ Exception e = new RuntimeException(errString);
+ AsyncResult.forMessage(response).exception = e;
+ response.sendToTarget();
+ }
+ }
+
+ /**
+ * Update an ADN-like record in EF by record index
+ *
+ * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
+ * @param adn is the new adn to be stored
+ * @param recordIndex is the 1-based adn record index
+ * @param pin2 is required to update EF_FDN, otherwise must be null
+ * @param response message to be posted when done
+ * response.exception hold the exception in error
+ */
+ void updateAdnByIndex(int efid, AdnRecord adn, int recordIndex, String pin2,
+ Message response) {
+
+ int extensionEF = extensionEfForEf(efid);
+ if (extensionEF < 0) {
+ sendErrorResponse(response, "EF is not known ADN-like EF:" + efid);
+ return;
+ }
+
+ Message pendingResponse = userWriteResponse.get(efid);
+ if (pendingResponse != null) {
+ sendErrorResponse(response, "Have pending update for EF:" + efid);
+ return;
+ }
+
+ userWriteResponse.put(efid, response);
+
+ new AdnRecordLoader(phone).updateEF(adn, efid, extensionEF,
+ recordIndex, pin2,
+ obtainMessage(EVENT_UPDATE_ADN_DONE, efid, recordIndex, adn));
+ }
+
+ /**
+ * Replace oldAdn with newAdn in ADN-like record in EF
+ *
+ * The ADN-like records must be read through requestLoadAllAdnLike() before
+ *
+ * @param efid must be one of EF_ADN, EF_FDN, and EF_SDN
+ * @param oldAdn is the adn to be replaced
+ * If oldAdn.isEmpty() is ture, it insert the newAdn
+ * @param newAdn is the adn to be stored
+ * If newAdn.isEmpty() is true, it delete the oldAdn
+ * @param pin2 is required to update EF_FDN, otherwise must be null
+ * @param response message to be posted when done
+ * response.exception hold the exception in error
+ */
+ void updateAdnBySearch(int efid, AdnRecord oldAdn, AdnRecord newAdn,
+ String pin2, Message response) {
+
+ int extensionEF;
+ extensionEF = extensionEfForEf(efid);
+
+ if (extensionEF < 0) {
+ sendErrorResponse(response, "EF is not known ADN-like EF:" + efid);
+ return;
+ }
+
+ ArrayList<AdnRecord> oldAdnList;
+ oldAdnList = getRecordsIfLoaded(efid);
+
+ if (oldAdnList == null) {
+ sendErrorResponse(response, "Adn list not exist for EF:" + efid);
+ return;
+ }
+
+ int index = -1;
+ int count = 1;
+ for (Iterator<AdnRecord> it = oldAdnList.iterator(); it.hasNext(); ) {
+ if (oldAdn.isEqual(it.next())) {
+ index = count;
+ break;
+ }
+ count++;
+ }
+
+ if (index == -1) {
+ sendErrorResponse(response, "Adn record don't exist for " + oldAdn);
+ return;
+ }
+
+ Message pendingResponse = userWriteResponse.get(efid);
+
+ if (pendingResponse != null) {
+ sendErrorResponse(response, "Have pending update for EF:" + efid);
+ return;
+ }
+
+ userWriteResponse.put(efid, response);
+
+ new AdnRecordLoader(phone).updateEF(newAdn, efid, extensionEF,
+ index, pin2,
+ obtainMessage(EVENT_UPDATE_ADN_DONE, efid, index, newAdn));
+ }
+
+
+ /**
+ * Responds with exception (in response) if efid is not a known ADN-like
+ * record
+ */
+ /*package*/ void
+ requestLoadAllAdnLike (int efid, Message response)
+ {
+ ArrayList<Message> waiters;
+ ArrayList<AdnRecord> result;
+
+ result = getRecordsIfLoaded(efid);
+
+ // Have we already loaded this efid?
+ if (result != null) {
+ if (response != null) {
+ AsyncResult.forMessage(response).result = result;
+ response.sendToTarget();
+ }
+
+ return;
+ }
+
+ // Have we already *started* loading this efid?
+
+ waiters = adnLikeWaiters.get(efid);
+
+ if (waiters != null) {
+ // There's a pending request for this EF already
+ // just add ourselves to it
+
+ waiters.add(response);
+ return;
+ }
+
+ // Start loading efid
+
+ waiters = new ArrayList<Message>();
+ waiters.add(response);
+
+ adnLikeWaiters.put(efid, waiters);
+
+ int extensionEF = extensionEfForEf(efid);
+
+ if (extensionEF < 0) {
+ // respond with error if not known ADN-like record
+
+ if (response != null) {
+ AsyncResult.forMessage(response).exception
+ = new RuntimeException("EF is not known ADN-like EF:" + efid);
+ response.sendToTarget();
+ }
+
+ return;
+ }
+
+ new AdnRecordLoader(phone).loadAllFromEF(efid, extensionEF,
+ obtainMessage(EVENT_LOAD_ALL_ADN_LIKE_DONE, efid, 0));
+ }
+
+ //***** Private methods
+
+ private void
+ notifyWaiters(ArrayList<Message> waiters, AsyncResult ar)
+ {
+
+ if (waiters == null) {
+ return;
+ }
+
+ for (int i = 0, s = waiters.size() ; i < s ; i++) {
+ Message waiter = waiters.get(i);
+
+ AsyncResult.forMessage(waiter, ar.result, ar.exception);
+ waiter.sendToTarget();
+ }
+ }
+
+ //***** Overridden from Handler
+
+ public void
+ handleMessage(Message msg) {
+ AsyncResult ar;
+ int efid;
+
+ switch(msg.what) {
+ case EVENT_LOAD_ALL_ADN_LIKE_DONE:
+ /* arg1 is efid, obj.result is ArrayList<AdnRecord>*/
+ ar = (AsyncResult) msg.obj;
+ efid = msg.arg1;
+ ArrayList<Message> waiters;
+
+ waiters = adnLikeWaiters.get(efid);
+ adnLikeWaiters.delete(efid);
+
+ if (ar.exception == null) {
+ adnLikeFiles.put(efid, (ArrayList<AdnRecord>) (ar.result));
+ }
+ notifyWaiters(waiters, ar);
+ break;
+ case EVENT_UPDATE_ADN_DONE:
+ ar = (AsyncResult)msg.obj;
+ efid = msg.arg1;
+ int index = msg.arg2;
+ AdnRecord adn = (AdnRecord) (ar.userObj);
+
+ if (ar.exception == null) {
+ adnLikeFiles.get(efid).set(index - 1, adn);
+ }
+
+ Message response = userWriteResponse.get(efid);
+ userWriteResponse.delete(efid);
+
+ AsyncResult.forMessage(response, null, ar.exception);
+ response.sendToTarget();
+ break;
+ }
+
+ }
+
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/ApnSetting.java b/telephony/java/com/android/internal/telephony/gsm/ApnSetting.java
new file mode 100644
index 0000000..2f91411
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/ApnSetting.java
@@ -0,0 +1,75 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import com.android.internal.telephony.*;
+/**
+ * This class represents a apn setting for create PDP link
+ */
+public class ApnSetting {
+
+ String carrier;
+ String apn;
+ String proxy;
+ String port;
+ String mmsc;
+ String mmsProxy;
+ String mmsPort;
+ String user;
+ String password;
+ String[] types;
+
+
+ ApnSetting(String carrier, String apn, String proxy, String port,
+ String mmsc, String mmsProxy, String mmsPort,
+ String user, String password, String[] types) {
+ this.carrier = carrier;
+ this.apn = apn;
+ this.proxy = proxy;
+ this.port = port;
+ this.mmsc = mmsc;
+ this.mmsProxy = mmsProxy;
+ this.mmsPort = mmsPort;
+ this.user = user;
+ this.password = password;
+ this.types = types;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(carrier)
+ .append(", ").append(apn)
+ .append(", ").append(proxy)
+ .append(", ").append(mmsc)
+ .append(", ").append(mmsProxy)
+ .append(", ").append(mmsPort)
+ .append(", ").append(port);
+ for (String t : types) {
+ sb.append(", ").append(t);
+ }
+ return sb.toString();
+ }
+
+ boolean canHandleType(String type) {
+ for (String t : types) {
+ if (t.equals(type) || t.equals(Phone.APN_TYPE_ALL)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/BaseCommands.java b/telephony/java/com/android/internal/telephony/gsm/BaseCommands.java
new file mode 100644
index 0000000..58a5bba
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/BaseCommands.java
@@ -0,0 +1,359 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.content.Context;
+import android.os.RegistrantList;
+import android.os.Registrant;
+import android.os.Handler;
+import android.os.AsyncResult;
+import android.os.SystemProperties;
+import android.provider.Checkin;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+public abstract class BaseCommands implements CommandsInterface
+{
+ static final String LOG_TAG = "GSM";
+
+ //***** Instance Variables
+ protected Context mContext;
+ protected RadioState mState = RadioState.RADIO_UNAVAILABLE;
+ protected Object mStateMonitor = new Object();
+
+ protected RegistrantList mRadioStateChangedRegistrants = new RegistrantList();
+ protected RegistrantList mOnRegistrants = new RegistrantList();
+ protected RegistrantList mAvailRegistrants = new RegistrantList();
+ protected RegistrantList mOffOrNotAvailRegistrants = new RegistrantList();
+ protected RegistrantList mNotAvailRegistrants = new RegistrantList();
+ protected RegistrantList mSIMReadyRegistrants = new RegistrantList();
+ protected RegistrantList mSIMLockedRegistrants = new RegistrantList();
+ protected RegistrantList mCallStateRegistrants = new RegistrantList();
+ protected RegistrantList mNetworkStateRegistrants = new RegistrantList();
+ protected RegistrantList mPDPRegistrants = new RegistrantList();
+ protected Registrant mSMSRegistrant;
+ protected Registrant mNITZTimeRegistrant;
+ protected Registrant mSignalStrengthRegistrant;
+ protected Registrant mUSSDRegistrant;
+ protected Registrant mSmsOnSimRegistrant;
+ /** Registrant for handling SMS Status Reports */
+ protected Registrant mSmsStatusRegistrant;
+ /** Registrant for handling Supplementary Service Notifications */
+ protected Registrant mSsnRegistrant;
+ protected Registrant mStkSessionEndRegistrant;
+ protected Registrant mStkProCmdRegistrant;
+ protected Registrant mStkEventRegistrant;
+ protected Registrant mStkCallSetUpRegistrant;
+ /** Registrant for handling SIM SMS storage full messages */
+ protected Registrant mSimSmsFullRegistrant;
+ /** Registrant for handling SIM Refresh notifications */
+ protected Registrant mSimRefreshRegistrant;
+ /** Registrant for handling RING notifications */
+ protected Registrant mRingRegistrant;
+
+ public BaseCommands(Context context) {
+ mContext = context; // May be null (if so we won't log statistics)
+ }
+
+ //***** CommandsInterface implementation
+
+ public RadioState
+ getRadioState()
+ {
+ return mState;
+ }
+
+
+ public void
+ registerForRadioStateChanged(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ synchronized (mStateMonitor) {
+ mRadioStateChangedRegistrants.add(r);
+ r.notifyRegistrant();
+ }
+ }
+
+ public void
+ registerForOn(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ synchronized (mStateMonitor) {
+ mOnRegistrants.add(r);
+
+ if (mState.isOn()) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ }
+
+
+ public void
+ registerForAvailable(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ synchronized (mStateMonitor) {
+ mAvailRegistrants.add(r);
+
+ if (mState.isAvailable()) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ }
+
+ public void
+ registerForNotAvailable(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ synchronized (mStateMonitor) {
+ mNotAvailRegistrants.add(r);
+
+ if (!mState.isAvailable()) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ }
+
+ public void
+ registerForOffOrNotAvailable(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ synchronized (mStateMonitor) {
+ mOffOrNotAvailRegistrants.add(r);
+
+ if (mState == RadioState.RADIO_OFF || !mState.isAvailable()) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ }
+
+
+ /** Any transition into SIM_READY */
+ public void
+ registerForSIMReady(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ synchronized (mStateMonitor) {
+ mSIMReadyRegistrants.add(r);
+
+ if (mState.isSIMReady()) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ }
+
+ public void
+ registerForSIMLockedOrAbsent(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ synchronized (mStateMonitor) {
+ mSIMLockedRegistrants.add(r);
+
+ if (mState == RadioState.SIM_LOCKED_OR_ABSENT) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ }
+
+ public void
+ registerForCallStateChanged(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ mCallStateRegistrants.add(r);
+ }
+
+ public void
+ registerForNetworkStateChanged(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ mNetworkStateRegistrants.add(r);
+ }
+
+ public void
+ registerForPDPStateChanged(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ mPDPRegistrants.add(r);
+ }
+
+ public void
+ setOnNewSMS(Handler h, int what, Object obj)
+ {
+ mSMSRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void
+ setOnSmsOnSim(Handler h, int what, Object obj)
+ {
+ mSmsOnSimRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void setOnSmsStatus(Handler h, int what, Object obj) {
+ mSmsStatusRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void
+ setOnSignalStrengthUpdate(Handler h, int what, Object obj)
+ {
+ mSignalStrengthRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void
+ setOnNITZTime(Handler h, int what, Object obj)
+ {
+ mNITZTimeRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void
+ setOnUSSD(Handler h, int what, Object obj)
+ {
+ mUSSDRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void
+ setOnSuppServiceNotification(Handler h, int what, Object obj)
+ {
+ mSsnRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void
+ setOnStkSessionEnd(Handler h, int what, Object obj)
+ {
+ mStkSessionEndRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void
+ setOnStkProactiveCmd(Handler h, int what, Object obj)
+ {
+ mStkProCmdRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void
+ setOnStkEvent(Handler h, int what, Object obj)
+ {
+ mStkEventRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void
+ setOnStkCallSetUp(Handler h, int what, Object obj)
+ {
+ mStkCallSetUpRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void setOnSimSmsFull(Handler h, int what, Object obj) {
+ mSimSmsFullRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void setOnSimRefresh(Handler h, int what, Object obj) {
+ mSimRefreshRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void setOnCallRing(Handler h, int what, Object obj) {
+ mRingRegistrant = new Registrant (h, what, obj);
+ }
+
+ //***** Protected Methods
+ /**
+ * Store new RadioState and send notification based on the changes
+ *
+ * This function is called only by RIL.java when receiving unsolicited
+ * RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED
+ *
+ * RadioState has 5 values : RADIO_OFF, RADIO_UNAVAILABLE, SIM_NOT_READY,
+ * SIM_LOCKED_OR_ABSENT, and SIM_READY.
+ *
+ * @param newState new RadioState decoded from RIL_UNSOL_RADIO_STATE_CHANGED
+ */
+ protected void setRadioState(RadioState newState) {
+ RadioState oldState;
+
+ synchronized (mStateMonitor) {
+ if (Config.LOGV) {
+ Log.v(LOG_TAG, "setRadioState old: " + mState
+ + " new " + newState);
+ }
+
+ oldState = mState;
+ mState = newState;
+
+ if (oldState == mState) {
+ // no state transition
+ return;
+ }
+
+ if (mContext != null &&
+ newState == RadioState.RADIO_UNAVAILABLE &&
+ oldState != RadioState.RADIO_OFF) {
+ Checkin.updateStats(mContext.getContentResolver(),
+ Checkin.Stats.Tag.PHONE_RADIO_RESETS, 1, 0.0);
+ }
+
+ mRadioStateChangedRegistrants.notifyRegistrants();
+
+ if (mState.isAvailable() && !oldState.isAvailable()) {
+ Log.d(LOG_TAG,"Notifying: radio available");
+ mAvailRegistrants.notifyRegistrants();
+ onRadioAvailable();
+ }
+
+ if (!mState.isAvailable() && oldState.isAvailable()) {
+ Log.d(LOG_TAG,"Notifying: radio not available");
+ mNotAvailRegistrants.notifyRegistrants();
+ }
+
+ if (mState.isSIMReady() && !oldState.isSIMReady()) {
+ Log.d(LOG_TAG,"Notifying: SIM ready");
+ mSIMReadyRegistrants.notifyRegistrants();
+ }
+
+ if (mState == RadioState.SIM_LOCKED_OR_ABSENT) {
+ Log.d(LOG_TAG,"Notifying: SIM locked or absent");
+ mSIMLockedRegistrants.notifyRegistrants();
+ }
+
+ if (mState.isOn() && !oldState.isOn()) {
+ Log.d(LOG_TAG,"Notifying: Radio On");
+ mOnRegistrants.notifyRegistrants();
+ }
+
+ if ((!mState.isOn() || !mState.isAvailable())
+ && !((!oldState.isOn() || !oldState.isAvailable()))
+ ) {
+ Log.d(LOG_TAG,"Notifying: radio off or not available");
+ mOffOrNotAvailRegistrants.notifyRegistrants();
+ }
+ }
+ }
+
+ protected void
+ onRadioAvailable()
+ {
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/CallFailCause.java b/telephony/java/com/android/internal/telephony/gsm/CallFailCause.java
new file mode 100644
index 0000000..e7fbf6b
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/CallFailCause.java
@@ -0,0 +1,50 @@
+/*
+ * 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.internal.telephony.gsm;
+
+/**
+ * Call fail causes from TS 24.008 .
+ * These are mostly the cause codes we need to distinguish for the UI.
+ * See 22.001 Annex F.4 for mapping of cause codes to local tones.
+ *
+ * {@hide}
+ *
+ */
+public interface CallFailCause {
+ static final int NORMAL_CLEARING = 16;
+ // Busy Tone
+ static final int USER_BUSY = 17;
+
+ // No Tone
+ static final int NUMBER_CHANGED = 22;
+ static final int STATUS_ENQUIRY = 30;
+ static final int NORMAL_UNSPECIFIED = 31;
+
+ // Congestion Tone
+ static final int NO_CIRCUIT_AVAIL = 34;
+ static final int TEMPORARY_FAILURE = 41;
+ static final int SWITCHING_CONGESTION = 42;
+ static final int CHANNEL_NOT_AVAIL = 44;
+ static final int QOS_NOT_AVAIL = 49;
+ static final int BEARER_NOT_AVAIL = 58;
+
+ // others
+ static final int ACM_LIMIT_EXCEEDED = 68;
+ static final int CALL_BARRED = 240;
+ static final int FDN_BLOCKED = 241;
+ static final int ERROR_UNSPECIFIED = 0xffff;
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/CallForwardInfo.java b/telephony/java/com/android/internal/telephony/gsm/CallForwardInfo.java
new file mode 100644
index 0000000..bf31b13
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/CallForwardInfo.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.telephony.PhoneNumberUtils;
+
+/**
+ * See also RIL_CallForwardInfo in include/telephony/ril.h
+ *
+ * {@hide}
+ */
+public class CallForwardInfo
+{
+ public int status; /*1 = active, 0 = not active */
+ public int reason; /* from TS 27.007 7.11 "reason" */
+ public int serviceClass; /* Sum of CommandsInterface.SERVICE_CLASS */
+ public int toa; /* "type" from TS 27.007 7.11 */
+ public String number; /* "number" from TS 27.007 7.11 */
+ public int timeSeconds; /* for CF no reply only */
+
+ public String toString()
+ {
+ return super.toString() + (status == 0 ? " not active " : " active ")
+ + " reason: " + reason
+ + " serviceClass: " + serviceClass
+ + " \"" + PhoneNumberUtils.stringFromStringAndTOA(number, toa) + "\" "
+ + timeSeconds + " seconds";
+
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/CallTracker.java b/telephony/java/com/android/internal/telephony/gsm/CallTracker.java
new file mode 100644
index 0000000..9821852
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/CallTracker.java
@@ -0,0 +1,952 @@
+/*
+ * 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.internal.telephony.gsm;
+import com.android.internal.telephony.*;
+import android.os.*;
+import android.os.AsyncResult;
+import android.util.Log;
+import android.provider.Checkin;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public final class CallTracker extends Handler
+{
+ static final String LOG_TAG = "GSM";
+ private static final boolean REPEAT_POLLING = false;
+
+ private static final boolean DBG_POLL = false;
+
+ //***** Constants
+
+ static final int POLL_DELAY_MSEC = 250;
+ static final int MAX_CONNECTIONS = 7; // only 7 connections allowed in GSM
+ static final int MAX_CONNECTIONS_PER_CALL = 5; // only 5 connections allowed per call
+
+ //***** Instance Variables
+
+ GSMConnection connections[] = new GSMConnection[MAX_CONNECTIONS];
+ RegistrantList voiceCallEndedRegistrants = new RegistrantList();
+ RegistrantList voiceCallStartedRegistrants = new RegistrantList();
+
+
+ // connections dropped durin last poll
+ ArrayList<GSMConnection> droppedDuringPoll
+ = new ArrayList<GSMConnection>(MAX_CONNECTIONS);
+
+ GSMCall ringingCall = new GSMCall(this);
+ // A call that is ringing or (call) waiting
+ GSMCall foregroundCall = new GSMCall(this);
+ GSMCall backgroundCall = new GSMCall(this);
+
+ GSMConnection pendingMO;
+ boolean hangupPendingMO;
+
+ GSMPhone phone;
+ CommandsInterface cm;
+ boolean desiredMute = false; // false = mute off
+
+ Phone.State state = Phone.State.IDLE;
+
+ int pendingOperations;
+ boolean needsPoll;
+ Message lastRelevantPoll;
+
+
+ //***** Events
+
+ static final int EVENT_POLL_CALLS_RESULT = 1;
+ static final int EVENT_CALL_STATE_CHANGE = 2;
+ static final int EVENT_REPOLL_AFTER_DELAY = 3;
+ static final int EVENT_OPERATION_COMPLETE = 4;
+ static final int EVENT_GET_LAST_CALL_FAIL_CAUSE = 5;
+
+ static final int EVENT_SWITCH_RESULT = 8;
+ static final int EVENT_RADIO_AVAILABLE = 9;
+ static final int EVENT_RADIO_NOT_AVAILABLE = 10;
+ static final int EVENT_CONFERENCE_RESULT = 11;
+ static final int EVENT_SEPARATE_RESULT = 12;
+ static final int EVENT_ECT_RESULT = 13;
+
+ //***** Constructors
+
+ CallTracker (GSMPhone phone)
+ {
+ this.phone = phone;
+ cm = phone.mCM;
+
+ cm.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
+
+ cm.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
+ cm.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null);
+ }
+
+ //***** Instance Methods
+
+ //***** Public Methods
+ public void registerForVoiceCallStarted(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant(h, what, obj);
+ voiceCallStartedRegistrants.add(r);
+ }
+
+ public void registerForVoiceCallEnded(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant(h, what, obj);
+ voiceCallEndedRegistrants.add(r);
+ }
+
+ private void
+ fakeHoldForegroundBeforeDial()
+ {
+ List<Connection> connCopy;
+
+ // We need to make a copy here, since fakeHoldBeforeDial()
+ // modifies the lists, and we don't want to reverse the order
+ connCopy = (List<Connection>) foregroundCall.connections.clone();
+
+ for (int i = 0, s = connCopy.size() ; i < s ; i++) {
+ GSMConnection conn = (GSMConnection)connCopy.get(i);
+
+ conn.fakeHoldBeforeDial();
+ }
+ }
+
+ /**
+ * clirMode is one of the CLIR_ constants
+ */
+ Connection
+ dial (String dialString, int clirMode) throws CallStateException {
+ // note that this triggers call state changed notif
+ clearDisconnected();
+
+ if (!canDial()) {
+ throw new CallStateException("cannot dial in current state");
+ }
+
+ // The new call must be assigned to the foreground call.
+ // That call must be idle, so place anything that's
+ // there on hold
+ if (foregroundCall.getState() == Call.State.ACTIVE) {
+ // this will probably be done by the radio anyway
+ // but the dial might fail before this happens
+ // and we need to make sure the foreground call is clear
+ // for the newly dialed connection
+ switchWaitingOrHoldingAndActive();
+
+ // Fake local state so that
+ // a) foregroundCall is empty for the newly dialed connection
+ // b) hasNonHangupStateChanged remains false in the
+ // next poll, so that we don't clear a failed dialing call
+ fakeHoldForegroundBeforeDial();
+ }
+
+ if (foregroundCall.getState() != Call.State.IDLE) {
+ //we should have failed in !canDial() above before we get here
+ throw new CallStateException("cannot dial in current state");
+ }
+
+ pendingMO = new GSMConnection(dialString, this, foregroundCall);
+ hangupPendingMO = false;
+
+ if (pendingMO.address == null || pendingMO.address.length() == 0
+ || pendingMO.address.indexOf(PhoneNumberUtils.WILD) >= 0
+ ) {
+ // Phone number is invalid
+ pendingMO.cause = Connection.DisconnectCause.INVALID_NUMBER;
+
+ // handlePollCalls() will notice this call not present
+ // and will mark it as dropped.
+ pollCallsWhenSafe();
+ } else {
+ // Always unmute when initiating a new call
+ setMute(false);
+
+ cm.dial(pendingMO.address, clirMode, obtainCompleteMessage());
+ }
+
+ updatePhoneState();
+ phone.notifyCallStateChanged();
+
+ return pendingMO;
+ }
+
+
+ Connection
+ dial (String dialString) throws CallStateException
+ {
+ return dial(dialString, CommandsInterface.CLIR_DEFAULT);
+ }
+
+ void
+ acceptCall () throws CallStateException
+ {
+ // FIXME if SWITCH fails, should retry with ANSWER
+ // in case the active/holding call disappeared and this
+ // is no longer call waiting
+
+ if (ringingCall.getState() == Call.State.INCOMING) {
+ Log.i("phone", "acceptCall: incoming...");
+ // Always unmute when answering a new call
+ setMute(false);
+ cm.acceptCall(obtainCompleteMessage());
+ } else if (ringingCall.getState() == Call.State.WAITING) {
+ setMute(false);
+ switchWaitingOrHoldingAndActive();
+ } else {
+ throw new CallStateException("phone not ringing");
+ }
+ }
+
+ void
+ rejectCall () throws CallStateException
+ {
+ // AT+CHLD=0 means "release held or UDUB"
+ // so if the phone isn't ringing, this could hang up held
+ if (ringingCall.getState().isRinging()) {
+ cm.rejectCall(obtainCompleteMessage());
+ } else {
+ throw new CallStateException("phone not ringing");
+ }
+ }
+
+ void
+ switchWaitingOrHoldingAndActive() throws CallStateException {
+ // Should we bother with this check?
+ if (ringingCall.getState() == Call.State.INCOMING) {
+ throw new CallStateException("cannot be in the incoming state");
+ } else {
+ cm.switchWaitingOrHoldingAndActive(
+ obtainCompleteMessage(EVENT_SWITCH_RESULT));
+ }
+ }
+
+ void
+ conference() throws CallStateException
+ {
+ cm.conference(obtainCompleteMessage(EVENT_CONFERENCE_RESULT));
+ }
+
+ void
+ explicitCallTransfer() throws CallStateException
+ {
+ cm.explicitCallTransfer(obtainCompleteMessage(EVENT_ECT_RESULT));
+ }
+
+ void
+ clearDisconnected()
+ {
+ internalClearDisconnected();
+
+ updatePhoneState();
+ phone.notifyCallStateChanged();
+ }
+
+ boolean
+ canConference()
+ {
+ return foregroundCall.getState() == Call.State.ACTIVE
+ && backgroundCall.getState() == Call.State.HOLDING
+ && !backgroundCall.isFull()
+ && !foregroundCall.isFull();
+ }
+
+ boolean
+ canDial()
+ {
+ boolean ret;
+ int serviceState = phone.getServiceState().getState();
+
+ ret = (serviceState != ServiceState.STATE_POWER_OFF) &&
+ pendingMO == null
+ && !ringingCall.isRinging()
+ && (!foregroundCall.getState().isAlive()
+ || !backgroundCall.getState().isAlive());
+
+ return ret;
+ }
+
+ boolean
+ canTransfer()
+ {
+ return foregroundCall.getState() == Call.State.ACTIVE
+ && backgroundCall.getState() == Call.State.HOLDING;
+ }
+
+ //***** Private Instance Methods
+
+ private void
+ internalClearDisconnected()
+ {
+ ringingCall.clearDisconnected();
+ foregroundCall.clearDisconnected();
+ backgroundCall.clearDisconnected();
+ }
+
+ /**
+ * @return true if we're idle or there's a call to getCurrentCalls() pending
+ * but nothing else
+ */
+ private boolean
+ checkNoOperationsPending()
+ {
+ if (DBG_POLL) log("checkNoOperationsPending: pendingOperations=" +
+ pendingOperations);
+ return pendingOperations == 0;
+ }
+
+
+ /**
+ * Obtain a message to use for signalling "invoke getCurrentCalls() when
+ * this operation and all other pending operations are complete
+ */
+ private Message
+ obtainCompleteMessage()
+ {
+ return obtainCompleteMessage(EVENT_OPERATION_COMPLETE);
+ }
+
+ /**
+ * Obtain a message to use for signalling "invoke getCurrentCalls() when
+ * this operation and all other pending operations are complete
+ */
+ private Message
+ obtainCompleteMessage(int what)
+ {
+ pendingOperations++;
+ lastRelevantPoll = null;
+ needsPoll = true;
+
+ if (DBG_POLL) log("obtainCompleteMessage: pendingOperations=" +
+ pendingOperations + ", needsPoll=" + needsPoll);
+
+ return obtainMessage(what);
+ }
+
+ /**
+ * Obtain a complete message that indicates that this operation
+ * does not require polling of getCurrentCalls(). However, if other
+ * operations that do need getCurrentCalls() are pending or are
+ * scheduled while this operation is pending, the invocatoin
+ * of getCurrentCalls() will be postponed until this
+ * operation is also complete.
+ */
+ private Message
+ obtainNoPollCompleteMessage(int what)
+ {
+ pendingOperations++;
+ lastRelevantPoll = null;
+ return obtainMessage(what);
+ }
+
+
+ private void
+ operationComplete()
+ {
+ pendingOperations--;
+
+ if (DBG_POLL) log("operationComplete: pendingOperations=" +
+ pendingOperations + ", needsPoll=" + needsPoll);
+
+ if (pendingOperations == 0 && needsPoll) {
+ lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
+ cm.getCurrentCalls(lastRelevantPoll);
+ } else if (pendingOperations < 0) {
+ // this should never happen
+ Log.e(LOG_TAG,"CallTracker.pendingOperations < 0");
+ pendingOperations = 0;
+ }
+ }
+
+ private void
+ pollCallsWhenSafe()
+ {
+ needsPoll = true;
+
+ if (checkNoOperationsPending()) {
+ lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
+ cm.getCurrentCalls(lastRelevantPoll);
+ }
+ }
+
+ private void
+ pollCallsAfterDelay()
+ {
+ Message msg = obtainMessage();
+
+ msg.what = EVENT_REPOLL_AFTER_DELAY;
+ sendMessageDelayed(msg, POLL_DELAY_MSEC);
+ }
+
+ private boolean
+ isCommandExceptionRadioNotAvailable(Throwable e)
+ {
+ return e != null && e instanceof CommandException
+ && ((CommandException)e).getCommandError()
+ == CommandException.Error.RADIO_NOT_AVAILABLE;
+ }
+
+ private void
+ updatePhoneState()
+ {
+ Phone.State oldState = state;
+
+ if (ringingCall.isRinging()) {
+ state = Phone.State.RINGING;
+ } else if (pendingMO != null ||
+ !(foregroundCall.isIdle() && backgroundCall.isIdle())) {
+ state = Phone.State.OFFHOOK;
+ } else {
+ state = Phone.State.IDLE;
+ }
+
+ if (state == Phone.State.IDLE && oldState != state) {
+ voiceCallEndedRegistrants.notifyRegistrants(
+ new AsyncResult(null, null, null));
+ } else if (oldState == Phone.State.IDLE && oldState != state) {
+ voiceCallStartedRegistrants.notifyRegistrants (
+ new AsyncResult(null, null, null));
+ }
+
+ if (state != oldState) {
+ phone.notifyPhoneStateChanged();
+ }
+ }
+
+ private void
+ handlePollCalls(AsyncResult ar)
+ {
+ List polledCalls;
+
+ if (ar.exception == null) {
+ polledCalls = (List)ar.result;
+ } else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
+ // just a dummy empty ArrayList to cause the loop
+ // to hang up all the calls
+ polledCalls = new ArrayList();
+ } else {
+ // Radio probably wasn't ready--try again in a bit
+ // But don't keep polling if the channel is closed
+ pollCallsAfterDelay();
+ return;
+ }
+
+ Connection newRinging = null; //or waiting
+ boolean hasNonHangupStateChanged = false; // Any change besides
+ // a dropped connection
+ boolean needsPollDelay = false;
+ boolean unknownConnectionAppeared = false;
+
+ for (int i = 0, curDC = 0, dcSize = polledCalls.size()
+ ; i < connections.length; i++) {
+ GSMConnection conn = connections[i];
+ DriverCall dc = null;
+
+ // polledCall list is sparse
+ if (curDC < dcSize) {
+ dc = (DriverCall) polledCalls.get(curDC);
+
+ if (dc.index == i+1) {
+ curDC++;
+ } else {
+ dc = null;
+ }
+ }
+
+ if (DBG_POLL) log("poll: conn[i=" + i + "]=" +
+ conn+", dc=" + dc);
+
+ if (conn == null && dc != null) {
+ // Connection appeared in CLCC response that we don't know about
+ if (pendingMO != null && pendingMO.compareTo(dc)) {
+
+ if (DBG_POLL) log("poll: pendingMO=" + pendingMO);
+
+ // It's our pending mobile originating call
+ connections[i] = pendingMO;
+ pendingMO.index = i;
+ pendingMO.update(dc);
+ pendingMO = null;
+
+ // Someone has already asked to hangup this call
+ if (hangupPendingMO) {
+ hangupPendingMO = false;
+ try {
+ if (Phone.DEBUG_PHONE) log(
+ "poll: hangupPendingMO, hangup conn " + i);
+ hangup(connections[i]);
+ } catch (CallStateException ex) {
+ Log.e(LOG_TAG, "unexpected error on hangup");
+ }
+
+ // Do not continue processing this poll
+ // Wait for hangup and repoll
+ return;
+ }
+ } else {
+ connections[i] = new GSMConnection(dc, this, i);
+
+ // it's a ringing call
+ if (connections[i].getCall() == ringingCall) {
+ newRinging = connections[i];
+ } else {
+ // Something strange happened: a call appeared
+ // which is neither a ringing call or one we created.
+ // Either we've crashed and re-attached to an existing
+ // call, or something else (eg, SIM) initiated the call.
+
+ Log.i(LOG_TAG,"Phantom call appeared " + dc);
+
+ // If it's a connected call, set the connect time so that
+ // it's non-zero. It may not be accurate, but at least
+ // it won't appear as a Missed Call.
+ if (dc.state != DriverCall.State.ALERTING
+ && dc.state != DriverCall.State.DIALING) {
+ connections[i].connectTime = System.currentTimeMillis();
+ }
+
+ unknownConnectionAppeared = true;
+ }
+ }
+ hasNonHangupStateChanged = true;
+ } else if (conn != null && dc == null) {
+ // Connection missing in CLCC response that we were
+ // tracking.
+ droppedDuringPoll.add(conn);
+ // Dropped connections are removed from the CallTracker
+ // list but kept in the GSMCall list
+ connections[i] = null;
+ } else if (conn != null && dc != null && !conn.compareTo(dc)) {
+ // Connection in CLCC response does not match what
+ // we were tracking. Assume dropped call and new call
+
+ droppedDuringPoll.add(conn);
+ connections[i] = new GSMConnection (dc, this, i);
+
+ if (connections[i].getCall() == ringingCall) {
+ newRinging = connections[i];
+ } // else something strange happened
+ hasNonHangupStateChanged = true;
+ } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */
+ boolean changed;
+ changed = conn.update(dc);
+ hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
+ }
+
+ if (REPEAT_POLLING) {
+ if (dc != null) {
+ // FIXME with RIL, we should not need this anymore
+ if ((dc.state == DriverCall.State.DIALING
+ /*&& cm.getOption(cm.OPTION_POLL_DIALING)*/)
+ || (dc.state == DriverCall.State.ALERTING
+ /*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/)
+ || (dc.state == DriverCall.State.INCOMING
+ /*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/)
+ || (dc.state == DriverCall.State.WAITING
+ /*&& cm.getOption(cm.OPTION_POLL_WAITING)*/)
+ ) {
+ // Sometimes there's no unsolicited notification
+ // for state transitions
+ needsPollDelay = true;
+ }
+ }
+ }
+ }
+
+ // This is the first poll after an ATD.
+ // We expect the pending call to appear in the list
+ // If it does not, we land here
+ if (pendingMO != null) {
+ Log.d(LOG_TAG,"Pending MO dropped before poll fg state:"
+ + foregroundCall.getState());
+
+ droppedDuringPoll.add(pendingMO);
+ pendingMO = null;
+ hangupPendingMO = false;
+ }
+
+ if (newRinging != null) {
+ phone.notifyNewRingingConnection(newRinging);
+ }
+
+ // clear the "local hangup" and "missed/rejected call"
+ // cases from the "dropped during poll" list
+ // These cases need no "last call fail" reason
+ for (int i = droppedDuringPoll.size() - 1; i >= 0 ; i--) {
+ GSMConnection conn = droppedDuringPoll.get(i);
+
+ if (conn.isIncoming() && conn.getConnectTime() == 0) {
+ // Missed or rejected call
+ Connection.DisconnectCause cause;
+ if (conn.cause == Connection.DisconnectCause.LOCAL) {
+ cause = Connection.DisconnectCause.INCOMING_REJECTED;
+ } else {
+ cause = Connection.DisconnectCause.INCOMING_MISSED;
+ }
+
+ if (Phone.DEBUG_PHONE) {
+ log("missed/rejected call, conn.cause=" + conn.cause);
+ log("setting cause to " + cause);
+ }
+ droppedDuringPoll.remove(i);
+ conn.onDisconnect(cause);
+ } else if (conn.cause == Connection.DisconnectCause.LOCAL) {
+ // Local hangup
+ droppedDuringPoll.remove(i);
+ conn.onDisconnect(Connection.DisconnectCause.LOCAL);
+ } else if (conn.cause ==
+ Connection.DisconnectCause.INVALID_NUMBER) {
+ droppedDuringPoll.remove(i);
+ conn.onDisconnect(Connection.DisconnectCause.INVALID_NUMBER);
+ }
+ }
+
+ // Any non-local disconnects: determine cause
+ if (droppedDuringPoll.size() > 0) {
+ cm.getLastCallFailCause(
+ obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
+ }
+
+ if (needsPollDelay) {
+ pollCallsAfterDelay();
+ }
+
+ // Cases when we can no longer keep disconnected Connection's
+ // with their previous calls
+ // 1) the phone has started to ring
+ // 2) A Call/Connection object has changed state...
+ // we may have switched or held or answered (but not hung up)
+ if (newRinging != null || hasNonHangupStateChanged) {
+ internalClearDisconnected();
+ }
+
+ updatePhoneState();
+
+ if (unknownConnectionAppeared) {
+ phone.notifyUnknownConnection();
+ }
+
+ if (hasNonHangupStateChanged || newRinging != null) {
+ phone.notifyCallStateChanged();
+ }
+
+ //dumpState();
+ }
+
+ private void
+ handleRadioAvailable()
+ {
+ pollCallsWhenSafe();
+ }
+
+ private void
+ handleRadioNotAvailable()
+ {
+ // handlePollCalls will clear out its
+ // call list when it gets the CommandException
+ // error result from this
+ pollCallsWhenSafe();
+ }
+
+ private void
+ dumpState()
+ {
+ List l;
+
+ Log.i(LOG_TAG,"Phone State:" + state);
+
+ Log.i(LOG_TAG,"Ringing call: " + ringingCall.toString());
+
+ l = ringingCall.getConnections();
+ for (int i = 0, s = l.size(); i < s; i++) {
+ Log.i(LOG_TAG,l.get(i).toString());
+ }
+
+ Log.i(LOG_TAG,"Foreground call: " + foregroundCall.toString());
+
+ l = foregroundCall.getConnections();
+ for (int i = 0, s = l.size(); i < s; i++) {
+ Log.i(LOG_TAG,l.get(i).toString());
+ }
+
+ Log.i(LOG_TAG,"Background call: " + backgroundCall.toString());
+
+ l = backgroundCall.getConnections();
+ for (int i = 0, s = l.size(); i < s; i++) {
+ Log.i(LOG_TAG,l.get(i).toString());
+ }
+
+ }
+
+ //***** Called from GSMConnection
+
+ /*package*/ void
+ hangup (GSMConnection conn) throws CallStateException
+ {
+ if (conn.owner != this) {
+ throw new CallStateException ("Connection " + conn
+ + "does not belong to CallTracker " + this);
+ }
+
+ if (conn == pendingMO) {
+ // We're hanging up an outgoing call that doesn't have it's
+ // GSM index assigned yet
+
+ if (Phone.DEBUG_PHONE) log("hangup: set hangupPendingMO to true");
+ hangupPendingMO = true;
+ } else {
+ try {
+ cm.hangupConnection (conn.getGSMIndex(), obtainCompleteMessage());
+ } catch (CallStateException ex) {
+ // Ignore "connection not found"
+ // Call may have hung up already
+ Log.w(LOG_TAG,"CallTracker WARN: hangup() on absent connection "
+ + conn);
+ }
+ }
+
+ conn.onHangupLocal();
+ }
+
+ /*package*/ void
+ separate (GSMConnection conn) throws CallStateException
+ {
+ if (conn.owner != this) {
+ throw new CallStateException ("Connection " + conn
+ + "does not belong to CallTracker " + this);
+ }
+ try {
+ cm.separateConnection (conn.getGSMIndex(),
+ obtainCompleteMessage(EVENT_SEPARATE_RESULT));
+ } catch (CallStateException ex) {
+ // Ignore "connection not found"
+ // Call may have hung up already
+ Log.w(LOG_TAG,"CallTracker WARN: separate() on absent connection "
+ + conn);
+ }
+ }
+
+ //***** Called from GSMPhone
+
+ /*package*/ void
+ setMute(boolean mute)
+ {
+ desiredMute = mute;
+ cm.setMute(desiredMute, null);
+ }
+
+ /*package*/ boolean
+ getMute()
+ {
+ return desiredMute;
+ }
+
+
+ //***** Called from GSMCall
+
+ /* package */ void
+ hangup (GSMCall call) throws CallStateException
+ {
+ if (call.getConnections().size() == 0) {
+ throw new CallStateException("no connections in call");
+ }
+
+ if (call == ringingCall) {
+ if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background");
+ cm.hangupWaitingOrBackground(obtainCompleteMessage());
+ } else if (call == foregroundCall) {
+ if (call.isDialingOrAlerting()) {
+ if (Phone.DEBUG_PHONE) {
+ log("(foregnd) hangup dialing or alerting...");
+ }
+ hangup((GSMConnection)(call.getConnections().get(0)));
+ } else {
+ hangupForegroundResumeBackground();
+ }
+ } else if (call == backgroundCall) {
+ if (ringingCall.isRinging()) {
+ if (Phone.DEBUG_PHONE) {
+ log("hangup all conns in background call");
+ }
+ hangupAllConnections(call);
+ } else {
+ hangupWaitingOrBackground();
+ }
+ } else {
+ throw new RuntimeException ("Call " + call +
+ "does not belong to CallTracker " + this);
+ }
+
+ call.onHangupLocal();
+ }
+
+ /* package */
+ void hangupWaitingOrBackground() {
+ if (Phone.DEBUG_PHONE) log("hangupWaitingOrBackground");
+ cm.hangupWaitingOrBackground(obtainCompleteMessage());
+ }
+
+ /* package */
+ void hangupForegroundResumeBackground() {
+ if (Phone.DEBUG_PHONE) log("hangupForegroundResumeBackground");
+ cm.hangupForegroundResumeBackground(obtainCompleteMessage());
+ }
+
+ void hangupConnectionByIndex(GSMCall call, int index)
+ throws CallStateException {
+ int count = call.connections.size();
+ for (int i = 0; i < count; i++) {
+ GSMConnection cn = (GSMConnection)call.connections.get(i);
+ if (cn.getGSMIndex() == index) {
+ cm.hangupConnection(index, obtainCompleteMessage());
+ return;
+ }
+ }
+
+ throw new CallStateException("no gsm index found");
+ }
+
+ void hangupAllConnections(GSMCall call) throws CallStateException{
+ try {
+ int count = call.connections.size();
+ for (int i = 0; i < count; i++) {
+ GSMConnection cn = (GSMConnection)call.connections.get(i);
+ cm.hangupConnection(cn.getGSMIndex(), obtainCompleteMessage());
+ }
+ } catch (CallStateException ex) {
+ Log.e(LOG_TAG, "hangupConnectionByIndex caught " + ex);
+ }
+ }
+
+ /* package */
+ GSMConnection getConnectionByIndex(GSMCall call, int index)
+ throws CallStateException {
+ int count = call.connections.size();
+ for (int i = 0; i < count; i++) {
+ GSMConnection cn = (GSMConnection)call.connections.get(i);
+ if (cn.getGSMIndex() == index) {
+ return cn;
+ }
+ }
+
+ return null;
+ }
+
+ private Phone.SuppService getFailedService(int what) {
+ switch (what) {
+ case EVENT_SWITCH_RESULT:
+ return Phone.SuppService.SWITCH;
+ case EVENT_CONFERENCE_RESULT:
+ return Phone.SuppService.CONFERENCE;
+ case EVENT_SEPARATE_RESULT:
+ return Phone.SuppService.SEPARATE;
+ case EVENT_ECT_RESULT:
+ return Phone.SuppService.TRANSFER;
+ }
+ return Phone.SuppService.UNKNOWN;
+ }
+
+ //****** Overridden from Handler
+
+ public void
+ handleMessage (Message msg)
+ {
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case EVENT_POLL_CALLS_RESULT:
+ ar = (AsyncResult)msg.obj;
+
+ if (msg == lastRelevantPoll) {
+ if (DBG_POLL) log(
+ "handle EVENT_POLL_CALL_RESULT: set needsPoll=F");
+ needsPoll = false;
+ lastRelevantPoll = null;
+ handlePollCalls((AsyncResult)msg.obj);
+ }
+ break;
+
+ case EVENT_OPERATION_COMPLETE:
+ ar = (AsyncResult)msg.obj;
+ operationComplete();
+ break;
+
+ case EVENT_SWITCH_RESULT:
+ case EVENT_CONFERENCE_RESULT:
+ case EVENT_SEPARATE_RESULT:
+ case EVENT_ECT_RESULT:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception != null) {
+ phone.notifySuppServiceFailed(getFailedService(msg.what));
+ }
+ operationComplete();
+ break;
+
+ case EVENT_GET_LAST_CALL_FAIL_CAUSE:
+ int causeCode;
+ ar = (AsyncResult)msg.obj;
+
+ operationComplete();
+
+ if (ar.exception != null) {
+ // An exception occurred...just treat the disconnect
+ // cause as "normal"
+ causeCode = CallFailCause.NORMAL_CLEARING;
+ Log.i(LOG_TAG,
+ "Exception during getLastCallFailCause, assuming normal disconnect");
+ } else {
+ causeCode = ((int[])ar.result)[0];
+ }
+
+ for (int i = 0, s = droppedDuringPoll.size()
+ ; i < s ; i++
+ ) {
+ GSMConnection conn = droppedDuringPoll.get(i);
+
+ conn.onRemoteDisconnect(causeCode);
+ }
+
+ updatePhoneState();
+
+ phone.notifyCallStateChanged();
+ droppedDuringPoll.clear();
+ break;
+
+ case EVENT_REPOLL_AFTER_DELAY:
+ case EVENT_CALL_STATE_CHANGE:
+ pollCallsWhenSafe();
+ break;
+
+ case EVENT_RADIO_AVAILABLE:
+ handleRadioAvailable();
+ break;
+
+ case EVENT_RADIO_NOT_AVAILABLE:
+ handleRadioNotAvailable();
+ break;
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[CallTracker] " + msg);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/CommandException.java b/telephony/java/com/android/internal/telephony/gsm/CommandException.java
new file mode 100644
index 0000000..5cf48f3
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/CommandException.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony.gsm;
+
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+public class CommandException extends RuntimeException
+{
+ private Error e;
+
+ public enum Error {
+ INVALID_RESPONSE,
+ RADIO_NOT_AVAILABLE,
+ GENERIC_FAILURE,
+ PASSWORD_INCORRECT,
+ SIM_PIN2,
+ SIM_PUK2,
+ REQUEST_NOT_SUPPORTED,
+ OP_NOT_ALLOWED_DURING_VOICE_CALL,
+ OP_NOT_ALLOWED_BEFORE_REG_NW,
+ SMS_FAIL_RETRY,
+ }
+
+ public CommandException(Error e)
+ {
+ super(e.toString());
+ this.e = e;
+ }
+
+ public static CommandException
+ fromRilErrno(int ril_errno)
+ {
+ switch(ril_errno) {
+ case RILConstants.SUCCESS: return null;
+ case RILConstants.RIL_ERRNO_INVALID_RESPONSE:
+ return new CommandException(Error.INVALID_RESPONSE);
+ case RILConstants.RADIO_NOT_AVAILABLE:
+ return new CommandException(Error.RADIO_NOT_AVAILABLE);
+ case RILConstants.GENERIC_FAILURE:
+ return new CommandException(Error.GENERIC_FAILURE);
+ case RILConstants.PASSWORD_INCORRECT:
+ return new CommandException(Error.PASSWORD_INCORRECT);
+ case RILConstants.SIM_PIN2:
+ return new CommandException(Error.SIM_PIN2);
+ case RILConstants.SIM_PUK2:
+ return new CommandException(Error.SIM_PUK2);
+ case RILConstants.REQUEST_NOT_SUPPORTED:
+ return new CommandException(Error.REQUEST_NOT_SUPPORTED);
+ case RILConstants.OP_NOT_ALLOWED_DURING_VOICE_CALL:
+ return new CommandException(Error.OP_NOT_ALLOWED_DURING_VOICE_CALL);
+ case RILConstants.OP_NOT_ALLOWED_BEFORE_REG_NW:
+ return new CommandException(Error.OP_NOT_ALLOWED_BEFORE_REG_NW);
+ case RILConstants.SMS_SEND_FAIL_RETRY:
+ return new CommandException(Error.SMS_FAIL_RETRY);
+ default:
+ Log.e("GSM", "Unrecognized RIL errno " + ril_errno);
+ return new CommandException(Error.INVALID_RESPONSE);
+ }
+ }
+
+ public Error getCommandError()
+ {
+ return e;
+ }
+
+
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/CommandsInterface.java b/telephony/java/com/android/internal/telephony/gsm/CommandsInterface.java
new file mode 100644
index 0000000..ececd72
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/CommandsInterface.java
@@ -0,0 +1,915 @@
+/*
+ * 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.internal.telephony.gsm;
+import com.android.internal.telephony.*;
+import android.os.Message;
+import android.os.Handler;
+
+/**
+ * {@hide}
+ */
+public interface CommandsInterface
+{
+ enum RadioState {
+ RADIO_OFF, /* Radio explictly powered off (eg CFUN=0) */
+ RADIO_UNAVAILABLE, /* Radio unavailable (eg, resetting or not booted) */
+ SIM_NOT_READY, /* Radio is on, but the SIM interface is not ready */
+ SIM_LOCKED_OR_ABSENT, /* SIM PIN locked, PUK required, network
+ personalization, or SIM absent */
+ SIM_READY; /* Radio is on and SIM interface is available */
+
+ boolean isOn() /* and available...*/
+ {
+ return this == SIM_NOT_READY
+ || this == SIM_LOCKED_OR_ABSENT
+ || this == SIM_READY;
+ }
+
+ boolean isAvailable()
+ {
+ return this != RADIO_UNAVAILABLE;
+ }
+
+ boolean isSIMReady()
+ {
+ // if you add new states after SIM_READY, include them too
+ return this == SIM_READY;
+ }
+ }
+
+ enum SimStatus {
+ SIM_ABSENT,
+ SIM_NOT_READY,
+ SIM_READY,
+ SIM_PIN,
+ SIM_PUK,
+ SIM_NETWORK_PERSONALIZATION
+ }
+
+ //***** Constants
+
+ // Used as parameter to dial() and setCLIR() below
+ static final int CLIR_DEFAULT = 0; // "use subscription default value"
+ static final int CLIR_INVOCATION = 1; // (restrict CLI presentation)
+ static final int CLIR_SUPPRESSION = 2; // (allow CLI presentation)
+
+
+ // Used as parameters for call forward methods below
+ static final int CF_ACTION_DISABLE = 0;
+ static final int CF_ACTION_ENABLE = 1;
+// static final int CF_ACTION_UNUSED = 2;
+ static final int CF_ACTION_REGISTRATION = 3;
+ static final int CF_ACTION_ERASURE = 4;
+
+ static final int CF_REASON_UNCONDITIONAL = 0;
+ static final int CF_REASON_BUSY = 1;
+ static final int CF_REASON_NO_REPLY = 2;
+ static final int CF_REASON_NOT_REACHABLE = 3;
+ static final int CF_REASON_ALL = 4;
+ static final int CF_REASON_ALL_CONDITIONAL = 5;
+
+ // Used for call barring methods below
+ static final String CB_FACILITY_BAOC = "AO";
+ static final String CB_FACILITY_BAOIC = "OI";
+ static final String CB_FACILITY_BAOICxH = "OX";
+ static final String CB_FACILITY_BAIC = "AI";
+ static final String CB_FACILITY_BAICr = "IR";
+ static final String CB_FACILITY_BA_ALL = "AB";
+ static final String CB_FACILITY_BA_MO = "AG";
+ static final String CB_FACILITY_BA_MT = "AC";
+ static final String CB_FACILITY_BA_SIM = "SC";
+ static final String CB_FACILITY_BA_FD = "FD";
+
+
+ // Used for various supp services apis
+ // See 27.007 +CCFC or +CLCK
+ static final int SERVICE_CLASS_NONE = 0; // no user input
+ static final int SERVICE_CLASS_VOICE = (1 << 0);
+ static final int SERVICE_CLASS_DATA = (1 << 1); //synoym for 16+32+64+128
+ static final int SERVICE_CLASS_FAX = (1 << 2);
+ static final int SERVICE_CLASS_SMS = (1 << 3);
+ static final int SERVICE_CLASS_DATA_SYNC = (1 << 4);
+ static final int SERVICE_CLASS_DATA_ASYNC = (1 << 5);
+ static final int SERVICE_CLASS_PACKET = (1 << 6);
+ static final int SERVICE_CLASS_PAD = (1 << 7);
+ static final int SERVICE_CLASS_MAX = (1 << 7); // Max SERVICE_CLASS value
+
+ // Numeric representation of string values returned
+ // by messages sent to setOnUSSD handler
+ static final int USSD_MODE_NOTIFY = 0;
+ static final int USSD_MODE_REQUEST = 1;
+
+ // SIM Refresh results, passed up from RIL.
+ static final int SIM_REFRESH_FILE_UPDATED = 0; // Single file updated
+ static final int SIM_REFRESH_INIT = 1; // SIM initialized; reload all
+ static final int SIM_REFRESH_RESET = 2; // SIM reset; may be locked
+
+ //***** Methods
+
+ RadioState getRadioState();
+
+ /**
+ * Fires on any RadioState transition
+ * Always fires immediately as well
+ *
+ * do not attempt to calculate transitions by storing getRadioState() values
+ * on previous invocations of this notification. Instead, use the other
+ * registration methods
+ */
+ void registerForRadioStateChanged(Handler h, int what, Object obj);
+
+ /**
+ * Fires on any transition into RadioState.isOn()
+ * Fires immediately if currently in that state
+ * In general, actions should be idempotent. State may change
+ * before event is received.
+ */
+ void registerForOn(Handler h, int what, Object obj);
+
+ /**
+ * Fires on any transition out of RadioState.isAvailable()
+ * Fires immediately if currently in that state
+ * In general, actions should be idempotent. State may change
+ * before event is received.
+ */
+ void registerForAvailable(Handler h, int what, Object obj);
+ //void unregisterForAvailable(Handler h);
+ /**
+ * Fires on any transition into !RadioState.isAvailable()
+ * Fires immediately if currently in that state
+ * In general, actions should be idempotent. State may change
+ * before event is received.
+ */
+ void registerForNotAvailable(Handler h, int what, Object obj);
+ //void unregisterForNotAvailable(Handler h);
+ /**
+ * Fires on any transition into RADIO_OFF or !RadioState.isAvailable()
+ * Fires immediately if currently in that state
+ * In general, actions should be idempotent. State may change
+ * before event is received.
+ */
+ void registerForOffOrNotAvailable(Handler h, int what, Object obj);
+ //void unregisterForNotAvailable(Handler h);
+
+ /**
+ * Fires on any transition into SIM_READY
+ * Fires immediately if if currently in that state
+ * In general, actions should be idempotent. State may change
+ * before event is received.
+ */
+ void registerForSIMReady(Handler h, int what, Object obj);
+ //void unregisterForSIMReady(Handler h);
+ /** Any transition into SIM_LOCKED_OR_ABSENT */
+ void registerForSIMLockedOrAbsent(Handler h, int what, Object obj);
+ //void unregisterForSIMLockedOrAbsent(Handler h);
+
+ void registerForCallStateChanged(Handler h, int what, Object obj);
+ //void unregisterForCallStateChanged(Handler h);
+ void registerForNetworkStateChanged(Handler h, int what, Object obj);
+ //void unregisterForNetworkStateChanged(Handler h);
+ void registerForPDPStateChanged(Handler h, int what, Object obj);
+ //void unregisterForPDPStateChanged(Handler h);
+
+ /**
+ * unlike the register* methods, there's only one new SMS handler
+ * if you need to unregister, you should also tell the radio to stop
+ * sending SMS's to you (via AT+CNMI)
+ *
+ * AsyncResult.result is a String containing the SMS PDU
+ */
+ void setOnNewSMS(Handler h, int what, Object obj);
+
+ /**
+ * Register for NEW_SMS_ON_SIM unsolicited message
+ *
+ * AsyncResult.result is an int array containing the index of new SMS
+ */
+ void setOnSmsOnSim(Handler h, int what, Object obj);
+
+ /**
+ * Register for NEW_SMS_STATUS_REPORT unsolicited message
+ *
+ * AsyncResult.result is a String containing the status report PDU
+ */
+ void setOnSmsStatus(Handler h, int what, Object obj);
+
+ /**
+ * unlike the register* methods, there's only one NITZ time handler
+ *
+ * AsyncResult.result is an Object[]
+ * ((Object[])AsyncResult.result)[0] is a String containing the NITZ time string
+ * ((Object[])AsyncResult.result)[0] is an Integer containing
+ * the UNIX time_t returned by time() when
+ * this NITZ time was posted.
+ *
+ * Please note that the delivery of this message may be delayed several
+ * seconds on system startup
+ */
+ void setOnNITZTime(Handler h, int what, Object obj);
+
+ /**
+ * unlike the register* methods, there's only one USSD notify handler
+ *
+ * Represents the arrival of a USSD "notify" message, which may
+ * or may not have been triggered by a previous USSD send
+ *
+ * AsyncResult.result is a String[]
+ * ((String[])(AsyncResult.result))[0] contains status code
+ * "0" USSD-Notify -- text in ((const char **)data)[1]
+ * "1" USSD-Request -- text in ((const char **)data)[1]
+ * "2" Session terminated by network
+ * "3" other local client (eg, SIM Toolkit) has responded
+ * "4" Operation not supported
+ * "5" Network timeout
+ *
+ * ((String[])(AsyncResult.result))[1] contains the USSD message
+ * The numeric representations of these are in USSD_MODE_*
+ */
+
+ void setOnUSSD(Handler h, int what, Object obj);
+
+ /**
+ * unlike the register* methods, there's only one signal strength handler
+ * AsyncResult.result is an int[2]
+ * response.obj.result[0] is received signal strength (0-31, 99)
+ * response.obj.result[1] is bit error rate (0-7, 99)
+ * as defined in TS 27.007 8.5
+ */
+
+ void setOnSignalStrengthUpdate(Handler h, int what, Object obj);
+
+ /**
+ * Sets the handler for SIM SMS storage full unsolicited message.
+ * Unlike the register* methods, there's only one notification handler
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void setOnSimSmsFull(Handler h, int what, Object obj);
+
+ /**
+ * Sets the handler for SIM Refresh notifications.
+ * Unlike the register* methods, there's only one notification handler
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void setOnSimRefresh(Handler h, int what, Object obj);
+
+ /**
+ * Sets the handler for RING notifications.
+ * Unlike the register* methods, there's only one notification handler
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void setOnCallRing(Handler h, int what, Object obj);
+
+ /**
+ * Sets the handler for Supplementary Service Notifications.
+ * Unlike the register* methods, there's only one notification handler
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void setOnSuppServiceNotification(Handler h, int what, Object obj);
+
+ /**
+ * Sets the handler for Session End Notifications for STK.
+ * Unlike the register* methods, there's only one notification handler
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void setOnStkSessionEnd(Handler h, int what, Object obj);
+
+ /**
+ * Sets the handler for Proactive Commands for STK.
+ * Unlike the register* methods, there's only one notification handler
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void setOnStkProactiveCmd(Handler h, int what, Object obj);
+
+ /**
+ * Sets the handler for Event Notifications for STK.
+ * Unlike the register* methods, there's only one notification handler
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void setOnStkEvent(Handler h, int what, Object obj);
+
+ /**
+ * Sets the handler for Call Set Up Notifications for STK.
+ * Unlike the register* methods, there's only one notification handler
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void setOnStkCallSetUp(Handler h, int what, Object obj);
+
+ /**
+ * Enables/disbables supplementary service related notifications from
+ * the network.
+ *
+ * @param enable true to enable notifications, false to disable.
+ * @param result Message to be posted when command completes.
+ */
+ void setSuppServiceNotifications(boolean enable, Message result);
+
+ /**
+ * Returns current SIM status.
+ *
+ * AsyncResult.result is SimStatus
+ *
+ */
+
+ void getSimStatus(Message result);
+
+ /**
+ * Supply the SIM PIN to the SIM card
+ *
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * This exception is CommandException with an error of PASSWORD_INCORRECT
+ * if the password is incorrect
+ *
+ * ar.exception and ar.result are null on success
+ */
+
+ void supplySimPin(String pin, Message result);
+
+ /**
+ * Supply the SIM PUK to the SIM card
+ *
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * This exception is CommandException with an error of PASSWORD_INCORRECT
+ * if the password is incorrect
+ *
+ * ar.exception and ar.result are null on success
+ */
+
+ void supplySimPuk(String puk, String newPin, Message result);
+
+ /**
+ * Supply the SIM PIN2 to the SIM card
+ * Only called following operation where SIM_PIN2 was
+ * returned as a a failure from a previous operation
+ *
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * This exception is CommandException with an error of PASSWORD_INCORRECT
+ * if the password is incorrect
+ *
+ * ar.exception and ar.result are null on success
+ */
+
+ void supplySimPin2(String pin2, Message result);
+
+ /**
+ * Supply the SIM PUK2 to the SIM card
+ * Only called following operation where SIM_PUK2 was
+ * returned as a a failure from a previous operation
+ *
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * This exception is CommandException with an error of PASSWORD_INCORRECT
+ * if the password is incorrect
+ *
+ * ar.exception and ar.result are null on success
+ */
+
+ void supplySimPuk2(String puk2, String newPin2, Message result);
+
+ void changeSimPin(String oldPin, String newPin, Message result);
+ void changeSimPin2(String oldPin2, String newPin2, Message result);
+
+ void changeBarringPassword(String facility, String oldPwd, String newPwd, Message result);
+
+ void supplyNetworkDepersonalization(String netpin, Message result);
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result contains a List of DriverCall
+ * The ar.result List is sorted by DriverCall.index
+ */
+ void getCurrentCalls (Message result);
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result contains a List of PDPContextState
+ */
+ void getPDPContextList(Message result);
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ *
+ * CLIR_DEFAULT == on "use subscription default value"
+ * CLIR_SUPPRESSION == on "CLIR suppression" (allow CLI presentation)
+ * CLIR_INVOCATION == on "CLIR invocation" (restrict CLI presentation)
+ */
+ void dial (String address, int clirMode, Message result);
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is String containing IMSI on success
+ */
+ void getIMSI(Message result);
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is String containing IMEI on success
+ */
+ void getIMEI(Message result);
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is String containing IMEISV on success
+ */
+ void getIMEISV(Message result);
+
+ /**
+ * Hang up one individual connection.
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ *
+ * 3GPP 22.030 6.5.5
+ * "Releases a specific active call X"
+ */
+ void hangupConnection (int gsmIndex, Message result);
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Releases all held calls or sets User Determined User Busy (UDUB)
+ * for a waiting call."
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void hangupWaitingOrBackground (Message result);
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Releases all active calls (if any exist) and accepts
+ * the other (held or waiting) call."
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void hangupForegroundResumeBackground (Message result);
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Places all active calls (if any exist) on hold and accepts
+ * the other (held or waiting) call."
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void switchWaitingOrHoldingAndActive (Message result);
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Adds a held call to the conversation"
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void conference (Message result);
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Places all active calls on hold except call X with which
+ * communication shall be supported."
+ */
+ void separateConnection (int gsmIndex, Message result);
+
+ /**
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void acceptCall (Message result);
+
+ /**
+ * also known as UDUB
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void rejectCall (Message result);
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Connects the two calls and disconnects the subscriber from both calls"
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void explicitCallTransfer (Message result);
+
+ /**
+ * cause code returned as int[0] in Message.obj.response
+ * Returns integer cause code defined in TS 24.008
+ * Annex H or closest approximation.
+ * Most significant codes:
+ * - Any defined in 22.001 F.4 (for generating busy/congestion)
+ * - Cause 68: ACM >= ACMMax
+ */
+ void getLastCallFailCause (Message result);
+
+
+ /**
+ * Reason for last PDP context deactivate or failure to activate
+ * cause code returned as int[0] in Message.obj.response
+ * returns an integer cause code defined in TS 24.008
+ * section 6.1.3.1.3 or close approximation
+ */
+ void getLastPdpFailCause (Message result);
+
+ void setMute (boolean enableMute, Message response);
+
+ void getMute (Message response);
+
+ /**
+ * response.obj is an AsyncResult
+ * response.obj.result is an int[2]
+ * response.obj.result[0] is received signal strength (0-31, 99)
+ * response.obj.result[1] is bit error rate (0-7, 99)
+ * as defined in TS 27.007 8.5
+ */
+ void getSignalStrength (Message response);
+
+
+ /**
+ * response.obj.result is an int[3]
+ * response.obj.result[0] is registration state 0-5 from TS 27.007 7.2
+ * response.obj.result[1] is LAC if registered or -1 if not
+ * response.obj.result[2] is CID if registered or -1 if not
+ * valid LAC and CIDs are 0x0000 - 0xffff
+ *
+ * Please note that registration state 4 ("unknown") is treated
+ * as "out of service" above
+ */
+ void getRegistrationState (Message response);
+
+ /**
+ * response.obj.result is an int[3]
+ * response.obj.result[0] is registration state 0-5 from TS 27.007 7.2
+ * response.obj.result[1] is LAC if registered or -1 if not
+ * response.obj.result[2] is CID if registered or -1 if not
+ * valid LAC and CIDs are 0x0000 - 0xffff
+ *
+ * Please note that registration state 4 ("unknown") is treated
+ * as "out of service" above
+ */
+ void getGPRSRegistrationState (Message response);
+
+ /**
+ * response.obj.result is a String[3]
+ * response.obj.result[0] is long alpha or null if unregistered
+ * response.obj.result[1] is short alpha or null if unregistered
+ * response.obj.result[2] is numeric or null if unregistered
+ */
+ void getOperator(Message response);
+
+ /**
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void sendDtmf(char c, Message result);
+
+
+ /**
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void startDtmf(char c, Message result);
+
+ /**
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void stopDtmf(Message result);
+
+
+ /**
+ * smscPDU is smsc address in PDU form GSM BCD format prefixed
+ * by a length byte (as expected by TS 27.005) or NULL for default SMSC
+ * pdu is SMS in PDU format as an ASCII hex string
+ * less the SMSC address
+ */
+ void sendSMS (String smscPDU, String pdu, Message response);
+
+ /**
+ * Deletes the specified SMS record from SIM memory (EF_SMS).
+ *
+ * @param index index of the SMS record to delete
+ * @param response sent when operation completes
+ */
+ void deleteSmsOnSim(int index, Message response);
+
+ /**
+ * Writes an SMS message to SIM memory (EF_SMS).
+ *
+ * @param status status of message on SIM. One of:
+ * SmsManger.STATUS_ON_SIM_READ
+ * SmsManger.STATUS_ON_SIM_UNREAD
+ * SmsManger.STATUS_ON_SIM_SENT
+ * SmsManger.STATUS_ON_SIM_UNSENT
+ * @param pdu message PDU, as hex string
+ * @param response sent when operation completes.
+ * response.obj will be an AsyncResult, and will indicate
+ * any error that may have occurred (eg, out of memory).
+ */
+ void writeSmsToSim(int status, String smsc, String pdu, Message response);
+
+ void setupDefaultPDP(String apn, String user, String password, Message response);
+
+ void deactivateDefaultPDP(int cid, Message response);
+
+ void setRadioPower(boolean on, Message response);
+
+ void acknowledgeLastIncomingSMS(boolean success, Message response);
+
+ /**
+ * parameters equivilient to 27.007 AT+CRSM command
+ * response.obj will be an AsyncResult
+ * response.obj.userObj will be a SimIoResult on success
+ */
+ void simIO (int command, int fileid, String path, int p1, int p2, int p3,
+ String data, String pin2, Message response);
+
+ /**
+ * (AsyncResult)response.obj).result is an int[] with element [0] set to
+ * 1 for "CLIP is provisioned", and 0 for "CLIP is not provisioned".
+ *
+ * @param response is callback message
+ */
+
+ void queryCLIP(Message response);
+
+ /**
+ * response.obj will be a an int[2]
+ *
+ * response.obj[0] will be TS 27.007 +CLIR parameter 'n'
+ * 0 presentation indicator is used according to the subscription of the CLIR service
+ * 1 CLIR invocation
+ * 2 CLIR suppression
+ *
+ * response.obj[1] will be TS 27.007 +CLIR parameter 'm'
+ * 0 CLIR not provisioned
+ * 1 CLIR provisioned in permanent mode
+ * 2 unknown (e.g. no network, etc.)
+ * 3 CLIR temporary mode presentation restricted
+ * 4 CLIR temporary mode presentation allowed
+ */
+
+ void getCLIR(Message response);
+
+ /**
+ * clirMode is one of the CLIR_* constants above
+ *
+ * response.obj is null
+ */
+
+ void setCLIR(int clirMode, Message response);
+
+ /**
+ * (AsyncResult)response.obj).result is an int[] with element [0] set to
+ * 0 for disabled, 1 for enabled.
+ *
+ * @param serviceClass is a sum of SERVICE_CLASS_*
+ * @param response is callback message
+ */
+
+ void queryCallWaiting(int serviceClass, Message response);
+
+ /**
+ * @param enable is true to enable, false to disable
+ * @param serviceClass is a sum of SERVICE_CLASS_*
+ * @param response is callback message
+ */
+
+ void setCallWaiting(boolean enable, int serviceClass, Message response);
+
+ /**
+ * @param action is one of CF_ACTION_*
+ * @param cfReason is one of CF_REASON_*
+ * @param serviceClass is a sum of SERVICE_CLASSS_*
+ */
+ void setCallForward(int action, int cfReason, int serviceClass,
+ String number, int timeSeconds, Message response);
+
+ /**
+ * cfReason is one of CF_REASON_*
+ *
+ * ((AsyncResult)response.obj).result will be an array of
+ * CallForwardInfo's
+ *
+ * An array of length 0 means "disabled for all codes"
+ */
+ void queryCallForwardStatus(int cfReason, int serviceClass,
+ String number, Message response);
+
+ void setNetworkSelectionModeAutomatic(Message response);
+
+ void setNetworkSelectionModeManual(String operatorNumeric, Message response);
+
+ /**
+ * Queries whether the current network selection mode is automatic
+ * or manual
+ *
+ * ((AsyncResult)response.obj).result is an int[] with element [0] being
+ * a 0 for automatic selection and a 1 for manual selection
+ */
+
+ void getNetworkSelectionMode(Message response);
+
+ /**
+ * Queries the currently available networks
+ *
+ * ((AsyncResult)response.obj).result is a List of NetworkInfo objects
+ */
+ void getAvailableNetworks(Message response);
+
+ void getBasebandVersion (Message response);
+
+
+ /**
+ * (AsyncResult)response.obj).result will be an Integer representing
+ * the sum of enabled serivice classes (sum of SERVICE_CLASS_*)
+ *
+ * @param facility one of CB_FACILTY_*
+ * @param password password or "" if not required
+ * @param serviceClass is a sum of SERVICE_CLASS_*
+ * @param response is callback message
+ */
+
+ void queryFacilityLock (String facility, String password, int serviceClass,
+ Message response);
+
+ /**
+ * @param facility one of CB_FACILTY_*
+ * @param lockState true means lock, false means unlock
+ * @param password password or "" if not required
+ * @param serviceClass is a sum of SERVICE_CLASS_*
+ * @param response is callback message
+ */
+ void setFacilityLock (String facility, boolean lockState, String password,
+ int serviceClass, Message response);
+
+
+ void sendUSSD (String ussdString, Message response);
+
+ /**
+ * Cancels a pending USSD session if one exists.
+ * @param response callback message
+ */
+ void cancelPendingUssd (Message response);
+
+ void resetRadio(Message result);
+
+ /**
+ * Assign a specified band for RF configuration.
+ *
+ * @param bandMode one of BM_*_BAND
+ * @param response is callback message
+ */
+ void setBandMode (int bandMode, Message response);
+
+ /**
+ * Query the list of band mode supported by RF.
+ *
+ * @param response is callback message
+ * ((AsyncResult)response.obj).result is an int[] with every
+ * element representing one avialable BM_*_BAND
+ */
+ void queryAvailableBandMode (Message response);
+
+ /**
+ * Requests to set the preferred network type for searching and registering
+ * (CS/PS domain, RAT, and operation mode)
+ * @param networkType one of NT_*_TYPE
+ * @param response is callback message
+ */
+ void setPreferredNetworkType(int networkType , Message response);
+
+ /**
+ * Query the preferred network type setting
+ *
+ * @param response is callback message to report one of NT_*_TYPE
+ */
+ void getPreferredNetworkType(Message response);
+
+ /**
+ * Query neighboring cell ids
+ *
+ * @param response s callback message to cell ids
+ */
+ void getNeighboringCids(Message response);
+
+ /**
+ * Request to enable/disable network state change notifications when
+ * location informateion (lac and/or cid) has changed.
+ *
+ * @param enable true to enable, false to disable
+ * @param response callback message
+ */
+ void setLocationUpdates(boolean enable, Message response);
+
+
+ void invokeOemRilRequestRaw(byte[] data, Message response);
+
+ void invokeOemRilRequestStrings(String[] strings, Message response);
+
+
+ /**
+ * Send TERMINAL RESPONSE to the SIM, after processing a proactive command
+ * sent by the SIM.
+ *
+ * @param contents String containing SAT/USAT response in hexadecimal
+ * format starting with first byte of response data. See
+ * TS 102 223 for details.
+ * @param response Callback message
+ */
+ public void sendTerminalResponse(String contents, Message response);
+
+ /**
+ * Send ENVELOPE to the SIM, after processing a proactive command sent by
+ * the SIM.
+ *
+ * @param contents String containing SAT/USAT response in hexadecimal
+ * format starting with command tag. See TS 102 223 for
+ * details.
+ * @param response Callback message
+ */
+ public void sendEnvelope(String contents, Message response);
+
+ /**
+ * Accept or reject the call setup request from SIM.
+ *
+ * @param accept true if the call is to be accepted, false otherwise.
+ * @param response Callback message
+ */
+ public void handleCallSetupRequestFromSim(boolean accept, Message response);
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/DataConnectionTracker.java
new file mode 100644
index 0000000..63d145e
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/DataConnectionTracker.java
@@ -0,0 +1,1512 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.INetStatService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.provider.Checkin;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.provider.Settings.SettingNotFoundException;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.gsm.PdpConnection.PdpFailCause;
+
+import android.telephony.ServiceState;
+import android.util.EventLog;
+import android.util.Log;
+import android.text.TextUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+final class DataConnectionTracker extends Handler
+{
+ private static final String LOG_TAG = "GSM";
+ private static final boolean DBG = true;
+
+ /**
+ * IDLE: ready to start data connection setup, default state
+ * INITING: state of issued setupDefaultPDP() but not finish yet
+ * CONNECTING: state of issued startPppd() but not finish yet
+ * SCANNING: data connection fails with one apn but other apns are available
+ * ready to start data connection on other apns (before INITING)
+ * CONNECTED: IP connection is setup
+ * FAILED: data connection fail for all apns settings
+ *
+ * getDataConnectionState() maps State to DataState
+ * FAILED or IDLE : DISCONNECTED
+ * INITING or CONNECTING or SCANNING: CONNECTING
+ * CONNECTED : CONNECTED
+ */
+ enum State {
+ IDLE,
+ INITING,
+ CONNECTING,
+ SCANNING,
+ CONNECTED,
+ FAILED
+ }
+
+ enum Activity {
+ NONE,
+ DATAIN,
+ DATAOUT,
+ DATAINANDOUT
+ }
+
+ /**
+ * Handles changes to the APN db.
+ */
+ private class ApnChangeObserver extends ContentObserver {
+ public ApnChangeObserver () {
+ super(mDataConnectionTracker);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ boolean isConnected;
+
+ isConnected = (state != State.IDLE && state != State.FAILED);
+ // TODO: It'd be nice to only do this if the changed entrie(s)
+ // match the current operator.
+ cleanUpConnection(isConnected, Phone.REASON_APN_CHANGED);
+ createAllApnList();
+ sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
+ }
+ }
+
+ //***** Instance Variables
+
+ GSMPhone phone;
+ INetStatService netstat;
+ State state = State.IDLE;
+ Activity activity = Activity.NONE;
+ boolean netStatPollEnabled = false;
+ // Indicates baseband will not auto-attach
+ private boolean noAutoAttach = false;
+ long nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS;
+ Handler mDataConnectionTracker = null;
+ private ContentResolver mResolver;
+
+ int txPkts, rxPkts, sentSinceLastRecv, netStatPollPeriod;
+ private int mNoRecvPollCount = 0;
+ private boolean mPingTestActive = false;
+ // Count of PDP reset attempts; reset when we see incoming,
+ // call reRegisterNetwork, or pingTest succeeds.
+ private int mPdpResetCount = 0;
+ private boolean mIsScreenOn = true;
+
+ //useful for debugging
+ boolean failNextConnect = false;
+
+ /**
+ * allApns holds all apns for this sim spn, retrieved from
+ * the Carrier DB.
+ *
+ * Create once after simcard info is loaded
+ */
+ private ArrayList<ApnSetting> allApns = null;
+
+ /**
+ * waitingApns holds all apns that are waiting to be connected
+ *
+ * It is a subset of allApns and has the same format
+ */
+ private ArrayList<ApnSetting> waitingApns = null;
+
+ /**
+ * pdpList holds all the PDP connection, i.e. IP Link in GPRS
+ */
+ private ArrayList<PdpConnection> pdpList;
+
+ /** CID of active PDP */
+ int cidActive;
+
+ /** Currently requested APN type */
+ private String mRequestedApnType = Phone.APN_TYPE_DEFAULT;
+
+ /** Currently active APN */
+ private ApnSetting mActiveApn;
+
+ /** Currently active PdpConnection */
+ private PdpConnection mActivePdp;
+
+ private static int APN_DEFAULT_ID = 0;
+ private static int APN_MMS_ID = 1;
+ private static int APN_NUM_TYPES = 2;
+
+ private boolean[] dataEnabled = new boolean[APN_NUM_TYPES];
+
+ //***** Constants
+
+ // TODO: Increase this to match the max number of simultaneous
+ // PDP contexts we plan to support.
+ /**
+ * Pool size of PdpConnection objects.
+ */
+ private static final int PDP_CONNECTION_POOL_SIZE = 1;
+
+ private static final int POLL_PDP_MILLIS = 5 * 1000;
+ private static final int RECONNECT_DELAY_INITIAL_MILLIS = 5 * 1000;
+
+ /** Slow poll when attempting connection recovery. */
+ private static final int POLL_NETSTAT_SLOW_MILLIS = 5000;
+
+ /** Default ping deadline, in seconds. */
+ private final int DEFAULT_PING_DEADLINE = 5;
+ /** Default max failure count before attempting to network re-registration. */
+ private final int DEFAULT_MAX_PDP_RESET_FAIL = 3;
+
+ /**
+ * After detecting a potential connection problem, this is the max number
+ * of subsequent polls before attempting a radio reset. At this point,
+ * poll interval is 5 seconds (POLL_NETSTAT_SLOW_MILLIS), so set this to
+ * poll for about 2 more minutes.
+ */
+ private static final int NO_RECV_POLL_LIMIT = 24;
+
+ // 1 sec. default polling interval when screen is on.
+ private static final int POLL_NETSTAT_MILLIS = 1000;
+ // 10 min. default polling interval when screen is off.
+ private static final int POLL_NETSTAT_SCREEN_OFF_MILLIS = 1000*60*10;
+ // 2 min for round trip time
+ private static final int POLL_LONGEST_RTT = 120 * 1000;
+ // 10 for packets without ack
+ private static final int NUMBER_SENT_PACKETS_OF_HANG = 10;
+ // how long to wait before switching back to default APN
+ private static final int RESTORE_DEFAULT_APN_DELAY = 1 * 60 * 1000;
+ // system property that can override the above value
+ private static final String APN_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore";
+ // represents an invalid IP address
+ private static final String NULL_IP = "0.0.0.0";
+
+ static final String INTENT_RECONNECT_ALARM = "com.android.internal.telephony.gprs-reconnect";
+
+ //***** Tag IDs for EventLog
+ private static final int EVENT_LOG_RADIO_RESET_COUNTDOWN_TRIGGERED = 50101;
+ private static final int EVENT_LOG_RADIO_RESET = 50102;
+ private static final int EVENT_LOG_PDP_RESET = 50103;
+ private static final int EVENT_LOG_REREGISTER_NETWORK = 50104;
+
+ //***** Event Codes
+ static final int EVENT_DATA_SETUP_COMPLETE = 1;
+ static final int EVENT_RADIO_AVAILABLE = 3;
+ static final int EVENT_RECORDS_LOADED = 4;
+ static final int EVENT_TRY_SETUP_DATA = 5;
+ static final int EVENT_PDP_STATE_CHANGED = 6;
+ static final int EVENT_POLL_PDP = 7;
+ static final int EVENT_GET_PDP_LIST_COMPLETE = 11;
+ static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 12;
+ static final int EVENT_VOICE_CALL_STARTED = 14;
+ static final int EVENT_VOICE_CALL_ENDED = 15;
+ static final int EVENT_GPRS_DETACHED = 19;
+ static final int EVENT_LINK_STATE_CHANGED = 20;
+ static final int EVENT_ROAMING_ON = 21;
+ static final int EVENT_ROAMING_OFF = 22;
+ static final int EVENT_ENABLE_NEW_APN = 23;
+ static final int EVENT_RESTORE_DEFAULT_APN = 24;
+ static final int EVENT_DISCONNECT_DONE = 25;
+ static final int EVENT_GPRS_ATTACHED = 26;
+ static final int EVENT_START_NETSTAT_POLL = 27;
+ static final int EVENT_START_RECOVERY = 28;
+
+ BroadcastReceiver screenOnOffReceiver = new BroadcastReceiver ()
+ {
+ public void onReceive(Context context, Intent intent)
+ {
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+ mIsScreenOn = true;
+ stopNetStatPoll();
+ startNetStatPoll();
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ mIsScreenOn = false;
+ stopNetStatPoll();
+ startNetStatPoll();
+ } else {
+ Log.w(LOG_TAG, "DataConnectionTracker received unexpected Intent: " + intent.getAction());
+ }
+ }
+ };
+
+ BroadcastReceiver alarmReceiver
+ = new BroadcastReceiver () {
+
+ public void onReceive(Context context, Intent intent)
+ {
+ Log.d(LOG_TAG, "GPRS reconnect alarm. Previous state was " + state);
+
+ if (state == State.FAILED) {
+ cleanUpConnection(false, null);
+ }
+
+ trySetupData(null);
+ }
+ };
+
+ /** Watches for changes to the APN db. */
+ private ApnChangeObserver apnObserver;
+
+ //***** Constructor
+
+ DataConnectionTracker(GSMPhone phone)
+ {
+ this.phone = phone;
+ phone.mCM.registerForAvailable (this, EVENT_RADIO_AVAILABLE, null);
+ phone.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+ phone.mSIMRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);
+ phone.mCM.registerForPDPStateChanged (this, EVENT_PDP_STATE_CHANGED, null);
+ phone.mCT.registerForVoiceCallEnded (this, EVENT_VOICE_CALL_ENDED, null);
+ phone.mCT.registerForVoiceCallStarted (this, EVENT_VOICE_CALL_STARTED, null);
+ phone.mSST.registerForGprsAttached(this, EVENT_GPRS_ATTACHED, null);
+ phone.mSST.registerForGprsDetached(this, EVENT_GPRS_DETACHED, null);
+ phone.mSST.registerForRoamingOn(this, EVENT_ROAMING_ON, null);
+ phone.mSST.registerForRoamingOff(this, EVENT_ROAMING_OFF, null);
+
+ this.netstat = INetStatService.Stub.asInterface(ServiceManager.getService("netstat"));
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(INTENT_RECONNECT_ALARM);
+ phone.getContext().registerReceiver(
+ alarmReceiver, filter, null, phone.h);
+
+ IntentFilter filterS = new IntentFilter();
+ filterS.addAction(Intent.ACTION_SCREEN_ON);
+ filterS.addAction(Intent.ACTION_SCREEN_OFF);
+ phone.getContext().registerReceiver(screenOnOffReceiver, filterS);
+
+ mDataConnectionTracker = this;
+ mResolver = phone.getContext().getContentResolver();
+
+ apnObserver = new ApnChangeObserver();
+ phone.getContext().getContentResolver().registerContentObserver(
+ Telephony.Carriers.CONTENT_URI, true, apnObserver);
+
+ createAllPdpList();
+
+ // This preference tells us 1) initial condition for "dataEnabled",
+ // and 2) whether the RIL will setup the baseband to auto-PS attach.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext());
+ dataEnabled[APN_DEFAULT_ID] = !sp.getBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, false);
+ noAutoAttach = !dataEnabled[APN_DEFAULT_ID];
+ }
+
+ void setState(State s) {
+ if (state != s) {
+ if (s == State.INITING) { // request PDP context
+ Checkin.updateStats(
+ phone.getContext().getContentResolver(),
+ Checkin.Stats.Tag.PHONE_GPRS_ATTEMPTED, 1, 0.0);
+ }
+
+ if (s == State.CONNECTED) { // pppd is up
+ Checkin.updateStats(
+ phone.getContext().getContentResolver(),
+ Checkin.Stats.Tag.PHONE_GPRS_CONNECTED, 1, 0.0);
+ }
+ }
+
+ state = s;
+
+ if (state == State.FAILED) {
+ if (waitingApns != null)
+ waitingApns.clear(); // when teardown the connection and set to IDLE
+ }
+ }
+ String[] getActiveApnTypes() {
+ String[] result;
+ if (mActiveApn != null) {
+ result = mActiveApn.types;
+ } else {
+ result = new String[1];
+ result[0] = Phone.APN_TYPE_DEFAULT;
+ }
+ return result;
+ }
+
+ String getActiveApnString() {
+ String result = null;
+ if (mActiveApn != null) {
+ result = mActiveApn.apn;
+ }
+ return result;
+ }
+
+ /**
+ * Ensure that we are connected to an APN of the specified type.
+ * @param type the APN type (currently the only valid value
+ * is {@link Phone#APN_TYPE_MMS})
+ * @return the result of the operation. Success is indicated by
+ * a return value of either {@code Phone.APN_ALREADY_ACTIVE} or
+ * {@code Phone.APN_REQUEST_STARTED}. In the latter case, a broadcast
+ * will be sent by the ConnectivityManager when a connection to
+ * the APN has been established.
+ */
+ int enableApnType(String type) {
+ if (!TextUtils.equals(type, Phone.APN_TYPE_MMS)) {
+ return Phone.APN_REQUEST_FAILED;
+ }
+ // If already active, return
+ Log.d(LOG_TAG, "enableApnType("+type+")");
+ if (isApnTypeActive(type)) {
+ setEnabled(type, true);
+ removeMessages(EVENT_RESTORE_DEFAULT_APN);
+ /**
+ * We're being asked to enable a non-default APN that's already in use.
+ * This means we should restart the timer that will automatically
+ * switch back to the default APN and disable the non-default APN
+ * when it expires.
+ */
+ sendMessageDelayed(
+ obtainMessage(EVENT_RESTORE_DEFAULT_APN),
+ getRestoreDefaultApnDelay());
+ return Phone.APN_ALREADY_ACTIVE;
+ }
+
+ if (!isApnTypeAvailable(type)) {
+ return Phone.APN_TYPE_NOT_AVAILABLE;
+ }
+
+ setEnabled(type, true);
+ mRequestedApnType = type;
+ sendMessage(obtainMessage(EVENT_ENABLE_NEW_APN));
+ return Phone.APN_REQUEST_STARTED;
+ }
+
+ /**
+ * The APN of the specified type is no longer needed. Ensure that if
+ * use of the default APN has not been explicitly disabled, we are connected
+ * to the default APN.
+ * @param type the APN type. The only valid value currently is {@link Phone#APN_TYPE_MMS}.
+ * @return
+ */
+ int disableApnType(String type) {
+ Log.d(LOG_TAG, "disableApnType("+type+")");
+ if (TextUtils.equals(type, Phone.APN_TYPE_MMS)) {
+ removeMessages(EVENT_RESTORE_DEFAULT_APN);
+ setEnabled(type, false);
+ if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
+ if (dataEnabled[APN_DEFAULT_ID]) {
+ return Phone.APN_ALREADY_ACTIVE;
+ } else {
+ cleanUpConnection(true, Phone.REASON_DATA_DISABLED);
+ return Phone.APN_REQUEST_STARTED;
+ }
+ } else {
+ /*
+ * Note that if default data is disabled, the following
+ * has the effect of disabling the MMS APN, and then
+ * ignoring the request to enable the default APN.
+ * The net result is that data is completely disabled.
+ */
+ sendMessage(obtainMessage(EVENT_RESTORE_DEFAULT_APN));
+ return Phone.APN_REQUEST_STARTED;
+ }
+ } else {
+ return Phone.APN_REQUEST_FAILED;
+ }
+ }
+
+ private boolean isApnTypeActive(String type) {
+ // TODO: to support simultaneous, mActiveApn can be a List instead.
+ return mActiveApn != null && mActiveApn.canHandleType(type);
+ }
+
+ private boolean isApnTypeAvailable(String type) {
+ if (allApns != null) {
+ for (ApnSetting apn : allApns) {
+ if (apn.canHandleType(type)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isEnabled(String apnType) {
+ if (TextUtils.equals(apnType, Phone.APN_TYPE_DEFAULT)) {
+ return dataEnabled[APN_DEFAULT_ID];
+ } else if (TextUtils.equals(apnType, Phone.APN_TYPE_MMS)) {
+ return dataEnabled[APN_MMS_ID];
+ } else {
+ return false;
+ }
+ }
+
+ private void setEnabled(String apnType, boolean enable) {
+ Log.d(LOG_TAG, "setEnabled(" + apnType + ", " + enable + ')');
+ if (TextUtils.equals(apnType, Phone.APN_TYPE_DEFAULT)) {
+ dataEnabled[APN_DEFAULT_ID] = enable;
+ } else if (TextUtils.equals(apnType, Phone.APN_TYPE_MMS)) {
+ dataEnabled[APN_MMS_ID] = enable;
+ }
+ Log.d(LOG_TAG, "dataEnabled[DEFAULT_APN]=" + dataEnabled[APN_DEFAULT_ID] +
+ " dataEnabled[MMS_APN]=" + dataEnabled[APN_MMS_ID]);
+ }
+
+ /**
+ * Prevent mobile data connections from being established,
+ * or once again allow mobile data connections. If the state
+ * toggles, then either tear down or set up data, as
+ * appropriate to match the new state.
+ * <p>This operation only affects the default APN, and if the same APN is
+ * currently being used for MMS traffic, the teardown will not happen
+ * even when {@code enable} is {@code false}.</p>
+ * @param enable indicates whether to enable ({@code true}) or disable ({@code false}) data
+ * @return {@code true} if the operation succeeded
+ */
+ public boolean setDataEnabled(boolean enable) {
+ boolean isEnabled = isEnabled(Phone.APN_TYPE_DEFAULT);
+ Log.d(LOG_TAG, "setDataEnabled("+enable+") isEnabled=" + isEnabled);
+ if (!isEnabled && enable) {
+ setEnabled(Phone.APN_TYPE_DEFAULT, true);
+ // trySetupData() will be a no-op if we are currently
+ // connected to the MMS APN
+ return trySetupData(Phone.REASON_DATA_ENABLED);
+ } else if (!enable) {
+ setEnabled(Phone.APN_TYPE_DEFAULT, false);
+ // Don't tear down if there is an active APN and it handles MMS.
+ // TODO: This isn't very general.
+ if (!isApnTypeActive(Phone.APN_TYPE_MMS) || !isEnabled(Phone.APN_TYPE_MMS)) {
+ cleanUpConnection(true, Phone.REASON_DATA_DISABLED);
+ return true;
+ }
+ return false;
+ } else // isEnabled && enable
+ return true;
+ }
+
+ /**
+ * Report the current state of data connectivity (enabled or disabled) for
+ * the default APN.
+ * @return {@code false} if data connectivity has been explicitly disabled,
+ * {@code true} otherwise.
+ */
+ public boolean getDataEnabled() {
+ return dataEnabled[APN_DEFAULT_ID];
+ }
+
+ /**
+ * Report on whether data connectivity is enabled for any APN.
+ * @return {@code false} if data connectivity has been explicitly disabled,
+ * {@code true} otherwise.
+ */
+ public boolean getAnyDataEnabled() {
+ return dataEnabled[APN_DEFAULT_ID] || dataEnabled[APN_MMS_ID];
+ }
+
+ //The data roaming setting is now located in the shared preferences.
+ // See if the requested preference value is the same as that stored in
+ // the shared values. If it is not, then update it.
+ public void setDataOnRoamingEnabled(boolean enabled) {
+ if (getDataOnRoamingEnabled() != enabled) {
+ Settings.System.putInt(phone.getContext().getContentResolver(),
+ Settings.System.DATA_ROAMING, enabled ? 1 : 0);
+ }
+ Message roamingMsg = phone.getServiceState().getRoaming() ?
+ obtainMessage(EVENT_ROAMING_ON) : obtainMessage(EVENT_ROAMING_OFF);
+ sendMessage(roamingMsg);
+ }
+
+ //Retrieve the data roaming setting from the shared preferences.
+ public boolean getDataOnRoamingEnabled() {
+ try {
+ return Settings.System.getInt(phone.getContext().getContentResolver(),
+ Settings.System.DATA_ROAMING) > 0;
+ } catch (SettingNotFoundException snfe) {
+ return false;
+ }
+ }
+
+ public ArrayList<PdpConnection> getAllPdps() {
+ ArrayList<PdpConnection> pdps = (ArrayList<PdpConnection>)pdpList.clone();
+ return pdps;
+ }
+
+ private boolean isDataAllowed() {
+ boolean roaming = phone.getServiceState().getRoaming();
+ return getAnyDataEnabled() && (!roaming || getDataOnRoamingEnabled());
+ }
+
+ //****** Called from ServiceStateTracker
+ /**
+ * Invoked when ServiceStateTracker observes a transition from GPRS
+ * attach to detach.
+ */
+ private void onGprsDetached()
+ {
+ /*
+ * We presently believe it is unnecessary to tear down the PDP context
+ * when GPRS detaches, but we should stop the network polling.
+ */
+ stopNetStatPoll();
+ phone.notifyDataConnection(Phone.REASON_GPRS_DETACHED);
+ }
+
+ private void onGprsAttached() {
+ if (state == State.CONNECTED) {
+ startNetStatPoll();
+ phone.notifyDataConnection(Phone.REASON_GPRS_ATTACHED);
+ } else {
+ trySetupData(Phone.REASON_GPRS_ATTACHED);
+ }
+ }
+
+ private boolean trySetupData(String reason)
+ {
+ if (DBG) log("***trySetupData due to " + (reason == null ? "(unspecified)" : reason));
+
+ if (phone.getSimulatedRadioControl() != null) {
+ // Assume data is connected on the simulator
+ // FIXME this can be improved
+ setState(State.CONNECTED);
+ phone.notifyDataConnection(reason);
+
+ Log.i(LOG_TAG, "(fix?) We're on the simulator; assuming data is connected");
+ return true;
+ }
+
+ int gprsState = phone.mSST.getCurrentGprsState();
+ boolean roaming = phone.getServiceState().getRoaming();
+
+ if ((state == State.IDLE || state == State.SCANNING)
+ && (gprsState == ServiceState.STATE_IN_SERVICE || noAutoAttach)
+ && phone.mSIMRecords.getRecordsLoaded()
+ && ( phone.mSST.isConcurrentVoiceAndData() ||
+ phone.getState() == Phone.State.IDLE )
+ && isDataAllowed()) {
+
+ if (state == State.IDLE) {
+ waitingApns = buildWaitingApns();
+ if (waitingApns.isEmpty()) {
+ if (DBG) log("No APN found");
+ notifyNoData(PdpConnection.PdpFailCause.BAD_APN);
+ return false;
+ } else {
+ log ("Create from allApns : " + apnListToString(allApns));
+ }
+ }
+
+ if (DBG) {
+ log ("Setup watingApns : " + apnListToString(waitingApns));
+ }
+ return setupData(reason);
+ } else {
+ if (DBG)
+ log("trySetupData: Not ready for data: " +
+ " dataState=" + state +
+ " gprsState=" + gprsState +
+ " sim=" + phone.mSIMRecords.getRecordsLoaded() +
+ " UMTS=" + phone.mSST.isConcurrentVoiceAndData() +
+ " phoneState=" + phone.getState() +
+ " dataEnabled=" + getAnyDataEnabled() +
+ " roaming=" + roaming +
+ " dataOnRoamingEnable=" + getDataOnRoamingEnabled());
+ return false;
+ }
+ }
+
+ /**
+ * If tearDown is true, this only tears down a CONNECTED session. Presently,
+ * there is no mechanism for abandoning an INITING/CONNECTING session,
+ * but would likely involve cancelling pending async requests or
+ * setting a flag or new state to ignore them when they came in
+ * @param tearDown true if the underlying PdpConnection should be
+ * disconnected.
+ * @param reason reason for the clean up.
+ */
+ private void cleanUpConnection(boolean tearDown, String reason) {
+ if (DBG) log("Clean up connection due to " + reason);
+ for (PdpConnection pdp : pdpList) {
+ if (tearDown) {
+ Message msg = obtainMessage(EVENT_DISCONNECT_DONE);
+ pdp.disconnect(msg);
+ } else {
+ pdp.clearSettings();
+ }
+ }
+ stopNetStatPoll();
+ setState(State.IDLE);
+ phone.notifyDataConnection(reason);
+ mActiveApn = null;
+ }
+
+ /**
+ * @param types comma delimited list of APN types
+ * @return array of APN types
+ */
+ private String[] parseTypes(String types) {
+ String[] result;
+ // If unset, set to DEFAULT.
+ if (types == null || types.equals("")) {
+ result = new String[1];
+ result[0] = Phone.APN_TYPE_ALL;
+ } else {
+ result = types.split(",");
+ }
+ return result;
+ }
+
+ private ArrayList<ApnSetting> createApnList(Cursor cursor) {
+ ArrayList<ApnSetting> result = new ArrayList<ApnSetting>();
+ if (cursor.moveToFirst()) {
+ do {
+ String[] types = parseTypes(
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE)));
+ ApnSetting apn = new ApnSetting(
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)),
+ types);
+ result.add(apn);
+ } while (cursor.moveToNext());
+ }
+ return result;
+ }
+
+ private PdpConnection findFreePdp() {
+ for (PdpConnection pdp : pdpList) {
+ if (pdp.getState() == PdpConnection.PdpState.INACTIVE) {
+ return pdp;
+ }
+ }
+ return null;
+ }
+
+ private boolean setupData(String reason) {
+ ApnSetting apn;
+ PdpConnection pdp;
+
+ apn = getNextApn();
+ if (apn == null) return false;
+ pdp = findFreePdp();
+ if (pdp == null) {
+ if (DBG) log("setupData: No free PdpConnection found!");
+ return false;
+ }
+ mActiveApn = apn;
+ mActivePdp = pdp;
+
+ Message msg = obtainMessage();
+ msg.what = EVENT_DATA_SETUP_COMPLETE;
+ pdp.connect(apn, msg);
+
+ setState(State.INITING);
+ phone.notifyDataConnection(reason);
+ return true;
+ }
+
+ String getInterfaceName(String apnType) {
+ if (mActivePdp != null
+ && (apnType == null || mActiveApn.canHandleType(apnType))) {
+ return mActivePdp.getInterface();
+ }
+ return null;
+ }
+
+ String getIpAddress(String apnType) {
+ if (mActivePdp != null
+ && (apnType == null || mActiveApn.canHandleType(apnType))) {
+ return mActivePdp.getIpAddress();
+ }
+ return null;
+ }
+
+ String getGateway(String apnType) {
+ if (mActivePdp != null
+ && (apnType == null || mActiveApn.canHandleType(apnType))) {
+ return mActivePdp.getGatewayAddress();
+ }
+ return null;
+ }
+
+ String[] getDnsServers(String apnType) {
+ if (mActivePdp != null
+ && (apnType == null || mActiveApn.canHandleType(apnType))) {
+ return mActivePdp.getDnsServers();
+ }
+ return null;
+ }
+
+ private boolean
+ pdpStatesHasCID (ArrayList<PDPContextState> states, int cid)
+ {
+ for (int i = 0, s = states.size() ; i < s ; i++) {
+ if (states.get(i).cid == cid) return true;
+ }
+
+ return false;
+ }
+
+ private boolean
+ pdpStatesHasActiveCID (ArrayList<PDPContextState> states, int cid)
+ {
+ for (int i = 0, s = states.size() ; i < s ; i++) {
+ if (states.get(i).cid == cid) return states.get(i).active;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param explicitPoll if true, indicates that *we* polled for this
+ * update while state == CONNECTED rather than having it delivered
+ * via an unsolicited response (which could have happened at any
+ * previous state
+ */
+ private void
+ onPdpStateChanged (AsyncResult ar, boolean explicitPoll)
+ {
+ ArrayList<PDPContextState> pdpStates;
+
+ pdpStates = (ArrayList<PDPContextState>)(ar.result);
+
+ if (ar.exception != null) {
+ // This is probably "radio not available" or something
+ // of that sort. If so, the whole connection is going
+ // to come down soon anyway
+ return;
+ }
+
+
+ // This is how things are supposed to work:
+ // The PDP list is supposed to be empty of the CID
+ // when it disconnects
+
+ if (state == State.CONNECTED
+ && !pdpStatesHasCID(pdpStates, cidActive)) {
+
+ // It looks like the PDP context has deactivated
+ // Tear everything down and try to reconnect
+
+ Log.i(LOG_TAG, "PDP connection has dropped. Reconnecting");
+
+ cleanUpConnection(true, null);
+ trySetupData(null);
+
+ return;
+ }
+
+ if (true) {
+ //
+ // Workaround for issue #655426
+ //
+
+ // --------------------------
+
+ // This is how some things work now: the PDP context is still
+ // listed with active = false, which makes it hard to
+ // distinguish an activating context from an activated-and-then
+ // deactivated one.
+ //
+ // Here, we only consider this authoritative if we asked for the
+ // PDP list. If it was an unsolicited response, we poll again
+ // to make sure everyone agrees on the initial state
+
+ if (state == State.CONNECTED
+ && !pdpStatesHasActiveCID(pdpStates, cidActive)) {
+
+ if (!explicitPoll) {
+ // We think it disconnected but aren't sure...poll from our side
+ phone.mCM.getPDPContextList(
+ this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE));
+ } else {
+ Log.i(LOG_TAG, "PDP connection has dropped (active=false case). "
+ + " Reconnecting");
+
+ cleanUpConnection(true, null);
+ trySetupData(null);
+ }
+ }
+ }
+ }
+
+ private void notifyDefaultData() {
+ setupDnsProperties();
+ setState(State.CONNECTED);
+ phone.notifyDataConnection(null);
+ startNetStatPoll();
+ // reset reconnect timer
+ nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS;
+ }
+
+ private void setupDnsProperties() {
+ int mypid = android.os.Process.myPid();
+ String[] servers = getDnsServers(null);
+ String propName;
+ String propVal;
+ int count;
+
+ count = 0;
+ for (int i = 0; i < servers.length; i++) {
+ String serverAddr = servers[i];
+ if (!TextUtils.equals(serverAddr, "0.0.0.0")) {
+ SystemProperties.set("net.dns" + (i+1) + "." + mypid, serverAddr);
+ count++;
+ }
+ }
+ for (int i = count+1; i <= 4; i++) {
+ propName = "net.dns" + i + "." + mypid;
+ propVal = SystemProperties.get(propName);
+ if (propVal.length() != 0) {
+ SystemProperties.set(propName, "");
+ }
+ }
+ /*
+ * Bump the property that tells the name resolver library
+ * to reread the DNS server list from the properties.
+ */
+ propVal = SystemProperties.get("net.dnschange");
+ if (propVal.length() != 0) {
+ try {
+ int n = Integer.parseInt(propVal);
+ SystemProperties.set("net.dnschange", "" + (n+1));
+ } catch (NumberFormatException e) {
+ }
+ }
+ }
+
+ /**
+ * This is a kludge to deal with the fact that
+ * the PDP state change notification doesn't always work
+ * with certain RIL impl's/basebands
+ *
+ */
+ private void
+ startPeriodicPdpPoll()
+ {
+ removeMessages(EVENT_POLL_PDP);
+
+ sendMessageDelayed(obtainMessage(EVENT_POLL_PDP), POLL_PDP_MILLIS);
+ }
+
+ private void resetPollStats() {
+ txPkts = -1;
+ rxPkts = -1;
+ sentSinceLastRecv = 0;
+ mNoRecvPollCount = 0;
+ }
+
+ private void doRecovery() {
+ if (state == State.CONNECTED) {
+ int maxPdpReset = Settings.Gservices.getInt(mResolver,
+ Settings.Gservices.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT,
+ DEFAULT_MAX_PDP_RESET_FAIL);
+ if (mPdpResetCount < maxPdpReset) {
+ mPdpResetCount++;
+ EventLog.writeEvent(EVENT_LOG_PDP_RESET, sentSinceLastRecv);
+ cleanUpConnection(true, Phone.REASON_PDP_RESET);
+ } else {
+ mPdpResetCount = 0;
+ EventLog.writeEvent(EVENT_LOG_REREGISTER_NETWORK, sentSinceLastRecv);
+ phone.mSST.reRegisterNetwork(null);
+ }
+ // TODO: Add increasingly drastic recovery steps, eg,
+ // reset the radio, reset the device.
+ }
+ }
+
+ private void
+ startNetStatPoll()
+ {
+ if (state == State.CONNECTED && mPingTestActive == false) {
+ Log.d(LOG_TAG, "[DataConnection] Start poll NetStat");
+ resetPollStats();
+ netStatPollEnabled = true;
+ mPollNetStat.run();
+ }
+ }
+
+ private void
+ stopNetStatPoll()
+ {
+ netStatPollEnabled = false;
+ removeCallbacks(mPollNetStat);
+ Log.d(LOG_TAG, "[DataConnection] Stop poll NetStat");
+ }
+
+ private void
+ restartRadio()
+ {
+ Log.d(LOG_TAG, "************TURN OFF RADIO**************");
+ cleanUpConnection(true, Phone.REASON_RADIO_TURNED_OFF);
+ phone.mCM.setRadioPower(false, null);
+ /* Note: no need to call setRadioPower(true). Assuming the desired
+ * radio power state is still ON (as tracked by ServiceStateTracker),
+ * ServiceStateTracker will call setRadioPower when it receives the
+ * RADIO_STATE_CHANGED notification for the power off. And if the
+ * desired power state has changed in the interim, we don't want to
+ * override it with an unconditional power on.
+ */
+
+ int reset = Integer.parseInt(SystemProperties.get("net.ppp.reset-by-timeout", "0"));
+ SystemProperties.set("net.ppp.reset-by-timeout", String.valueOf(reset+1));
+ }
+
+ Runnable mPollNetStat = new Runnable()
+ {
+
+ public void run() {
+ int sent, received;
+ int preTxPkts = -1, preRxPkts = -1;
+
+ Activity newActivity;
+
+ preTxPkts = txPkts;
+ preRxPkts = rxPkts;
+
+ try {
+ txPkts = netstat.getTxPackets();
+ rxPkts = netstat.getRxPackets();
+ } catch (RemoteException e) {
+ txPkts = 0;
+ rxPkts = 0;
+ }
+
+ //Log.d(LOG_TAG, "rx " + String.valueOf(rxPkts) + " tx " + String.valueOf(txPkts));
+
+ if (netStatPollEnabled && (preTxPkts > 0 || preRxPkts > 0)) {
+ sent = txPkts - preTxPkts;
+ received = rxPkts - preRxPkts;
+
+ if ( sent > 0 && received > 0 ) {
+ sentSinceLastRecv = 0;
+ newActivity = Activity.DATAINANDOUT;
+ mPdpResetCount = 0;
+ } else if (sent > 0 && received == 0) {
+ if (phone.mCT.state == Phone.State.IDLE) {
+ sentSinceLastRecv += sent;
+ } else {
+ sentSinceLastRecv = 0;
+ }
+ newActivity = Activity.DATAOUT;
+ } else if (sent == 0 && received > 0) {
+ sentSinceLastRecv = 0;
+ newActivity = Activity.DATAIN;
+ mPdpResetCount = 0;
+ } else if (sent == 0 && received == 0) {
+ newActivity = Activity.NONE;
+ } else {
+ sentSinceLastRecv = 0;
+ newActivity = Activity.NONE;
+ }
+
+ if (activity != newActivity && mIsScreenOn) {
+ activity = newActivity;
+ phone.notifyDataActivity();
+ }
+ }
+
+ int watchdogTrigger = Settings.Gservices.getInt(mResolver,
+ Settings.Gservices.PDP_WATCHDOG_TRIGGER_PACKET_COUNT, NUMBER_SENT_PACKETS_OF_HANG);
+
+ if (sentSinceLastRecv >= watchdogTrigger) {
+ // we already have NUMBER_SENT_PACKETS sent without ack
+ if (mNoRecvPollCount == 0) {
+ EventLog.writeEvent(EVENT_LOG_RADIO_RESET_COUNTDOWN_TRIGGERED,
+ sentSinceLastRecv);
+ }
+
+ int noRecvPollLimit = Settings.Gservices.getInt(mResolver,
+ Settings.Gservices.PDP_WATCHDOG_ERROR_POLL_COUNT, NO_RECV_POLL_LIMIT);
+
+ if (mNoRecvPollCount < noRecvPollLimit) {
+ // It's possible the PDP context went down and we weren't notified.
+ // Start polling the context list in an attempt to recover.
+ if (DBG) log("no DATAIN in a while; polling PDP");
+ phone.mCM.getPDPContextList(obtainMessage(EVENT_GET_PDP_LIST_COMPLETE));
+
+ mNoRecvPollCount++;
+
+ // Slow down the poll interval to let things happen
+ netStatPollPeriod = Settings.Gservices.getInt(mResolver,
+ Settings.Gservices.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS, POLL_NETSTAT_SLOW_MILLIS);
+ } else {
+ if (DBG) log("Sent " + String.valueOf(sentSinceLastRecv) +
+ " pkts since last received");
+ // We've exceeded the threshold. Run ping test as a final check;
+ // it will proceed with recovery if ping fails.
+ netStatPollEnabled = false;
+ stopNetStatPoll();
+ Thread pingTest = new Thread() {
+ public void run() {
+ mPingTestActive = true;
+ runPingTest();
+ }
+ };
+ pingTest.start();
+ }
+ } else {
+ mNoRecvPollCount = 0;
+ if (mIsScreenOn) {
+ netStatPollPeriod = Settings.Gservices.getInt(mResolver,
+ Settings.Gservices.PDP_WATCHDOG_POLL_INTERVAL_MS, POLL_NETSTAT_MILLIS);
+ } else {
+ netStatPollPeriod = Settings.Gservices.getInt(mResolver,
+ Settings.Gservices.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
+ POLL_NETSTAT_SCREEN_OFF_MILLIS);
+ }
+ }
+
+ if (netStatPollEnabled) {
+ mDataConnectionTracker.postDelayed(this, netStatPollPeriod);
+ }
+ }
+ };
+
+ private void runPingTest () {
+ int status = -1;
+ try {
+ String address = Settings.Gservices.getString(mResolver,
+ Settings.Gservices.PDP_WATCHDOG_PING_ADDRESS);
+ int deadline = Settings.Gservices.getInt(mResolver,
+ Settings.Gservices.PDP_WATCHDOG_PING_DEADLINE, DEFAULT_PING_DEADLINE);
+ if (DBG) log("pinging " + address + " for " + deadline + "s");
+ if (address != null && !NULL_IP.equals(address)) {
+ Process p = Runtime.getRuntime()
+ .exec("ping -c 1 -i 1 -w "+ deadline + " " + address);
+ status = p.waitFor();
+ }
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "ping failed: IOException");
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "exception trying to ping");
+ }
+
+ if (status == 0) {
+ // ping succeeded. False alarm. Reset netStatPoll.
+ // ("-1" for this event indicates a false alarm)
+ EventLog.writeEvent(EVENT_LOG_PDP_RESET, -1);
+ mPdpResetCount = 0;
+ sendMessage(obtainMessage(EVENT_START_NETSTAT_POLL));
+ } else {
+ // ping failed. Proceed with recovery.
+ sendMessage(obtainMessage(EVENT_START_RECOVERY));
+ }
+ }
+
+ /**
+ * Returns true if the last fail cause is something that
+ * seems like it deserves an error notification.
+ * Transient errors are ignored
+ */
+ private boolean
+ shouldPostNotification(PdpConnection.PdpFailCause cause)
+ {
+ boolean shouldPost = true;
+ // TODO CHECK
+ // if (dataLink != null) {
+ // shouldPost = dataLink.getLastLinkExitCode() != DataLink.EXIT_OPEN_FAILED;
+ //}
+ return (shouldPost && cause != PdpConnection.PdpFailCause.UNKNOWN);
+ }
+
+ private void
+ reconnectAfterFail(PdpConnection.PdpFailCause lastFailCauseCode)
+ {
+ if (state == State.FAILED) {
+ Log.d(LOG_TAG, "PDP activate failed. Scheduling next attempt for "
+ + (nextReconnectDelay / 1000) + "s");
+
+ try {
+ IAlarmManager am = IAlarmManager.Stub.asInterface(
+ ServiceManager.getService(Context.ALARM_SERVICE));
+ PendingIntent sender = PendingIntent.getBroadcast(
+ phone.getContext(), 0,
+ new Intent(INTENT_RECONNECT_ALARM), 0);
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + nextReconnectDelay,
+ sender);
+ } catch (RemoteException ex) {
+ }
+
+ // double it for next time
+ nextReconnectDelay *= 2;
+
+ if (!shouldPostNotification(lastFailCauseCode)) {
+ Log.d(LOG_TAG,"NOT Posting GPRS Unavailable notification "
+ + "-- likely transient error");
+ } else {
+ notifyNoData(lastFailCauseCode);
+ }
+ }
+ }
+
+ private void notifyNoData(PdpConnection.PdpFailCause lastFailCauseCode) {
+ setState(State.FAILED);
+ }
+
+
+ private void log(String s) {
+ Log.d(LOG_TAG, "[DataConnectionTracker] " + s);
+ }
+
+ //***** Overridden from Handler
+ public void
+ handleMessage (Message msg)
+ {
+ AsyncResult ar;
+ String reason = null;
+
+ switch (msg.what) {
+ case EVENT_RECORDS_LOADED:
+ createAllApnList();
+ if (state == State.FAILED) {
+ cleanUpConnection(false, null);
+ }
+ sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
+ break;
+
+ case EVENT_ENABLE_NEW_APN:
+ // TODO: To support simultaneous PDP contexts, this should really only call
+ // cleanUpConnection if it needs to free up a PdpConnection.
+ reason = Phone.REASON_APN_SWITCHED;
+ cleanUpConnection(true, reason);
+ // Fall through to EVENT_TRY_SETUP_DATA.
+ case EVENT_TRY_SETUP_DATA:
+ trySetupData(reason);
+ break;
+
+ case EVENT_RESTORE_DEFAULT_APN:
+ if (DBG) Log.d(LOG_TAG, "Restore default APN");
+ setEnabled(Phone.APN_TYPE_MMS, false);
+ if (!isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
+ cleanUpConnection(true, Phone.REASON_RESTORE_DEFAULT_APN);
+ mRequestedApnType = Phone.APN_TYPE_DEFAULT;
+ trySetupData(Phone.REASON_RESTORE_DEFAULT_APN );
+ }
+ break;
+
+ case EVENT_ROAMING_OFF:
+ trySetupData(Phone.REASON_ROAMING_OFF);
+ break;
+
+ case EVENT_GPRS_DETACHED:
+ onGprsDetached();
+ break;
+
+ case EVENT_GPRS_ATTACHED:
+ onGprsAttached();
+ break;
+
+ case EVENT_ROAMING_ON:
+ if (getDataOnRoamingEnabled()) {
+ trySetupData(Phone.REASON_ROAMING_ON);
+ } else {
+ if (DBG) log("Tear down data connection on roaming.");
+ cleanUpConnection(true, Phone.REASON_ROAMING_ON);
+ }
+ break;
+
+ case EVENT_RADIO_AVAILABLE:
+ if (phone.getSimulatedRadioControl() != null) {
+ // Assume data is connected on the simulator
+ // FIXME this can be improved
+ setState(State.CONNECTED);
+ phone.notifyDataConnection(null);
+
+
+ Log.i(LOG_TAG, "We're on the simulator; assuming data is connected");
+ }
+
+ if (state != State.IDLE) {
+ cleanUpConnection(true, null);
+ }
+ break;
+
+ case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
+ // Make sure our reconnect delay starts at the initial value
+ // next time the radio comes on
+ nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS;
+
+ if (phone.getSimulatedRadioControl() != null) {
+ // Assume data is connected on the simulator
+ // FIXME this can be improved
+ Log.i(LOG_TAG, "We're on the simulator; assuming radio off is meaningless");
+ } else {
+ if (DBG) log("Radio is off and clean up all connection");
+ // TODO: Should we reset mRequestedApnType to "default"?
+ cleanUpConnection(false, Phone.REASON_RADIO_TURNED_OFF);
+ }
+ break;
+
+ case EVENT_DATA_SETUP_COMPLETE:
+ ar = (AsyncResult) msg.obj;
+
+ if (ar.exception == null) {
+ // everything is setup
+
+ // arg1 contains CID for this PDP context
+ cidActive = msg.arg1;
+ /*
+ * We may have switched away from the default PDP context
+ * in order to enable a "special" APN (e.g., for MMS
+ * traffic). Set a timer to switch back and/or disable the
+ * special APN, so that a negligient application doesn't
+ * permanently prevent data connectivity. What we are
+ * protecting against here is not malicious apps, but
+ * rather an app that inadvertantly fails to reset to the
+ * default APN, or that dies before doing so.
+ */
+ if (dataEnabled[APN_MMS_ID]) {
+ removeMessages(EVENT_RESTORE_DEFAULT_APN);
+ sendMessageDelayed(
+ obtainMessage(EVENT_RESTORE_DEFAULT_APN),
+ getRestoreDefaultApnDelay());
+ }
+ if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
+ SystemProperties.set("gsm.defaultpdpcontext.active", "true");
+ } else {
+ SystemProperties.set("gsm.defaultpdpcontext.active", "false");
+ }
+ notifyDefaultData();
+
+ // TODO: For simultaneous PDP support, we need to build another
+ // trigger another TRY_SETUP_DATA for the next APN type. (Note
+ // that the existing connection may service that type, in which
+ // case we should try the next type, etc.
+ } else {
+ PdpConnection.PdpFailCause cause;
+ cause = (PdpConnection.PdpFailCause) (ar.result);
+ if(DBG)
+ log("PDP setup failed " + cause);
+
+ // No try for permanent failure
+ if (cause.isPermanentFail()) {
+ notifyNoData(cause);
+ }
+
+ if (tryNextApn(cause)) {
+ waitingApns.remove(0);
+ if (waitingApns.isEmpty()) {
+ // No more to try, start delayed retry
+ notifyNoData(cause);
+ reconnectAfterFail(cause);
+ } else {
+ // we still have more apns to try
+ setState(State.SCANNING);
+ trySetupData(null);
+ }
+ } else {
+ notifyNoData(cause);
+ reconnectAfterFail(cause);
+ }
+ }
+ break;
+
+ case EVENT_DISCONNECT_DONE:
+ if(DBG) log("EVENT_DISCONNECT_DONE");
+ trySetupData(null);
+ break;
+
+ case EVENT_PDP_STATE_CHANGED:
+ ar = (AsyncResult) msg.obj;
+
+ onPdpStateChanged(ar, false);
+ break;
+
+ case EVENT_GET_PDP_LIST_COMPLETE:
+ ar = (AsyncResult) msg.obj;
+
+ onPdpStateChanged(ar, true);
+ break;
+
+ case EVENT_POLL_PDP:
+ /* See comment in startPeriodicPdpPoll */
+ ar = (AsyncResult) msg.obj;
+
+ if (!(state == State.CONNECTED)) {
+ // not connected; don't poll anymore
+ break;
+ }
+
+ phone.mCM.getPDPContextList(this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE));
+
+ sendMessageDelayed(obtainMessage(EVENT_POLL_PDP),
+ POLL_PDP_MILLIS);
+ break;
+
+ case EVENT_VOICE_CALL_STARTED:
+ if (state == State.CONNECTED &&
+ !phone.mSST.isConcurrentVoiceAndData()) {
+ stopNetStatPoll();
+ phone.notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
+ }
+ break;
+
+ case EVENT_VOICE_CALL_ENDED:
+ // in case data setup was attempted when we were on a voice call
+ trySetupData(Phone.REASON_VOICE_CALL_ENDED);
+ if (state == State.CONNECTED &&
+ !phone.mSST.isConcurrentVoiceAndData()) {
+ startNetStatPoll();
+ phone.notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
+ } else {
+ // clean slate after call end.
+ resetPollStats();
+ }
+ break;
+
+ case EVENT_START_NETSTAT_POLL:
+ mPingTestActive = false;
+ startNetStatPoll();
+ break;
+
+ case EVENT_START_RECOVERY:
+ mPingTestActive = false;
+ doRecovery();
+ break;
+ }
+ }
+
+ private boolean tryNextApn(PdpFailCause cause) {
+ return (cause != PdpFailCause.RADIO_NOT_AVIALABLE)
+ && (cause != PdpFailCause.RADIO_OFF)
+ && (cause != PdpFailCause.RADIO_ERROR_RETRY)
+ && (cause != PdpFailCause.NO_SIGNAL)
+ && (cause != PdpFailCause.SIM_LOCKED);
+ }
+
+ private int getRestoreDefaultApnDelay() {
+ String restoreApnDelayStr = SystemProperties.get(APN_RESTORE_DELAY_PROP_NAME);
+
+ if (restoreApnDelayStr != null && restoreApnDelayStr.length() != 0) {
+ try {
+ return Integer.valueOf(restoreApnDelayStr);
+ } catch (NumberFormatException e) {
+ }
+ }
+ return RESTORE_DEFAULT_APN_DELAY;
+ }
+
+ /**
+ * Based on the sim operator numeric, create a list for all possible pdps
+ * with all apns associated with that pdp
+ *
+ *
+ */
+ private void createAllApnList() {
+ allApns = new ArrayList<ApnSetting>();
+ String operator = phone.mSIMRecords.getSIMOperatorNumeric();
+
+ if (operator != null) {
+ String selection = "numeric = '" + operator + "'";
+
+ Cursor cursor = phone.getContext().getContentResolver().query(
+ Telephony.Carriers.CONTENT_URI, null, selection, null, null);
+
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ allApns = createApnList(cursor);
+ // TODO: Figure out where this fits in. This basically just
+ // writes the pap-secrets file. No longer tied to PdpConnection
+ // object. Not used on current platform (no ppp).
+ //PdpConnection pdp = pdpList.get(pdp_name);
+ //if (pdp != null && pdp.dataLink != null) {
+ // pdp.dataLink.setPasswordInfo(cursor);
+ //}
+ }
+ cursor.close();
+ }
+ }
+
+ if (allApns.isEmpty()) {
+ if (DBG) log("No APN found for carrier: " + operator);
+ notifyNoData(PdpConnection.PdpFailCause.BAD_APN);
+ }
+ }
+
+ private void createAllPdpList() {
+ pdpList = new ArrayList<PdpConnection>();
+ PdpConnection pdp;
+
+ for (int i = 0; i < PDP_CONNECTION_POOL_SIZE; i++) {
+ pdp = new PdpConnection(phone);
+ pdpList.add(pdp);
+ }
+ }
+
+ /**
+ *
+ * @return waitingApns list to be used to create PDP
+ * error when waitingApns.isEmpty()
+ */
+ private ArrayList<ApnSetting> buildWaitingApns() {
+ ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>();
+
+ if (allApns != null) {
+ for (ApnSetting apn : allApns) {
+ if (apn.canHandleType(mRequestedApnType)) {
+ apnList.add(apn);
+ }
+ }
+ }
+ return apnList;
+ }
+
+ /**
+ * Get next apn in waitingApns
+ * @return the first apn found in waitingApns, null if none
+ */
+ private ApnSetting getNextApn() {
+ ArrayList<ApnSetting> list = waitingApns;
+ ApnSetting apn = null;
+
+ if (list != null) {
+ if (!list.isEmpty()) {
+ apn = list.get(0);
+ }
+ }
+ return apn;
+ }
+
+ private String apnListToString (ArrayList<ApnSetting> apns) {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0, size = apns.size(); i < size; i++) {
+ result.append('[')
+ .append(apns.get(i).toString())
+ .append(']');
+ }
+ return result.toString();
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/DataLink.java b/telephony/java/com/android/internal/telephony/gsm/DataLink.java
new file mode 100644
index 0000000..b822ab4
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/DataLink.java
@@ -0,0 +1,41 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.os.Handler;
+import android.os.Registrant;
+
+/**
+ * Base class representing the data link layer (eg, PPP).
+ *
+ * {@hide}
+ */
+abstract class DataLink extends Handler implements DataLinkInterface {
+
+ /** Registrant for link status change notifications. */
+ Registrant mLinkChangeRegistrant;
+
+ protected DataConnectionTracker dataConnection;
+
+ DataLink(DataConnectionTracker dc) {
+ dataConnection = dc;
+ }
+
+ public void setOnLinkChange(Handler h, int what, Object obj) {
+ mLinkChangeRegistrant = new Registrant(h, what, obj);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/DataLinkInterface.java b/telephony/java/com/android/internal/telephony/gsm/DataLinkInterface.java
new file mode 100644
index 0000000..bca63f2
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/DataLinkInterface.java
@@ -0,0 +1,77 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.database.Cursor;
+import android.os.Handler;
+
+/**
+ * Data link interface.
+ *
+ * {@hide}
+ */
+interface DataLinkInterface {
+ /**
+ * Link state enumeration.
+ *
+ */
+ enum LinkState {
+ LINK_UNKNOWN,
+ LINK_UP,
+ LINK_DOWN,
+ LINK_EXITED
+ }
+
+ /** Normal exit */
+ final static int EXIT_OK = 0;
+ /** Open failed */
+ final static int EXIT_OPEN_FAILED = 7;
+
+ /**
+ * Sets the handler for link state change events.
+ *
+ * @param h Handler
+ * @param what User-defined message code
+ * @param obj User object
+ */
+ void setOnLinkChange(Handler h, int what, Object obj);
+
+ /**
+ * Sets up the data link.
+ */
+ void connect();
+
+ /**
+ * Tears down the data link.
+ */
+ void disconnect();
+
+ /**
+ * Returns the exit code for a data link failure.
+ *
+ * @return exit code
+ */
+ int getLastLinkExitCode();
+
+ /**
+ * Sets password information that may be required by the data link
+ * (eg, PAP secrets).
+ *
+ * @param cursor cursor to carriers table
+ */
+ void setPasswordInfo(Cursor cursor);
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/DriverCall.java b/telephony/java/com/android/internal/telephony/gsm/DriverCall.java
new file mode 100644
index 0000000..178e661
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/DriverCall.java
@@ -0,0 +1,144 @@
+/*
+ * 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.internal.telephony.gsm;
+import com.android.internal.telephony.*;
+import android.util.Log;
+import java.lang.Comparable;
+import android.telephony.PhoneNumberUtils;
+
+/**
+ * {@hide}
+ */
+public class DriverCall implements Comparable
+{
+ static final String LOG_TAG = "GSM";
+
+ public enum State {
+ ACTIVE,
+ HOLDING,
+ DIALING, // MO call only
+ ALERTING, // MO call only
+ INCOMING, // MT call only
+ WAITING; // MT call only
+ // If you add a state, make sure to look for the switch()
+ // statements that use this enum
+ };
+
+ public int index;
+ public boolean isMT;
+ public State state; // May be null if unavail
+ public boolean isMpty;
+ public String number;
+ public int TOA;
+ public boolean isVoice;
+ public int als;
+
+ /** returns null on error */
+ static DriverCall
+ fromCLCCLine(String line)
+ {
+ DriverCall ret = new DriverCall();
+
+ //+CLCC: 1,0,2,0,0,\"+18005551212\",145
+ // index,isMT,state,mode,isMpty(,number,TOA)?
+ ATResponseParser p = new ATResponseParser(line);
+
+ try {
+ ret.index = p.nextInt();
+ ret.isMT = p.nextBoolean();
+ ret.state = stateFromCLCC(p.nextInt());
+
+ ret.isVoice = (0 == p.nextInt());
+ ret.isMpty = p.nextBoolean();
+
+ if (p.hasMore()) {
+ // Some lame implementations return strings
+ // like "NOT AVAILABLE" in the CLCC line
+ ret.number = PhoneNumberUtils.extractNetworkPortion(
+ p.nextString());
+
+ if (ret.number.length() == 0) {
+ ret.number = null;
+ }
+
+ ret.TOA = p.nextInt();
+
+ // Make sure there's a leading + on addresses with a TOA
+ // of 145
+
+ ret.number = PhoneNumberUtils.stringFromStringAndTOA(
+ ret.number, ret.TOA);
+
+ }
+ } catch (ATParseEx ex) {
+ Log.e(LOG_TAG,"Invalid CLCC line: '" + line + "'");
+ return null;
+ }
+
+ return ret;
+ }
+
+ public
+ DriverCall()
+ {
+ }
+
+ public String
+ toString()
+ {
+ return "id=" + index + ","
+ + (isMT ? "mt" : "mo") + ","
+ + state + ","
+ + (isVoice ? "voice" : "no_voc") + ","
+ + (isMpty ? "conf" : "norm") + ","
+ + TOA + "," + als;
+ }
+
+ public static State
+ stateFromCLCC(int state) throws ATParseEx
+ {
+ switch(state) {
+ case 0: return State.ACTIVE;
+ case 1: return State.HOLDING;
+ case 2: return State.DIALING;
+ case 3: return State.ALERTING;
+ case 4: return State.INCOMING;
+ case 5: return State.WAITING;
+ default:
+ throw new ATParseEx("illegal call state " + state);
+ }
+ }
+
+ //***** Comparable Implementation
+
+ /** For sorting by index */
+ public int
+ compareTo (Object o)
+ {
+ DriverCall dc;
+
+ dc = (DriverCall)o;
+
+ if (index < dc.index) {
+ return -1;
+ } else if (index == dc.index) {
+ return 0;
+ } else { /*index > dc.index*/
+ return 1;
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/EncodeException.java b/telephony/java/com/android/internal/telephony/gsm/EncodeException.java
new file mode 100644
index 0000000..d546cef
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/EncodeException.java
@@ -0,0 +1,39 @@
+/*
+ * 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.internal.telephony.gsm;
+
+/**
+ * {@hide}
+ */
+public class EncodeException extends Exception
+{
+ public EncodeException()
+ {
+ super();
+ }
+
+ public EncodeException(String s)
+ {
+ super(s);
+ }
+
+ public EncodeException(char c)
+ {
+ super("Unencodable char: '" + c + "'");
+ }
+}
+
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMCall.java b/telephony/java/com/android/internal/telephony/gsm/GSMCall.java
new file mode 100644
index 0000000..7179bb7
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMCall.java
@@ -0,0 +1,221 @@
+/*
+ * 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.internal.telephony.gsm;
+import com.android.internal.telephony.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+class GSMCall extends Call
+{
+ /*************************** Instance Variables **************************/
+
+ /*package*/ ArrayList<Connection> connections = new ArrayList<Connection>();
+ /*package*/ State state = State.IDLE;
+ /*package*/ CallTracker owner;
+
+ /***************************** Class Methods *****************************/
+
+ static State
+ stateFromDCState (DriverCall.State dcState)
+ {
+ switch (dcState) {
+ case ACTIVE: return State.ACTIVE;
+ case HOLDING: return State.HOLDING;
+ case DIALING: return State.DIALING;
+ case ALERTING: return State.ALERTING;
+ case INCOMING: return State.INCOMING;
+ case WAITING: return State.WAITING;
+ default: throw new RuntimeException ("illegal call state:" + dcState);
+ }
+ }
+
+
+ /****************************** Constructors *****************************/
+ /*package*/
+ GSMCall (CallTracker owner)
+ {
+ this.owner = owner;
+ }
+
+ /************************** Overridden from Call *************************/
+
+ public List<Connection>
+ getConnections()
+ {
+ // FIXME should return Collections.unmodifiableList();
+ return connections;
+ }
+
+ public State
+ getState()
+ {
+ return state;
+ }
+
+ public Phone
+ getPhone()
+ {
+ //TODO
+ return null;
+ }
+
+ public boolean
+ isMultiparty()
+ {
+ return connections.size() > 1;
+ }
+
+ /** Please note: if this is the foreground call and a
+ * background call exists, the background call will be resumed
+ * because an AT+CHLD=1 will be sent
+ */
+ public void
+ hangup() throws CallStateException
+ {
+ owner.hangup(this);
+ }
+
+ public String
+ toString()
+ {
+ return state.toString();
+ }
+
+ //***** Called from GSMConnection
+
+ /*package*/ void
+ attach(GSMConnection conn, DriverCall dc)
+ {
+ connections.add(conn);
+
+ state = stateFromDCState (dc.state);
+ }
+
+ /*package*/ void
+ attachFake(GSMConnection conn, State state)
+ {
+ connections.add(conn);
+
+ this.state = state;
+ }
+
+ /**
+ * Called by GSMConnection when it has disconnected
+ */
+ void
+ connectionDisconnected(GSMConnection conn)
+ {
+ if (state != State.DISCONNECTED) {
+ /* If only disconnected connections remain, we are disconnected*/
+
+ boolean hasOnlyDisconnectedConnections = true;
+
+ for (int i = 0, s = connections.size() ; i < s; i ++) {
+ if (connections.get(i).getState()
+ != State.DISCONNECTED
+ ) {
+ hasOnlyDisconnectedConnections = false;
+ break;
+ }
+ }
+
+ if (hasOnlyDisconnectedConnections) {
+ state = State.DISCONNECTED;
+ }
+ }
+ }
+
+
+ /*package*/ void
+ detach(GSMConnection conn)
+ {
+ connections.remove(conn);
+
+ if (connections.size() == 0) {
+ state = State.IDLE;
+ }
+ }
+
+ /*package*/ boolean
+ update (GSMConnection conn, DriverCall dc)
+ {
+ State newState;
+ boolean changed = false;
+
+ newState = stateFromDCState(dc.state);
+
+ if (newState != state) {
+ state = newState;
+ changed = true;
+ }
+
+ return changed;
+ }
+
+ /**
+ * @return true if there's no space in this call for additional
+ * connections to be added via "conference"
+ */
+ /*package*/ boolean
+ isFull()
+ {
+ return connections.size() == CallTracker.MAX_CONNECTIONS_PER_CALL;
+ }
+
+ //***** Called from CallTracker
+
+
+ /**
+ * Called when this Call is being hung up locally (eg, user pressed "end")
+ * Note that at this point, the hangup request has been dispatched to the radio
+ * but no response has yet been received so update() has not yet been called
+ */
+ void
+ onHangupLocal()
+ {
+ for (int i = 0, s = connections.size()
+ ; i < s; i++
+ ) {
+ GSMConnection cn = (GSMConnection)connections.get(i);
+
+ cn.onHangupLocal();
+ }
+ }
+
+ /**
+ * Called when it's time to clean up disconnected Connection objects
+ */
+ void
+ clearDisconnected()
+ {
+ for (int i = connections.size() - 1 ; i >= 0 ; i--) {
+ GSMConnection cn = (GSMConnection)connections.get(i);
+
+ if (cn.getState() == State.DISCONNECTED) {
+ connections.remove(i);
+ }
+ }
+
+ if (connections.size() == 0) {
+ state = State.IDLE;
+ }
+ }
+}
+
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMConnection.java b/telephony/java/com/android/internal/telephony/gsm/GSMConnection.java
new file mode 100644
index 0000000..c316a23
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMConnection.java
@@ -0,0 +1,678 @@
+/*
+ * 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.internal.telephony.gsm;
+import com.android.internal.telephony.*;
+import android.os.Handler;
+import android.os.Registrant;
+import android.os.Looper;
+import android.os.Message;
+import android.os.AsyncResult;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Config;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+
+/**
+ * {@hide}
+ */
+public class GSMConnection extends Connection {
+ static final String LOG_TAG = "GSM";
+
+ //***** Instance Variables
+
+ CallTracker owner;
+ GSMCall parent;
+
+ String address; // MAY BE NULL!!!
+ String dialString; // outgoing calls only
+ String postDialString; // outgoing calls only
+ boolean isIncoming;
+ boolean disconnected;
+
+ int index; // index in CallTracker.connections[], -1 if unassigned
+ // The GSM index is 1 + this
+
+ /*
+ * These time/timespan values are based on System.currentTimeMillis(),
+ * i.e., "wall clock" time.
+ */
+ long createTime;
+ long connectTime;
+ long disconnectTime;
+
+ /*
+ * These time/timespan values are based on SystemClock.elapsedRealTime(),
+ * i.e., time since boot. They are appropriate for comparison and
+ * calculating deltas.
+ */
+ long connectTimeReal;
+ long duration;
+ long holdingStartTime; // The time when the Connection last transitioned
+ // into HOLDING
+
+ int nextPostDialChar; // index into postDialString
+
+ DisconnectCause cause = DisconnectCause.NOT_DISCONNECTED;
+ PostDialState postDialState = PostDialState.NOT_STARTED;
+
+ Handler h;
+
+ //***** Event Constants
+
+ static final int EVENT_DTMF_DONE = 1;
+ static final int EVENT_PAUSE_DONE = 2;
+ static final int EVENT_NEXT_POST_DIAL = 3;
+
+ //***** Constants
+
+ static final int PAUSE_DELAY_FIRST_MILLIS = 100;
+ static final int PAUSE_DELAY_MILLIS = 3 * 1000;
+
+ //***** Inner Classes
+
+ class MyHandler extends Handler {
+ MyHandler(Looper l) {super(l);}
+
+ public void
+ handleMessage(Message msg)
+ {
+ switch (msg.what) {
+ case EVENT_NEXT_POST_DIAL:
+ case EVENT_DTMF_DONE:
+ case EVENT_PAUSE_DONE:
+ processNextPostDialChar();
+ break;
+
+ }
+ }
+ }
+
+ //***** Constructors
+
+ /** This is probably an MT call that we first saw in a CLCC response */
+ /*package*/
+ GSMConnection (DriverCall dc, CallTracker ct, int index)
+ {
+ owner = ct;
+ h = new MyHandler(owner.getLooper());
+
+ address = dc.number;
+
+ isIncoming = dc.isMT;
+ createTime = System.currentTimeMillis();
+
+ this.index = index;
+
+ parent = parentFromDCState (dc.state);
+ parent.attach(this, dc);
+ }
+
+ /** This is an MO call, created when dialing */
+ /*package*/
+ GSMConnection (String dialString, CallTracker ct, GSMCall parent)
+ {
+ owner = ct;
+ h = new MyHandler(owner.getLooper());
+
+ this.dialString = dialString;
+
+ this.address = PhoneNumberUtils.extractNetworkPortion(dialString);
+ this.postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
+
+ index = -1;
+
+ isIncoming = false;
+ createTime = System.currentTimeMillis();
+
+ this.parent = parent;
+ parent.attachFake(this, Call.State.DIALING);
+ }
+
+ static boolean
+ equalsHandlesNulls (Object a, Object b)
+ {
+ return (a == null) ? (b == null) : a.equals (b);
+ }
+
+ /*package*/ boolean
+ compareTo(DriverCall c)
+ {
+ // On mobile originated (MO) calls, the phone number may have changed
+ // due to a SIM Toolkit call control modification.
+ //
+ // We assume we know when MO calls are created (since we created them)
+ // and therefore don't need to compare the phone number anyway.
+ if (! (isIncoming || c.isMT)) return true;
+
+ // ... but we can compare phone numbers on MT calls, and we have
+ // no control over when they begin, so we might as well
+
+ String cAddress = PhoneNumberUtils.stringFromStringAndTOA(c.number, c.TOA);
+ return isIncoming == c.isMT && equalsHandlesNulls(address, cAddress);
+ }
+
+ public String
+ toString()
+ {
+ return (isIncoming ? "incoming" : "outgoing");
+ }
+
+ public String getAddress()
+ {
+ return address;
+ }
+
+
+ public Call getCall()
+ {
+ return parent;
+ }
+
+ public long getCreateTime()
+ {
+ return createTime;
+ }
+
+ public long getConnectTime()
+ {
+ return connectTime;
+ }
+
+ public long getDisconnectTime()
+ {
+ return disconnectTime;
+ }
+
+ public long getDurationMillis()
+ {
+ if (connectTimeReal == 0) {
+ return 0;
+ } else if (duration == 0) {
+ return SystemClock.elapsedRealtime() - connectTimeReal;
+ } else {
+ return duration;
+ }
+ }
+
+ public long getHoldDurationMillis()
+ {
+ if (getState() != Call.State.HOLDING) {
+ // If not holding, return 0
+ return 0;
+ } else {
+ return SystemClock.elapsedRealtime() - holdingStartTime;
+ }
+ }
+
+ public DisconnectCause getDisconnectCause()
+ {
+ return cause;
+ }
+
+ public boolean isIncoming()
+ {
+ return isIncoming;
+ }
+
+ public Call.State getState()
+ {
+ if (disconnected) {
+ return Call.State.DISCONNECTED;
+ } else {
+ return super.getState();
+ }
+ }
+
+ public void hangup() throws CallStateException
+ {
+ if (!disconnected) {
+ owner.hangup(this);
+ } else {
+ throw new CallStateException ("disconnected");
+ }
+ }
+
+ public void separate() throws CallStateException
+ {
+ if (!disconnected) {
+ owner.separate(this);
+ } else {
+ throw new CallStateException ("disconnected");
+ }
+ }
+
+ public PostDialState getPostDialState()
+ {
+ return postDialState;
+ }
+
+ public void proceedAfterWaitChar()
+ {
+ if (postDialState != PostDialState.WAIT) {
+ Log.w(LOG_TAG, "Connection.proceedAfterWaitChar(): Expected "
+ + "getPostDialState() to be WAIT but was " + postDialState);
+ return;
+ }
+
+ postDialState = PostDialState.STARTED;
+
+ processNextPostDialChar();
+ }
+
+ public void proceedAfterWildChar(String str) {
+ if (postDialState != PostDialState.WILD) {
+ Log.w(LOG_TAG, "Connection.proceedAfterWaitChar(): Expected "
+ + "getPostDialState() to be WILD but was " + postDialState);
+ return;
+ }
+
+ postDialState = PostDialState.STARTED;
+
+ if (false) {
+ boolean playedTone = false;
+ int len = (str != null ? str.length() : 0);
+
+ for (int i=0; i<len; i++) {
+ char c = str.charAt(i);
+ Message msg = null;
+
+ if (i == len-1) {
+ msg = h.obtainMessage(EVENT_DTMF_DONE);
+ }
+
+ if (PhoneNumberUtils.is12Key(c)) {
+ owner.cm.sendDtmf(c, msg);
+ playedTone = true;
+ }
+ }
+
+ if (!playedTone) {
+ processNextPostDialChar();
+ }
+ } else {
+ // make a new postDialString, with the wild char replacement string
+ // at the beginning, followed by the remaining postDialString.
+
+ StringBuilder buf = new StringBuilder(str);
+ buf.append(postDialString.substring(nextPostDialChar));
+ postDialString = buf.toString();
+ nextPostDialChar = 0;
+ if (Phone.DEBUG_PHONE) {
+ log("proceedAfterWildChar: new postDialString is " +
+ postDialString);
+ }
+
+ processNextPostDialChar();
+ }
+ }
+
+ public void cancelPostDial()
+ {
+ postDialState = PostDialState.CANCELLED;
+ }
+
+ /**
+ * Called when this Connection is being hung up locally (eg, user pressed "end")
+ * Note that at this point, the hangup request has been dispatched to the radio
+ * but no response has yet been received so update() has not yet been called
+ */
+ void
+ onHangupLocal()
+ {
+ cause = DisconnectCause.LOCAL;
+ }
+
+ DisconnectCause
+ disconnectCauseFromCode(int causeCode)
+ {
+ /**
+ * See 22.001 Annex F.4 for mapping of cause codes
+ * to local tones
+ */
+
+ switch (causeCode) {
+ case CallFailCause.USER_BUSY:
+ return DisconnectCause.BUSY;
+
+ case CallFailCause.NO_CIRCUIT_AVAIL:
+ case CallFailCause.TEMPORARY_FAILURE:
+ case CallFailCause.SWITCHING_CONGESTION:
+ case CallFailCause.CHANNEL_NOT_AVAIL:
+ case CallFailCause.QOS_NOT_AVAIL:
+ case CallFailCause.BEARER_NOT_AVAIL:
+ return DisconnectCause.CONGESTION;
+
+ case CallFailCause.ACM_LIMIT_EXCEEDED:
+ return DisconnectCause.LIMIT_EXCEEDED;
+
+ case CallFailCause.CALL_BARRED:
+ return DisconnectCause.CALL_BARRED;
+
+ case CallFailCause.FDN_BLOCKED:
+ return DisconnectCause.FDN_BLOCKED;
+
+ case CallFailCause.ERROR_UNSPECIFIED:
+ case CallFailCause.NORMAL_CLEARING:
+ default:
+ GSMPhone phone = owner.phone;
+ int serviceState = phone.getServiceState().getState();
+ if (serviceState == ServiceState.STATE_POWER_OFF) {
+ return DisconnectCause.POWER_OFF;
+ } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE
+ || serviceState == ServiceState.STATE_EMERGENCY_ONLY ) {
+ return DisconnectCause.OUT_OF_SERVICE;
+ } else if (phone.getSimCard().getState() != GsmSimCard.State.READY) {
+ return DisconnectCause.SIM_ERROR;
+ } else {
+ return DisconnectCause.NORMAL;
+ }
+ }
+ }
+
+ /*package*/ void
+ onRemoteDisconnect(int causeCode)
+ {
+ onDisconnect(disconnectCauseFromCode(causeCode));
+ }
+
+ /** Called when the radio indicates the connection has been disconnected */
+ /*package*/ void
+ onDisconnect(DisconnectCause cause)
+ {
+ this.cause = cause;
+
+ if (!disconnected) {
+ index = -1;
+
+ disconnectTime = System.currentTimeMillis();
+ duration = SystemClock.elapsedRealtime() - connectTimeReal;
+ disconnected = true;
+
+ if (Config.LOGD) Log.d(LOG_TAG,
+ "[GSMConn] onDisconnect: cause=" + cause);
+
+ owner.phone.notifyDisconnect(this);
+
+ if (parent != null) {
+ parent.connectionDisconnected(this);
+ }
+ }
+ }
+
+ // Returns true if state has changed, false if nothing changed
+ /*package*/ boolean
+ update (DriverCall dc) {
+ GSMCall newParent;
+ boolean changed = false;
+ boolean wasConnectingInOrOut = isConnectingInOrOut();
+ boolean wasHolding = (getState() == Call.State.HOLDING);
+
+ newParent = parentFromDCState(dc.state);
+
+ if (!equalsHandlesNulls(address, dc.number)) {
+ if (Phone.DEBUG_PHONE) log("update: phone # changed!");
+ address = dc.number;
+ changed = true;
+ }
+
+ if (newParent != parent) {
+ if (parent != null) {
+ parent.detach(this);
+ }
+ newParent.attach(this, dc);
+ parent = newParent;
+ changed = true;
+ } else {
+ boolean parentStateChange;
+ parentStateChange = parent.update (this, dc);
+ changed = changed || parentStateChange;
+ }
+
+ /** Some state-transition events */
+
+ if (Phone.DEBUG_PHONE) log(
+ "update: parent=" + parent +
+ ", hasNewParent=" + (newParent != parent) +
+ ", wasConnectingInOrOut=" + wasConnectingInOrOut +
+ ", wasHolding=" + wasHolding +
+ ", isConnectingInOrOut=" + isConnectingInOrOut() +
+ ", changed=" + changed);
+
+
+ if (wasConnectingInOrOut && !isConnectingInOrOut()) {
+ onConnectedInOrOut();
+ }
+
+ if (changed && !wasHolding && (getState() == Call.State.HOLDING)) {
+ // We've transitioned into HOLDING
+ onStartedHolding();
+ }
+
+ return changed;
+ }
+
+ /**
+ * Called when this Connection is in the foregroundCall
+ * when a dial is initiated.
+ * We know we're ACTIVE, and we know we're going to end up
+ * HOLDING in the backgroundCall
+ */
+ void
+ fakeHoldBeforeDial()
+ {
+ if (parent != null) {
+ parent.detach(this);
+ }
+
+ parent = owner.backgroundCall;
+ parent.attachFake(this, Call.State.HOLDING);
+
+ onStartedHolding();
+ }
+
+ /*package*/ int
+ getGSMIndex() throws CallStateException {
+ if (index >= 0) {
+ return index + 1;
+ } else {
+ throw new CallStateException ("GSM index not yet assigned");
+ }
+ }
+
+ /**
+ * An incoming or outgoing call has connected
+ */
+ void
+ onConnectedInOrOut() {
+ connectTime = System.currentTimeMillis();
+ connectTimeReal = SystemClock.elapsedRealtime();
+ duration = 0;
+
+ // bug #678474: incoming call interpreted as missed call, even though
+ // it sounds like the user has picked up the call.
+ if (Phone.DEBUG_PHONE) {
+ log("onConnectedInOrOut: connectTime=" + connectTime);
+ }
+
+ if (!isIncoming) {
+ // outgoing calls only
+ processNextPostDialChar();
+ }
+ }
+
+ private void
+ onStartedHolding() {
+ holdingStartTime = SystemClock.elapsedRealtime();
+ }
+ /**
+ * Performs the appropriate action for a post-dial char, but does not
+ * notify application. returns false if the character is invalid and
+ * should be ignored
+ */
+ private boolean
+ processPostDialChar(char c)
+ {
+ if (PhoneNumberUtils.is12Key(c)) {
+ owner.cm.sendDtmf(c, h.obtainMessage(EVENT_DTMF_DONE));
+ } else if (c == PhoneNumberUtils.PAUSE) {
+ // From TS 22.101:
+
+ // "The first occurrence of the "DTMF Control Digits Separator"
+ // shall be used by the ME to distinguish between the addressing
+ // digits (i.e. the phone number) and the DTMF digits...."
+
+ if (nextPostDialChar == 1) {
+ // The first occurrence.
+ // We don't need to pause here, but wait for just a bit anyway
+ h.sendMessageDelayed(h.obtainMessage(EVENT_PAUSE_DONE),
+ PAUSE_DELAY_FIRST_MILLIS);
+ } else {
+ // It continues...
+ // "Upon subsequent occurrences of the separator, the UE shall
+ // pause again for 3 seconds (\u00B1 20 %) before sending any
+ // further DTMF digits."
+ h.sendMessageDelayed(h.obtainMessage(EVENT_PAUSE_DONE),
+ PAUSE_DELAY_MILLIS);
+ }
+ } else if (c == PhoneNumberUtils.WAIT) {
+ postDialState = PostDialState.WAIT;
+ } else if (c == PhoneNumberUtils.WILD) {
+ postDialState = PostDialState.WILD;
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ public String
+ getRemainingPostDialString()
+ {
+ if (postDialState == PostDialState.CANCELLED
+ || postDialState == PostDialState.COMPLETE
+ || postDialString == null
+ || postDialString.length() <= nextPostDialChar
+ ) {
+ return "";
+ }
+
+ return postDialString.substring(nextPostDialChar);
+ }
+
+ private void
+ processNextPostDialChar()
+ {
+ char c = 0;
+ Registrant postDialHandler;
+
+ if (postDialState == PostDialState.CANCELLED) {
+ //Log.v("GSM", "##### processNextPostDialChar: postDialState == CANCELLED, bail");
+ return;
+ }
+
+ if (postDialString == null ||
+ postDialString.length() <= nextPostDialChar) {
+ postDialState = PostDialState.COMPLETE;
+
+ // notifyMessage.arg1 is 0 on complete
+ c = 0;
+ } else {
+ boolean isValid;
+
+ postDialState = PostDialState.STARTED;
+
+ c = postDialString.charAt(nextPostDialChar++);
+
+ isValid = processPostDialChar(c);
+
+ if (!isValid) {
+ // Will call processNextPostDialChar
+ h.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
+ // Don't notify application
+ Log.e("GSM", "processNextPostDialChar: c=" + c + " isn't valid!");
+ return;
+ }
+ }
+
+ postDialHandler = owner.phone.mPostDialHandler;
+
+ Message notifyMessage;
+
+ if (postDialHandler != null && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
+ // The AsyncResult.result is the Connection object
+ PostDialState state = postDialState;
+ AsyncResult ar = AsyncResult.forMessage(notifyMessage);
+ ar.result = this;
+ ar.userObj = state;
+
+ // arg1 is the character that was/is being processed
+ notifyMessage.arg1 = c;
+
+ //Log.v("GSM", "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
+ notifyMessage.sendToTarget();
+ }
+ /*
+ else {
+ if (postDialHandler == null)
+ Log.v("GSM", "##### processNextPostDialChar: postDialHandler is NULL!");
+ else
+ Log.v("GSM", "##### processNextPostDialChar: postDialHandler.messageForRegistrant() returned NULL!");
+ }
+ */
+ }
+
+
+ /** "connecting" means "has never been ACTIVE" for both incoming
+ * and outgoing calls
+ */
+ private boolean
+ isConnectingInOrOut()
+ {
+ return parent == null || parent == owner.ringingCall
+ || parent.state == Call.State.DIALING
+ || parent.state == Call.State.ALERTING;
+ }
+
+ private GSMCall
+ parentFromDCState (DriverCall.State state)
+ {
+ switch (state) {
+ case ACTIVE:
+ case DIALING:
+ case ALERTING:
+ return owner.foregroundCall;
+ //break;
+
+ case HOLDING:
+ return owner.backgroundCall;
+ //break;
+
+ case INCOMING:
+ case WAITING:
+ return owner.ringingCall;
+ //break;
+
+ default:
+ throw new RuntimeException("illegal call state: " + state);
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[GSMConn] " + msg);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
new file mode 100644
index 0000000..f2e5799
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -0,0 +1,1520 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.content.*;
+import android.database.SQLException;
+import android.os.*;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import com.android.internal.telephony.*;
+import com.android.internal.telephony.gsm.stk.Service;
+import static com.android.internal.telephony.gsm.CommandsInterface.*;
+
+import com.android.internal.telephony.test.SimulatedRadioControl;
+import android.text.TextUtils;
+import android.util.Log;
+import static com.android.internal.telephony.TelephonyProperties.*;
+import android.net.Uri;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.CellLocation;
+import android.telephony.ServiceState;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.*;
+
+/**
+ * {@hide}
+ */
+public class GSMPhone extends PhoneBase {
+ // NOTE that LOG_TAG here is "GSM", which means that log messages
+ // from this file will go into the radio log rather than the main
+ // log. (Use "adb logcat -b radio" to see them.)
+ static final String LOG_TAG = "GSM";
+ private static final boolean LOCAL_DEBUG = false;
+
+ // Key used to read and write the saved network selection value
+ public static final String NETWORK_SELECTION_KEY = "network_selection_key";
+ // Key used to read/write "disable data connection on boot" pref (used for testing)
+ public static final String DATA_DISABLED_ON_BOOT_KEY = "disabled_on_boot_key";
+ // Key used to read/write current ciphering state
+ public static final String CIPHERING_KEY = "ciphering_key";
+ // Key used to read/write current CLIR setting
+ public static final String CLIR_KEY = "clir_key";
+
+
+ //***** Instance Variables
+
+ CallTracker mCT;
+ ServiceStateTracker mSST;
+ CommandsInterface mCM;
+ SMSDispatcher mSMS;
+ DataConnectionTracker mDataConnection;
+ SIMFileHandler mSIMFileHandler;
+ SIMRecords mSIMRecords;
+ GsmSimCard mSimCard;
+ Service mStkService;
+ MyHandler h;
+ ArrayList <GsmMmiCode> mPendingMMIs = new ArrayList<GsmMmiCode>();
+ SimPhoneBookInterfaceManager mSimPhoneBookIntManager;
+ SimSmsInterfaceManager mSimSmsIntManager;
+ PhoneSubInfo mSubInfo;
+
+ Registrant mPostDialHandler;
+
+ /** List of Registrants to receive Supplementary Service Notifications. */
+ RegistrantList mSsnRegistrants = new RegistrantList();
+
+ Thread debugPortThread;
+ ServerSocket debugSocket;
+
+ private int mReportedRadioResets;
+ private int mReportedAttemptedConnects;
+ private int mReportedSuccessfulConnects;
+
+ private String mImei;
+ private String mImeiSv;
+
+ //***** Event Constants
+
+ static final int EVENT_RADIO_AVAILABLE = 1;
+ /** Supplemnetary Service Notification received. */
+ static final int EVENT_SSN = 2;
+ static final int EVENT_SIM_RECORDS_LOADED = 3;
+ static final int EVENT_MMI_DONE = 4;
+ static final int EVENT_RADIO_ON = 5;
+ static final int EVENT_GET_BASEBAND_VERSION_DONE = 6;
+ static final int EVENT_USSD = 7;
+ static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 8;
+ static final int EVENT_GET_IMEI_DONE = 9;
+ static final int EVENT_GET_IMEISV_DONE = 10;
+ static final int EVENT_GET_SIM_STATUS_DONE = 11;
+ static final int EVENT_SET_CALL_FORWARD_DONE = 12;
+ static final int EVENT_GET_CALL_FORWARD_DONE = 13;
+ static final int EVENT_CALL_RING = 14;
+ // Used to intercept the carriere selection calls so that
+ // we can save the values.
+ static final int EVENT_SET_NETWORK_MANUAL_COMPLETE = 15;
+ static final int EVENT_SET_NETWORK_AUTOMATIC_COMPLETE = 16;
+ static final int EVENT_SET_CLIR_COMPLETE = 17;
+ static final int EVENT_REGISTERED_TO_NETWORK = 18;
+
+ //***** Constructors
+
+ public
+ GSMPhone (Context context, CommandsInterface ci, PhoneNotifier notifier)
+ {
+ this(context,ci,notifier, false);
+ }
+
+ public
+ GSMPhone (Context context, CommandsInterface ci, PhoneNotifier notifier, boolean unitTestMode)
+ {
+ super(notifier, context, unitTestMode);
+
+ h = new MyHandler();
+ mCM = ci;
+
+ if (ci instanceof SimulatedRadioControl) {
+ mSimulatedRadioControl = (SimulatedRadioControl) ci;
+ }
+
+ mCT = new CallTracker(this);
+ mSST = new ServiceStateTracker (this);
+ mSMS = new SMSDispatcher(this);
+ mSIMFileHandler = new SIMFileHandler(this);
+ mSIMRecords = new SIMRecords(this);
+ mDataConnection = new DataConnectionTracker (this);
+ mSimCard = new GsmSimCard(this);
+ if (!unitTestMode) {
+ mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this);
+ mSimSmsIntManager = new SimSmsInterfaceManager(this);
+ mSubInfo = new PhoneSubInfo(this);
+ }
+ mStkService = Service.getInstance(mCM, mSIMRecords, mContext,
+ mSIMFileHandler, mSimCard);
+
+ mCM.registerForAvailable(h, EVENT_RADIO_AVAILABLE, null);
+ mSIMRecords.registerForRecordsLoaded(h, EVENT_SIM_RECORDS_LOADED, null);
+ mCM.registerForOffOrNotAvailable(h, EVENT_RADIO_OFF_OR_NOT_AVAILABLE,
+ null);
+ mCM.registerForOn(h, EVENT_RADIO_ON, null);
+ mCM.setOnUSSD(h, EVENT_USSD, null);
+ mCM.setOnSuppServiceNotification(h, EVENT_SSN, null);
+ mCM.setOnCallRing(h, EVENT_CALL_RING, null);
+ mSST.registerForNetworkAttach(h, EVENT_REGISTERED_TO_NETWORK, null);
+
+ if (false) {
+ try {
+ //debugSocket = new LocalServerSocket("com.android.internal.telephony.debug");
+ debugSocket = new ServerSocket();
+ debugSocket.setReuseAddress(true);
+ debugSocket.bind (new InetSocketAddress("127.0.0.1", 6666));
+
+ debugPortThread
+ = new Thread(
+ new Runnable() {
+ public void run() {
+ for(;;) {
+ try {
+ Socket sock;
+ sock = debugSocket.accept();
+ Log.i(LOG_TAG, "New connection; resetting radio");
+ mCM.resetRadio(null);
+ sock.close();
+ } catch (IOException ex) {
+ Log.w(LOG_TAG,
+ "Exception accepting socket", ex);
+ }
+ }
+ }
+ },
+ "GSMPhone debug");
+
+ debugPortThread.start();
+
+ } catch (IOException ex) {
+ Log.w(LOG_TAG, "Failure to open com.android.internal.telephony.debug socket", ex);
+ }
+ }
+ }
+
+ //***** Overridden from Phone
+
+ public ServiceState
+ getServiceState()
+ {
+ return mSST.ss;
+ }
+
+ public CellLocation getCellLocation() {
+ return mSST.cellLoc;
+ }
+
+ public Phone.State
+ getState()
+ {
+ return mCT.state;
+ }
+
+ public String
+ getPhoneName()
+ {
+ return "GSM";
+ }
+
+ public String[] getActiveApnTypes() {
+ return mDataConnection.getActiveApnTypes();
+ }
+
+ public String getActiveApn() {
+ return mDataConnection.getActiveApnString();
+ }
+
+ public int
+ getSignalStrengthASU()
+ {
+ return mSST.rssi == 99 ? -1 : mSST.rssi;
+ }
+
+ public boolean
+ getMessageWaitingIndicator()
+ {
+ return mSIMRecords.getVoiceMessageWaiting();
+ }
+
+ public boolean
+ getCallForwardingIndicator() {
+ return mSIMRecords.getVoiceCallForwardingFlag();
+ }
+
+ public List<? extends MmiCode>
+ getPendingMmiCodes()
+ {
+ return mPendingMMIs;
+ }
+
+ public DataState getDataConnectionState() {
+ DataState ret = DataState.DISCONNECTED;
+
+ if ((SystemProperties.get("adb.connected", "").length() > 0)
+ && (SystemProperties.get("android.net.use-adb-networking", "")
+ .length() > 0)) {
+ // We're connected to an ADB host and we have USB networking
+ // turned on. No matter what the radio state is,
+ // we report data connected
+
+ ret = DataState.CONNECTED;
+ } else if (mSST.getCurrentGprsState()
+ != ServiceState.STATE_IN_SERVICE) {
+ // If we're out of service, open TCP sockets may still work
+ // but no data will flow
+ ret = DataState.DISCONNECTED;
+ } else { /* mSST.gprsState == ServiceState.STATE_IN_SERVICE */
+ switch (mDataConnection.state) {
+ case FAILED:
+ case IDLE:
+ ret = DataState.DISCONNECTED;
+ break;
+
+ case CONNECTED:
+ if ( mCT.state != Phone.State.IDLE
+ && !mSST.isConcurrentVoiceAndData())
+ ret = DataState.SUSPENDED;
+ else
+ ret = DataState.CONNECTED;
+ break;
+
+ case INITING:
+ case CONNECTING:
+ case SCANNING:
+ ret = DataState.CONNECTING;
+ break;
+ }
+ }
+
+ return ret;
+ }
+
+ public DataActivityState getDataActivityState() {
+ DataActivityState ret = DataActivityState.NONE;
+
+ if (mSST.getCurrentGprsState() == ServiceState.STATE_IN_SERVICE) {
+ switch (mDataConnection.activity) {
+
+ case DATAIN:
+ ret = DataActivityState.DATAIN;
+ break;
+
+ case DATAOUT:
+ ret = DataActivityState.DATAOUT;
+ break;
+
+ case DATAINANDOUT:
+ ret = DataActivityState.DATAINANDOUT;
+ break;
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * Notify any interested party of a Phone state change.
+ */
+ /*package*/ void notifyPhoneStateChanged() {
+ mNotifier.notifyPhoneState(this);
+ }
+
+ /**
+ * Notifies registrants (ie, activities in the Phone app) about
+ * changes to call state (including Phone and Connection changes).
+ */
+ /*package*/ void
+ notifyCallStateChanged()
+ {
+ /* we'd love it if this was package-scoped*/
+ super.notifyCallStateChangedP();
+ }
+
+ /*package*/ void
+ notifyNewRingingConnection(Connection c)
+ {
+ /* we'd love it if this was package-scoped*/
+ super.notifyNewRingingConnectionP(c);
+ }
+
+ /**
+ * Notifiy registrants of a RING event.
+ */
+ void notifyIncomingRing() {
+ AsyncResult ar = new AsyncResult(null, this, null);
+ mIncomingRingRegistrants.notifyRegistrants(ar);
+ }
+
+ /*package*/ void
+ notifyDisconnect(Connection cn)
+ {
+ mDisconnectRegistrants.notifyResult(cn);
+ }
+
+ void notifyUnknownConnection() {
+ mUnknownConnectionRegistrants.notifyResult(this);
+ }
+
+ void notifySuppServiceFailed(SuppService code) {
+ mSuppServiceFailedRegistrants.notifyResult(code);
+ }
+
+ /*package*/ void
+ notifyServiceStateChanged(ServiceState ss)
+ {
+ super.notifyServiceStateChangedP(ss);
+ }
+
+ /*package*/
+ void notifyLocationChanged() {
+ mNotifier.notifyCellLocation(this);
+ }
+
+ /*package*/ void
+ notifySignalStrength()
+ {
+ mNotifier.notifySignalStrength(this);
+ }
+
+ /*package*/ void
+ notifyDataConnection(String reason) {
+ mNotifier.notifyDataConnection(this, reason);
+ }
+
+ /*package*/ void
+ notifyDataConnectionFailed(String reason) {
+ mNotifier.notifyDataConnectionFailed(this, reason);
+ }
+
+ /*package*/ void
+ notifyDataActivity() {
+ mNotifier.notifyDataActivity(this);
+ }
+
+ /*package*/ void
+ updateMessageWaitingIndicator(boolean mwi)
+ {
+ // this also calls notifyMessageWaitingIndicator()
+ mSIMRecords.setVoiceMessageWaiting(1, mwi ? -1 : 0);
+ }
+
+ /*package*/ void
+ notifyMessageWaitingIndicator()
+ {
+ mNotifier.notifyMessageWaitingChanged(this);
+ }
+
+ /*package*/ void
+ notifyCallForwardingIndicator() {
+ mNotifier.notifyCallForwardingChanged(this);
+ }
+
+ /**
+ * Set a system property, unless we're in unit test mode
+ */
+
+ /*package*/ void
+ setSystemProperty(String property, String value)
+ {
+ if(getUnitTestMode()) {
+ return;
+ }
+ SystemProperties.set(property, value);
+ }
+
+ public void registerForSuppServiceNotification(
+ Handler h, int what, Object obj) {
+ mSsnRegistrants.addUnique(h, what, obj);
+ if (mSsnRegistrants.size() == 1) mCM.setSuppServiceNotifications(true, null);
+ }
+
+ public void unregisterForSuppServiceNotification(Handler h) {
+ mSsnRegistrants.remove(h);
+ if (mSsnRegistrants.size() == 0) mCM.setSuppServiceNotifications(false, null);
+ }
+
+ public void
+ acceptCall() throws CallStateException
+ {
+ mCT.acceptCall();
+ }
+
+ public void
+ rejectCall() throws CallStateException
+ {
+ mCT.rejectCall();
+ }
+
+ public void
+ switchHoldingAndActive() throws CallStateException
+ {
+ mCT.switchWaitingOrHoldingAndActive();
+ }
+
+
+ public boolean canConference()
+ {
+ return mCT.canConference();
+ }
+
+ public boolean canDial()
+ {
+ return mCT.canDial();
+ }
+
+ public void conference() throws CallStateException
+ {
+ mCT.conference();
+ }
+
+ public void clearDisconnected()
+ {
+
+ mCT.clearDisconnected();
+ }
+
+ public boolean canTransfer()
+ {
+ return mCT.canTransfer();
+ }
+
+ public void explicitCallTransfer() throws CallStateException
+ {
+ mCT.explicitCallTransfer();
+ }
+
+ public Call
+ getForegroundCall()
+ {
+ return mCT.foregroundCall;
+ }
+
+ public Call
+ getBackgroundCall()
+ {
+ return mCT.backgroundCall;
+ }
+
+ public Call
+ getRingingCall()
+ {
+ return mCT.ringingCall;
+ }
+
+ private boolean handleCallDeflectionIncallSupplementaryService(
+ String dialString) throws CallStateException {
+ if (dialString.length() > 1) {
+ return false;
+ }
+
+ if (getRingingCall().getState() != Call.State.IDLE) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 0: rejectCall");
+ try {
+ mCT.rejectCall();
+ } catch (CallStateException e) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "reject failed", e);
+ notifySuppServiceFailed(Phone.SuppService.REJECT);
+ }
+ } else if (getBackgroundCall().getState() != Call.State.IDLE) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "MmiCode 0: hangupWaitingOrBackground");
+ mCT.hangupWaitingOrBackground();
+ }
+
+ return true;
+ }
+
+ private boolean handleCallWaitingIncallSupplementaryService(
+ String dialString) throws CallStateException {
+ int len = dialString.length();
+
+ if (len > 2) {
+ return false;
+ }
+
+ GSMCall call = (GSMCall) getForegroundCall();
+
+ try {
+ if (len > 1) {
+ char ch = dialString.charAt(1);
+ int callIndex = ch - '0';
+
+ if (callIndex >= 1 && callIndex <= CallTracker.MAX_CONNECTIONS) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "MmiCode 1: hangupConnectionByIndex " +
+ callIndex);
+ mCT.hangupConnectionByIndex(call, callIndex);
+ }
+ } else {
+ if (call.getState() != Call.State.IDLE) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "MmiCode 1: hangup foreground");
+ //mCT.hangupForegroundResumeBackground();
+ mCT.hangup(call);
+ } else {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "MmiCode 1: switchWaitingOrHoldingAndActive");
+ mCT.switchWaitingOrHoldingAndActive();
+ }
+ }
+ } catch (CallStateException e) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "hangup failed", e);
+ notifySuppServiceFailed(Phone.SuppService.HANGUP);
+ }
+
+ return true;
+ }
+
+ private boolean handleCallHoldIncallSupplementaryService(String dialString)
+ throws CallStateException {
+ int len = dialString.length();
+
+ if (len > 2) {
+ return false;
+ }
+
+ GSMCall call = (GSMCall) getForegroundCall();
+
+ if (len > 1) {
+ try {
+ char ch = dialString.charAt(1);
+ int callIndex = ch - '0';
+ GSMConnection conn = mCT.getConnectionByIndex(call, callIndex);
+
+ // gsm index starts at 1, up to 5 connections in a call,
+ if (conn != null && callIndex >= 1 && callIndex <= CallTracker.MAX_CONNECTIONS) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 2: separate call "+
+ callIndex);
+ mCT.separate(conn);
+ } else {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG, "separate: invalid call index "+
+ callIndex);
+ notifySuppServiceFailed(Phone.SuppService.SEPARATE);
+ }
+ } catch (CallStateException e) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "separate failed", e);
+ notifySuppServiceFailed(Phone.SuppService.SEPARATE);
+ }
+ } else {
+ try {
+ if (getRingingCall().getState() != Call.State.IDLE) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "MmiCode 2: accept ringing call");
+ mCT.acceptCall();
+ } else {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "MmiCode 2: switchWaitingOrHoldingAndActive");
+ mCT.switchWaitingOrHoldingAndActive();
+ }
+ } catch (CallStateException e) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "switch failed", e);
+ notifySuppServiceFailed(Phone.SuppService.SWITCH);
+ }
+ }
+
+ return true;
+ }
+
+ private boolean handleMultipartyIncallSupplementaryService(
+ String dialString) throws CallStateException {
+ if (dialString.length() > 1) {
+ return false;
+ }
+
+ if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 3: merge calls");
+ try {
+ conference();
+ } catch (CallStateException e) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "conference failed", e);
+ notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
+ }
+ return true;
+ }
+
+ private boolean handleEctIncallSupplementaryService(String dialString)
+ throws CallStateException {
+
+ int len = dialString.length();
+
+ if (len != 1) {
+ return false;
+ }
+
+ if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 4: explicit call transfer");
+ try {
+ explicitCallTransfer();
+ } catch (CallStateException e) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "transfer failed", e);
+ notifySuppServiceFailed(Phone.SuppService.TRANSFER);
+ }
+ return true;
+ }
+
+ private boolean handleCcbsIncallSupplementaryService(String dialString)
+ throws CallStateException {
+ if (dialString.length() > 1) {
+ return false;
+ }
+
+ Log.i(LOG_TAG, "MmiCode 5: CCBS not supported!");
+ // Treat it as an "unknown" service.
+ notifySuppServiceFailed(Phone.SuppService.UNKNOWN);
+ return true;
+ }
+
+ public boolean handleInCallMmiCommands(String dialString)
+ throws CallStateException {
+ if (!isInCall()) {
+ return false;
+ }
+
+ if (TextUtils.isEmpty(dialString)) {
+ return false;
+ }
+
+ boolean result = false;
+ char ch = dialString.charAt(0);
+ switch (ch) {
+ case '0':
+ result = handleCallDeflectionIncallSupplementaryService(
+ dialString);
+ break;
+ case '1':
+ result = handleCallWaitingIncallSupplementaryService(
+ dialString);
+ break;
+ case '2':
+ result = handleCallHoldIncallSupplementaryService(dialString);
+ break;
+ case '3':
+ result = handleMultipartyIncallSupplementaryService(dialString);
+ break;
+ case '4':
+ result = handleEctIncallSupplementaryService(dialString);
+ break;
+ case '5':
+ result = handleCcbsIncallSupplementaryService(dialString);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ boolean isInCall() {
+ Call.State foregroundCallState = getForegroundCall().getState();
+ Call.State backgroundCallState = getBackgroundCall().getState();
+ Call.State ringingCallState = getRingingCall().getState();
+
+ return (foregroundCallState.isAlive() ||
+ backgroundCallState.isAlive() ||
+ ringingCallState.isAlive());
+ }
+
+ public Connection
+ dial (String dialString) throws CallStateException {
+ // Need to make sure dialString gets parsed properly
+ String newDialString = PhoneNumberUtils.stripSeparators(dialString);
+
+ // handle in-call MMI first if applicable
+ if (handleInCallMmiCommands(newDialString)) {
+ return null;
+ }
+
+ GsmMmiCode mmi = GsmMmiCode.newFromDialString(newDialString, this);
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "dialing w/ mmi '" + mmi + "'...");
+
+ if (mmi == null) {
+ return mCT.dial(newDialString);
+ } else if (mmi.isTemporaryModeCLIR()) {
+ return mCT.dial(mmi.dialingNumber, mmi.getCLIRMode());
+ } else {
+ mPendingMMIs.add(mmi);
+ mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
+ mmi.processCode();
+
+ // FIXME should this return null or something else?
+ return null;
+ }
+ }
+
+ public boolean handlePinMmi(String dialString) {
+ GsmMmiCode mmi = GsmMmiCode.newFromDialString(dialString, this);
+
+ if (mmi != null && mmi.isPinCommand()) {
+ mPendingMMIs.add(mmi);
+ mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
+ mmi.processCode();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void sendUssdResponse(String ussdMessge) {
+ GsmMmiCode mmi = GsmMmiCode.newFromUssdUserInput(ussdMessge, this);
+ mPendingMMIs.add(mmi);
+ mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
+ mmi.sendUssd(ussdMessge);
+ }
+
+ public void
+ sendDtmf(char c) {
+ if (!PhoneNumberUtils.is12Key(c)) {
+ Log.e(LOG_TAG,
+ "sendDtmf called with invalid character '" + c + "'");
+ } else {
+ if (mCT.state == Phone.State.OFFHOOK) {
+ mCM.sendDtmf(c, null);
+ }
+ }
+ }
+
+ public void
+ startDtmf(char c) {
+ if (!PhoneNumberUtils.is12Key(c)) {
+ Log.e(LOG_TAG,
+ "startDtmf called with invalid character '" + c + "'");
+ } else {
+ mCM.startDtmf(c, null);
+ }
+ }
+
+ public void
+ stopDtmf() {
+ mCM.stopDtmf(null);
+ }
+
+ public void
+ setRadioPower(boolean power) {
+ mSST.setRadioPower(power);
+ }
+
+
+ public String getVoiceMailNumber() {
+ return mSIMRecords.getVoiceMailNumber();
+ }
+
+ public String getVoiceMailAlphaTag() {
+ String ret;
+
+ ret = mSIMRecords.getVoiceMailAlphaTag();
+
+ if (ret == null || ret.length() == 0) {
+ return mContext.getText(
+ com.android.internal.R.string.defaultVoiceMailAlphaTag).toString();
+ }
+
+ return ret;
+ }
+
+ public String getDeviceId() {
+ return mImei;
+ }
+
+ public String getDeviceSvn() {
+ return mImeiSv;
+ }
+
+ public String getSubscriberId() {
+ return mSIMRecords.imsi;
+ }
+
+ public String getSimSerialNumber() {
+ return mSIMRecords.iccid;
+ }
+
+ public String getLine1Number() {
+ return mSIMRecords.getMsisdnNumber();
+ }
+
+ public String getLine1AlphaTag() {
+ String ret;
+
+ ret = mSIMRecords.getMsisdnAlphaTag();
+
+ if (ret == null || ret.length() == 0) {
+ return mContext.getText(
+ com.android.internal.R.string.defaultMsisdnAlphaTag).toString();
+ }
+
+ return ret;
+ }
+
+ public void setLine1Number(String alphaTag, String number, Message onComplete) {
+ mSIMRecords.setMsisdnNumber(alphaTag, number, onComplete);
+ }
+
+ public void setVoiceMailNumber(String alphaTag,
+ String voiceMailNumber,
+ Message onComplete) {
+ mSIMRecords.setVoiceMailNumber(alphaTag, voiceMailNumber, onComplete);
+ }
+
+ private boolean isValidCommandInterfaceCFReason (int commandInterfaceCFReason) {
+ switch (commandInterfaceCFReason) {
+ case CF_REASON_UNCONDITIONAL:
+ case CF_REASON_BUSY:
+ case CF_REASON_NO_REPLY:
+ case CF_REASON_NOT_REACHABLE:
+ case CF_REASON_ALL:
+ case CF_REASON_ALL_CONDITIONAL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean isValidCommandInterfaceCFAction (int commandInterfaceCFAction) {
+ switch (commandInterfaceCFAction) {
+ case CF_ACTION_DISABLE:
+ case CF_ACTION_ENABLE:
+ case CF_ACTION_REGISTRATION:
+ case CF_ACTION_ERASURE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean isCfEnable(int action) {
+ return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION);
+ }
+
+ public void getCallForwardingOption(int commandInterfaceCFReason,
+ Message onComplete) {
+
+ if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG, "requesting call forwarding query.");
+ Message resp;
+ if (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL) {
+ resp = h.obtainMessage(EVENT_GET_CALL_FORWARD_DONE, onComplete);
+ } else {
+ resp = onComplete;
+ }
+ mCM.queryCallForwardStatus(commandInterfaceCFReason,0,null,resp);
+ }
+ }
+
+ public void setCallForwardingOption(int commandInterfaceCFAction,
+ int commandInterfaceCFReason,
+ String dialingNumber,
+ int timerSeconds,
+ Message onComplete) {
+
+ if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
+ (isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
+
+ Message resp;
+ if (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL) {
+ resp = h.obtainMessage(EVENT_SET_CALL_FORWARD_DONE,
+ isCfEnable(commandInterfaceCFAction) ? 1 : 0, 0, onComplete);
+ } else {
+ resp = onComplete;
+ }
+ mCM.setCallForward(commandInterfaceCFAction,
+ commandInterfaceCFReason,
+ CommandsInterface.SERVICE_CLASS_VOICE,
+ dialingNumber,
+ timerSeconds,
+ resp);
+ }
+ }
+
+ public void getOutgoingCallerIdDisplay(Message onComplete) {
+ mCM.getCLIR(onComplete);
+ }
+
+ public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
+ Message onComplete) {
+ mCM.setCLIR(commandInterfaceCLIRMode,
+ h.obtainMessage(EVENT_SET_CLIR_COMPLETE, commandInterfaceCLIRMode, 0, onComplete));
+ }
+
+ public void getCallWaiting(Message onComplete) {
+ mCM.queryCallWaiting(CommandsInterface.SERVICE_CLASS_VOICE, onComplete);
+ }
+
+ public void setCallWaiting(boolean enable, Message onComplete) {
+ mCM.setCallWaiting(enable, CommandsInterface.SERVICE_CLASS_VOICE, onComplete);
+ }
+
+ public boolean
+ getSimRecordsLoaded() {
+ return mSIMRecords.getRecordsLoaded();
+ }
+
+ public SimCard
+ getSimCard() {
+ return mSimCard;
+ }
+
+ public void
+ getAvailableNetworks(Message response) {
+ mCM.getAvailableNetworks(response);
+ }
+
+ /**
+ * Small container class used to hold information relevant to
+ * the carrier selection process. operatorNumeric can be ""
+ * if we are looking for automatic selection.
+ */
+ private static class NetworkSelectMessage {
+ public Message message;
+ public String operatorNumeric;
+ }
+
+ public void
+ setNetworkSelectionModeAutomatic(Message response) {
+ // wrap the response message in our own message along with
+ // an empty string (to indicate automatic selection) for the
+ // operator's id.
+ NetworkSelectMessage nsm = new NetworkSelectMessage();
+ nsm.message = response;
+ nsm.operatorNumeric = "";
+
+ // get the message
+ Message msg = h.obtainMessage(EVENT_SET_NETWORK_AUTOMATIC_COMPLETE, nsm);
+ if (LOCAL_DEBUG)
+ Log.d(LOG_TAG, "wrapping and sending message to connect automatically");
+
+ mCM.setNetworkSelectionModeAutomatic(msg);
+ }
+
+ public void
+ selectNetworkManually(com.android.internal.telephony.gsm.NetworkInfo network,
+ Message response) {
+ // wrap the response message in our own message along with
+ // the operator's id.
+ NetworkSelectMessage nsm = new NetworkSelectMessage();
+ nsm.message = response;
+ nsm.operatorNumeric = network.operatorNumeric;
+
+ // get the message
+ Message msg = h.obtainMessage(EVENT_SET_NETWORK_MANUAL_COMPLETE, nsm);
+
+ mCM.setNetworkSelectionModeManual(network.operatorNumeric, msg);
+ }
+
+ /**
+ * Method to retrieve the saved operator id from the Shared Preferences
+ */
+ private String getSavedNetworkSelection() {
+ // open the shared preferences and search with our key.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ return sp.getString(NETWORK_SELECTION_KEY, "");
+ }
+
+ /**
+ * Method to restore the previously saved operator id, or reset to
+ * automatic selection, all depending upon the value in the shared
+ * preferences.
+ */
+ void restoreSavedNetworkSelection(Message response) {
+ // retrieve the operator id
+ String networkSelection = getSavedNetworkSelection();
+
+ // set to auto if the id is empty, otherwise select the network.
+ if (TextUtils.isEmpty(networkSelection)) {
+ mCM.setNetworkSelectionModeAutomatic(response);
+ } else {
+ mCM.setNetworkSelectionModeManual(networkSelection, response);
+ }
+ }
+
+ public void
+ setPreferredNetworkType(int networkType, Message response) {
+ mCM.setPreferredNetworkType(networkType, response);
+ }
+
+ public void
+ getPreferredNetworkType(Message response) {
+ mCM.getPreferredNetworkType(response);
+ }
+
+ public void
+ getNeighboringCids(Message response) {
+ mCM.getNeighboringCids(response);
+ }
+
+ public void setOnPostDialCharacter(Handler h, int what, Object obj)
+ {
+ mPostDialHandler = new Registrant(h, what, obj);
+ }
+
+
+ public void setMute(boolean muted)
+ {
+ mCT.setMute(muted);
+ }
+
+ public boolean getMute()
+ {
+ return mCT.getMute();
+ }
+
+
+ public void invokeOemRilRequestRaw(byte[] data, Message response)
+ {
+ mCM.invokeOemRilRequestRaw(data, response);
+ }
+
+ public void invokeOemRilRequestStrings(String[] strings, Message response)
+ {
+ mCM.invokeOemRilRequestStrings(strings, response);
+ }
+
+ public void getPdpContextList(Message response) {
+ mCM.getPDPContextList(response);
+ }
+
+ public List<PdpConnection> getCurrentPdpList () {
+ return mDataConnection.getAllPdps();
+ }
+
+ public void updateServiceLocation(Message response) {
+ mSST.getLacAndCid(response);
+ }
+
+ public void enableLocationUpdates() {
+ mSST.enableLocationUpdates();
+ }
+
+ public void disableLocationUpdates() {
+ mSST.disableLocationUpdates();
+ }
+
+ public void setBandMode(int bandMode, Message response) {
+ mCM.setBandMode(bandMode, response);
+ }
+
+ public void queryAvailableBandMode(Message response) {
+ mCM.queryAvailableBandMode(response);
+ }
+
+ public boolean getDataRoamingEnabled() {
+ return mDataConnection.getDataOnRoamingEnabled();
+ }
+
+ public void setDataRoamingEnabled(boolean enable) {
+ mDataConnection.setDataOnRoamingEnabled(enable);
+ }
+
+ public boolean enableDataConnectivity() {
+ return mDataConnection.setDataEnabled(true);
+ }
+
+ public int enableApnType(String type) {
+ return mDataConnection.enableApnType(type);
+ }
+
+ public int disableApnType(String type) {
+ return mDataConnection.disableApnType(type);
+ }
+
+ public boolean disableDataConnectivity() {
+ return mDataConnection.setDataEnabled(false);
+ }
+
+ public String getInterfaceName(String apnType) {
+ return mDataConnection.getInterfaceName(apnType);
+ }
+
+ public String getIpAddress(String apnType) {
+ return mDataConnection.getIpAddress(apnType);
+ }
+
+ public String getGateway(String apnType) {
+ return mDataConnection.getGateway(apnType);
+ }
+
+ public String[] getDnsServers(String apnType) {
+ return mDataConnection.getDnsServers(apnType);
+ }
+
+ /**
+ * The only circumstances under which we report that data connectivity is not
+ * possible are
+ * <ul>
+ * <li>Data roaming is disallowed and we are roaming.</li>
+ * <li>The current data state is {@code DISCONNECTED} for a reason other than
+ * having explicitly disabled connectivity. In other words, data is not available
+ * because the phone is out of coverage or some like reason.</li>
+ * </ul>
+ * @return {@code true} if data connectivity is possible, {@code false} otherwise.
+ */
+ public boolean isDataConnectivityPossible() {
+ // TODO: Currently checks if any GPRS connection is active. Should it only
+ // check for "default"?
+ boolean noData = mDataConnection.getDataEnabled() &&
+ getDataConnectionState() == DataState.DISCONNECTED;
+ return !noData && getSimCard().getState() == SimCard.State.READY &&
+ getServiceState().getState() == ServiceState.STATE_IN_SERVICE &&
+ (mDataConnection.getDataOnRoamingEnabled() || !getServiceState().getRoaming());
+ }
+
+ /**
+ * Removes the given MMI from the pending list and notifies
+ * registrants that it is complete.
+ * @param mmi MMI that is done
+ */
+ /*package*/ void
+ onMMIDone(GsmMmiCode mmi)
+ {
+ /* Only notify complete if it's on the pending list.
+ * Otherwise, it's already been handled (eg, previously canceled).
+ * The exception is cancellation of an incoming USSD-REQUEST, which is
+ * not on the list.
+ */
+ if (mPendingMMIs.remove(mmi) || mmi.isUssdRequest()) {
+ mMmiCompleteRegistrants.notifyRegistrants(
+ new AsyncResult(null, mmi, null));
+ }
+ }
+
+
+ private void
+ onNetworkInitiatedUssd(GsmMmiCode mmi)
+ {
+ mMmiCompleteRegistrants.notifyRegistrants(
+ new AsyncResult(null, mmi, null));
+ }
+
+
+ /** ussdMode is one of CommandsInterface.USSD_MODE_* */
+ private void
+ onIncomingUSSD (int ussdMode, String ussdMessage)
+ {
+ boolean isUssdError;
+ boolean isUssdRequest;
+
+ isUssdRequest
+ = (ussdMode == CommandsInterface.USSD_MODE_REQUEST);
+
+ isUssdError
+ = (ussdMode != CommandsInterface.USSD_MODE_NOTIFY
+ && ussdMode != CommandsInterface.USSD_MODE_REQUEST);
+
+ // See comments in GsmMmiCode.java
+ // USSD requests aren't finished until one
+ // of these two events happen
+ GsmMmiCode found = null;
+ for (int i = 0, s = mPendingMMIs.size() ; i < s; i++) {
+ if(mPendingMMIs.get(i).isPendingUSSD()) {
+ found = mPendingMMIs.get(i);
+ break;
+ }
+ }
+
+ if (found != null) {
+ // Complete pending USSD
+
+ if (isUssdError) {
+ found.onUssdFinishedError();
+ } else {
+ found.onUssdFinished(ussdMessage, isUssdRequest);
+ }
+ } else { // pending USSD not found
+ // The network may initiate its own USSD request
+
+ // ignore everything that isnt a Notify or a Request
+ // also, discard if there is no message to present
+ if (!isUssdError && ussdMessage != null) {
+ GsmMmiCode mmi;
+ mmi = GsmMmiCode.newNetworkInitiatedUssd(ussdMessage,
+ isUssdRequest,
+ GSMPhone.this);
+ onNetworkInitiatedUssd(mmi);
+ }
+ }
+ }
+
+ /**
+ * Make sure the network knows our preferred setting.
+ */
+ private void syncClirSetting() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ int clirSetting = sp.getInt(CLIR_KEY, -1);
+ if (clirSetting >= 0) {
+ mCM.setCLIR(clirSetting, null);
+ }
+ }
+
+ //***** Inner Classes
+
+ class MyHandler extends Handler
+ {
+ MyHandler()
+ {
+ }
+
+ MyHandler(Looper l)
+ {
+ super(l);
+ }
+
+ public void
+ handleMessage (Message msg)
+ {
+ AsyncResult ar;
+ Message onComplete;
+
+ switch (msg.what) {
+ case EVENT_RADIO_AVAILABLE: {
+ mCM.getBasebandVersion(
+ obtainMessage(EVENT_GET_BASEBAND_VERSION_DONE));
+
+ mCM.getIMEI(obtainMessage(EVENT_GET_IMEI_DONE));
+ mCM.getIMEISV(obtainMessage(EVENT_GET_IMEISV_DONE));
+ }
+ break;
+
+ case EVENT_RADIO_ON:
+ break;
+
+ case EVENT_REGISTERED_TO_NETWORK:
+ syncClirSetting();
+ break;
+
+ case EVENT_SIM_RECORDS_LOADED:
+ mSIMRecords.getSIMOperatorNumeric();
+
+ try {
+ //set the current field the telephony provider according to
+ //the SIM's operator
+ Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
+ ContentValues map = new ContentValues();
+ map.put(Telephony.Carriers.NUMERIC, mSIMRecords.getSIMOperatorNumeric());
+ mContext.getContentResolver().insert(uri, map);
+ } catch (SQLException e) {
+ Log.e(LOG_TAG, "Can't store current operator", e);
+ }
+
+ break;
+
+ case EVENT_GET_BASEBAND_VERSION_DONE:
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ if (LOCAL_DEBUG) Log.d(LOG_TAG, "Baseband version: " + ar.result);
+ setSystemProperty(PROPERTY_BASEBAND_VERSION, (String)ar.result);
+ break;
+
+ case EVENT_GET_IMEI_DONE:
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ mImei = (String)ar.result;
+ break;
+
+ case EVENT_GET_IMEISV_DONE:
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ mImeiSv = (String)ar.result;
+ break;
+
+
+ case EVENT_USSD:
+ ar = (AsyncResult)msg.obj;
+
+ String[] ussdResult = (String[]) ar.result;
+
+ if (ussdResult.length > 1) {
+ try {
+ onIncomingUSSD(Integer.parseInt(ussdResult[0]), ussdResult[1]);
+ } catch (NumberFormatException e) {
+ Log.w(LOG_TAG, "error parsing USSD");
+ }
+ }
+ break;
+
+ case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
+ // Some MMI requests (eg USSD) are not completed
+ // within the course of a CommandsInterface request
+ // If the radio shuts off or resets while one of these
+ // is pending, we need to clean up.
+
+ for (int i = 0, s = mPendingMMIs.size() ; i < s; i++) {
+ if (mPendingMMIs.get(i).isPendingUSSD()) {
+ mPendingMMIs.get(i).onUssdFinishedError();
+ }
+ }
+ break;
+
+ case EVENT_SSN:
+ ar = (AsyncResult)msg.obj;
+ SuppServiceNotification not = (SuppServiceNotification) ar.result;
+ mSsnRegistrants.notifyRegistrants(ar);
+ break;
+
+ case EVENT_SET_CALL_FORWARD_DONE:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ mSIMRecords.setVoiceCallForwardingFlag(1, msg.arg1 == 1);
+ }
+ onComplete = (Message) ar.userObj;
+ if (onComplete != null) {
+ AsyncResult.forMessage(onComplete, ar.result, ar.exception);
+ onComplete.sendToTarget();
+ }
+ break;
+
+ case EVENT_GET_CALL_FORWARD_DONE:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ handleCfuQueryResult((CallForwardInfo[])ar.result);
+ }
+ onComplete = (Message) ar.userObj;
+ if (onComplete != null) {
+ AsyncResult.forMessage(onComplete, ar.result, ar.exception);
+ onComplete.sendToTarget();
+ }
+ break;
+
+ case EVENT_CALL_RING:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ notifyIncomingRing();
+ }
+ break;
+
+ // handle the select network completion callbacks.
+ case EVENT_SET_NETWORK_MANUAL_COMPLETE:
+ case EVENT_SET_NETWORK_AUTOMATIC_COMPLETE:
+ handleSetSelectNetwork((AsyncResult) msg.obj);
+ break;
+
+ case EVENT_SET_CLIR_COMPLETE:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ saveClirSetting(msg.arg1);
+ }
+ onComplete = (Message) ar.userObj;
+ if (onComplete != null) {
+ AsyncResult.forMessage(onComplete, ar.result, ar.exception);
+ onComplete.sendToTarget();
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Used to track the settings upon completion of the network change.
+ */
+ private void handleSetSelectNetwork(AsyncResult ar) {
+ // look for our wrapper within the asyncresult, skip the rest if it
+ // is null.
+ if (!(ar.userObj instanceof NetworkSelectMessage)) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG, "unexpected result from user object.");
+ return;
+ }
+
+ NetworkSelectMessage nsm = (NetworkSelectMessage) ar.userObj;
+
+ // found the object, now we send off the message we had originally
+ // attached to the request.
+ if (nsm.message != null) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG, "sending original message to recipient");
+ AsyncResult.forMessage(nsm.message, ar.result, ar.exception);
+ nsm.message.sendToTarget();
+ }
+
+ // open the shared preferences editor, and write the value.
+ // nsm.operatorNumeric is "" if we're in automatic.selection.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(NETWORK_SELECTION_KEY, nsm.operatorNumeric);
+
+ // commit and log the result.
+ if (! editor.commit()) {
+ Log.e(LOG_TAG, "failed to commit network selection preference");
+ }
+
+ }
+
+ /**
+ * Saves CLIR setting so that we can re-apply it as necessary
+ * (in case the RIL resets it across reboots).
+ */
+ /* package */ void saveClirSetting(int commandInterfaceCLIRMode) {
+ // open the shared preferences editor, and write the value.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putInt(CLIR_KEY, commandInterfaceCLIRMode);
+
+ // commit and log the result.
+ if (! editor.commit()) {
+ Log.e(LOG_TAG, "failed to commit CLIR preference");
+ }
+
+ }
+
+ private void handleCfuQueryResult(CallForwardInfo[] infos) {
+ if (infos == null || infos.length == 0) {
+ // Assume the default is not active
+ // Set unconditional CFF in SIM to false
+ mSIMRecords.setVoiceCallForwardingFlag(1, false);
+ } else {
+ for (int i = 0, s = infos.length; i < s; i++) {
+ if ((infos[i].serviceClass & SERVICE_CLASS_VOICE) != 0) {
+ mSIMRecords.setVoiceCallForwardingFlag(1, (infos[i].status == 1));
+ // should only have the one
+ break;
+ }
+ }
+ }
+ }
+ /**
+ * simulateDataConnection
+ *
+ * simulates various data connection states. This messes with
+ * DataConnectionTracker's internal states, but doesn't actually change
+ * the underlying radio connection states.
+ *
+ * @param state Phone.DataState enum.
+ */
+ public void simulateDataConnection(Phone.DataState state) {
+ DataConnectionTracker.State dcState;
+
+ switch (state) {
+ case CONNECTED:
+ dcState = DataConnectionTracker.State.CONNECTED;
+ break;
+ case SUSPENDED:
+ dcState = DataConnectionTracker.State.CONNECTED;
+ break;
+ case DISCONNECTED:
+ dcState = DataConnectionTracker.State.FAILED;
+ break;
+ default:
+ dcState = DataConnectionTracker.State.CONNECTING;
+ break;
+ }
+
+ mDataConnection.setState(dcState);
+ notifyDataConnection(null);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/gsm/GsmAlphabet.java
new file mode 100644
index 0000000..7baaeca
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmAlphabet.java
@@ -0,0 +1,813 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.telephony.gsm.SmsMessage;
+import android.util.SparseIntArray;
+
+import android.util.Log;
+
+/**
+ * This class implements the character set mapping between
+ * the GSM SMS 7-bit alphabet specifed in TS 23.038 6.2.1
+ * and UTF-16
+ *
+ * {@hide}
+ */
+public class GsmAlphabet
+{
+ static final String LOG_TAG = "GSM";
+
+
+
+ //***** Constants
+
+ /**
+ * This escapes extended characters, and when present indicates that the
+ * following character should
+ * be looked up in the "extended" table
+ *
+ * gsmToChar(GSM_EXTENDED_ESCAPE) returns 0xffff
+ */
+
+ public static final byte GSM_EXTENDED_ESCAPE = 0x1B;
+
+
+ /**
+ * char to GSM alphabet char
+ * Returns ' ' in GSM alphabet if there's no possible match
+ * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table
+ * In this case, you must call charToGsmExtended() for the value that
+ * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string
+ */
+ public static int
+ charToGsm(char c)
+ {
+ try {
+ return charToGsm(c, false);
+ } catch (EncodeException ex) {
+ // this should never happen
+ return sGsmSpaceChar;
+ }
+ }
+
+ /**
+ * char to GSM alphabet char
+ * @param throwException If true, throws EncodeException on invalid char.
+ * If false, returns GSM alphabet ' ' char.
+ *
+ * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table
+ * In this case, you must call charToGsmExtended() for the value that
+ * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string
+ */
+
+ public static int
+ charToGsm(char c, boolean throwException) throws EncodeException
+ {
+ int ret;
+
+ ret = charToGsm.get(c, -1);
+
+ if (ret == -1) {
+ ret = charToGsmExtended.get(c, -1);
+
+ if (ret == -1) {
+ if (throwException) {
+ throw new EncodeException(c);
+ } else {
+ return sGsmSpaceChar;
+ }
+ } else {
+ return GSM_EXTENDED_ESCAPE;
+ }
+ }
+
+ return ret;
+
+ }
+
+
+ /**
+ * char to extended GSM alphabet char
+ *
+ * Extended chars should be escaped with GSM_EXTENDED_ESCAPE
+ *
+ * Returns ' ' in GSM alphabet if there's no possible match
+ *
+ */
+ public static int
+ charToGsmExtended(char c)
+ {
+ int ret;
+
+ ret = charToGsmExtended.get(c, -1);
+
+ if (ret == -1) {
+ return sGsmSpaceChar;
+ }
+
+ return ret;
+ }
+
+ /**
+ * Converts a character in the GSM alphabet into a char
+ *
+ * if GSM_EXTENDED_ESCAPE is passed, 0xffff is returned. In this case,
+ * the following character in the stream should be decoded with
+ * gsmExtendedToChar()
+ *
+ * If an unmappable value is passed (one greater than 127), ' ' is returned
+ */
+
+ public static char
+ gsmToChar(int gsmChar)
+ {
+ return (char)gsmToChar.get(gsmChar, ' ');
+ }
+
+ /**
+ * Converts a character in the extended GSM alphabet into a char
+ *
+ * if GSM_EXTENDED_ESCAPE is passed, ' ' is returned since no second
+ * extension page has yet been defined (see Note 1 in table 6.2.1.1 of
+ * TS 23.038 v7.00)
+ *
+ * If an unmappable value is passed , ' ' is returned
+ */
+
+ public static char
+ gsmExtendedToChar(int gsmChar)
+ {
+ int ret;
+
+ ret = gsmExtendedToChar.get(gsmChar, -1);
+
+ if (ret == -1) {
+ return ' ';
+ }
+
+ return (char)ret;
+ }
+
+ /**
+ * Converts a String into a byte array containing the 7-bit packed
+ * GSM Alphabet representation of the string. If a header is provided,
+ * this is included in the returned byte array and padded to a septet
+ * boundary.
+ *
+ * Unencodable chars are encoded as spaces
+ *
+ * Byte 0 in the returned byte array is the count of septets used,
+ * including the header and header padding. The returned byte array is
+ * the minimum size required to store the packed septets. The returned
+ * array cannot contain more than 255 septets.
+ *
+ * @param data The text string to encode.
+ * @param header Optional header (includeing length byte) that precedes
+ * the encoded data, padded to septet boundary.
+ * @return Byte array containing header and encoded data.
+ */
+ public static byte[] stringToGsm7BitPackedWithHeader(String data, byte[] header)
+ throws EncodeException {
+
+ if (header == null || header.length == 0) {
+ return stringToGsm7BitPacked(data);
+ }
+
+ int headerBits = header.length * 8;
+ int headerSeptets = headerBits / 7;
+ headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
+
+ int sz = data.length();
+ int septetCount;
+ septetCount = countGsmSeptets(data, true) + headerSeptets;
+
+ byte[] ret = stringToGsm7BitPacked(data, 0, septetCount,
+ (headerSeptets*7), true);
+
+ // Paste in the header
+ System.arraycopy(header, 0, ret, 1, header.length);
+ return ret;
+ }
+
+ /**
+ * Converts a String into a byte array containing
+ * the 7-bit packed GSM Alphabet representation of the string.
+ *
+ * Unencodable chars are encoded as spaces
+ *
+ * Byte 0 in the returned byte array is the count of septets used
+ * The returned byte array is the minimum size required to store
+ * the packed septets. The returned array cannot contain more than 255
+ * septets.
+ *
+ * @param data the data string to endcode
+ * @throws EncodeException if String is too large to encode
+ */
+ public static byte[] stringToGsm7BitPacked(String data)
+ throws EncodeException {
+ return stringToGsm7BitPacked(data, 0, -1, 0, true);
+ }
+
+ /**
+ * Converts a String into a byte array containing
+ * the 7-bit packed GSM Alphabet representation of the string.
+ *
+ * Byte 0 in the returned byte array is the count of septets used
+ * The returned byte array is the minimum size required to store
+ * the packed septets. The returned array cannot contain more than 255
+ * septets.
+ *
+ * @param data the text to convert to septets
+ * @param dataOffset the character offset in data to start the encoding from
+ * @param maxSeptets the maximum number of septets to convert, or -1 for no
+ * enforced maximum.
+ * @param startingBitOffset the number of padding bits to put before
+ * the start of the first septet at the begining of the array
+ * @param throwException If true, throws EncodeException on invalid char.
+ * If false, replaces unencodable char with GSM alphabet space char.
+ *
+ * @throws EncodeException if String is too large to encode
+ */
+ public static byte[] stringToGsm7BitPacked(String data, int dataOffset,
+ int maxSeptets, int startingBitOffset, boolean throwException)
+ throws EncodeException {
+
+ int sz = data.length();
+ int septetCount;
+ if (maxSeptets == -1) {
+ septetCount = countGsmSeptets(data, true);
+ } else {
+ septetCount = maxSeptets;
+ }
+
+ if(septetCount > 0xff) {
+ throw new EncodeException("Payload cannot exceed " + Short.MAX_VALUE
+ + " septets");
+ }
+
+ // Enough for all the septets and the length 2 byte prefix
+ byte[] ret = new byte[1 + (((septetCount * 7) + 7) / 8)];
+
+ int bitOffset = startingBitOffset;
+ int septets = startingBitOffset/7;
+ for (int i = dataOffset; i < sz && septets < septetCount; i++, bitOffset += 7) {
+ char c = data.charAt(i);
+
+ int v = GsmAlphabet.charToGsm(c, throwException);
+ if (v == GSM_EXTENDED_ESCAPE) {
+ // Lookup the extended char
+ v = GsmAlphabet.charToGsmExtended(c);
+
+ packSmsChar(ret, bitOffset, GSM_EXTENDED_ESCAPE);
+ bitOffset += 7;
+ septets++;
+ }
+
+ packSmsChar(ret, bitOffset, v);
+ septets++;
+ }
+
+ // See check for > 0xff above
+ ret[0] = (byte)septets;
+
+ return ret;
+ }
+
+ /**
+ * Pack a 7-bit char into its appropirate place in a byte array
+ *
+ * @param bitOffset the bit offset that the septet should be packed at
+ * (septet index * 7)
+ */
+ private static void
+ packSmsChar(byte[] packedChars, int bitOffset, int value)
+ {
+ int byteOffset = bitOffset / 8;
+ int shift = bitOffset % 8;
+
+ packedChars[++byteOffset] |= value << shift;
+
+ if (shift > 1) {
+ packedChars[++byteOffset] = (byte)(value >> (8 - shift));
+ }
+ }
+
+ /**
+ * Convert a GSM alphabet 7 bit packed string (SMS string) into a
+ * {@link java.lang.String}.
+ *
+ * See TS 23.038 6.1.2.1 for SMS Character Packing
+ *
+ * @param pdu the raw data from the pdu
+ * @param offset the byte offset of
+ * @param lengthSeptets string length in septets, not bytes
+ * @return String representation or null on decoding exception
+ */
+ public static String gsm7BitPackedToString(byte[] pdu, int offset,
+ int lengthSeptets) {
+ return gsm7BitPackedToString(pdu, offset, lengthSeptets, 0);
+ }
+
+ /**
+ * Convert a GSM alphabet 7 bit packed string (SMS string) into a
+ * {@link java.lang.String}.
+ *
+ * See TS 23.038 6.1.2.1 for SMS Character Packing
+ *
+ * @param pdu the raw data from the pdu
+ * @param offset the byte offset of
+ * @param lengthSeptets string length in septets, not bytes
+ * @param numPaddingBits the number of padding bits before the start of the
+ * string in the first byte
+ * @return String representation or null on decoding exception
+ */
+ public static String gsm7BitPackedToString(byte[] pdu, int offset,
+ int lengthSeptets, int numPaddingBits)
+ {
+ StringBuilder ret = new StringBuilder(lengthSeptets);
+ boolean prevCharWasEscape;
+
+ try {
+ prevCharWasEscape = false;
+
+ for (int i = 0 ; i < lengthSeptets ; i++) {
+ int bitOffset = (7 * i) + numPaddingBits;
+
+ int byteOffset = bitOffset / 8;
+ int shift = bitOffset % 8;
+ int gsmVal;
+
+ gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
+
+ // if it crosses a byte boundry
+ if (shift > 1) {
+ // set msb bits to 0
+ gsmVal &= 0x7f >> (shift - 1);
+
+ gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
+ }
+
+ if (prevCharWasEscape) {
+ ret.append(GsmAlphabet.gsmExtendedToChar(gsmVal));
+ prevCharWasEscape = false;
+ } else if (gsmVal == GSM_EXTENDED_ESCAPE) {
+ prevCharWasEscape = true;
+ } else {
+ ret.append(GsmAlphabet.gsmToChar(gsmVal));
+ }
+ }
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "Error GSM 7 bit packed: ", ex);
+ return null;
+ }
+
+ return ret.toString();
+ }
+
+
+ /**
+ * Convert a GSM alphabet string that's stored in 8-bit unpacked
+ * format (as it often appears in SIM records) into a String
+ *
+ * Field may be padded with trailing 0xff's. The decode stops
+ * at the first 0xff encountered.
+ */
+ public static String
+ gsm8BitUnpackedToString(byte[] data, int offset, int length)
+ {
+ boolean prevWasEscape;
+ StringBuilder ret = new StringBuilder(length);
+
+ prevWasEscape = false;
+ for (int i = offset ; i < offset + length ; i++) {
+ // Never underestimate the pain that can be caused
+ // by signed bytes
+ int c = data[i] & 0xff;
+
+ if (c == 0xff) {
+ break;
+ } else if (c == GSM_EXTENDED_ESCAPE) {
+ if (prevWasEscape) {
+ // Two escape chars in a row
+ // We treat this as a space
+ // See Note 1 in table 6.2.1.1 of TS 23.038 v7.00
+ ret.append(' ');
+ prevWasEscape = false;
+ } else {
+ prevWasEscape = true;
+ }
+ } else {
+ if (prevWasEscape) {
+ ret.append((char)gsmExtendedToChar.get(c, ' '));
+ } else {
+ ret.append((char)gsmToChar.get(c, ' '));
+ }
+ prevWasEscape = false;
+ }
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * Convert a string into an 8-bit unpacked GSM alphabet byte
+ * array
+ */
+ public static byte[]
+ stringToGsm8BitPacked(String s)
+ {
+ byte[] ret;
+
+ int septets = 0;
+
+ septets = countGsmSeptets(s);
+
+ // Enough for all the septets and the length byte prefix
+ ret = new byte[septets];
+
+ stringToGsm8BitUnpackedField(s, ret, 0, ret.length);
+
+ return ret;
+ }
+
+
+ /**
+ * Write a String into a GSM 8-bit unpacked field of
+ * @param length size at @param offset in @param dest
+ *
+ * Field is padded with 0xff's, string is truncated if necessary
+ */
+
+ public static void
+ stringToGsm8BitUnpackedField(String s, byte dest[], int offset, int length)
+ {
+ int outByteIndex = offset;
+
+ // Septets are stored in byte-aligned octets
+ for (int i = 0, sz = s.length()
+ ; i < sz && (outByteIndex - offset) < length
+ ; i++
+ ) {
+ char c = s.charAt(i);
+
+ int v = GsmAlphabet.charToGsm(c);
+
+ if (v == GSM_EXTENDED_ESCAPE) {
+ // make sure we can fit an escaped char
+ if (! (outByteIndex + 1 - offset < length)) {
+ break;
+ }
+
+ dest[outByteIndex++] = GSM_EXTENDED_ESCAPE;
+
+ v = GsmAlphabet.charToGsmExtended(c);
+ }
+
+ dest[outByteIndex++] = (byte)v;
+ }
+
+ // pad with 0xff's
+ while((outByteIndex - offset) < length) {
+ dest[outByteIndex++] = (byte)0xff;
+ }
+ }
+
+ /**
+ * Returns the count of 7-bit GSM alphabet characters
+ * needed to represent this character. Counts unencodable char as 1 septet.
+ */
+ public static int
+ countGsmSeptets(char c)
+ {
+ try {
+ return countGsmSeptets(c, true);
+ } catch (EncodeException ex) {
+ // This should never happen.
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the count of 7-bit GSM alphabet characters
+ * needed to represent this character
+ * @param throwsException If true, throws EncodeException if unencodable
+ * char. Otherwise, counts invalid char as 1 septet
+ */
+ public static int
+ countGsmSeptets(char c, boolean throwsException) throws EncodeException
+ {
+ if (charToGsm.get(c, -1) != -1) {
+ return 1;
+ }
+
+ if (charToGsmExtended.get(c, -1) != -1) {
+ return 2;
+ }
+
+ if (throwsException) {
+ throw new EncodeException(c);
+ } else {
+ // count as a space char
+ return 1;
+ }
+ }
+
+ /**
+ * Returns the count of 7-bit GSM alphabet characters
+ * needed to represent this string. Counts unencodable char as 1 septet.
+ */
+ public static int
+ countGsmSeptets(String s)
+ {
+ try {
+ return countGsmSeptets(s, true);
+ } catch (EncodeException ex) {
+ // this should never happen
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the count of 7-bit GSM alphabet characters
+ * needed to represent this string.
+ * @param throwsException If true, throws EncodeException if unencodable
+ * char. Otherwise, counts invalid char as 1 septet
+ */
+ public static int
+ countGsmSeptets(String s, boolean throwsException) throws EncodeException
+ {
+ int charIndex = 0;
+ int sz = s.length();
+ int count = 0;
+
+ while (charIndex < sz) {
+ count += countGsmSeptets(s.charAt(charIndex), throwsException);
+ charIndex++;
+ }
+
+ return count;
+ }
+
+ /**
+ * Returns the index into <code>s</code> of the first character
+ * after <code>limit</code> septets have been reached, starting at
+ * index <code>start</code>. This is used when dividing messages
+ * into units within the SMS message size limit.
+ *
+ * @param s source string
+ * @param start index of where to start counting septets
+ * @param limit maximum septets to include,
+ * e.g. <code>MAX_USER_DATA_SEPTETS</code>
+ * @return index of first character that won't fit, or the length
+ * of the entire string if everything fits
+ */
+ public static int
+ findGsmSeptetLimitIndex(String s, int start, int limit) {
+ int accumulator = 0;
+ int size = s.length();
+
+ for (int i = start; i < size; i++) {
+ accumulator += countGsmSeptets(s.charAt(i));
+ if (accumulator > limit) {
+ return i;
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Returns the index into <code>s</code> of the first character
+ * after <code>limit</code> octets have been reached, starting at
+ * index <code>start</code>. This is used when dividing messages
+ * in UCS2 encoding into units within the SMS message size limit.
+ *
+ * @param s source string
+ * @param start index of where to start counting septets
+ * @param limit maximum septets to include,
+ * e.g. <code>MAX_USER_DATA_BYTES</code>
+ * @return index of first character that won't fit, or the length
+ * of the entire string if everything fits
+ */
+ public static int
+ findUCS2LimitIndex(String s, int start, int limit) {
+ int numCharToBeEncoded = s.length() - start;
+ return ((numCharToBeEncoded*2 > limit)? limit/2: numCharToBeEncoded) + start;
+ }
+
+ /**
+ * Returns the index into <code>s</code> of the first character
+ * after <code>limit</code> septets/octets have been reached
+ * according to the <code>encodingType</code>, starting at
+ * index <code>start</code>. This is used when dividing messages
+ * units within the SMS message size limit.
+ *
+ * @param s source string
+ * @param start index of where to start counting septets
+ * @param limit maximum septets to include,
+ * e.g. <code>MAX_USER_DATA_BYTES</code>
+ * @return index of first character that won't fit, or the length
+ * of the entire string if everything fits
+ */
+ public static int
+ findLimitIndex(String s, int start, int limit, int encodingType) throws EncodeException {
+ if (encodingType == SmsMessage.ENCODING_7BIT) {
+ return findGsmSeptetLimitIndex(s, start, limit);
+ }
+ else if (encodingType == SmsMessage.ENCODING_16BIT) {
+ return findUCS2LimitIndex(s, start, limit);
+ }
+ else {
+ throw new EncodeException("Unsupported encoding type: " + encodingType);
+ }
+ }
+
+ // Set in the static initializer
+ private static int sGsmSpaceChar;
+
+ private static final SparseIntArray charToGsm = new SparseIntArray();
+ private static final SparseIntArray gsmToChar = new SparseIntArray();
+ private static final SparseIntArray charToGsmExtended = new SparseIntArray();
+ private static final SparseIntArray gsmExtendedToChar = new SparseIntArray();
+
+ static {
+ int i = 0;
+
+ charToGsm.put('@', i++);
+ charToGsm.put('\u00a3', i++);
+ charToGsm.put('$', i++);
+ charToGsm.put('\u00a5', i++);
+ charToGsm.put('\u00e8', i++);
+ charToGsm.put('\u00e9', i++);
+ charToGsm.put('\u00f9', i++);
+ charToGsm.put('\u00ec', i++);
+ charToGsm.put('\u00f2', i++);
+ charToGsm.put('\u00c7', i++);
+ charToGsm.put('\n', i++);
+ charToGsm.put('\u00d8', i++);
+ charToGsm.put('\u00f8', i++);
+ charToGsm.put('\r', i++);
+ charToGsm.put('\u00c5', i++);
+ charToGsm.put('\u00e5', i++);
+
+ charToGsm.put('\u0394', i++);
+ charToGsm.put('_', i++);
+ charToGsm.put('\u03a6', i++);
+ charToGsm.put('\u0393', i++);
+ charToGsm.put('\u039b', i++);
+ charToGsm.put('\u03a9', i++);
+ charToGsm.put('\u03a0', i++);
+ charToGsm.put('\u03a8', i++);
+ charToGsm.put('\u03a3', i++);
+ charToGsm.put('\u0398', i++);
+ charToGsm.put('\u039e', i++);
+ charToGsm.put('\uffff', i++);
+ charToGsm.put('\u00c6', i++);
+ charToGsm.put('\u00e6', i++);
+ charToGsm.put('\u00df', i++);
+ charToGsm.put('\u00c9', i++);
+
+ charToGsm.put(' ', i++);
+ charToGsm.put('!', i++);
+ charToGsm.put('"', i++);
+ charToGsm.put('#', i++);
+ charToGsm.put('\u00a4', i++);
+ charToGsm.put('%', i++);
+ charToGsm.put('&', i++);
+ charToGsm.put('\'', i++);
+ charToGsm.put('(', i++);
+ charToGsm.put(')', i++);
+ charToGsm.put('*', i++);
+ charToGsm.put('+', i++);
+ charToGsm.put(',', i++);
+ charToGsm.put('-', i++);
+ charToGsm.put('.', i++);
+ charToGsm.put('/', i++);
+
+ charToGsm.put('0', i++);
+ charToGsm.put('1', i++);
+ charToGsm.put('2', i++);
+ charToGsm.put('3', i++);
+ charToGsm.put('4', i++);
+ charToGsm.put('5', i++);
+ charToGsm.put('6', i++);
+ charToGsm.put('7', i++);
+ charToGsm.put('8', i++);
+ charToGsm.put('9', i++);
+ charToGsm.put(':', i++);
+ charToGsm.put(';', i++);
+ charToGsm.put('<', i++);
+ charToGsm.put('=', i++);
+ charToGsm.put('>', i++);
+ charToGsm.put('?', i++);
+
+ charToGsm.put('\u00a1', i++);
+ charToGsm.put('A', i++);
+ charToGsm.put('B', i++);
+ charToGsm.put('C', i++);
+ charToGsm.put('D', i++);
+ charToGsm.put('E', i++);
+ charToGsm.put('F', i++);
+ charToGsm.put('G', i++);
+ charToGsm.put('H', i++);
+ charToGsm.put('I', i++);
+ charToGsm.put('J', i++);
+ charToGsm.put('K', i++);
+ charToGsm.put('L', i++);
+ charToGsm.put('M', i++);
+ charToGsm.put('N', i++);
+ charToGsm.put('O', i++);
+
+ charToGsm.put('P', i++);
+ charToGsm.put('Q', i++);
+ charToGsm.put('R', i++);
+ charToGsm.put('S', i++);
+ charToGsm.put('T', i++);
+ charToGsm.put('U', i++);
+ charToGsm.put('V', i++);
+ charToGsm.put('W', i++);
+ charToGsm.put('X', i++);
+ charToGsm.put('Y', i++);
+ charToGsm.put('Z', i++);
+ charToGsm.put('\u00c4', i++);
+ charToGsm.put('\u00d6', i++);
+ charToGsm.put('\u0147', i++);
+ charToGsm.put('\u00dc', i++);
+ charToGsm.put('\u00a7', i++);
+
+ charToGsm.put('\u00bf', i++);
+ charToGsm.put('a', i++);
+ charToGsm.put('b', i++);
+ charToGsm.put('c', i++);
+ charToGsm.put('d', i++);
+ charToGsm.put('e', i++);
+ charToGsm.put('f', i++);
+ charToGsm.put('g', i++);
+ charToGsm.put('h', i++);
+ charToGsm.put('i', i++);
+ charToGsm.put('j', i++);
+ charToGsm.put('k', i++);
+ charToGsm.put('l', i++);
+ charToGsm.put('m', i++);
+ charToGsm.put('n', i++);
+ charToGsm.put('o', i++);
+
+ charToGsm.put('p', i++);
+ charToGsm.put('q', i++);
+ charToGsm.put('r', i++);
+ charToGsm.put('s', i++);
+ charToGsm.put('t', i++);
+ charToGsm.put('u', i++);
+ charToGsm.put('v', i++);
+ charToGsm.put('w', i++);
+ charToGsm.put('x', i++);
+ charToGsm.put('y', i++);
+ charToGsm.put('z', i++);
+ charToGsm.put('\u00e4', i++);
+ charToGsm.put('\u00f6', i++);
+ charToGsm.put('\u00f1', i++);
+ charToGsm.put('\u00fc', i++);
+ charToGsm.put('\u00e0', i++);
+
+
+ charToGsmExtended.put('\f', 10);
+ charToGsmExtended.put('^', 20);
+ charToGsmExtended.put('{', 40);
+ charToGsmExtended.put('}', 41);
+ charToGsmExtended.put('\\', 47);
+ charToGsmExtended.put('[', 60);
+ charToGsmExtended.put('~', 61);
+ charToGsmExtended.put(']', 62);
+ charToGsmExtended.put('|', 64);
+ charToGsmExtended.put('\u20ac', 101);
+
+ int size = charToGsm.size();
+ for (int j=0; j<size; j++) {
+ gsmToChar.put(charToGsm.valueAt(j), charToGsm.keyAt(j));
+ }
+
+ size = charToGsmExtended.size();
+ for (int j=0; j<size; j++) {
+ gsmExtendedToChar.put(charToGsmExtended.valueAt(j), charToGsmExtended.keyAt(j));
+ }
+
+
+ sGsmSpaceChar = charToGsm.get(' ');
+ }
+
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java
new file mode 100644
index 0000000..c85fb7c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -0,0 +1,1252 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.content.Context;
+import com.android.internal.telephony.*;
+import android.os.*;
+import android.os.AsyncResult;
+import android.util.Log;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.telephony.PhoneNumberUtils;
+import static com.android.internal.telephony.gsm.CommandsInterface.*;
+
+/**
+ * The motto for this file is:
+ *
+ * "NOTE: By using the # as a separator, most cases are expected to be unambiguous."
+ * -- TS 22.030 6.5.2
+ *
+ * {@hide}
+ *
+ */
+public final class GsmMmiCode extends Handler implements MmiCode
+{
+ static final String LOG_TAG = "GSM";
+
+ //***** Constants
+
+ // From TS 22.030 6.5.2
+ static final String ACTION_ACTIVATE = "*";
+ static final String ACTION_DEACTIVATE = "#";
+ static final String ACTION_INTERROGATE = "*#";
+ static final String ACTION_REGISTER = "**";
+ static final String ACTION_ERASURE = "##";
+
+ // Supp Service cocdes from TS 22.030 Annex B
+
+ //Called line presentation
+ static final String SC_CLIP = "30";
+ static final String SC_CLIR = "31";
+
+ // Call Forwarding
+ static final String SC_CFU = "21";
+ static final String SC_CFB = "67";
+ static final String SC_CFNRy = "61";
+ static final String SC_CFNR = "62";
+
+ static final String SC_CF_All = "002";
+ static final String SC_CF_All_Conditional = "004";
+
+ // Call Waiting
+ static final String SC_WAIT = "43";
+
+ // Call Barring
+ static final String SC_BAOC = "33";
+ static final String SC_BAOIC = "331";
+ static final String SC_BAOICxH = "332";
+ static final String SC_BAIC = "35";
+ static final String SC_BAICr = "351";
+
+ static final String SC_BA_ALL = "330";
+ static final String SC_BA_MO = "333";
+ static final String SC_BA_MT = "353";
+
+ // Supp Service Password registration
+ static final String SC_PWD = "03";
+
+ // PIN/PIN2/PUK/PUK2
+ static final String SC_PIN = "04";
+ static final String SC_PIN2 = "042";
+ static final String SC_PUK = "05";
+ static final String SC_PUK2 = "052";
+
+ //***** Event Constants
+
+ static final int EVENT_SET_COMPLETE = 1;
+ static final int EVENT_GET_CLIR_COMPLETE = 2;
+ static final int EVENT_QUERY_CF_COMPLETE = 3;
+ static final int EVENT_USSD_COMPLETE = 4;
+ static final int EVENT_QUERY_COMPLETE = 5;
+ static final int EVENT_SET_CFF_COMPLETE = 6;
+ static final int EVENT_USSD_CANCEL_COMPLETE = 7;
+
+ //***** Instance Variables
+
+ GSMPhone phone;
+ Context context;
+
+ String action; // One of ACTION_*
+ String sc; // Service Code
+ String sia, sib, sic; // Service Info a,b,c
+ String poundString; // Entire MMI string up to and including #
+ String dialingNumber;
+ String pwd; // For password registration
+
+
+ /** Set to true in processCode, not at newFromDialString time */
+ private boolean isPendingUSSD;
+
+ private boolean isUssdRequest;
+
+ State state = State.PENDING;
+ CharSequence message;
+
+ //***** Class Variables
+
+
+ // See TS 22.030 6.5.2 "Structure of the MMI"
+
+ static Pattern sPatternSuppService = Pattern.compile(
+ "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
+/* 1 2 3 4 5 6 7 8 9 10 11 12
+
+ 1 = Full string up to and including #
+ 2 = action (activation/interrogation/registration/erasure)
+ 3 = service code
+ 5 = SIA
+ 7 = SIB
+ 9 = SIC
+ 10 = dialing number
+*/
+
+ static final int MATCH_GROUP_POUND_STRING = 1;
+
+ static final int MATCH_GROUP_ACTION = 2;
+ //(activation/interrogation/registration/erasure)
+
+ static final int MATCH_GROUP_SERVICE_CODE = 3;
+ static final int MATCH_GROUP_SIA = 5;
+ static final int MATCH_GROUP_SIB = 7;
+ static final int MATCH_GROUP_SIC = 9;
+ static final int MATCH_GROUP_PWD_CONFIRM = 11;
+ static final int MATCH_GROUP_DIALING_NUMBER = 12;
+
+
+ //***** Public Class methods
+
+ /**
+ * Some dial strings in GSM are defined to do non-call setup
+ * things, such as modify or query supplementry service settings (eg, call
+ * forwarding). These are generally referred to as "MMI codes".
+ * We look to see if the dial string contains a valid MMI code (potentially
+ * with a dial string at the end as well) and return info here.
+ *
+ * If the dial string contains no MMI code, we return an instance with
+ * only "dialingNumber" set
+ *
+ * Please see flow chart in TS 22.030 6.5.3.2
+ */
+
+ static GsmMmiCode
+ newFromDialString(String dialString, GSMPhone phone)
+ {
+ Matcher m;
+ GsmMmiCode ret = null;
+
+ m = sPatternSuppService.matcher(dialString);
+
+ // Is this formatted like a standard supplementary service code?
+ if (m.matches()) {
+ ret = new GsmMmiCode(phone);
+ ret.poundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
+ ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
+ ret.sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
+ ret.sia = makeEmptyNull(m.group(MATCH_GROUP_SIA));
+ ret.sib = makeEmptyNull(m.group(MATCH_GROUP_SIB));
+ ret.sic = makeEmptyNull(m.group(MATCH_GROUP_SIC));
+ ret.pwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
+ ret.dialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));
+
+ } else if (dialString.endsWith("#")) {
+ // TS 22.030 sec 6.5.3.2
+ // "Entry of any characters defined in the 3GPP TS 23.038 [8] Default Alphabet
+ // (up to the maximum defined in 3GPP TS 24.080 [10]), followed by #SEND".
+
+ ret = new GsmMmiCode(phone);
+ ret.poundString = dialString;
+ } else if (isShortCode(dialString, phone)) {
+ // this may be a short code, as defined in TS 22.030, 6.5.3.2
+ ret = new GsmMmiCode(phone);
+ ret.dialingNumber = dialString;
+ }
+
+ return ret;
+ }
+
+ static GsmMmiCode
+ newNetworkInitiatedUssd (String ussdMessage,
+ boolean isUssdRequest, GSMPhone phone)
+ {
+ GsmMmiCode ret;
+
+ ret = new GsmMmiCode(phone);
+
+ ret.message = ussdMessage;
+ ret.isUssdRequest = isUssdRequest;
+
+ // If it's a request, set to PENDING so that it's cancelable.
+ if (isUssdRequest) {
+ ret.isPendingUSSD = true;
+ ret.state = State.PENDING;
+ } else {
+ ret.state = State.COMPLETE;
+ }
+
+ return ret;
+ }
+
+ static GsmMmiCode newFromUssdUserInput(String ussdMessge, GSMPhone phone) {
+ GsmMmiCode ret = new GsmMmiCode(phone);
+
+ ret.message = ussdMessge;
+ ret.state = State.PENDING;
+ ret.isPendingUSSD = true;
+
+ return ret;
+ }
+
+ //***** Private Class methods
+
+ /** make empty strings be null.
+ * Regexp returns empty strings for empty groups
+ */
+ private static String
+ makeEmptyNull (String s)
+ {
+ if (s != null && s.length() == 0) return null;
+
+ return s;
+ }
+
+ /** returns true of the string is empty or null */
+ private static boolean
+ isEmptyOrNull(CharSequence s)
+ {
+ return s == null || (s.length() == 0);
+ }
+
+
+ private static int
+ scToCallForwardReason(String sc)
+ {
+ if (sc == null) {
+ throw new RuntimeException ("invalid call forward sc");
+ }
+
+ if (sc.equals(SC_CF_All)) {
+ return CommandsInterface.CF_REASON_ALL;
+ } else if (sc.equals(SC_CFU)) {
+ return CommandsInterface.CF_REASON_UNCONDITIONAL;
+ } else if (sc.equals(SC_CFB)) {
+ return CommandsInterface.CF_REASON_BUSY;
+ } else if (sc.equals(SC_CFNR)) {
+ return CommandsInterface.CF_REASON_NOT_REACHABLE;
+ } else if (sc.equals(SC_CFNRy)) {
+ return CommandsInterface.CF_REASON_NO_REPLY;
+ } else if (sc.equals(SC_CF_All_Conditional)) {
+ return CommandsInterface.CF_REASON_ALL_CONDITIONAL;
+ } else {
+ throw new RuntimeException ("invalid call forward sc");
+ }
+ }
+
+ private static int
+ siToServiceClass(String si)
+ {
+ if (si == null || si.length() == 0) {
+ return SERVICE_CLASS_NONE;
+ } else {
+ // NumberFormatException should cause MMI fail
+ int serviceCode = Integer.parseInt(si, 10);
+
+ switch (serviceCode) {
+ case 10: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE;
+ case 11: return SERVICE_CLASS_VOICE;
+ case 12: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX;
+ case 13: return SERVICE_CLASS_FAX;
+
+ case 16: return SERVICE_CLASS_SMS;
+
+ case 19: return SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE;
+/*
+ Note for code 20:
+ From TS 22.030 Annex C:
+ "All GPRS bearer services" are not included in "All tele and bearer services"
+ and "All bearer services"."
+....so SERVICE_CLASS_DATA, which (according to 27.007) includes GPRS
+*/
+ case 20: return SERVICE_CLASS_DATA_ASYNC + SERVICE_CLASS_DATA_SYNC;
+
+ case 21: return SERVICE_CLASS_PAD + SERVICE_CLASS_DATA_ASYNC;
+ case 22: return SERVICE_CLASS_PACKET + SERVICE_CLASS_DATA_SYNC;
+ case 24: return SERVICE_CLASS_DATA_SYNC;
+ case 25: return SERVICE_CLASS_DATA_ASYNC;
+ case 26: return SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
+ case 99: return SERVICE_CLASS_PACKET;
+
+ default:
+ throw new RuntimeException("unsupported MMI service code " + si);
+ }
+ }
+ }
+
+ private static int
+ siToTime (String si)
+ {
+ if (si == null || si.length() == 0) {
+ return 0;
+ } else {
+ // NumberFormatException should cause MMI fail
+ return Integer.parseInt(si, 10);
+ }
+ }
+
+ static boolean
+ isServiceCodeCallForwarding(String sc)
+ {
+ return sc != null &&
+ (sc.equals(SC_CFU)
+ || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
+ || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
+ || sc.equals(SC_CF_All_Conditional));
+ }
+
+ static boolean
+ isServiceCodeCallBarring(String sc)
+ {
+ return sc != null &&
+ (sc.equals(SC_BAOC)
+ || sc.equals(SC_BAOIC)
+ || sc.equals(SC_BAOICxH)
+ || sc.equals(SC_BAIC)
+ || sc.equals(SC_BAICr)
+ || sc.equals(SC_BA_ALL)
+ || sc.equals(SC_BA_MO)
+ || sc.equals(SC_BA_MT));
+ }
+
+ static String
+ scToBarringFacility(String sc)
+ {
+ if (sc == null) {
+ throw new RuntimeException ("invalid call barring sc");
+ }
+
+ if (sc.equals(SC_BAOC)) {
+ return CommandsInterface.CB_FACILITY_BAOC;
+ } else if (sc.equals(SC_BAOIC)) {
+ return CommandsInterface.CB_FACILITY_BAOIC;
+ } else if (sc.equals(SC_BAOICxH)) {
+ return CommandsInterface.CB_FACILITY_BAOICxH;
+ } else if (sc.equals(SC_BAIC)) {
+ return CommandsInterface.CB_FACILITY_BAIC;
+ } else if (sc.equals(SC_BAICr)) {
+ return CommandsInterface.CB_FACILITY_BAICr;
+ } else if (sc.equals(SC_BA_ALL)) {
+ return CommandsInterface.CB_FACILITY_BA_ALL;
+ } else if (sc.equals(SC_BA_MO)) {
+ return CommandsInterface.CB_FACILITY_BA_MO;
+ } else if (sc.equals(SC_BA_MT)) {
+ return CommandsInterface.CB_FACILITY_BA_MT;
+ } else {
+ throw new RuntimeException ("invalid call barring sc");
+ }
+ }
+
+ //***** Constructor
+
+ GsmMmiCode (GSMPhone phone)
+ {
+ // The telephony unit-test cases may create GsmMmiCode's
+ // in secondary threads
+ super(phone.h.getLooper());
+ this.phone = phone;
+ this.context = phone.getContext();
+ }
+
+ //***** MmiCode implementation
+
+ public State
+ getState()
+ {
+ return state;
+ }
+
+ public CharSequence
+ getMessage()
+ {
+ return message;
+ }
+
+ // inherited javadoc suffices
+ public void
+ cancel()
+ {
+ // Complete or failed cannot be cancelled
+ if (state == State.COMPLETE || state == State.FAILED) {
+ return;
+ }
+
+ state = State.CANCELLED;
+
+ if (isPendingUSSD) {
+ /*
+ * There can only be one pending USSD session, so tell the radio to
+ * cancel it.
+ */
+ phone.mCM.cancelPendingUssd(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this));
+
+ /*
+ * Don't call phone.onMMIDone here; wait for CANCEL_COMPLETE notice
+ * from RIL.
+ */
+ } else {
+ // TODO in cases other than USSD, it would be nice to cancel
+ // the pending radio operation. This requires RIL cancellation
+ // support, which does not presently exist.
+
+ phone.onMMIDone (this);
+ }
+
+
+ }
+
+ public boolean isCancelable() {
+ /* Can only cancel pending USSD sessions. */
+ return isPendingUSSD;
+ }
+
+ //***** Instance Methods
+
+
+ /** Does this dial string contain a structured or unstructured MMI code? */
+ boolean
+ isMMI()
+ {
+ return poundString != null;
+ }
+
+ /* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */
+ boolean
+ isShortCode()
+ {
+ return poundString == null
+ && dialingNumber != null && dialingNumber.length() <= 2;
+
+ }
+
+ /**
+ * Helper function for newFromDialString. Returns true if dialString appears to be a short code
+ * AND conditions are correct for it to be treated as such.
+ */
+ static private boolean isShortCode(String dialString, GSMPhone phone) {
+ // Refer to TS 22.030 Figure 3.5.3.2:
+ // A 1 or 2 digit "short code" is treated as USSD if it is entered while on a call or
+ // does not satisfy the condition (exactly 2 digits && starts with '1').
+ return ((dialString != null && dialString.length() <= 2)
+ && !PhoneNumberUtils.isEmergencyNumber(dialString)
+ && (phone.isInCall()
+ || !((dialString.length() == 2 && dialString.charAt(0) == '1')
+ /* While contrary to TS 22.030, there is strong precendence
+ * for treating "0" and "00" as call setup strings.
+ */
+ || dialString.equals("0")
+ || dialString.equals("00"))));
+ }
+ /**
+ * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
+ */
+ boolean isPinCommand() {
+ return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
+ || sc.equals(SC_PUK) || sc.equals(SC_PUK2));
+ }
+
+ /**
+ * *See TS 22.030 Annex B
+ * In temporary mode, to suppress CLIR for a single call, enter:
+ * " * 31 # <called number> SEND "
+ * In temporary mode, to invoke CLIR for a single call enter:
+ * " # 31 # <called number> SEND "
+ */
+
+ boolean
+ isTemporaryModeCLIR()
+ {
+ return sc != null && sc.equals(SC_CLIR) && dialingNumber != null
+ && (isActivate() || isDeactivate());
+ }
+
+ /**
+ * returns CommandsInterface.CLIR_*
+ * See also isTemporaryModeCLIR()
+ */
+ int
+ getCLIRMode()
+ {
+ if (sc != null && sc.equals(SC_CLIR)) {
+ if (isActivate()) {
+ return CommandsInterface.CLIR_SUPPRESSION;
+ } else if (isDeactivate()) {
+ return CommandsInterface.CLIR_INVOCATION;
+ }
+ }
+
+ return CommandsInterface.CLIR_DEFAULT;
+ }
+
+ boolean isActivate()
+ {
+ return action != null && action.equals(ACTION_ACTIVATE);
+ }
+
+ boolean isDeactivate()
+ {
+ return action != null && action.equals(ACTION_DEACTIVATE);
+ }
+
+ boolean isInterrogate()
+ {
+ return action != null && action.equals(ACTION_INTERROGATE);
+ }
+
+ boolean isRegister()
+ {
+ return action != null && action.equals(ACTION_REGISTER);
+ }
+
+ boolean isErasure()
+ {
+ return action != null && action.equals(ACTION_ERASURE);
+ }
+
+ /**
+ * Returns true if this is a USSD code that's been submitted to the
+ * network...eg, after processCode() is called
+ */
+ public boolean isPendingUSSD()
+ {
+ return isPendingUSSD;
+ }
+
+ public boolean isUssdRequest() {
+ return isUssdRequest;
+ }
+
+ /** Process a MMI code or short code...anything that isn't a dialing number */
+ void
+ processCode ()
+ {
+ try {
+ if (isShortCode()) {
+ Log.d(LOG_TAG, "isShortCode");
+ // These just get treated as USSD.
+ sendUssd(dialingNumber);
+ } else if (dialingNumber != null) {
+ // We should have no dialing numbers here
+ throw new RuntimeException ("Invalid or Unsupported MMI Code");
+ } else if (sc != null && sc.equals(SC_CLIP)) {
+ Log.d(LOG_TAG, "is CLIP");
+ if (isInterrogate()) {
+ phone.mCM.queryCLIP(
+ obtainMessage(EVENT_QUERY_COMPLETE, this));
+ } else {
+ throw new RuntimeException ("Invalid or Unsupported MMI Code");
+ }
+ } else if (sc != null && sc.equals(SC_CLIR)) {
+ Log.d(LOG_TAG, "is CLIR");
+ if (isActivate()) {
+ phone.mCM.setCLIR(CommandsInterface.CLIR_INVOCATION,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ } else if (isDeactivate()) {
+ phone.mCM.setCLIR(CommandsInterface.CLIR_SUPPRESSION,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ } else if (isInterrogate()) {
+ phone.mCM.getCLIR(
+ obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
+ } else {
+ throw new RuntimeException ("Invalid or Unsupported MMI Code");
+ }
+ } else if (isServiceCodeCallForwarding(sc)) {
+ Log.d(LOG_TAG, "is CF");
+
+ String dialingNumber = sia;
+ int serviceClass = siToServiceClass(sib);
+ int reason = scToCallForwardReason(sc);
+ int time = siToTime(sic);
+
+ if (isInterrogate()) {
+ phone.mCM.queryCallForwardStatus(
+ reason, serviceClass, dialingNumber,
+ obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
+ } else {
+ int cfAction;
+
+ if (isActivate()) {
+ cfAction = CommandsInterface.CF_ACTION_ENABLE;
+ } else if (isDeactivate()) {
+ cfAction = CommandsInterface.CF_ACTION_DISABLE;
+ } else if (isRegister()) {
+ cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
+ } else if (isErasure()) {
+ cfAction = CommandsInterface.CF_ACTION_ERASURE;
+ } else {
+ throw new RuntimeException ("invalid action");
+ }
+
+ int isSettingUnconditionalVoice = (
+ (reason == CommandsInterface.CF_REASON_UNCONDITIONAL) &&
+ ((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0)
+ ) ? 1 : 0;
+
+ int isEnableDesired =
+ ((cfAction == CommandsInterface.CF_ACTION_ENABLE)
+ || (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
+
+ phone.mCM.setCallForward(cfAction, reason,
+ serviceClass, dialingNumber, time,
+ obtainMessage(EVENT_SET_CFF_COMPLETE,
+ isSettingUnconditionalVoice,
+ isEnableDesired,
+ this));
+ }
+ } else if (isServiceCodeCallBarring(sc)) {
+ // sia = password
+ // sib = basic service group
+
+ String password = sia;
+ int serviceClass = siToServiceClass(sib);
+ String facility = scToBarringFacility(sc);
+
+ if (isInterrogate()) {
+ phone.mCM.queryFacilityLock(facility, password,
+ serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
+ } else if (isActivate() || isDeactivate()) {
+ phone.mCM.setFacilityLock(facility, isActivate(), password,
+ serviceClass, obtainMessage(EVENT_SET_COMPLETE, this));
+ } else {
+ throw new RuntimeException ("Invalid or Unsupported MMI Code");
+ }
+
+ } else if (sc != null && sc.equals(SC_PWD)) {
+ // sia = fac
+ // sib = old pwd
+ // sic = new pwd
+ // pwd = new pwd
+ String facility;
+ String oldPwd = sib;
+ String newPwd = sic;
+ if (isActivate() || isRegister()) {
+ // Even though ACTIVATE is acceptable, this is really termed a REGISTER
+ action = ACTION_REGISTER;
+
+ if (sia == null) {
+ // If sc was not specified, treat it as BA_ALL.
+ facility = CommandsInterface.CB_FACILITY_BA_ALL;
+ } else {
+ facility = scToBarringFacility(sia);
+ }
+ if (newPwd.equals(pwd)) {
+ phone.mCM.changeBarringPassword(facility, oldPwd,
+ newPwd, obtainMessage(EVENT_SET_COMPLETE, this));
+ } else {
+ // password mismatch; return error
+ handlePasswordError(com.android.internal.R.string.passwordIncorrect);
+ }
+ } else {
+ throw new RuntimeException ("Invalid or Unsupported MMI Code");
+ }
+
+ } else if (sc != null && sc.equals(SC_WAIT)) {
+ // sia = basic service group
+ int serviceClass = siToServiceClass(sia);
+
+ if (isActivate() || isDeactivate()) {
+ phone.mCM.setCallWaiting(isActivate(), serviceClass,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ } else if (isInterrogate()) {
+ phone.mCM.queryCallWaiting(serviceClass,
+ obtainMessage(EVENT_QUERY_COMPLETE, this));
+ } else {
+ throw new RuntimeException ("Invalid or Unsupported MMI Code");
+ }
+ } else if (isPinCommand()) {
+ // sia = old PIN or PUK
+ // sib = new PIN
+ // sic = new PIN
+ String oldPinOrPuk = sia;
+ String newPin = sib;
+ int pinLen = newPin.length();
+ if (isRegister()) {
+ if (!newPin.equals(sic)) {
+ // password mismatch; return error
+ handlePasswordError(com.android.internal.R.string.mismatchPin);
+ } else if (pinLen < 4 || pinLen > 8 ) {
+ // invalid length
+ handlePasswordError(com.android.internal.R.string.invalidPin);
+ } else {
+ // pre-checks OK
+ if (sc.equals(SC_PIN)) {
+ phone.mCM.changeSimPin(oldPinOrPuk, newPin,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ } else if (sc.equals(SC_PIN2)) {
+ phone.mCM.changeSimPin2(oldPinOrPuk, newPin,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ } else if (sc.equals(SC_PUK)) {
+ phone.mCM.supplySimPuk(oldPinOrPuk, newPin,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ } else if (sc.equals(SC_PUK2)) {
+ phone.mCM.supplySimPuk2(oldPinOrPuk, newPin,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ }
+ }
+ } else {
+ throw new RuntimeException ("Invalid or Unsupported MMI Code");
+ }
+ } else if (poundString != null) {
+ sendUssd(poundString);
+ } else {
+ throw new RuntimeException ("Invalid or Unsupported MMI Code");
+ }
+ } catch (RuntimeException exc) {
+ state = State.FAILED;
+ message = context.getText(com.android.internal.R.string.mmiError);
+ phone.onMMIDone(this);
+ }
+ }
+
+ private void handlePasswordError(int res) {
+ state = State.FAILED;
+ StringBuilder sb = new StringBuilder(getScString());
+ sb.append("\n");
+ sb.append(context.getText(res));
+ message = sb;
+ phone.onMMIDone(this);
+ }
+
+ /**
+ * Called from GSMPhone
+ *
+ * An unsolicited USSD NOTIFY or REQUEST has come in matching
+ * up with this pending USSD request
+ *
+ * Note: If REQUEST, this exchange is complete, but the session remains
+ * active (ie, the network expects user input).
+ */
+ void
+ onUssdFinished(String ussdMessage, boolean isUssdRequest)
+ {
+ if (state == State.PENDING) {
+ if (ussdMessage == null) {
+ message = context.getText(com.android.internal.R.string.mmiComplete);
+ } else {
+ message = ussdMessage;
+ }
+ this.isUssdRequest = isUssdRequest;
+ // If it's a request, leave it PENDING so that it's cancelable.
+ if (!isUssdRequest) {
+ state = State.COMPLETE;
+ }
+
+ phone.onMMIDone(this);
+ }
+ }
+
+ /**
+ * Called from GSMPhone
+ *
+ * The radio has reset, and this is still pending
+ */
+
+ void
+ onUssdFinishedError()
+ {
+ if (state == State.PENDING) {
+ state = State.FAILED;
+ message = context.getText(com.android.internal.R.string.mmiError);
+
+ phone.onMMIDone(this);
+ }
+ }
+
+ void sendUssd(String ussdMessage) {
+ // Treat this as a USSD string
+ isPendingUSSD = true;
+
+ // Note that unlike most everything else, the USSD complete
+ // response does not complete this MMI code...we wait for
+ // an unsolicited USSD "Notify" or "Request".
+ // The matching up of this is doene in GSMPhone.
+
+ phone.mCM.sendUSSD(ussdMessage,
+ obtainMessage(EVENT_USSD_COMPLETE, this));
+ }
+
+ /** Called from GSMPhone.handleMessage; not a Handler subclass */
+ public void
+ handleMessage (Message msg)
+ {
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case EVENT_SET_COMPLETE:
+ ar = (AsyncResult) (msg.obj);
+
+ onSetComplete(ar);
+ break;
+
+ case EVENT_SET_CFF_COMPLETE:
+ ar = (AsyncResult) (msg.obj);
+
+ /*
+ * msg.arg1 = 1 means to set unconditional voice call forwarding
+ * msg.arg2 = 1 means to enable voice call forwarding
+ */
+ if ((ar.exception == null) && (msg.arg1 == 1)) {
+ boolean cffEnabled = (msg.arg2 == 1);
+ phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
+ }
+
+ onSetComplete(ar);
+ break;
+
+ case EVENT_GET_CLIR_COMPLETE:
+ ar = (AsyncResult) (msg.obj);
+ onGetClirComplete(ar);
+ break;
+
+ case EVENT_QUERY_CF_COMPLETE:
+ ar = (AsyncResult) (msg.obj);
+ onQueryCfComplete(ar);
+ break;
+
+ case EVENT_QUERY_COMPLETE:
+ ar = (AsyncResult) (msg.obj);
+ onQueryComplete(ar);
+ break;
+
+ case EVENT_USSD_COMPLETE:
+ ar = (AsyncResult) (msg.obj);
+
+ if (ar.exception != null) {
+ state = State.FAILED;
+ message = context.getText(
+ com.android.internal.R.string.mmiError);
+
+ phone.onMMIDone(this);
+ }
+
+ // Note that unlike most everything else, the USSD complete
+ // response does not complete this MMI code...we wait for
+ // an unsolicited USSD "Notify" or "Request".
+ // The matching up of this is done in GSMPhone.
+
+ break;
+
+ case EVENT_USSD_CANCEL_COMPLETE:
+ phone.onMMIDone(this);
+ break;
+ }
+ }
+ //***** Private instance methods
+
+ private CharSequence getScString() {
+ if (sc != null) {
+ if (isServiceCodeCallBarring(sc)) {
+ return context.getText(com.android.internal.R.string.BaMmi);
+ } else if (isServiceCodeCallForwarding(sc)) {
+ return context.getText(com.android.internal.R.string.CfMmi);
+ } else if (sc.equals(SC_CLIP)) {
+ return context.getText(com.android.internal.R.string.ClipMmi);
+ } else if (sc.equals(SC_CLIR)) {
+ return context.getText(com.android.internal.R.string.ClirMmi);
+ } else if (sc.equals(SC_PWD)) {
+ return context.getText(com.android.internal.R.string.PwdMmi);
+ } else if (sc.equals(SC_WAIT)) {
+ return context.getText(com.android.internal.R.string.CwMmi);
+ } else if (isPinCommand()) {
+ return context.getText(com.android.internal.R.string.PinMmi);
+ }
+ }
+
+ return "";
+ }
+
+ private void
+ onSetComplete(AsyncResult ar){
+ StringBuilder sb = new StringBuilder(getScString());
+ sb.append("\n");
+
+ if (ar.exception != null) {
+ state = State.FAILED;
+ if (ar.exception instanceof CommandException) {
+ CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
+ if (err == CommandException.Error.PASSWORD_INCORRECT) {
+ if (isPinCommand()) {
+ // look specifically for the PUK commands and adjust
+ // the message accordingly.
+ if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) {
+ sb.append(context.getText(
+ com.android.internal.R.string.badPuk));
+ } else {
+ sb.append(context.getText(
+ com.android.internal.R.string.badPin));
+ }
+ } else {
+ sb.append(context.getText(
+ com.android.internal.R.string.passwordIncorrect));
+ }
+ } else if (err == CommandException.Error.SIM_PUK2) {
+ sb.append(context.getText(
+ com.android.internal.R.string.badPin));
+ sb.append("\n");
+ sb.append(context.getText(
+ com.android.internal.R.string.needPuk2));
+ } else {
+ sb.append(context.getText(
+ com.android.internal.R.string.mmiError));
+ }
+ } else {
+ sb.append(context.getText(
+ com.android.internal.R.string.mmiError));
+ }
+ } else if (isActivate()) {
+ state = State.COMPLETE;
+ sb.append(context.getText(
+ com.android.internal.R.string.serviceEnabled));
+ // Record CLIR setting
+ if (sc.equals(SC_CLIR)) {
+ phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
+ }
+ } else if (isDeactivate()) {
+ state = State.COMPLETE;
+ sb.append(context.getText(
+ com.android.internal.R.string.serviceDisabled));
+ // Record CLIR setting
+ if (sc.equals(SC_CLIR)) {
+ phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
+ }
+ } else if (isRegister()) {
+ state = State.COMPLETE;
+ sb.append(context.getText(
+ com.android.internal.R.string.serviceRegistered));
+ } else if (isErasure()) {
+ state = State.COMPLETE;
+ sb.append(context.getText(
+ com.android.internal.R.string.serviceErased));
+ } else {
+ state = State.FAILED;
+ sb.append(context.getText(
+ com.android.internal.R.string.mmiError));
+ }
+
+ message = sb;
+ phone.onMMIDone(this);
+ }
+
+ private void
+ onGetClirComplete(AsyncResult ar)
+ {
+ StringBuilder sb = new StringBuilder(getScString());
+ sb.append("\n");
+
+ if (ar.exception != null) {
+ state = State.FAILED;
+ sb.append(context.getText(com.android.internal.R.string.mmiError));
+ } else {
+ int clirArgs[];
+
+ clirArgs = (int[])ar.result;
+
+ // the 'm' parameter from TS 27.007 7.7
+ switch (clirArgs[1]) {
+ case 0: // CLIR not provisioned
+ sb.append(context.getText(
+ com.android.internal.R.string.serviceNotProvisioned));
+ state = State.COMPLETE;
+ break;
+
+ case 1: // CLIR provisioned in permanent mode
+ sb.append(context.getText(
+ com.android.internal.R.string.CLIRPermanent));
+ state = State.COMPLETE;
+ break;
+
+ case 2: // unknown (e.g. no network, etc.)
+ sb.append(context.getText(
+ com.android.internal.R.string.mmiError));
+ state = State.FAILED;
+ break;
+
+ case 3: // CLIR temporary mode presentation restricted
+
+ // the 'n' parameter from TS 27.007 7.7
+ switch (clirArgs[0]) {
+ default:
+ case 0: // Default
+ sb.append(context.getText(
+ com.android.internal.R.string.CLIRDefaultOnNextCallOn));
+ break;
+ case 1: // CLIR invocation
+ sb.append(context.getText(
+ com.android.internal.R.string.CLIRDefaultOnNextCallOn));
+ break;
+ case 2: // CLIR suppression
+ sb.append(context.getText(
+ com.android.internal.R.string.CLIRDefaultOnNextCallOff));
+ break;
+ }
+ state = State.COMPLETE;
+ break;
+
+ case 4: // CLIR temporary mode presentation allowed
+ // the 'n' parameter from TS 27.007 7.7
+ switch (clirArgs[0]) {
+ default:
+ case 0: // Default
+ sb.append(context.getText(
+ com.android.internal.R.string.CLIRDefaultOffNextCallOff));
+ break;
+ case 1: // CLIR invocation
+ sb.append(context.getText(
+ com.android.internal.R.string.CLIRDefaultOffNextCallOn));
+ break;
+ case 2: // CLIR suppression
+ sb.append(context.getText(
+ com.android.internal.R.string.CLIRDefaultOffNextCallOff));
+ break;
+ }
+
+ state = State.COMPLETE;
+ break;
+ }
+ }
+
+ message = sb;
+ phone.onMMIDone(this);
+ }
+
+ /**
+ * @param serviceClass 1 bit of the service class bit vectory
+ * @return String to be used for call forward query MMI response text.
+ * Returns null if unrecognized
+ */
+
+ private CharSequence
+ serviceClassToCFString (int serviceClass)
+ {
+ switch (serviceClass) {
+ case SERVICE_CLASS_VOICE: return context.getText(com.android.internal.R.string.serviceClassVoice);
+ case SERVICE_CLASS_DATA: return context.getText(com.android.internal.R.string.serviceClassData);
+ case SERVICE_CLASS_FAX: return context.getText(com.android.internal.R.string.serviceClassFAX);
+ case SERVICE_CLASS_SMS: return context.getText(com.android.internal.R.string.serviceClassSMS);
+ case SERVICE_CLASS_DATA_SYNC: return context.getText(com.android.internal.R.string.serviceClassDataSync);
+ case SERVICE_CLASS_DATA_ASYNC: return context.getText(com.android.internal.R.string.serviceClassDataAsync);
+ case SERVICE_CLASS_PACKET: return context.getText(com.android.internal.R.string.serviceClassPacket);
+ case SERVICE_CLASS_PAD: return context.getText(com.android.internal.R.string.serviceClassPAD);
+
+ default:
+ return null;
+ }
+ }
+
+
+ /** one CallForwardInfo + serviceClassMask -> one line of text */
+ private CharSequence
+ makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask)
+ {
+ CharSequence template;
+ String sources[] = {"{0}", "{1}", "{2}"};
+ CharSequence destinations[] = new CharSequence[3];
+ boolean needTimeTemplate;
+
+ // CF_REASON_NO_REPLY also has a time value associated with
+ // it. All others don't.
+
+ needTimeTemplate =
+ (info.reason == CommandsInterface.CF_REASON_NO_REPLY);
+
+ if (info.status == 1) {
+ if (needTimeTemplate) {
+ template = context.getText(
+ com.android.internal.R.string.cfTemplateForwardedTime);
+ } else {
+ template = context.getText(
+ com.android.internal.R.string.cfTemplateForwarded);
+ }
+ } else if (info.status == 0 && isEmptyOrNull(info.number)) {
+ template = context.getText(
+ com.android.internal.R.string.cfTemplateNotForwarded);
+ } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
+ // A call forward record that is not active but contains
+ // a phone number is considered "registered"
+
+ if (needTimeTemplate) {
+ template = context.getText(
+ com.android.internal.R.string.cfTemplateRegisteredTime);
+ } else {
+ template = context.getText(
+ com.android.internal.R.string.cfTemplateRegistered);
+ }
+ }
+
+ // In the template (from strings.xmls)
+ // {0} is one of "bearerServiceCode*"
+ // {1} is dialing number
+ // {2} is time in seconds
+
+ destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
+ destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
+ destinations[2] = Integer.toString(info.timeSeconds);
+
+ if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
+ (info.serviceClass & serviceClassMask)
+ == CommandsInterface.SERVICE_CLASS_VOICE) {
+ boolean cffEnabled = (info.status == 1);
+ phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
+ }
+
+ return TextUtils.replace(template, sources, destinations);
+ }
+
+
+ private void
+ onQueryCfComplete(AsyncResult ar)
+ {
+ StringBuilder sb = new StringBuilder(getScString());
+ sb.append("\n");
+
+ if (ar.exception != null) {
+ state = State.FAILED;
+ sb.append(context.getText(com.android.internal.R.string.mmiError));
+ } else {
+ CallForwardInfo infos[];
+
+ infos = (CallForwardInfo[]) ar.result;
+
+ if (infos.length == 0) {
+ // Assume the default is not active
+ sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
+
+ // Set unconditional CFF in SIM to false
+ phone.mSIMRecords.setVoiceCallForwardingFlag(1, false);
+ } else {
+
+ SpannableStringBuilder tb = new SpannableStringBuilder();
+
+ // Each bit in the service class gets its own result line
+ // The service classes may be split up over multiple
+ // CallForwardInfos. So, for each service classs, find out
+ // which CallForwardInfo represents it and then build
+ // the response text based on that
+
+ for (int serviceClassMask = 1
+ ; serviceClassMask <= SERVICE_CLASS_MAX
+ ; serviceClassMask <<= 1
+ ) {
+ for (int i = 0, s = infos.length; i < s ; i++) {
+ if ((serviceClassMask & infos[i].serviceClass) != 0) {
+ tb.append(makeCFQueryResultMessage(infos[i],
+ serviceClassMask));
+ tb.append("\n");
+ }
+ }
+ }
+ sb.append(tb);
+ }
+
+ state = State.COMPLETE;
+ }
+
+ message = sb;
+ phone.onMMIDone(this);
+
+ }
+
+ private void
+ onQueryComplete(AsyncResult ar)
+ {
+ StringBuilder sb = new StringBuilder(getScString());
+ sb.append("\n");
+
+ if (ar.exception != null) {
+ state = State.FAILED;
+ sb.append(context.getText(com.android.internal.R.string.mmiError));
+ } else {
+ int[] ints = (int[])ar.result;
+
+ if (ints.length != 0) {
+ if (ints[0] == 0) {
+ sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
+ } else if (sc.equals(SC_WAIT)) {
+ // Call Waiting includes additional data in the response.
+ sb.append(createQueryCallWaitingResultMessage(ints[1]));
+ } else if (isServiceCodeCallBarring(sc) || (ints[0] == 1)) {
+ // ints[0] for Call Barring is a bit vector of services
+ // for all other services, treat it as a boolean
+ sb.append(context.getText(com.android.internal.R.string.serviceEnabled));
+ } else {
+ sb.append(context.getText(com.android.internal.R.string.mmiError));
+ }
+ } else {
+ sb.append(context.getText(com.android.internal.R.string.mmiError));
+ }
+ state = State.COMPLETE;
+ }
+
+ message = sb;
+ phone.onMMIDone(this);
+ }
+
+ private CharSequence
+ createQueryCallWaitingResultMessage(int serviceClass)
+ {
+ StringBuilder sb = new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
+
+ for (int classMask = 1
+ ; classMask <= SERVICE_CLASS_MAX
+ ; classMask <<= 1
+ ) {
+ if ((classMask & serviceClass) != 0) {
+ sb.append("\n");
+ sb.append(serviceClassToCFString(classMask & serviceClass));
+ }
+ }
+ return sb;
+ }
+
+ /***
+ * TODO: It would be nice to have a method here that can take in a dialstring and
+ * figure out if there is an MMI code embedded within it. This code would replace
+ * some of the string parsing functionality in the Phone App's
+ * SpecialCharSequenceMgr class.
+ */
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSimCard.java b/telephony/java/com/android/internal/telephony/gsm/GsmSimCard.java
new file mode 100644
index 0000000..0859806
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSimCard.java
@@ -0,0 +1,506 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.os.AsyncResult;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.util.Log;
+import com.android.internal.telephony.SimCard;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyIntents;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.app.ActivityManagerNative;
+
+import static android.Manifest.permission.READ_PHONE_STATE;
+
+/**
+ * {@hide}
+ */
+public final class GsmSimCard extends Handler implements SimCard {
+ static final String LOG_TAG="GSM";
+
+ //***** Instance Variables
+ private static final boolean DBG = true;
+
+ private GSMPhone phone;
+ private CommandsInterface.SimStatus status = null;
+ private boolean mSimPinLocked = true; // Default to locked
+ private boolean mSimFdnEnabled = false; // Default to disabled.
+ // Will be updated when SIM_READY.
+ private boolean mDesiredPinLocked;
+ private boolean mDesiredFdnEnabled;
+
+ //***** Constants
+
+ // FIXME I hope this doesn't conflict with the Dialer's notifications
+ static final int NOTIFICATION_ID_SIM_STATUS = 33456;
+
+ //***** Event Constants
+
+ static final int EVENT_SIM_LOCKED_OR_ABSENT = 1;
+ static final int EVENT_GET_SIM_STATUS_DONE = 2;
+ static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 3;
+ static final int EVENT_PINPUK_DONE = 4;
+ static final int EVENT_REPOLL_STATUS_DONE = 5;
+ static final int EVENT_SIM_READY = 6;
+ static final int EVENT_QUERY_FACILITY_LOCK_DONE = 7;
+ static final int EVENT_CHANGE_FACILITY_LOCK_DONE = 8;
+ static final int EVENT_CHANGE_SIM_PASSWORD_DONE = 9;
+ static final int EVENT_QUERY_FACILITY_FDN_DONE = 10;
+ static final int EVENT_CHANGE_FACILITY_FDN_DONE = 11;
+
+
+ //***** Constructor
+
+ GsmSimCard(GSMPhone phone)
+ {
+ this.phone = phone;
+
+ phone.mCM.registerForSIMLockedOrAbsent(
+ this, EVENT_SIM_LOCKED_OR_ABSENT, null);
+
+ phone.mCM.registerForOffOrNotAvailable(
+ this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+
+ phone.mCM.registerForSIMReady(
+ this, EVENT_SIM_READY, null);
+
+ updateStateProperty();
+ }
+
+ //***** SimCard implementation
+
+ public State
+ getState()
+ {
+ if (status == null) {
+ switch(phone.mCM.getRadioState()) {
+ /* This switch block must not return anything in
+ * State.isLocked() or State.ABSENT.
+ * If it does, handleSimStatus() may break
+ */
+ case RADIO_OFF:
+ case RADIO_UNAVAILABLE:
+ case SIM_NOT_READY:
+ return State.UNKNOWN;
+ case SIM_LOCKED_OR_ABSENT:
+ //this should be transient-only
+ return State.UNKNOWN;
+ case SIM_READY:
+ return State.READY;
+ }
+ } else {
+ switch (status) {
+ case SIM_ABSENT: return State.ABSENT;
+ case SIM_NOT_READY: return State.UNKNOWN;
+ case SIM_READY: return State.READY;
+ case SIM_PIN: return State.PIN_REQUIRED;
+ case SIM_PUK: return State.PUK_REQUIRED;
+ case SIM_NETWORK_PERSONALIZATION: return State.NETWORK_LOCKED;
+ }
+ }
+
+ Log.e(LOG_TAG, "GsmSimCard.getState(): case should never be reached");
+ return State.UNKNOWN;
+ }
+
+ private RegistrantList absentRegistrants = new RegistrantList();
+ private RegistrantList pinLockedRegistrants = new RegistrantList();
+ private RegistrantList networkLockedRegistrants = new RegistrantList();
+
+
+ public void registerForAbsent(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ absentRegistrants.add(r);
+
+ if (getState() == State.ABSENT) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForAbsent(Handler h) {
+ absentRegistrants.remove(h);
+ }
+
+ public void registerForNetworkLocked(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+
+ networkLockedRegistrants.add(r);
+
+ if (getState() == State.NETWORK_LOCKED) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForNetworkLocked(Handler h) {
+ networkLockedRegistrants.remove(h);
+ }
+
+ public void registerForLocked(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant (h, what, obj);
+
+ pinLockedRegistrants.add(r);
+
+ if (getState().isPinLocked()) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForLocked(Handler h)
+ {
+ pinLockedRegistrants.remove(h);
+ }
+
+
+ public void supplyPin (String pin, Message onComplete)
+ {
+ phone.mCM.supplySimPin(pin,
+ obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+
+ public void supplyPuk (String puk, String newPin, Message onComplete)
+ {
+ phone.mCM.supplySimPuk(puk, newPin,
+ obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+ public void supplyPin2 (String pin2, Message onComplete)
+ {
+ phone.mCM.supplySimPin2(pin2,
+ obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+ public void supplyPuk2 (String puk2, String newPin2, Message onComplete)
+ {
+ phone.mCM.supplySimPuk2(puk2, newPin2,
+ obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+
+ public void supplyNetworkDepersonalization (String pin, Message onComplete)
+ {
+ if(DBG) log("Network Despersonalization: " + pin);
+ phone.mCM.supplyNetworkDepersonalization(pin,
+ obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+
+ public boolean getSimLockEnabled() {
+ return mSimPinLocked;
+ }
+
+ public boolean getSimFdnEnabled() {
+ return mSimFdnEnabled;
+ }
+
+ public void setSimLockEnabled (boolean enabled,
+ String password, Message onComplete) {
+ int serviceClassX;
+ serviceClassX = CommandsInterface.SERVICE_CLASS_VOICE +
+ CommandsInterface.SERVICE_CLASS_DATA +
+ CommandsInterface.SERVICE_CLASS_FAX;
+
+ mDesiredPinLocked = enabled;
+
+ phone.mCM.setFacilityLock(CommandsInterface.CB_FACILITY_BA_SIM,
+ enabled, password, serviceClassX,
+ obtainMessage(EVENT_CHANGE_FACILITY_LOCK_DONE, onComplete));
+ }
+
+ public void setSimFdnEnabled (boolean enabled,
+ String password, Message onComplete) {
+ int serviceClassX;
+ serviceClassX = CommandsInterface.SERVICE_CLASS_VOICE +
+ CommandsInterface.SERVICE_CLASS_DATA +
+ CommandsInterface.SERVICE_CLASS_FAX +
+ CommandsInterface.SERVICE_CLASS_SMS;
+
+ mDesiredFdnEnabled = enabled;
+
+ phone.mCM.setFacilityLock(CommandsInterface.CB_FACILITY_BA_FD,
+ enabled, password, serviceClassX,
+ obtainMessage(EVENT_CHANGE_FACILITY_FDN_DONE, onComplete));
+ }
+
+ public void changeSimLockPassword(String oldPassword, String newPassword,
+ Message onComplete) {
+ if(DBG) log("Change Pin1 old: " + oldPassword + " new: " + newPassword);
+ phone.mCM.changeSimPin(oldPassword, newPassword,
+ obtainMessage(EVENT_CHANGE_SIM_PASSWORD_DONE, onComplete));
+
+ }
+
+ public void changeSimFdnPassword(String oldPassword, String newPassword,
+ Message onComplete) {
+ if(DBG) log("Change Pin2 old: " + oldPassword + " new: " + newPassword);
+ phone.mCM.changeSimPin2(oldPassword, newPassword,
+ obtainMessage(EVENT_CHANGE_SIM_PASSWORD_DONE, onComplete));
+
+ }
+
+ public String getServiceProviderName () {
+ return phone.mSIMRecords.getServiceProvideName();
+ }
+
+ //***** Handler implementation
+ @Override
+ public void handleMessage(Message msg){
+ AsyncResult ar;
+ int serviceClassX;
+
+ serviceClassX = CommandsInterface.SERVICE_CLASS_VOICE +
+ CommandsInterface.SERVICE_CLASS_DATA +
+ CommandsInterface.SERVICE_CLASS_FAX;
+
+ switch (msg.what) {
+ case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
+ status = null;
+ updateStateProperty();
+ broadcastSimStateChangedIntent(SimCard.INTENT_VALUE_SIM_NOT_READY, null);
+ break;
+ case EVENT_SIM_READY:
+ //TODO: put facility read in SIM_READY now, maybe in REG_NW
+ phone.mCM.getSimStatus(obtainMessage(EVENT_GET_SIM_STATUS_DONE));
+ phone.mCM.queryFacilityLock (
+ CommandsInterface.CB_FACILITY_BA_SIM, "", serviceClassX,
+ obtainMessage(EVENT_QUERY_FACILITY_LOCK_DONE));
+ phone.mCM.queryFacilityLock (
+ CommandsInterface.CB_FACILITY_BA_FD, "", serviceClassX,
+ obtainMessage(EVENT_QUERY_FACILITY_FDN_DONE));
+ break;
+ case EVENT_SIM_LOCKED_OR_ABSENT:
+ phone.mCM.getSimStatus(obtainMessage(EVENT_GET_SIM_STATUS_DONE));
+ phone.mCM.queryFacilityLock (
+ CommandsInterface.CB_FACILITY_BA_SIM, "", serviceClassX,
+ obtainMessage(EVENT_QUERY_FACILITY_LOCK_DONE));
+ break;
+ case EVENT_GET_SIM_STATUS_DONE:
+ ar = (AsyncResult)msg.obj;
+
+ getSimStatusDone(ar);
+ break;
+ case EVENT_PINPUK_DONE:
+ // a PIN/PUK/PIN2/PUK2/Network Personalization
+ // request has completed. ar.userObj is the response Message
+ // Repoll before returning
+ ar = (AsyncResult)msg.obj;
+ // TODO should abstract these exceptions
+ AsyncResult.forMessage(((Message)ar.userObj)).exception
+ = ar.exception;
+ phone.mCM.getSimStatus(
+ obtainMessage(EVENT_REPOLL_STATUS_DONE, ar.userObj));
+ break;
+ case EVENT_REPOLL_STATUS_DONE:
+ // Finished repolling status after PIN operation
+ // ar.userObj is the response messaeg
+ // ar.userObj.obj is already an AsyncResult with an
+ // appropriate exception filled in if applicable
+
+ ar = (AsyncResult)msg.obj;
+ getSimStatusDone(ar);
+ ((Message)ar.userObj).sendToTarget();
+ break;
+ case EVENT_QUERY_FACILITY_LOCK_DONE:
+ ar = (AsyncResult)msg.obj;
+ onQueryFacilityLock(ar);
+ break;
+ case EVENT_QUERY_FACILITY_FDN_DONE:
+ ar = (AsyncResult)msg.obj;
+ onQueryFdnEnabled(ar);
+ break;
+ case EVENT_CHANGE_FACILITY_LOCK_DONE:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ mSimPinLocked = mDesiredPinLocked;
+ if (DBG) log( "EVENT_CHANGE_FACILITY_LOCK_DONE: " +
+ "mSimPinLocked= " + mSimPinLocked);
+ } else {
+ Log.e(LOG_TAG, "Error change facility lock with exception "
+ + ar.exception);
+ }
+ AsyncResult.forMessage(((Message)ar.userObj)).exception
+ = ar.exception;
+ ((Message)ar.userObj).sendToTarget();
+ break;
+ case EVENT_CHANGE_FACILITY_FDN_DONE:
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception == null) {
+ mSimFdnEnabled = mDesiredFdnEnabled;
+ if (DBG) log("EVENT_CHANGE_FACILITY_FDN_DONE: " +
+ "mSimFdnEnabled=" + mSimFdnEnabled);
+ } else {
+ Log.e(LOG_TAG, "Error change facility fdn with exception "
+ + ar.exception);
+ }
+ AsyncResult.forMessage(((Message)ar.userObj)).exception
+ = ar.exception;
+ ((Message)ar.userObj).sendToTarget();
+ break;
+ case EVENT_CHANGE_SIM_PASSWORD_DONE:
+ ar = (AsyncResult)msg.obj;
+ if(ar.exception != null) {
+ Log.e(LOG_TAG, "Error in change sim password with exception"
+ + ar.exception);
+ }
+ AsyncResult.forMessage(((Message)ar.userObj)).exception
+ = ar.exception;
+ ((Message)ar.userObj).sendToTarget();
+ break;
+ default:
+ Log.e(LOG_TAG, "[GsmSimCard] Unknown Event " + msg.what);
+ }
+ }
+
+
+ //***** Private methods
+
+ /**
+ * Interperate EVENT_QUERY_FACILITY_LOCK_DONE
+ * @param ar is asyncResult of Query_Facility_Locked
+ */
+ private void onQueryFacilityLock(AsyncResult ar) {
+ if(ar.exception != null) {
+ if (DBG) log("Error in querying facility lock:" + ar.exception);
+ return;
+ }
+
+ int[] ints = (int[])ar.result;
+ if(ints.length != 0) {
+ mSimPinLocked = (0!=ints[0]);
+ if(DBG) log("Query facility lock : " + mSimPinLocked);
+ } else {
+ Log.e(LOG_TAG, "[GsmSimCard] Bogus facility lock response");
+ }
+ }
+
+ /**
+ * Interperate EVENT_QUERY_FACILITY_LOCK_DONE
+ * @param ar is asyncResult of Query_Facility_Locked
+ */
+ private void onQueryFdnEnabled(AsyncResult ar) {
+ if(ar.exception != null) {
+ if(DBG) log("Error in querying facility lock:" + ar.exception);
+ return;
+ }
+
+ int[] ints = (int[])ar.result;
+ if(ints.length != 0) {
+ mSimFdnEnabled = (0!=ints[0]);
+ if(DBG) log("Query facility lock : " + mSimFdnEnabled);
+ } else {
+ Log.e(LOG_TAG, "[GsmSimCard] Bogus facility lock response");
+ }
+ }
+
+ private void
+ getSimStatusDone(AsyncResult ar) {
+ if (ar.exception != null) {
+ Log.e(LOG_TAG,"Error getting SIM status. "
+ + "RIL_REQUEST_GET_SIM_STATUS should "
+ + "never return an error", ar.exception);
+ return;
+ }
+
+ CommandsInterface.SimStatus newStatus
+ = (CommandsInterface.SimStatus) ar.result;
+
+ handleSimStatus(newStatus);
+ }
+
+ private void
+ handleSimStatus(CommandsInterface.SimStatus newStatus) {
+ boolean transitionedIntoPinLocked;
+ boolean transitionedIntoAbsent;
+ boolean transitionedIntoNetworkLocked;
+
+ SimCard.State oldState, newState;
+
+ oldState = getState();
+ status = newStatus;
+ newState = getState();
+
+ updateStateProperty();
+
+ transitionedIntoPinLocked = (
+ (oldState != State.PIN_REQUIRED && newState == State.PIN_REQUIRED)
+ || (oldState != State.PUK_REQUIRED && newState == State.PUK_REQUIRED));
+ transitionedIntoAbsent = (oldState != State.ABSENT && newState == State.ABSENT);
+ transitionedIntoNetworkLocked = (oldState != State.NETWORK_LOCKED
+ && newState == State.NETWORK_LOCKED);
+
+ if (transitionedIntoPinLocked) {
+ if(DBG) log("Notify SIM pin or puk locked.");
+ pinLockedRegistrants.notifyRegistrants();
+ broadcastSimStateChangedIntent(SimCard.INTENT_VALUE_SIM_LOCKED,
+ (newState == State.PIN_REQUIRED) ?
+ INTENT_VALUE_LOCKED_ON_PIN : INTENT_VALUE_LOCKED_ON_PUK);
+ } else if (transitionedIntoAbsent) {
+ if(DBG) log("Notify SIM missing.");
+ absentRegistrants.notifyRegistrants();
+ broadcastSimStateChangedIntent(SimCard.INTENT_VALUE_SIM_ABSENT, null);
+ } else if (transitionedIntoNetworkLocked) {
+ if(DBG) log("Notify SIM network locked.");
+ networkLockedRegistrants.notifyRegistrants();
+ broadcastSimStateChangedIntent(SimCard.INTENT_VALUE_SIM_LOCKED,
+ INTENT_VALUE_LOCKED_NETWORK);
+ }
+ }
+
+ public void broadcastSimStateChangedIntent(String value, String reason) {
+ Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ intent.putExtra(Phone.PHONE_NAME_KEY, phone.getPhoneName());
+ intent.putExtra(SimCard.INTENT_KEY_SIM_STATE, value);
+ intent.putExtra(SimCard.INTENT_KEY_LOCKED_REASON, reason);
+ if(DBG) log("Broadcasting intent SIM_STATE_CHANGED_ACTION " + value
+ + " reason " + reason);
+ ActivityManagerNative.broadcastStickyIntent(intent, READ_PHONE_STATE);
+ }
+
+ public void updateImsiConfiguration(String imsi) {
+ if (imsi.length() >= 6) {
+ Configuration config = new Configuration();
+ config.mcc = ((imsi.charAt(0)-'0')*100)
+ + ((imsi.charAt(1)-'0')*10)
+ + (imsi.charAt(2)-'0');
+ config.mnc = ((imsi.charAt(3)-'0')*100)
+ + ((imsi.charAt(4)-'0')*10)
+ + (imsi.charAt(5)-'0');
+ try {
+ ActivityManagerNative.getDefault().updateConfiguration(config);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ private void
+ updateStateProperty() {
+ phone.setSystemProperty(
+ TelephonyProperties.PROPERTY_SIM_STATE,
+ getState().toString());
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[GsmSimCard] " + msg);
+ }
+}
+
diff --git a/telephony/java/com/android/internal/telephony/gsm/ISimPhoneBook.aidl b/telephony/java/com/android/internal/telephony/gsm/ISimPhoneBook.aidl
new file mode 100644
index 0000000..77033a7
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/ISimPhoneBook.aidl
@@ -0,0 +1,101 @@
+/*
+** Copyright 2007, 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.internal.telephony.gsm;
+
+import com.android.internal.telephony.gsm.AdnRecord;
+
+import java.util.List;
+
+/** Interface for applications to access the SIM phone book.
+ *
+ * <p>The following code snippet demonstrates a static method to
+ * retrieve the ISimPhoneBook interface from Android:</p>
+ * <pre>private static ISimPhoneBook getSimPhoneBookInterface()
+ throws DeadObjectException {
+ IServiceManager sm = ServiceManagerNative.getDefault();
+ ISimPhoneBook spb;
+ spb = ISimPhoneBook.Stub.asInterface(sm.getService("simphonebook"));
+ return spb;
+}
+ * </pre>
+ */
+
+interface ISimPhoneBook {
+
+ /**
+ * Loads the AdnRecords in efid and returns them as a
+ * List of AdnRecords
+ *
+ * @param efid the EF id of a ADN-like SIM
+ * @return List of AdnRecord
+ */
+ List<AdnRecord> getAdnRecordsInEf(int efid);
+
+ /**
+ * Replace oldAdn with newAdn in ADN-like record in EF
+ *
+ * getAdnRecordsInEf must be called at least once before this function,
+ * otherwise an error will be returned
+ *
+ * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
+ * @param oldTag adn tag to be replaced
+ * @param oldPhoneNumber adn number to be replaced
+ * Set both oldTag and oldPhoneNubmer to "" means to replace an
+ * empty record, aka, insert new record
+ * @param newTag adn tag to be stored
+ * @param newPhoneNumber adn number ot be stored
+ * Set both newTag and newPhoneNubmer to "" means to replace the old
+ * record with empty one, aka, delete old record
+ * @param pin2 required to update EF_FDN, otherwise must be null
+ * @return true for success
+ */
+ boolean updateAdnRecordsInEfBySearch(int efid,
+ String oldTag, String oldPhoneNumber,
+ String newTag, String newPhoneNumber,
+ String pin2);
+
+ /**
+ * Update an ADN-like EF record by record index
+ *
+ * This is useful for iteration the whole ADN file, such as write the whole
+ * phone book or erase/format the whole phonebook
+ *
+ * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
+ * @param newTag adn tag to be stored
+ * @param newPhoneNumber adn number to be stored
+ * Set both newTag and newPhoneNubmer to "" means to replace the old
+ * record with empty one, aka, delete old record
+ * @param index is 1-based adn record index to be updated
+ * @param pin2 required to update EF_FDN, otherwise must be null
+ * @return true for success
+ */
+ boolean updateAdnRecordsInEfByIndex(int efid, String newTag,
+ String newPhoneNumber, int index,
+ String pin2);
+
+ /**
+ * Get the max munber of records in efid
+ *
+ * @param efid the EF id of a ADN-like SIM
+ * @return int[3] array
+ * recordSizes[0] is the single record length
+ * recordSizes[1] is the total length of the EF file
+ * recordSizes[2] is the number of records in the EF file
+ */
+ int[] getAdnRecordsSize(int efid);
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/ISms.aidl b/telephony/java/com/android/internal/telephony/gsm/ISms.aidl
new file mode 100644
index 0000000..904a54e
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/ISms.aidl
@@ -0,0 +1,115 @@
+/*
+** Copyright 2007, 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.internal.telephony.gsm;
+
+import android.app.PendingIntent;
+import com.android.internal.telephony.gsm.SmsRawData;
+
+/** Interface for applications to access the SIM phone book.
+ *
+ * <p>The following code snippet demonstrates a static method to
+ * retrieve the ISimSms interface from Android:</p>
+ * <pre>private static ISimSms getSimSmsInterface()
+ throws DeadObjectException {
+ IServiceManager sm = ServiceManagerNative.getDefault();
+ ISimSms ss;
+ ss = ISimSms.Stub.asInterface(sm.getService("isms"));
+ return ss;
+}
+ * </pre>
+ */
+
+interface ISms {
+ /**
+ * Retrieves all messages currently stored on SIM.
+ *
+ * @return list of SmsRawData of all sms on SIM
+ */
+ List<SmsRawData> getAllMessagesFromSimEf();
+
+ /**
+ * Update the specified message on the SIM.
+ *
+ * @param messageIndex record index of message to update
+ * @param newStatus new message status (STATUS_ON_SIM_READ,
+ * STATUS_ON_SIM_UNREAD, STATUS_ON_SIM_SENT,
+ * STATUS_ON_SIM_UNSENT, STATUS_ON_SIM_FREE)
+ * @param pdu the raw PDU to store
+ * @return success or not
+ *
+ */
+ boolean updateMessageOnSimEf(int messageIndex, int newStatus,
+ in byte[] pdu);
+
+ /**
+ * Copy a raw SMS PDU to the SIM.
+ *
+ * @param pdu the raw PDU to store
+ * @param status message status (STATUS_ON_SIM_READ, STATUS_ON_SIM_UNREAD,
+ * STATUS_ON_SIM_SENT, STATUS_ON_SIM_UNSENT)
+ * @return success or not
+ *
+ */
+ boolean copyMessageToSimEf(int status, in byte[] pdu, in byte[] smsc);
+
+ /**
+ * Send a SMS
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * defatult SMSC
+ * @param pdu the raw PDU to send
+ * @param sentIntent if not NULL this <code>Intent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * @param deliveryIntent if not NULL this <code>Intent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ */
+ void sendRawPdu(in byte[] smsc, in byte[] pdu, in PendingIntent sentIntent,
+ in PendingIntent deliveryIntent);
+
+ /**
+ * Send a multi-part text based SMS.
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ */
+ void sendMultipartText(in String destinationAddress, in String scAddress,
+ in List<String> parts, in List<PendingIntent> sentIntents,
+ in List<PendingIntent> deliveryIntents);
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/MccTable.java b/telephony/java/com/android/internal/telephony/gsm/MccTable.java
new file mode 100644
index 0000000..57aa012
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/MccTable.java
@@ -0,0 +1,363 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Mobile Country Code
+ *
+ * {@hide}
+ */
+public final class MccTable
+{
+ static ArrayList<MccEntry> table;
+
+ static class MccEntry implements Comparable<MccEntry>
+ {
+ int mcc;
+ String iso;
+ int smallestDigitsMnc;
+
+ MccEntry(int mnc, String iso, int smallestDigitsMCC)
+ {
+ this.mcc = mnc;
+ this.iso = iso;
+ this.smallestDigitsMnc = smallestDigitsMCC;
+ }
+
+ public int compareTo(MccEntry o)
+ {
+ return mcc - o.mcc;
+ }
+ }
+
+ private static MccEntry
+ entryForMcc(int mcc)
+ {
+ int index;
+
+ MccEntry m;
+
+ m = new MccEntry(mcc, null, 0);
+
+ index = Collections.binarySearch(table, m);
+
+ if (index < 0) {
+ return null;
+ } else {
+ return table.get(index);
+ }
+ }
+
+ /**
+ * Given a GSM Mobile Country Code, returns
+ * an ISO two-character country code if available.
+ * Returns "" if unavailable.
+ */
+ public static String
+ countryCodeForMcc(int mcc)
+ {
+ MccEntry entry;
+
+ entry = entryForMcc(mcc);
+
+ if (entry == null) {
+ return "";
+ } else {
+ return entry.iso;
+ }
+ }
+
+
+ /**
+ * Given a GSM Mobile Country Code, returns
+ * the smallest number of digits that M if available.
+ * Returns "" if unavailable.
+ */
+ public static int
+ smallestDigitsMccForMnc(int mcc)
+ {
+ MccEntry entry;
+
+ entry = entryForMcc(mcc);
+
+ if (entry == null) {
+ return 2;
+ } else {
+ return entry.smallestDigitsMnc;
+ }
+ }
+
+ static {
+ table = new ArrayList<MccEntry>(240);
+
+
+ /*
+ * The table below is built from two resources:
+ *
+ * 1) ITU "Mobile Network Code (MNC) for the international
+ * identification plan for mobile terminals and mobile users"
+ * which is available as an annex to the ITU operational bulletin
+ * available here: http://www.itu.int/itu-t/bulletin/annex.html
+ *
+ * 2) The ISO 3166 country codes list, available here:
+ * http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/index.html
+ *
+ * This table has not been verified.
+ *
+ * FIXME(mkf) this should be stored in a more efficient representation
+ */
+
+ table.add(new MccEntry(202,"gr",2)); //Greece
+ table.add(new MccEntry(204,"nl",2)); //Netherlands (Kingdom of the)
+ table.add(new MccEntry(206,"be",2)); //Belgium
+ table.add(new MccEntry(208,"fr",2)); //France
+ table.add(new MccEntry(212,"mc",2)); //Monaco (Principality of)
+ table.add(new MccEntry(213,"ad",2)); //Andorra (Principality of)
+ table.add(new MccEntry(214,"es",2)); //Spain
+ table.add(new MccEntry(216,"hu",2)); //Hungary (Republic of)
+ table.add(new MccEntry(218,"ba",2)); //Bosnia and Herzegovina
+ table.add(new MccEntry(219,"hr",2)); //Croatia (Republic of)
+ table.add(new MccEntry(220,"rs",2)); //Serbia and Montenegro
+ table.add(new MccEntry(222,"it",2)); //Italy
+ table.add(new MccEntry(225,"va",2)); //Vatican City State
+ table.add(new MccEntry(226,"ro",2)); //Romania
+ table.add(new MccEntry(228,"ch",2)); //Switzerland (Confederation of)
+ table.add(new MccEntry(230,"cz",2)); //Czech Republic
+ table.add(new MccEntry(231,"sk",2)); //Slovak Republic
+ table.add(new MccEntry(232,"at",2)); //Austria
+ table.add(new MccEntry(234,"gb",2)); //United Kingdom of Great Britain and Northern Ireland
+ table.add(new MccEntry(235,"gb",2)); //United Kingdom of Great Britain and Northern Ireland
+ table.add(new MccEntry(238,"dk",2)); //Denmark
+ table.add(new MccEntry(240,"se",2)); //Sweden
+ table.add(new MccEntry(242,"no",2)); //Norway
+ table.add(new MccEntry(244,"fi",2)); //Finland
+ table.add(new MccEntry(246,"lt",2)); //Lithuania (Republic of)
+ table.add(new MccEntry(247,"lv",2)); //Latvia (Republic of)
+ table.add(new MccEntry(248,"ee",2)); //Estonia (Republic of)
+ table.add(new MccEntry(250,"ru",2)); //Russian Federation
+ table.add(new MccEntry(255,"ua",2)); //Ukraine
+ table.add(new MccEntry(257,"by",2)); //Belarus (Republic of)
+ table.add(new MccEntry(259,"md",2)); //Moldova (Republic of)
+ table.add(new MccEntry(260,"pl",2)); //Poland (Republic of)
+ table.add(new MccEntry(262,"de",2)); //Germany (Federal Republic of)
+ table.add(new MccEntry(266,"gi",2)); //Gibraltar
+ table.add(new MccEntry(268,"pt",2)); //Portugal
+ table.add(new MccEntry(270,"lu",2)); //Luxembourg
+ table.add(new MccEntry(272,"ie",2)); //Ireland
+ table.add(new MccEntry(274,"is",2)); //Iceland
+ table.add(new MccEntry(276,"al",2)); //Albania (Republic of)
+ table.add(new MccEntry(278,"mt",2)); //Malta
+ table.add(new MccEntry(280,"cy",2)); //Cyprus (Republic of)
+ table.add(new MccEntry(282,"ge",2)); //Georgia
+ table.add(new MccEntry(283,"am",2)); //Armenia (Republic of)
+ table.add(new MccEntry(284,"bg",2)); //Bulgaria (Republic of)
+ table.add(new MccEntry(286,"tr",2)); //Turkey
+ table.add(new MccEntry(288,"fo",2)); //Faroe Islands
+ table.add(new MccEntry(290,"gl",2)); //Greenland (Denmark)
+ table.add(new MccEntry(292,"sm",2)); //San Marino (Republic of)
+ table.add(new MccEntry(293,"sl",2)); //Slovenia (Republic of)
+ table.add(new MccEntry(294,"mk",2)); //The Former Yugoslav Republic of Macedonia
+ table.add(new MccEntry(295,"li",2)); //Liechtenstein (Principality of)
+ table.add(new MccEntry(302,"ca",2)); //Canada
+ table.add(new MccEntry(308,"pm",2)); //Saint Pierre and Miquelon (Collectivit territoriale de la Rpublique franaise)
+ table.add(new MccEntry(310,"us",3)); //United States of America
+ table.add(new MccEntry(311,"us",3)); //United States of America
+ table.add(new MccEntry(312,"us",3)); //United States of America
+ table.add(new MccEntry(313,"us",3)); //United States of America
+ table.add(new MccEntry(314,"us",3)); //United States of America
+ table.add(new MccEntry(315,"us",3)); //United States of America
+ table.add(new MccEntry(316,"us",3)); //United States of America
+ table.add(new MccEntry(330,"pr",2)); //Puerto Rico
+ table.add(new MccEntry(332,"vi",2)); //United States Virgin Islands
+ table.add(new MccEntry(334,"mx",3)); //Mexico
+ table.add(new MccEntry(338,"jm",3)); //Jamaica
+ table.add(new MccEntry(340,"gp",2)); //Guadeloupe (French Department of)
+ table.add(new MccEntry(342,"bb",3)); //Barbados
+ table.add(new MccEntry(344,"ag",3)); //Antigua and Barbuda
+ table.add(new MccEntry(346,"ky",3)); //Cayman Islands
+ table.add(new MccEntry(348,"vg",3)); //British Virgin Islands
+ table.add(new MccEntry(350,"bm",2)); //Bermuda
+ table.add(new MccEntry(352,"gd",2)); //Grenada
+ table.add(new MccEntry(354,"ms",2)); //Montserrat
+ table.add(new MccEntry(356,"kn",2)); //Saint Kitts and Nevis
+ table.add(new MccEntry(358,"lc",2)); //Saint Lucia
+ table.add(new MccEntry(360,"vc",2)); //Saint Vincent and the Grenadines
+ table.add(new MccEntry(362,"nl",2)); //Netherlands Antilles
+ table.add(new MccEntry(363,"aw",2)); //Aruba
+ table.add(new MccEntry(364,"bs",2)); //Bahamas (Commonwealth of the)
+ table.add(new MccEntry(365,"ai",3)); //Anguilla
+ table.add(new MccEntry(366,"dm",2)); //Dominica (Commonwealth of)
+ table.add(new MccEntry(368,"cu",2)); //Cuba
+ table.add(new MccEntry(370,"do",2)); //Dominican Republic
+ table.add(new MccEntry(372,"ht",2)); //Haiti (Republic of)
+ table.add(new MccEntry(374,"tt",2)); //Trinidad and Tobago
+ table.add(new MccEntry(376,"tc",2)); //Turks and Caicos Islands
+ table.add(new MccEntry(400,"az",2)); //Azerbaijani Republic
+ table.add(new MccEntry(401,"kz",2)); //Kazakhstan (Republic of)
+ table.add(new MccEntry(402,"bt",2)); //Bhutan (Kingdom of)
+ table.add(new MccEntry(404,"in",2)); //India (Republic of)
+ table.add(new MccEntry(405,"in",2)); //India (Republic of)
+ table.add(new MccEntry(410,"pk",2)); //Pakistan (Islamic Republic of)
+ table.add(new MccEntry(412,"af",2)); //Afghanistan
+ table.add(new MccEntry(413,"lk",2)); //Sri Lanka (Democratic Socialist Republic of)
+ table.add(new MccEntry(414,"mm",2)); //Myanmar (Union of)
+ table.add(new MccEntry(415,"lb",2)); //Lebanon
+ table.add(new MccEntry(416,"jo",2)); //Jordan (Hashemite Kingdom of)
+ table.add(new MccEntry(417,"sy",2)); //Syrian Arab Republic
+ table.add(new MccEntry(418,"iq",2)); //Iraq (Republic of)
+ table.add(new MccEntry(419,"kw",2)); //Kuwait (State of)
+ table.add(new MccEntry(420,"sa",2)); //Saudi Arabia (Kingdom of)
+ table.add(new MccEntry(421,"ye",2)); //Yemen (Republic of)
+ table.add(new MccEntry(422,"om",2)); //Oman (Sultanate of)
+ table.add(new MccEntry(424,"ae",2)); //United Arab Emirates
+ table.add(new MccEntry(425,"il",2)); //Israel (State of)
+ table.add(new MccEntry(426,"bh",2)); //Bahrain (Kingdom of)
+ table.add(new MccEntry(427,"qa",2)); //Qatar (State of)
+ table.add(new MccEntry(428,"mn",2)); //Mongolia
+ table.add(new MccEntry(429,"np",2)); //Nepal
+ table.add(new MccEntry(430,"ae",2)); //United Arab Emirates
+ table.add(new MccEntry(431,"ae",2)); //United Arab Emirates
+ table.add(new MccEntry(432,"ir",2)); //Iran (Islamic Republic of)
+ table.add(new MccEntry(434,"uz",2)); //Uzbekistan (Republic of)
+ table.add(new MccEntry(436,"tj",2)); //Tajikistan (Republic of)
+ table.add(new MccEntry(437,"kg",2)); //Kyrgyz Republic
+ table.add(new MccEntry(438,"tm",2)); //Turkmenistan
+ table.add(new MccEntry(440,"jp",2)); //Japan
+ table.add(new MccEntry(441,"jp",2)); //Japan
+ table.add(new MccEntry(450,"kr",2)); //Korea (Republic of)
+ table.add(new MccEntry(452,"vn",2)); //Viet Nam (Socialist Republic of)
+ table.add(new MccEntry(454,"hk",2)); //"Hong Kong, China"
+ table.add(new MccEntry(455,"mo",2)); //"Macao, China"
+ table.add(new MccEntry(456,"kh",2)); //Cambodia (Kingdom of)
+ table.add(new MccEntry(457,"la",2)); //Lao People's Democratic Republic
+ table.add(new MccEntry(460,"cn",2)); //China (People's Republic of)
+ table.add(new MccEntry(461,"cn",2)); //China (People's Republic of)
+ table.add(new MccEntry(466,"tw",2)); //"Taiwan, China"
+ table.add(new MccEntry(467,"kp",2)); //Democratic People's Republic of Korea
+ table.add(new MccEntry(470,"bd",2)); //Bangladesh (People's Republic of)
+ table.add(new MccEntry(472,"mv",2)); //Maldives (Republic of)
+ table.add(new MccEntry(502,"my",2)); //Malaysia
+ table.add(new MccEntry(505,"au",2)); //Australia
+ table.add(new MccEntry(510,"id",2)); //Indonesia (Republic of)
+ table.add(new MccEntry(514,"tl",2)); //Democratic Republic of Timor-Leste
+ table.add(new MccEntry(515,"ph",2)); //Philippines (Republic of the)
+ table.add(new MccEntry(520,"th",2)); //Thailand
+ table.add(new MccEntry(525,"sg",2)); //Singapore (Republic of)
+ table.add(new MccEntry(528,"bn",2)); //Brunei Darussalam
+ table.add(new MccEntry(530,"nz",2)); //New Zealand
+ table.add(new MccEntry(534,"mp",2)); //Northern Mariana Islands (Commonwealth of the)
+ table.add(new MccEntry(535,"gu",2)); //Guam
+ table.add(new MccEntry(536,"nr",2)); //Nauru (Republic of)
+ table.add(new MccEntry(537,"pg",2)); //Papua New Guinea
+ table.add(new MccEntry(539,"to",2)); //Tonga (Kingdom of)
+ table.add(new MccEntry(540,"sb",2)); //Solomon Islands
+ table.add(new MccEntry(541,"vu",2)); //Vanuatu (Republic of)
+ table.add(new MccEntry(542,"fj",2)); //Fiji (Republic of)
+ table.add(new MccEntry(543,"wf",2)); //Wallis and Futuna (Territoire franais d'outre-mer)
+ table.add(new MccEntry(544,"as",2)); //American Samoa
+ table.add(new MccEntry(545,"ki",2)); //Kiribati (Republic of)
+ table.add(new MccEntry(546,"nc",2)); //New Caledonia (Territoire franais d'outre-mer)
+ table.add(new MccEntry(547,"pf",2)); //French Polynesia (Territoire franais d'outre-mer)
+ table.add(new MccEntry(548,"ck",2)); //Cook Islands
+ table.add(new MccEntry(549,"ws",2)); //Samoa (Independent State of)
+ table.add(new MccEntry(550,"fm",2)); //Micronesia (Federated States of)
+ table.add(new MccEntry(551,"mh",2)); //Marshall Islands (Republic of the)
+ table.add(new MccEntry(552,"pw",2)); //Palau (Republic of)
+ table.add(new MccEntry(602,"eg",2)); //Egypt (Arab Republic of)
+ table.add(new MccEntry(603,"dz",2)); //Algeria (People's Democratic Republic of)
+ table.add(new MccEntry(604,"ma",2)); //Morocco (Kingdom of)
+ table.add(new MccEntry(605,"tn",2)); //Tunisia
+ table.add(new MccEntry(606,"ly",2)); //Libya (Socialist People's Libyan Arab Jamahiriya)
+ table.add(new MccEntry(607,"gm",2)); //Gambia (Republic of the)
+ table.add(new MccEntry(608,"sn",2)); //Senegal (Republic of)
+ table.add(new MccEntry(609,"mr",2)); //Mauritania (Islamic Republic of)
+ table.add(new MccEntry(610,"ml",2)); //Mali (Republic of)
+ table.add(new MccEntry(611,"gn",2)); //Guinea (Republic of)
+ table.add(new MccEntry(612,"ci",2)); //Cte d'Ivoire (Republic of)
+ table.add(new MccEntry(613,"bf",2)); //Burkina Faso
+ table.add(new MccEntry(614,"ne",2)); //Niger (Republic of the)
+ table.add(new MccEntry(615,"tg",2)); //Togolese Republic
+ table.add(new MccEntry(616,"bj",2)); //Benin (Republic of)
+ table.add(new MccEntry(617,"mu",2)); //Mauritius (Republic of)
+ table.add(new MccEntry(618,"lr",2)); //Liberia (Republic of)
+ table.add(new MccEntry(619,"sl",2)); //Sierra Leone
+ table.add(new MccEntry(620,"gh",2)); //Ghana
+ table.add(new MccEntry(621,"ng",2)); //Nigeria (Federal Republic of)
+ table.add(new MccEntry(622,"td",2)); //Chad (Republic of)
+ table.add(new MccEntry(623,"cf",2)); //Central African Republic
+ table.add(new MccEntry(624,"cm",2)); //Cameroon (Republic of)
+ table.add(new MccEntry(625,"cv",2)); //Cape Verde (Republic of)
+ table.add(new MccEntry(626,"st",2)); //Sao Tome and Principe (Democratic Republic of)
+ table.add(new MccEntry(627,"gq",2)); //Equatorial Guinea (Republic of)
+ table.add(new MccEntry(628,"ga",2)); //Gabonese Republic
+ table.add(new MccEntry(629,"cg",2)); //Congo (Republic of the)
+ table.add(new MccEntry(630,"cg",2)); //Democratic Republic of the Congo
+ table.add(new MccEntry(631,"ao",2)); //Angola (Republic of)
+ table.add(new MccEntry(632,"gw",2)); //Guinea-Bissau (Republic of)
+ table.add(new MccEntry(633,"sc",2)); //Seychelles (Republic of)
+ table.add(new MccEntry(634,"sd",2)); //Sudan (Republic of the)
+ table.add(new MccEntry(635,"rw",2)); //Rwanda (Republic of)
+ table.add(new MccEntry(636,"et",2)); //Ethiopia (Federal Democratic Republic of)
+ table.add(new MccEntry(637,"so",2)); //Somali Democratic Republic
+ table.add(new MccEntry(638,"dj",2)); //Djibouti (Republic of)
+ table.add(new MccEntry(639,"ke",2)); //Kenya (Republic of)
+ table.add(new MccEntry(640,"tz",2)); //Tanzania (United Republic of)
+ table.add(new MccEntry(641,"ug",2)); //Uganda (Republic of)
+ table.add(new MccEntry(642,"bi",2)); //Burundi (Republic of)
+ table.add(new MccEntry(643,"mz",2)); //Mozambique (Republic of)
+ table.add(new MccEntry(645,"zm",2)); //Zambia (Republic of)
+ table.add(new MccEntry(646,"mg",2)); //Madagascar (Republic of)
+ table.add(new MccEntry(647,"re",2)); //Reunion (French Department of)
+ table.add(new MccEntry(648,"zw",2)); //Zimbabwe (Republic of)
+ table.add(new MccEntry(649,"na",2)); //Namibia (Republic of)
+ table.add(new MccEntry(650,"mw",2)); //Malawi
+ table.add(new MccEntry(651,"ls",2)); //Lesotho (Kingdom of)
+ table.add(new MccEntry(652,"bw",2)); //Botswana (Republic of)
+ table.add(new MccEntry(653,"sz",2)); //Swaziland (Kingdom of)
+ table.add(new MccEntry(654,"km",2)); //Comoros (Union of the)
+ table.add(new MccEntry(655,"za",2)); //South Africa (Republic of)
+ table.add(new MccEntry(657,"er",2)); //Eritrea
+ table.add(new MccEntry(702,"bz",2)); //Belize
+ table.add(new MccEntry(704,"gt",2)); //Guatemala (Republic of)
+ table.add(new MccEntry(706,"sv",2)); //El Salvador (Republic of)
+ table.add(new MccEntry(708,"hn",3)); //Honduras (Republic of)
+ table.add(new MccEntry(710,"ni",2)); //Nicaragua
+ table.add(new MccEntry(712,"cr",2)); //Costa Rica
+ table.add(new MccEntry(714,"pa",2)); //Panama (Republic of)
+ table.add(new MccEntry(716,"pe",2)); //Peru
+ table.add(new MccEntry(722,"ar",3)); //Argentine Republic
+ table.add(new MccEntry(724,"br",2)); //Brazil (Federative Republic of)
+ table.add(new MccEntry(730,"cl",2)); //Chile
+ table.add(new MccEntry(732,"co",3)); //Colombia (Republic of)
+ table.add(new MccEntry(734,"ve",2)); //Venezuela (Bolivarian Republic of)
+ table.add(new MccEntry(736,"bo",2)); //Bolivia (Republic of)
+ table.add(new MccEntry(738,"gy",2)); //Guyana
+ table.add(new MccEntry(740,"ec",2)); //Ecuador
+ table.add(new MccEntry(742,"gf",2)); //French Guiana (French Department of)
+ table.add(new MccEntry(744,"py",2)); //Paraguay (Republic of)
+ table.add(new MccEntry(746,"sr",2)); //Suriname (Republic of)
+ table.add(new MccEntry(748,"uy",2)); //Uruguay (Eastern Republic of)
+ table.add(new MccEntry(750,"fk",2)); //Falkland Islands (Malvinas)
+ //table.add(new MccEntry(901,"",2)); //"International Mobile, shared code"
+
+ Collections.sort(table);
+ }
+}
+
diff --git a/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.aidl b/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.aidl
new file mode 100644
index 0000000..c600530
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2008 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.internal.telephony.gsm;
+
+/**
+ * Used to indicate that the NetworkInfo object is parcelable to aidl.
+ * This is a simple effort to make NetworkInfo parcelable rather than
+ * trying to make the conventional containing object (AsyncResult),
+ * implement parcelable. This functionality is needed for the
+ * NetworkQueryService to fix 1128695
+ */
+parcelable NetworkInfo;
diff --git a/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.java b/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.java
new file mode 100644
index 0000000..bebf9ba
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.java
@@ -0,0 +1,158 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * {@hide}
+ */
+public class NetworkInfo implements Parcelable
+{
+ public enum State {
+ UNKNOWN,
+ AVAILABLE,
+ CURRENT,
+ FORBIDDEN;
+ }
+
+ String operatorAlphaLong;
+ String operatorAlphaShort;
+ String operatorNumeric;
+
+ State state = State.UNKNOWN;
+
+
+ public String
+ getOperatorAlphaLong()
+ {
+ return operatorAlphaLong;
+ }
+
+ public String
+ getOperatorAlphaShort()
+ {
+ return operatorAlphaShort;
+ }
+
+ public String
+ getOperatorNumeric()
+ {
+ return operatorNumeric;
+ }
+
+ public State
+ getState()
+ {
+ return state;
+ }
+
+ NetworkInfo(String operatorAlphaLong,
+ String operatorAlphaShort,
+ String operatorNumeric,
+ State state)
+ {
+
+ this.operatorAlphaLong = operatorAlphaLong;
+ this.operatorAlphaShort = operatorAlphaShort;
+ this.operatorNumeric = operatorNumeric;
+
+ this.state = state;
+ }
+
+
+ NetworkInfo(String operatorAlphaLong,
+ String operatorAlphaShort,
+ String operatorNumeric,
+ String stateString)
+ {
+ this (operatorAlphaLong, operatorAlphaShort,
+ operatorNumeric, rilStateToState(stateString));
+ }
+
+ /**
+ * See state strings defined in ril.h RIL_REQUEST_QUERY_AVAILABLE_NETWORKS
+ */
+ private static State rilStateToState(String s)
+ {
+ if (s.equals("unknown")) {
+ return State.UNKNOWN;
+ } else if (s.equals("available")) {
+ return State.AVAILABLE;
+ } else if (s.equals("current")) {
+ return State.CURRENT;
+ } else if (s.equals("forbidden")) {
+ return State.FORBIDDEN;
+ } else {
+ throw new RuntimeException(
+ "RIL impl error: Invalid network state '" + s + "'");
+ }
+ }
+
+
+ public String toString()
+ {
+ return "NetworkInfo " + operatorAlphaLong
+ + "/" + operatorAlphaShort
+ + "/" + operatorNumeric
+ + "/" + state;
+ }
+
+ /**
+ * Parcelable interface implemented below.
+ * This is a simple effort to make NetworkInfo parcelable rather than
+ * trying to make the conventional containing object (AsyncResult),
+ * implement parcelable. This functionality is needed for the
+ * NetworkQueryService to fix 1128695.
+ */
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * Method to serialize a NetworkInfo object.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(operatorAlphaLong);
+ dest.writeString(operatorAlphaShort);
+ dest.writeString(operatorNumeric);
+ dest.writeSerializable(state);
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * Method to deserialize a NetworkInfo object, or an array thereof.
+ */
+ public static final Creator<NetworkInfo> CREATOR =
+ new Creator<NetworkInfo>() {
+ public NetworkInfo createFromParcel(Parcel in) {
+ NetworkInfo netInfo = new NetworkInfo(
+ in.readString(), /*operatorAlphaLong*/
+ in.readString(), /*operatorAlphaShort*/
+ in.readString(), /*operatorNumeric*/
+ (State) in.readSerializable()); /*state*/
+ return netInfo;
+ }
+
+ public NetworkInfo[] newArray(int size) {
+ return new NetworkInfo[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/PDPContextState.java b/telephony/java/com/android/internal/telephony/gsm/PDPContextState.java
new file mode 100644
index 0000000..d5d481a
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/PDPContextState.java
@@ -0,0 +1,39 @@
+/*
+ * 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.internal.telephony.gsm;
+
+/**
+ * {@hide}
+ */
+public class PDPContextState
+{
+ public int cid;
+ public boolean active;
+ public String type;
+ public String apn;
+ public String address;
+
+ public String toString() {
+ return "com.android.internal.telephony.gsm.PDPContextState: {" +
+ " cid: " + cid +
+ ", active: " + active +
+ ", type: " + type +
+ ", apn: " + apn +
+ ", address: " + address +
+ " }";
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/PdpConnection.java b/telephony/java/com/android/internal/telephony/gsm/PdpConnection.java
new file mode 100644
index 0000000..79e48f9
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/PdpConnection.java
@@ -0,0 +1,473 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.os.*;
+import android.database.Cursor;
+import android.provider.Telephony;
+import android.util.EventLog;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public class PdpConnection extends Handler {
+
+ private static final String LOG_TAG = "GSM";
+ private static final boolean DBG = true;
+ private static final boolean FAKE_FAIL = false;
+
+ public enum PdpState {
+ ACTIVE, /* has active pdp context */
+ ACTIVATING, /* during connecting process */
+ INACTIVE; /* has empty pdp context */
+
+ public String toString() {
+ switch (this) {
+ case ACTIVE: return "active";
+ case ACTIVATING: return "setting up";
+ default: return "inactive";
+ }
+ }
+
+ public boolean isActive() {
+ return this == ACTIVE;
+ }
+
+ public boolean isInactive() {
+ return this == INACTIVE;
+ }
+ }
+
+ public enum PdpFailCause {
+ NONE,
+ BAD_APN,
+ BAD_PAP_SECRET,
+ BARRED,
+ USER_AUTHENTICATION,
+ SERVICE_OPTION_NOT_SUPPORTED,
+ SERVICE_OPTION_NOT_SUBSCRIBED,
+ SIM_LOCKED,
+ RADIO_OFF,
+ NO_SIGNAL,
+ NO_DATA_PLAN,
+ RADIO_NOT_AVIALABLE,
+ SUSPENED_TEMPORARY,
+ RADIO_ERROR_RETRY,
+ UNKNOWN;
+
+ public boolean isPermanentFail() {
+ return (this == RADIO_OFF);
+ }
+
+ public String toString() {
+ switch (this) {
+ case NONE: return "no error";
+ case BAD_APN: return "bad apn";
+ case BAD_PAP_SECRET:return "bad pap secret";
+ case BARRED: return "barred";
+ case USER_AUTHENTICATION: return "error user autentication";
+ case SERVICE_OPTION_NOT_SUPPORTED: return "data not supported";
+ case SERVICE_OPTION_NOT_SUBSCRIBED: return "datt not subcribed";
+ case SIM_LOCKED: return "sim locked";
+ case RADIO_OFF: return "radio is off";
+ case NO_SIGNAL: return "no signal";
+ case NO_DATA_PLAN: return "no data plan";
+ case RADIO_NOT_AVIALABLE: return "radio not available";
+ case SUSPENED_TEMPORARY: return "suspend temporary";
+ case RADIO_ERROR_RETRY: return "transient radio error";
+ default: return "unknown data error";
+ }
+ }
+ }
+
+ /** Fail cause of last PDP activate, from RIL_LastPDPActivateFailCause */
+ private static final int PDP_FAIL_RIL_BARRED = 8;
+ private static final int PDP_FAIL_RIL_BAD_APN = 27;
+ private static final int PDP_FAIL_RIL_USER_AUTHENTICATION = 29;
+ private static final int PDP_FAIL_RIL_SERVICE_OPTION_NOT_SUPPORTED = 32;
+ private static final int PDP_FAIL_RIL_SERVICE_OPTION_NOT_SUBSCRIBED = 33;
+ private static final int PDP_FAIL_RIL_ERROR_UNSPECIFIED = 0xffff;
+
+ //***** Event codes
+ private static final int EVENT_SETUP_PDP_DONE = 1;
+ private static final int EVENT_GET_LAST_FAIL_DONE = 2;
+ private static final int EVENT_LINK_STATE_CHANGED = 3;
+ private static final int EVENT_DEACTIVATE_DONE = 4;
+ private static final int EVENT_FORCE_RETRY = 5;
+
+ //***** Tag IDs for EventLog
+ private static final int EVENT_LOG_BAD_DNS_ADDRESS = 50100;
+
+ //***** Instance Variables
+ private GSMPhone phone;
+ private String pdp_name;
+ private PdpState state;
+ private Message onConnectCompleted;
+ private Message onDisconnect;
+ private int cid;
+ private long createTime;
+ private long lastFailTime;
+ private PdpFailCause lastFailCause;
+ private ApnSetting apn;
+ private String interfaceName;
+ private String ipAddress;
+ private String gatewayAddress;
+ private String[] dnsServers;
+
+ private static final String NULL_IP = "0.0.0.0";
+
+ // dataLink is only used to support pppd link
+ DataLink dataLink;
+ // receivedDisconnectReq is set when disconnect pdp link during activating
+ private boolean receivedDisconnectReq;
+
+ //***** Constructor
+ PdpConnection(GSMPhone phone)
+ {
+ this.phone = phone;
+ this.state = PdpState.INACTIVE;
+ onConnectCompleted = null;
+ onDisconnect = null;
+ this.cid = -1;
+ this.createTime = -1;
+ this.lastFailTime = -1;
+ this.lastFailCause = PdpFailCause.NONE;
+ this.apn = null;
+ this.dataLink = null;
+ receivedDisconnectReq = false;
+ this.dnsServers = new String[2];
+
+ if (SystemProperties.get("ro.radio.use-ppp","no").equals("yes")) {
+ dataLink = new PppLink(phone.mDataConnection);
+ dataLink.setOnLinkChange(this, EVENT_LINK_STATE_CHANGED, null);
+ }
+ }
+
+ /**
+ * Setup PDP connection for provided apn
+ * @param apn for this connection
+ * @param onCompleted notify success or not after down
+ */
+ void connect(ApnSetting apn, Message onCompleted) {
+ if (DBG) log("Connecting to carrier: '" + apn.carrier
+ + "' APN: '" + apn.apn
+ + "' proxy: '" + apn.proxy + "' port: '" + apn.port);
+
+ setHttpProxy (apn.proxy, apn.port);
+
+ state = PdpState.ACTIVATING;
+ this.apn = apn;
+ onConnectCompleted = onCompleted;
+ createTime = -1;
+ lastFailTime = -1;
+ lastFailCause = PdpFailCause.NONE;
+ receivedDisconnectReq = false;
+
+ if (FAKE_FAIL) {
+ // for debug before baseband implement error in setup PDP
+ if (apn.apn.equalsIgnoreCase("badapn")){
+ notifyFail(PdpFailCause.BAD_APN, onConnectCompleted);
+ return;
+ }
+ }
+
+ phone.mCM.setupDefaultPDP(apn.apn, apn.user, apn.password,
+ obtainMessage(EVENT_SETUP_PDP_DONE));
+ }
+
+ void disconnect(Message msg) {
+ onDisconnect = msg;
+ if (state == PdpState.ACTIVE) {
+ if (dataLink != null) {
+ dataLink.disconnect();
+ }
+
+ if (phone.mCM.getRadioState().isOn()) {
+ phone.mCM.deactivateDefaultPDP(cid, obtainMessage(EVENT_DEACTIVATE_DONE, msg));
+ }
+ } else if (state == PdpState.ACTIVATING) {
+ receivedDisconnectReq = true;
+ }
+ }
+
+ private void
+ setHttpProxy(String httpProxy, String httpPort)
+ {
+ if (httpProxy == null || httpProxy.length() == 0) {
+ phone.setSystemProperty("net.gprs.http-proxy", null);
+ return;
+ }
+
+ if (httpPort == null || httpPort.length() == 0) {
+ httpPort = "8080"; // Default to port 8080
+ }
+
+ phone.setSystemProperty("net.gprs.http-proxy",
+ "http://" + httpProxy + ":" + httpPort + "/");
+ }
+
+ public String toString() {
+ return "State=" + state + " Apn=" + apn +
+ " create=" + createTime + " lastFail=" + lastFailTime +
+ " lastFailCause=" + lastFailCause;
+ }
+
+ public long getConnectionTime() {
+ return createTime;
+ }
+
+ public long getLastFailTime() {
+ return lastFailTime;
+ }
+
+ public PdpFailCause getLastFailCause() {
+ return lastFailCause;
+ }
+
+ public ApnSetting getApn() {
+ return apn;
+ }
+
+ String getInterface() {
+ return interfaceName;
+ }
+
+ String getIpAddress() {
+ return ipAddress;
+ }
+
+ String getGatewayAddress() {
+ return gatewayAddress;
+ }
+
+ String[] getDnsServers() {
+ return dnsServers;
+ }
+
+ public PdpState getState() {
+ return state;
+ }
+
+ private void notifyFail(PdpFailCause cause, Message onCompleted) {
+ if (onCompleted == null) return;
+
+ state = PdpState.INACTIVE;
+ lastFailCause = cause;
+ lastFailTime = System.currentTimeMillis();
+ onConnectCompleted = null;
+
+ if (DBG) log("Notify PDP fail at " + lastFailTime
+ + " due to " + lastFailCause);
+
+ AsyncResult.forMessage(onCompleted, cause, new Exception());
+ onCompleted.sendToTarget();
+ }
+
+ private void notifySuccess(Message onCompleted) {
+ if (onCompleted == null) return;
+
+ state = PdpState.ACTIVE;
+ createTime = System.currentTimeMillis();
+ onConnectCompleted = null;
+ onCompleted.arg1 = cid;
+
+ if (DBG) log("Notify PDP success at " + createTime);
+
+ AsyncResult.forMessage(onCompleted);
+ onCompleted.sendToTarget();
+ }
+
+ private void notifyDisconnect(Message msg) {
+ if (DBG) log("Notify PDP disconnect");
+
+ if (msg != null) {
+ AsyncResult.forMessage(msg);
+ msg.sendToTarget();
+ }
+ clearSettings();
+ }
+
+ void clearSettings() {
+ state = PdpState.INACTIVE;
+ receivedDisconnectReq = false;
+ createTime = -1;
+ lastFailTime = -1;
+ lastFailCause = PdpFailCause.NONE;
+ apn = null;
+ onConnectCompleted = null;
+ interfaceName = null;
+ ipAddress = null;
+ gatewayAddress = null;
+ dnsServers[0] = null;
+ dnsServers[1] = null;
+ }
+
+ private void onLinkStateChanged(DataLink.LinkState linkState) {
+ switch (linkState) {
+ case LINK_UP:
+ notifySuccess(onConnectCompleted);
+ break;
+
+ case LINK_DOWN:
+ case LINK_EXITED:
+ phone.mCM.getLastPdpFailCause(
+ obtainMessage (EVENT_GET_LAST_FAIL_DONE));
+ break;
+ }
+ }
+
+ private PdpFailCause getFailCauseFromRequest(int rilCause) {
+ PdpFailCause cause;
+
+ switch (rilCause) {
+ case PDP_FAIL_RIL_BARRED:
+ cause = PdpFailCause.BARRED;
+ break;
+ case PDP_FAIL_RIL_BAD_APN:
+ cause = PdpFailCause.BAD_APN;
+ break;
+ case PDP_FAIL_RIL_USER_AUTHENTICATION:
+ cause = PdpFailCause.USER_AUTHENTICATION;
+ break;
+ case PDP_FAIL_RIL_SERVICE_OPTION_NOT_SUPPORTED:
+ cause = PdpFailCause.SERVICE_OPTION_NOT_SUPPORTED;
+ break;
+ case PDP_FAIL_RIL_SERVICE_OPTION_NOT_SUBSCRIBED:
+ cause = PdpFailCause.SERVICE_OPTION_NOT_SUBSCRIBED;
+ break;
+ default:
+ cause = PdpFailCause.UNKNOWN;
+ }
+ return cause;
+ }
+
+
+ private void log(String s) {
+ Log.d(LOG_TAG, "[PdpConnection] " + s);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case EVENT_SETUP_PDP_DONE:
+ ar = (AsyncResult) msg.obj;
+
+ if (ar.exception != null) {
+ Log.e(LOG_TAG, "PDP Context Init failed " + ar.exception);
+
+ if (receivedDisconnectReq) {
+ // Don't bother reporting the error if there's already a
+ // pending disconnect request, since DataConnectionTracker
+ // has already updated its state.
+ notifyDisconnect(onDisconnect);
+ } else {
+ if ( ar.exception instanceof CommandException &&
+ ((CommandException) (ar.exception)).getCommandError()
+ == CommandException.Error.RADIO_NOT_AVAILABLE) {
+ notifyFail(PdpFailCause.RADIO_NOT_AVIALABLE,
+ onConnectCompleted);
+ } else {
+ phone.mCM.getLastPdpFailCause(
+ obtainMessage(EVENT_GET_LAST_FAIL_DONE));
+ }
+ }
+ } else {
+ if (receivedDisconnectReq) {
+ // Don't bother reporting success if there's already a
+ // pending disconnect request, since DataConnectionTracker
+ // has already updated its state.
+ disconnect(onDisconnect);
+ } else {
+ String[] response = ((String[]) ar.result);
+ cid = Integer.parseInt(response[0]);
+
+ if (response.length > 2) {
+ interfaceName = response[1];
+ ipAddress = response[2];
+ String prefix = "net." + interfaceName + ".";
+ gatewayAddress = SystemProperties.get(prefix + "gw");
+ dnsServers[0] = SystemProperties.get(prefix + "dns1");
+ dnsServers[1] = SystemProperties.get(prefix + "dns2");
+ if (DBG) {
+ log("interface=" + interfaceName + " ipAddress=" + ipAddress
+ + " gateway=" + gatewayAddress + " DNS1=" + dnsServers[0]
+ + " DNS2=" + dnsServers[1]);
+ }
+
+ if (NULL_IP.equals(dnsServers[0]) && NULL_IP.equals(dnsServers[1])) {
+ // Work around a race condition where QMI does not fill in DNS:
+ // Deactivate PDP and let DataConnectionTracker retry.
+ EventLog.writeEvent(EVENT_LOG_BAD_DNS_ADDRESS, dnsServers[0]);
+ phone.mCM.deactivateDefaultPDP(cid,
+ obtainMessage(EVENT_FORCE_RETRY));
+ break;
+ }
+ }
+
+ if (dataLink != null) {
+ dataLink.connect();
+ } else {
+ onLinkStateChanged(DataLink.LinkState.LINK_UP);
+ }
+
+ if (DBG) log("PDP setup on cid = " + cid);
+ }
+ }
+ break;
+ case EVENT_FORCE_RETRY:
+ if (receivedDisconnectReq) {
+ notifyDisconnect(onDisconnect);
+ } else {
+ ar = (AsyncResult) msg.obj;
+ notifyFail(PdpFailCause.RADIO_ERROR_RETRY, onConnectCompleted);
+ }
+ break;
+ case EVENT_GET_LAST_FAIL_DONE:
+ if (receivedDisconnectReq) {
+ // Don't bother reporting the error if there's already a
+ // pending disconnect request, since DataConnectionTracker
+ // has already updated its state.
+ notifyDisconnect(onDisconnect);
+ } else {
+ ar = (AsyncResult) msg.obj;
+ PdpFailCause cause = PdpFailCause.UNKNOWN;
+
+ if (ar.exception == null) {
+ int rilFailCause = ((int[]) (ar.result))[0];
+ cause = getFailCauseFromRequest(rilFailCause);
+ }
+ notifyFail(cause, onConnectCompleted);
+ }
+
+ break;
+ case EVENT_LINK_STATE_CHANGED:
+ ar = (AsyncResult) msg.obj;
+ DataLink.LinkState ls = (DataLink.LinkState) ar.result;
+ onLinkStateChanged(ls);
+ break;
+ case EVENT_DEACTIVATE_DONE:
+ ar = (AsyncResult) msg.obj;
+ notifyDisconnect((Message) ar.userObj);
+ break;
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/PppLink.java b/telephony/java/com/android/internal/telephony/gsm/PppLink.java
new file mode 100644
index 0000000..43d4f1f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/PppLink.java
@@ -0,0 +1,209 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import android.database.Cursor;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.os.SystemService;
+import com.android.internal.telephony.gsm.DataConnectionTracker.State;
+import com.android.internal.util.ArrayUtils;
+import android.util.Log;
+
+/**
+ * Represents a PPP link.
+ *
+ * Ideally this would be managed by the RIL implementation, but
+ * we currently have implementations where this is not the case.
+ *
+ * {@hide}
+ */
+final class PppLink extends DataLink implements DataLinkInterface {
+ private static final String LOG_TAG = "GSM";
+
+ static final String PATH_PPP_OPERSTATE = "/sys/class/net/ppp0/operstate";
+ static final String SERVICE_PPPD_GPRS = "pppd_gprs";
+ static final String PROPERTY_PPPD_EXIT_CODE = "net.gprs.ppp-exit";
+ static final int POLL_SYSFS_MILLIS = 5 * 1000;
+ static final int EVENT_POLL_DATA_CONNECTION = 2;
+ static final int EVENT_PPP_OPERSTATE_CHANGED = 8;
+ static final int EVENT_PPP_PIDFILE_CHANGED = 9;
+
+ static final byte[] UP_ASCII_STRING = new byte[] {
+ 'u' & 0xff,
+ 'p' & 0xff,
+ };
+ static final byte[] DOWN_ASCII_STRING = new byte[] {
+ 'd' & 0xff,
+ 'o' & 0xff,
+ 'w' & 0xff,
+ 'n' & 0xff,
+ };
+ static final byte[] UNKNOWN_ASCII_STRING = new byte[] {
+ 'u' & 0xff,
+ 'n' & 0xff,
+ 'k' & 0xff,
+ 'n' & 0xff,
+ 'o' & 0xff,
+ 'w' & 0xff,
+ 'n' & 0xff,
+ };
+ private final byte[] mCheckPPPBuffer = new byte[32];
+
+ int lastPppdExitCode = EXIT_OK;
+
+
+ PppLink(DataConnectionTracker dc) {
+ super(dc);
+ }
+
+ public void connect() {
+ // Clear any previous exit code
+ SystemProperties.set(PROPERTY_PPPD_EXIT_CODE, "");
+ SystemService.start(SERVICE_PPPD_GPRS);
+ removeMessages(EVENT_POLL_DATA_CONNECTION);
+ Message poll = obtainMessage();
+ poll.what = EVENT_POLL_DATA_CONNECTION;
+ sendMessageDelayed(poll, POLL_SYSFS_MILLIS);
+ }
+
+ public void disconnect() {
+ SystemService.stop(SERVICE_PPPD_GPRS);
+ }
+
+ public int getLastLinkExitCode() {
+ return lastPppdExitCode;
+ }
+
+ public void setPasswordInfo(Cursor cursor) {
+ StringBuilder builder = new StringBuilder();
+ FileOutputStream output = null;
+
+ try {
+ output = new FileOutputStream("/etc/ppp/pap-secrets");
+ if (cursor.moveToFirst()) {
+ do {
+ builder.append(cursor.getString(cursor.getColumnIndex("user")));
+ builder.append(" ");
+ builder.append(cursor.getString(cursor.getColumnIndex("server")));
+ builder.append(" ");
+ builder.append(cursor.getString(cursor.getColumnIndex("password")));
+ builder.append("\n");
+ } while (cursor.moveToNext());
+ }
+
+ output.write(builder.toString().getBytes());
+ } catch (java.io.IOException e) {
+ Log.e(LOG_TAG, "Could not create '/etc/ppp/pap-secrets'", e);
+ } finally {
+ try {
+ if (output != null) output.close();
+ } catch (java.io.IOException e) {
+ Log.e(LOG_TAG, "Error closing '/etc/ppp/pap-secrets'", e);
+ }
+ }
+ }
+
+ public void handleMessage (Message msg) {
+
+ switch (msg.what) {
+
+ case EVENT_POLL_DATA_CONNECTION:
+ checkPPP();
+
+ // keep polling in case interface goes down
+ if (dataConnection.state != State.IDLE) {
+ Message poll = obtainMessage();
+ poll.what = EVENT_POLL_DATA_CONNECTION;
+ sendMessageDelayed(poll, POLL_SYSFS_MILLIS);
+ }
+ break;
+ }
+ }
+
+ private void checkPPP() {
+ boolean connecting = (dataConnection.state == State.CONNECTING);
+
+ try {
+ RandomAccessFile file = new RandomAccessFile(PATH_PPP_OPERSTATE, "r");
+ file.read(mCheckPPPBuffer);
+ file.close();
+
+ // Unfortunately, we're currently seeing operstate
+ // "unknown" where one might otherwise expect "up"
+ if (ArrayUtils.equals(mCheckPPPBuffer, UP_ASCII_STRING, UP_ASCII_STRING.length)
+ || ArrayUtils.equals(mCheckPPPBuffer, UNKNOWN_ASCII_STRING,
+ UNKNOWN_ASCII_STRING.length)
+ && dataConnection.state == State.CONNECTING) {
+
+ Log.i(LOG_TAG,
+ "found ppp interface. Notifying GPRS connected");
+
+ if (mLinkChangeRegistrant != null) {
+ mLinkChangeRegistrant.notifyResult(LinkState.LINK_UP);
+ }
+
+ connecting = false;
+ } else if (dataConnection.state == State.CONNECTED
+ && ArrayUtils.equals(mCheckPPPBuffer, DOWN_ASCII_STRING,
+ DOWN_ASCII_STRING.length)) {
+
+ Log.i(LOG_TAG,
+ "ppp interface went down. Reconnecting...");
+
+ if (mLinkChangeRegistrant != null) {
+ mLinkChangeRegistrant.notifyResult(LinkState.LINK_DOWN);
+ }
+ }
+ } catch (IOException ex) {
+ if (! (ex instanceof FileNotFoundException)) {
+ Log.i(LOG_TAG, "Poll ppp0 ex " + ex.toString());
+ }
+
+ if (dataConnection.state == State.CONNECTED &&
+ mLinkChangeRegistrant != null) {
+ mLinkChangeRegistrant.notifyResult(LinkState.LINK_DOWN);
+ }
+ }
+
+ // CONNECTING means pppd has started but negotiation is not complete
+ // If we're still CONNECTING here, check to see if pppd has
+ // already exited
+ if (connecting) {
+ String exitCode;
+
+ exitCode = SystemProperties.get(PROPERTY_PPPD_EXIT_CODE, "");
+
+ if (!exitCode.equals("")) {
+ // pppd has exited. Let's figure out why
+ lastPppdExitCode = Integer.parseInt(exitCode);
+
+ Log.d(LOG_TAG,"pppd exited with " + exitCode);
+
+ if (mLinkChangeRegistrant != null) {
+ mLinkChangeRegistrant.notifyResult(LinkState.LINK_EXITED);
+ }
+ }
+ }
+
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/RIL.java b/telephony/java/com/android/internal/telephony/gsm/RIL.java
new file mode 100644
index 0000000..6739fba
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/RIL.java
@@ -0,0 +1,2506 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import com.android.internal.telephony.*;
+import android.os.Parcel;
+import java.io.IOException;
+import android.os.Message;
+import android.os.Handler;
+import android.net.LocalSocketAddress;
+import android.net.LocalSocket;
+import com.android.internal.os.HandlerThread;
+import android.os.HandlerInterface;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.io.InputStream;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.gsm.SmsManager;
+import android.telephony.gsm.SmsMessage;
+import android.util.Log;
+import android.util.Config;
+import android.os.AsyncResult;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+
+import static com.android.internal.telephony.gsm.RILConstants.*;
+
+/**
+ * {@hide}
+ */
+class RILRequest
+{
+ static final String LOG_TAG = "RILJ";
+
+ //***** Class Variables
+ static int sNextSerial = 0;
+ static Object sSerialMonitor = new Object();
+ private static Object sPoolSync = new Object();
+ private static RILRequest sPool = null;
+ private static int sPoolSize = 0;
+ private static final int MAX_POOL_SIZE = 4;
+
+ //***** Instance Variables
+ int mSerial;
+ int mRequest;
+ Message mResult;
+ Parcel mp;
+ RILRequest mNext;
+
+ /**
+ * Retrieves a new RILRequest instance from the pool.
+ *
+ * @param request RIL_REQUEST_*
+ * @param result sent when operation completes
+ * @return a RILRequest instance from the pool.
+ */
+ static RILRequest obtain(int request, Message result) {
+ RILRequest rr = null;
+
+ synchronized(sPoolSync) {
+ if (sPool != null) {
+ rr = sPool;
+ sPool = rr.mNext;
+ rr.mNext = null;
+ sPoolSize--;
+ }
+ }
+
+ if (rr == null) {
+ rr = new RILRequest();
+ }
+
+ synchronized(sSerialMonitor) {
+ rr.mSerial = sNextSerial++;
+ }
+ rr.mRequest = request;
+ rr.mResult = result;
+ rr.mp = Parcel.obtain();
+
+ if (result != null && result.getTarget() == null) {
+ throw new NullPointerException("Message target must not be null");
+ }
+
+ // first elements in any RIL Parcel
+ rr.mp.writeInt(request);
+ rr.mp.writeInt(rr.mSerial);
+
+ return rr;
+ }
+
+ /**
+ * Returns a RILRequest instance to the pool.
+ *
+ * Note: This should only be called once per use.
+ */
+ void release() {
+ synchronized (sPoolSync) {
+ if (sPoolSize < MAX_POOL_SIZE) {
+ this.mNext = sPool;
+ sPool = this;
+ sPoolSize++;
+ }
+ }
+ }
+
+ private RILRequest()
+ {
+ }
+
+ static void
+ resetSerial()
+ {
+ synchronized(sSerialMonitor) {
+ sNextSerial = 0;
+ }
+ }
+
+ String
+ serialString()
+ {
+ //Cheesy way to do %04d
+ StringBuilder sb = new StringBuilder(8);
+ String sn;
+
+ sn = Integer.toString(mSerial);
+
+ //sb.append("J[");
+ sb.append('[');
+ for (int i = 0, s = sn.length() ; i < 4 - s; i++) {
+ sb.append('0');
+ }
+
+ sb.append(sn);
+ sb.append(']');
+ return sb.toString();
+ }
+
+ void
+ onError(int error)
+ {
+ CommandException ex;
+
+ ex = CommandException.fromRilErrno(error);
+
+ if (RIL.RILJ_LOG) Log.d(LOG_TAG, serialString() + "< "
+ + RIL.requestToString(mRequest)
+ + " error: " + ex);
+
+ if (mResult != null) {
+ AsyncResult.forMessage(mResult, null, ex);
+ mResult.sendToTarget();
+ }
+
+ if (mp != null) {
+ mp.recycle();
+ mp = null;
+ }
+ }
+}
+
+
+/**
+ * RIL implementation of the CommandsInterface.
+ * FIXME public only for testing
+ *
+ * {@hide}
+ */
+public final class RIL extends BaseCommands implements CommandsInterface
+{
+ static final String LOG_TAG = "RILJ";
+ private static final boolean DBG = true;
+ static final boolean RILJ_LOG = true;
+ static int WAKE_LOCK_TIMEOUT = 5000;
+
+ //***** Instance Variables
+
+ LocalSocket mSocket;
+ HandlerThread mSenderThread;
+ Handler mSenderH;
+ RILSender mSender;
+ Thread mReceiverThread;
+ RILReceiver mReceiver;
+ private Context mContext;
+ WakeLock mWakeLock;
+ int mRequestMessagesPending;
+
+ // Is this the first radio state change?
+ private boolean mInitialRadioStateChange = true;
+
+ //I'd rather this be LinkedList or something
+ ArrayList<RILRequest> mRequestsList = new ArrayList<RILRequest>();
+
+ Object mLastNITZTimeInfo;
+
+ //***** Events
+
+ static final int EVENT_SEND = 1;
+ static final int EVENT_WAKE_LOCK_TIMEOUT = 2;
+
+ //***** Constants
+
+ // match with constant in ril.cpp
+ static final int RIL_MAX_COMMAND_BYTES = (8 * 1024);
+ static final int RESPONSE_SOLICITED = 0;
+ static final int RESPONSE_UNSOLICITED = 1;
+
+ static final String SOCKET_NAME_RIL = "rild";
+
+ static final int SOCKET_OPEN_RETRY_MILLIS = 4 * 1000;
+
+
+ BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+ sendScreenState(true);
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ sendScreenState(false);
+ } else {
+ Log.w(LOG_TAG, "RIL received unexpected Intent: " + intent.getAction());
+ }
+ }
+ };
+
+ class RILSender implements HandlerInterface,Runnable
+ {
+ // Only allocated once
+ byte[] dataLength = new byte[4];
+
+ //***** Runnable implementation
+ public void
+ run()
+ {
+ //setup if needed
+ }
+
+
+ //***** Handler implemementation
+
+ public void
+ handleMessage(Message msg)
+ {
+ RILRequest rr = (RILRequest)(msg.obj);
+ RILRequest req = null;
+
+ switch (msg.what) {
+ case EVENT_SEND:
+ /**
+ * mRequestMessagePending++ already happened for every
+ * EVENT_SEND, thus we must make sure
+ * mRequestMessagePending-- happens once and only once
+ */
+ boolean alreadySubtracted = false;
+ try {
+ LocalSocket s;
+
+ s = mSocket;
+
+ if (s == null) {
+ rr.onError(RADIO_NOT_AVAILABLE);
+ rr.release();
+ mRequestMessagesPending--;
+ alreadySubtracted = true;
+ return;
+ }
+
+ synchronized (mRequestsList) {
+ mRequestsList.add(rr);
+ }
+
+ mRequestMessagesPending--;
+ alreadySubtracted = true;
+
+ byte[] data;
+
+ data = rr.mp.marshall();
+ rr.mp.recycle();
+ rr.mp = null;
+
+ if (data.length > RIL_MAX_COMMAND_BYTES) {
+ throw new RuntimeException(
+ "Parcel larger than max bytes allowed! "
+ + data.length);
+ }
+
+ // parcel length in big endian
+ dataLength[0] = dataLength[1] = 0;
+ dataLength[2] = (byte)((data.length >> 8) & 0xff);
+ dataLength[3] = (byte)((data.length) & 0xff);
+
+ //Log.v(LOG_TAG, "writing packet: " + data.length + " bytes");
+
+ s.getOutputStream().write(dataLength);
+ s.getOutputStream().write(data);
+ } catch (IOException ex) {
+ Log.e(LOG_TAG, "IOException", ex);
+ req = findAndRemoveRequestFromList(rr.mSerial);
+ // make sure this request has not already been handled,
+ // eg, if RILReceiver cleared the list.
+ if (req != null || !alreadySubtracted) {
+ rr.onError(RADIO_NOT_AVAILABLE);
+ rr.release();
+ }
+ } catch (RuntimeException exc) {
+ Log.e(LOG_TAG, "Uncaught exception ", exc);
+ req = findAndRemoveRequestFromList(rr.mSerial);
+ // make sure this request has not already been handled,
+ // eg, if RILReceiver cleared the list.
+ if (req != null || !alreadySubtracted) {
+ rr.onError(GENERIC_FAILURE);
+ rr.release();
+ }
+ }
+
+ if (!alreadySubtracted) {
+ mRequestMessagesPending--;
+ }
+
+ break;
+
+ case EVENT_WAKE_LOCK_TIMEOUT:
+ // Haven't heard back from the last request. Assume we're
+ // not getting a response and release the wake lock.
+ // TODO should we clean up mRequestList and mRequestPending
+ synchronized (mWakeLock) {
+ if (mWakeLock.isHeld()) {
+ if (DBG) {
+ synchronized (mRequestsList) {
+ int count = mRequestsList.size();
+ Log.d(LOG_TAG, "WAKE_LOCK_TIMEOUT " +
+ " mReqPending=" + mRequestMessagesPending +
+ " mRequestList=" + count);
+
+ for (int i = 0; i < count; i++) {
+ rr = mRequestsList.get(i);
+ Log.d(LOG_TAG, i + ": [" + rr.mSerial + "] " +
+ requestToString(rr.mRequest));
+
+ }
+ }
+ }
+ mWakeLock.release();
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads in a single RIL message off the wire. A RIL message consists
+ * of a 4-byte little-endian length and a subsequent series of bytes.
+ * The final message (length header omitted) is read into
+ * <code>buffer</code> and the length of the final message (less header)
+ * is returned. A return value of -1 indicates end-of-stream.
+ *
+ * @param is non-null; Stream to read from
+ * @param buffer Buffer to fill in. Must be as large as maximum
+ * message size, or an ArrayOutOfBounds exception will be thrown.
+ * @return Length of message less header, or -1 on end of stream.
+ * @throws IOException
+ */
+ private static int readRilMessage(InputStream is, byte[] buffer)
+ throws IOException
+ {
+ int countRead;
+ int offset;
+ int remaining;
+ int messageLength;
+
+ // First, read in the length of the message
+ offset = 0;
+ remaining = 4;
+ do {
+ countRead = is.read(buffer, offset, remaining);
+
+ if (countRead < 0 ) {
+ Log.e(LOG_TAG, "Hit EOS reading message length");
+ return -1;
+ }
+
+ offset += countRead;
+ remaining -= countRead;
+ } while (remaining > 0);
+
+ messageLength = ((buffer[0] & 0xff) << 24)
+ | ((buffer[1] & 0xff) << 16)
+ | ((buffer[2] & 0xff) << 8)
+ | (buffer[3] & 0xff);
+
+ // Then, re-use the buffer and read in the message itself
+ offset = 0;
+ remaining = messageLength;
+ do {
+ countRead = is.read(buffer, offset, remaining);
+
+ if (countRead < 0 ) {
+ Log.e(LOG_TAG, "Hit EOS reading message. messageLength=" + messageLength
+ + " remaining=" + remaining);
+ return -1;
+ }
+
+ offset += countRead;
+ remaining -= countRead;
+ } while (remaining > 0);
+
+ return messageLength;
+ }
+
+ class RILReceiver implements Runnable
+ {
+ byte[] buffer;
+
+ RILReceiver()
+ {
+ buffer = new byte[RIL_MAX_COMMAND_BYTES];
+ }
+
+ public void
+ run()
+ {
+ int retryCount = 0;
+
+ try {for (;;) {
+ LocalSocket s = null;
+ LocalSocketAddress l;
+
+ try {
+ s = new LocalSocket();
+ l = new LocalSocketAddress(SOCKET_NAME_RIL,
+ LocalSocketAddress.Namespace.RESERVED);
+ s.connect(l);
+ } catch (IOException ex){
+ try {
+ if (s != null) {
+ s.close();
+ }
+ } catch (IOException ex2) {
+ //ignore failure to close after failure to connect
+ }
+
+ // don't print an error message after the the first time
+ // or after the 8th time
+
+ if (retryCount == 8) {
+ Log.e (LOG_TAG,
+ "Couldn't find '" + SOCKET_NAME_RIL
+ + "' socket after " + retryCount
+ + " times, continuing to retry silently");
+ } else if (retryCount > 0 && retryCount < 8) {
+ Log.i (LOG_TAG,
+ "Couldn't find '" + SOCKET_NAME_RIL
+ + "' socket; retrying after timeout");
+ }
+
+ try {
+ Thread.sleep(SOCKET_OPEN_RETRY_MILLIS);
+ } catch (InterruptedException er) {
+ }
+
+ retryCount++;
+ continue;
+ }
+
+ retryCount = 0;
+
+ mSocket = s;
+ Log.i(LOG_TAG, "Connected to '" + SOCKET_NAME_RIL + "' socket");
+
+ int length = 0;
+ try {
+ InputStream is = mSocket.getInputStream();
+
+ for (;;) {
+ Parcel p;
+
+ length = readRilMessage(is, buffer);
+
+ if (length < 0) {
+ // End-of-stream reached
+ break;
+ }
+
+ p = Parcel.obtain();
+ p.unmarshall(buffer, 0, length);
+ p.setDataPosition(0);
+
+ //Log.v(LOG_TAG, "Read packet: " + length + " bytes");
+
+ processResponse(p);
+ p.recycle();
+ }
+ } catch (java.io.IOException ex) {
+ Log.i(LOG_TAG, "'" + SOCKET_NAME_RIL + "' socket closed",
+ ex);
+ } catch (Throwable tr) {
+ Log.e(LOG_TAG, "Uncaught exception read length=" + length, tr);
+ }
+
+ Log.i(LOG_TAG, "Disconnected from '" + SOCKET_NAME_RIL
+ + "' socket");
+
+ setRadioState (RadioState.RADIO_UNAVAILABLE);
+
+ try {
+ mSocket.close();
+ } catch (IOException ex) {
+ }
+
+ mSocket = null;
+ RILRequest.resetSerial();
+
+ // Clear request list on close
+ synchronized (mRequestsList) {
+ for (int i = 0, sz = mRequestsList.size() ; i < sz ; i++) {
+ RILRequest rr = mRequestsList.get(i);
+ rr.onError(RADIO_NOT_AVAILABLE);
+ rr.release();
+ }
+
+ mRequestsList.clear();
+ }
+ }} catch (Throwable tr) {
+ Log.e(LOG_TAG,"Uncaught exception", tr);
+ }
+ }
+ }
+
+
+
+ //***** Constructor
+
+ public
+ RIL(Context context)
+ {
+ super(context);
+
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+ mWakeLock.setReferenceCounted(false);
+ mRequestMessagesPending = 0;
+
+ mContext = context;
+ mSender = new RILSender();
+ mSenderThread = new HandlerThread(mSender, mSender, "RILSender");
+ mSenderH = mSenderThread.getHandler();
+ mReceiver = new RILReceiver();
+ mReceiverThread = new Thread(mReceiver, "RILReceiver");
+ mReceiverThread.start();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ context.registerReceiver(mIntentReceiver, filter);
+ }
+
+ //***** CommandsInterface implementation
+
+ @Override public void
+ setOnNITZTime(Handler h, int what, Object obj)
+ {
+ super.setOnNITZTime(h, what, obj);
+
+ // Send the last NITZ time if we have it
+ if (mLastNITZTimeInfo != null) {
+ mNITZTimeRegistrant
+ .notifyRegistrant(
+ new AsyncResult (null, mLastNITZTimeInfo, null));
+ mLastNITZTimeInfo = null;
+ }
+ }
+
+ public void
+ getSimStatus(Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_SIM_STATUS, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ supplySimPin(String pin, Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PIN, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(1);
+ rr.mp.writeString(pin);
+
+ send(rr);
+ }
+
+ public void
+ supplySimPuk(String puk, String newPin, Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PUK, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(2);
+ rr.mp.writeString(puk);
+ rr.mp.writeString(newPin);
+
+ send(rr);
+ }
+
+ public void
+ supplySimPin2(String pin, Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PIN2, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(1);
+ rr.mp.writeString(pin);
+
+ send(rr);
+ }
+
+ public void
+ supplySimPuk2(String puk, String newPin2, Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PUK2, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(2);
+ rr.mp.writeString(puk);
+ rr.mp.writeString(newPin2);
+
+ send(rr);
+ }
+
+ public void
+ changeSimPin(String oldPin, String newPin, Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CHANGE_SIM_PIN, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(2);
+ rr.mp.writeString(oldPin);
+ rr.mp.writeString(newPin);
+
+ send(rr);
+ }
+
+ public void
+ changeSimPin2(String oldPin2, String newPin2, Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CHANGE_SIM_PIN2, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(2);
+ rr.mp.writeString(oldPin2);
+ rr.mp.writeString(newPin2);
+
+ send(rr);
+ }
+
+ public void
+ changeBarringPassword(String facility, String oldPwd, String newPwd, Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CHANGE_BARRING_PASSWORD, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(3);
+ rr.mp.writeString(facility);
+ rr.mp.writeString(oldPwd);
+ rr.mp.writeString(newPwd);
+
+ send(rr);
+ }
+
+ public void
+ supplyNetworkDepersonalization(String netpin, Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(1);
+ rr.mp.writeString(netpin);
+
+ send(rr);
+ }
+
+ public void
+ getCurrentCalls (Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_CURRENT_CALLS, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getPDPContextList(Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_PDP_CONTEXT_LIST, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ dial (String address, int clirMode, Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);
+
+ rr.mp.writeString(address);
+ rr.mp.writeInt(clirMode);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getIMSI(Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMSI, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> getIMSI:RIL_REQUEST_GET_IMSI " + RIL_REQUEST_GET_IMSI + " " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getIMEI(Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMEI, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getIMEISV(Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMEISV, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ public void
+ hangupConnection (int gsmIndex, Message result)
+ {
+ if (RILJ_LOG) riljLog("hangupConnection: gsmIndex=" + gsmIndex);
+
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_HANGUP, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " + gsmIndex);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(gsmIndex);
+
+ send(rr);
+ }
+
+ public void
+ hangupWaitingOrBackground (Message result)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND,
+ result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ hangupForegroundResumeBackground (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(
+ RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND,
+ result);
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ switchWaitingOrHoldingAndActive (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(
+ RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE,
+ result);
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ conference (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_CONFERENCE, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ public void
+ separateConnection (int gsmIndex, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SEPARATE_CONNECTION, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + gsmIndex);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(gsmIndex);
+
+ send(rr);
+ }
+
+ public void
+ acceptCall (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_ANSWER, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ rejectCall (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_UDUB, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ explicitCallTransfer (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_EXPLICIT_CALL_TRANSFER, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getLastCallFailCause (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_LAST_CALL_FAIL_CAUSE, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getLastPdpFailCause (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_LAST_PDP_FAIL_CAUSE, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ setMute (boolean enableMute, Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_MUTE, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + enableMute);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(enableMute ? 1 : 0);
+
+ send(rr);
+ }
+
+ public void
+ getMute (Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_GET_MUTE, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getSignalStrength (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SIGNAL_STRENGTH, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getRegistrationState (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_REGISTRATION_STATE, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getGPRSRegistrationState (Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_GPRS_REGISTRATION_STATE, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getOperator(Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_OPERATOR, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ sendDtmf(char c, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_DTMF, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeString(Character.toString(c));
+
+ send(rr);
+ }
+
+ public void
+ startDtmf(char c, Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_DTMF_START, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeString(Character.toString(c));
+
+ send(rr);
+ }
+
+ public void
+ stopDtmf(Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_DTMF_STOP, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ public void
+ sendSMS (String smscPDU, String pdu, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SEND_SMS, result);
+
+ rr.mp.writeInt(2);
+ rr.mp.writeString(smscPDU);
+ rr.mp.writeString(pdu);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void deleteSmsOnSim(int index, Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_DELETE_SMS_ON_SIM,
+ response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(index);
+
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest)
+ + " " + index);
+ }
+
+ send(rr);
+ }
+
+ public void writeSmsToSim(int status, String smsc, String pdu, Message response) {
+ status = translateStatus(status);
+
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_WRITE_SMS_TO_SIM,
+ response);
+
+ rr.mp.writeInt(status);
+ rr.mp.writeString(pdu);
+ rr.mp.writeString(smsc);
+
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest)
+ + " " + status);
+ }
+
+ send(rr);
+ }
+
+ /**
+ * Translates EF_SMS status bits to a status value compatible with
+ * SMS AT commands. See TS 27.005 3.1.
+ */
+ private int translateStatus(int status) {
+ switch(status & 0x7) {
+ case SmsManager.STATUS_ON_SIM_READ:
+ return 1;
+ case SmsManager.STATUS_ON_SIM_UNREAD:
+ return 0;
+ case SmsManager.STATUS_ON_SIM_SENT:
+ return 3;
+ case SmsManager.STATUS_ON_SIM_UNSENT:
+ return 2;
+ }
+
+ // Default to READ.
+ return 1;
+ }
+
+ public void
+ setupDefaultPDP(String apn, String user, String password, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SETUP_DEFAULT_PDP, result);
+
+ rr.mp.writeInt(3);
+ rr.mp.writeString(apn);
+ rr.mp.writeString(user);
+ rr.mp.writeString(password);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " "
+ + apn);
+
+ send(rr);
+ }
+
+ public void
+ deactivateDefaultPDP(int cid, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_DEACTIVATE_DEFAULT_PDP, result);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeString(Integer.toString(cid));
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " + cid);
+
+ send(rr);
+ }
+
+ public void
+ setRadioPower(boolean on, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_RADIO_POWER, result);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(on ? 1 : 0);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ setSuppServiceNotifications(boolean enable, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION, result);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(enable ? 1 : 0);
+
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest));
+ }
+
+ send(rr);
+ }
+
+ public void
+ acknowledgeLastIncomingSMS(boolean success, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SMS_ACKNOWLEDGE, result);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(success ? 1 : 0);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ simIO (int command, int fileid, String path, int p1, int p2, int p3,
+ String data, String pin2, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SIM_IO, result);
+
+ rr.mp.writeInt(command);
+ rr.mp.writeInt(fileid);
+ rr.mp.writeString(path);
+ rr.mp.writeInt(p1);
+ rr.mp.writeInt(p2);
+ rr.mp.writeInt(p3);
+ rr.mp.writeString(data);
+ rr.mp.writeString(pin2);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> simIO: " + requestToString(rr.mRequest)
+ + " 0x" + Integer.toHexString(command)
+ + " 0x" + Integer.toHexString(fileid) + " "
+ + p1 + "," + p2 + "," + p3);
+
+ send(rr);
+ }
+
+ public void
+ getCLIR(Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_GET_CLIR, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ setCLIR(int clirMode, Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_CLIR, result);
+
+ // count ints
+ rr.mp.writeInt(1);
+
+ rr.mp.writeInt(clirMode);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + clirMode);
+
+ send(rr);
+ }
+
+ public void
+ queryCallWaiting(int serviceClass, Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_QUERY_CALL_WAITING, response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(serviceClass);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + serviceClass);
+
+ send(rr);
+ }
+
+ public void
+ setCallWaiting(boolean enable, int serviceClass, Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_CALL_WAITING, response);
+
+ rr.mp.writeInt(2);
+ rr.mp.writeInt(enable ? 1 : 0);
+ rr.mp.writeInt(serviceClass);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + enable + ", " + serviceClass);
+
+ send(rr);
+ }
+
+ public void
+ setNetworkSelectionModeAutomatic(Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC,
+ response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ setNetworkSelectionModeManual(String operatorNumeric, Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL,
+ response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + operatorNumeric);
+
+ rr.mp.writeString(operatorNumeric);
+
+ send(rr);
+ }
+
+ public void
+ getNetworkSelectionMode(Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE,
+ response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getAvailableNetworks(Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_QUERY_AVAILABLE_NETWORKS,
+ response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ setCallForward(int action, int cfReason, int serviceClass,
+ String number, int timeSeconds, Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_CALL_FORWARD, response);
+
+ rr.mp.writeInt(action);
+ rr.mp.writeInt(cfReason);
+ rr.mp.writeInt(serviceClass);
+ rr.mp.writeInt(PhoneNumberUtils.toaFromString(number));
+ rr.mp.writeString(number);
+ rr.mp.writeInt (timeSeconds);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + action + " " + cfReason + " " + serviceClass
+ + timeSeconds);
+
+ send(rr);
+ }
+
+ public void
+ queryCallForwardStatus(int cfReason, int serviceClass,
+ String number, Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_QUERY_CALL_FORWARD_STATUS, response);
+
+ rr.mp.writeInt(2); // 2 is for query action, not in used anyway
+ rr.mp.writeInt(cfReason);
+ rr.mp.writeInt(serviceClass);
+ rr.mp.writeInt(PhoneNumberUtils.toaFromString(number));
+ rr.mp.writeString(number);
+ rr.mp.writeInt (0);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + cfReason + " " + serviceClass);
+
+ send(rr);
+ }
+
+ public void
+ queryCLIP(Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_QUERY_CLIP, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ public void
+ getBasebandVersion (Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_BASEBAND_VERSION, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ queryFacilityLock (String facility, String password, int serviceClass,
+ Message response)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_QUERY_FACILITY_LOCK, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ // count strings
+ rr.mp.writeInt(3);
+
+ rr.mp.writeString(facility);
+ rr.mp.writeString(password);
+
+ rr.mp.writeString(Integer.toString(serviceClass));
+
+ send(rr);
+ }
+
+ public void
+ setFacilityLock (String facility, boolean lockState, String password,
+ int serviceClass, Message response)
+ {
+ String lockString;
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_FACILITY_LOCK, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ // count strings
+ rr.mp.writeInt(4);
+
+ rr.mp.writeString(facility);
+ lockString = (lockState)?"1":"0";
+ rr.mp.writeString(lockString);
+ rr.mp.writeString(password);
+ rr.mp.writeString(Integer.toString(serviceClass));
+
+ send(rr);
+
+ }
+
+ public void
+ sendUSSD (String ussdString, Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SEND_USSD, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + ussdString);
+
+ rr.mp.writeString(ussdString);
+
+ send(rr);
+ }
+
+ // inherited javadoc suffices
+ public void cancelPendingUssd (Message response) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_CANCEL_USSD, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString()
+ + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ public void resetRadio(Message result)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_RESET_RADIO, result);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void invokeOemRilRequestRaw(byte[] data, Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_OEM_HOOK_RAW, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + "[" + SimUtils.bytesToHexString(data) + "]");
+
+ rr.mp.writeByteArray(data);
+
+ send(rr);
+
+ }
+
+ public void invokeOemRilRequestStrings(String[] strings, Message response)
+ {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_OEM_HOOK_STRINGS, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeStringArray(strings);
+
+ send(rr);
+ }
+
+ /**
+ * Assign a specified band for RF configuration.
+ *
+ * @param bandMode one of BM_*_BAND
+ * @param response is callback message
+ */
+ public void setBandMode (int bandMode, Message response) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_BAND_MODE, response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(bandMode);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + bandMode);
+
+ send(rr);
+ }
+
+ /**
+ * Query the list of band mode supported by RF.
+ *
+ * @param response is callback message
+ * ((AsyncResult)response.obj).result is an int[] with every
+ * element representing one avialable BM_*_BAND
+ */
+ public void queryAvailableBandMode (Message response) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE,
+ response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendTerminalResponse(String contents, Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeString(contents);
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendEnvelope(String contents, Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeString(contents);
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleCallSetupRequestFromSim(
+ boolean accept, Message response) {
+
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM,
+ response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ int[] param = new int[1];
+ param[0] = accept ? 1 : 0;
+ rr.mp.writeIntArray(param);
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setPreferredNetworkType(int networkType , Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(networkType);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " : " + networkType);
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void getPreferredNetworkType(Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void getNeighboringCids(Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_GET_NEIGHBORING_CELL_IDS, response);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setLocationUpdates(boolean enable, Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_SET_LOCATION_UPDATES, response);
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(enable ? 1 : 0);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest) + ": " + enable);
+
+ send(rr);
+ }
+
+ //***** Private Methods
+
+ private void sendScreenState(boolean on)
+ {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_SCREEN_STATE, null);
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(on ? 1 : 0);
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + ": " + on);
+
+ send(rr);
+ }
+
+ protected void
+ onRadioAvailable()
+ {
+ // In case screen state was lost (due to process crash),
+ // this ensures that the RIL knows the correct screen state.
+
+ // TODO: Should query Power Manager and send the actual
+ // screen state. Just send true for now.
+ sendScreenState(true);
+ }
+
+ private void setRadioStateFromRILInt(int state) {
+ RadioState newState;
+
+ /* RIL_RadioState ril.h */
+ switch(state) {
+ case 0: newState = RadioState.RADIO_OFF; break;
+ case 1: newState = RadioState.RADIO_UNAVAILABLE; break;
+ case 2: newState = RadioState.SIM_NOT_READY; break;
+ case 3: newState = RadioState.SIM_LOCKED_OR_ABSENT; break;
+ case 4: newState = RadioState.SIM_READY; break;
+ default:
+ throw new RuntimeException(
+ "Unrecognized RIL_RadioState: " +state);
+ }
+
+ if (mInitialRadioStateChange) {
+ mInitialRadioStateChange = false;
+ if (newState.isOn()) {
+ /* If this is our first notification, make sure the radio
+ * is powered off. This gets the radio into a known state,
+ * since it's possible for the phone proc to have restarted
+ * (eg, if it or the runtime crashed) without the RIL
+ * and/or radio knowing.
+ */
+ if (DBG) Log.d(LOG_TAG, "Radio ON @ init; reset to OFF");
+ setRadioPower(false, null);
+ return;
+ }
+ }
+
+ setRadioState(newState);
+ }
+
+ /**
+ * Holds a PARTIAL_WAKE_LOCK whenever
+ * a) There is outstanding RIL request sent to RIL deamon and no replied
+ * b) There is a request waiting to be sent out.
+ *
+ * There is a WAKE_LOCK_TIMEOUT to release the lock, though it shouldn't
+ * happen often.
+ */
+
+ private void
+ acquireWakeLock()
+ {
+ synchronized (mWakeLock) {
+ mWakeLock.acquire();
+ mRequestMessagesPending++;
+
+ mSenderH.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
+ Message msg = mSenderH.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
+ mSenderH.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT);
+ }
+ }
+
+ private void
+ releaseWakeLockIfDone()
+ {
+ synchronized (mWakeLock) {
+ if (mWakeLock.isHeld() &&
+ (mRequestMessagesPending == 0) &&
+ (mRequestsList.size() == 0)) {
+ mSenderH.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
+ mWakeLock.release();
+ }
+ }
+ }
+
+ private void
+ send(RILRequest rr)
+ {
+ Message msg;
+
+ msg = mSenderH.obtainMessage(EVENT_SEND, rr);
+
+ acquireWakeLock();
+
+ msg.sendToTarget();
+ }
+
+ private void
+ processResponse (Parcel p)
+ {
+ int type;
+
+ type = p.readInt();
+
+ if (type == RESPONSE_UNSOLICITED) {
+ processUnsolicited (p);
+ } else if (type == RESPONSE_SOLICITED) {
+ processSolicited (p);
+ }
+
+ releaseWakeLockIfDone();
+ }
+
+
+
+ private RILRequest findAndRemoveRequestFromList(int serial)
+ {
+ synchronized (mRequestsList) {
+ for (int i = 0, s = mRequestsList.size() ; i < s ; i++) {
+ RILRequest rr = mRequestsList.get(i);
+
+ if (rr.mSerial == serial) {
+ mRequestsList.remove(i);
+ return rr;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void
+ processSolicited (Parcel p)
+ {
+ int serial, error;
+ boolean found = false;
+
+ serial = p.readInt();
+ error = p.readInt();
+
+ RILRequest rr;
+
+ rr = findAndRemoveRequestFromList(serial);
+
+ if (rr == null) {
+ Log.w(LOG_TAG, "Unexpected solicited response! sn: "
+ + serial + " error: " + error);
+ return;
+ }
+
+ if (error != 0) {
+ rr.onError(error);
+ rr.release();
+ return;
+ }
+
+ Object ret;
+
+ try {switch (rr.mRequest) {
+/*
+ cat libs/telephony/ril_commands.h \
+ | egrep "^ *{RIL_" \
+ | sed -re 's/\{([^,]+),[^,]+,([^}]+).+/case \1: ret = \2(p); break;/'
+*/
+ case RIL_REQUEST_GET_SIM_STATUS: ret = responseSimStatus(p); break;
+ case RIL_REQUEST_ENTER_SIM_PIN: ret = responseVoid(p); break;
+ case RIL_REQUEST_ENTER_SIM_PUK: ret = responseVoid(p); break;
+ case RIL_REQUEST_ENTER_SIM_PIN2: ret = responseVoid(p); break;
+ case RIL_REQUEST_ENTER_SIM_PUK2: ret = responseVoid(p); break;
+ case RIL_REQUEST_CHANGE_SIM_PIN: ret = responseVoid(p); break;
+ case RIL_REQUEST_CHANGE_SIM_PIN2: ret = responseVoid(p); break;
+ case RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION: ret = responseVoid(p); break;
+ case RIL_REQUEST_GET_CURRENT_CALLS: ret = responseCallList(p); break;
+ case RIL_REQUEST_DIAL: ret = responseVoid(p); break;
+ case RIL_REQUEST_GET_IMSI: ret = responseString(p); break;
+ case RIL_REQUEST_HANGUP: ret = responseVoid(p); break;
+ case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND: ret = responseVoid(p); break;
+ case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND: ret = responseVoid(p); break;
+ case RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE: ret = responseVoid(p); break;
+ case RIL_REQUEST_CONFERENCE: ret = responseVoid(p); break;
+ case RIL_REQUEST_UDUB: ret = responseVoid(p); break;
+ case RIL_REQUEST_LAST_CALL_FAIL_CAUSE: ret = responseInts(p); break;
+ case RIL_REQUEST_SIGNAL_STRENGTH: ret = responseInts(p); break;
+ case RIL_REQUEST_REGISTRATION_STATE: ret = responseStrings(p); break;
+ case RIL_REQUEST_GPRS_REGISTRATION_STATE: ret = responseStrings(p); break;
+ case RIL_REQUEST_OPERATOR: ret = responseStrings(p); break;
+ case RIL_REQUEST_RADIO_POWER: ret = responseVoid(p); break;
+ case RIL_REQUEST_DTMF: ret = responseVoid(p); break;
+ case RIL_REQUEST_SEND_SMS: ret = responseSMS(p); break;
+ case RIL_REQUEST_SEND_SMS_EXPECT_MORE: ret = responseSMS(p); break;
+ case RIL_REQUEST_SETUP_DEFAULT_PDP: ret = responseStrings(p); break;
+ case RIL_REQUEST_SIM_IO: ret = responseSIM_IO(p); break;
+ case RIL_REQUEST_SEND_USSD: ret = responseVoid(p); break;
+ case RIL_REQUEST_CANCEL_USSD: ret = responseVoid(p); break;
+ case RIL_REQUEST_GET_CLIR: ret = responseInts(p); break;
+ case RIL_REQUEST_SET_CLIR: ret = responseVoid(p); break;
+ case RIL_REQUEST_QUERY_CALL_FORWARD_STATUS: ret = responseCallForward(p); break;
+ case RIL_REQUEST_SET_CALL_FORWARD: ret = responseVoid(p); break;
+ case RIL_REQUEST_QUERY_CALL_WAITING: ret = responseInts(p); break;
+ case RIL_REQUEST_SET_CALL_WAITING: ret = responseVoid(p); break;
+ case RIL_REQUEST_SMS_ACKNOWLEDGE: ret = responseVoid(p); break;
+ case RIL_REQUEST_GET_IMEI: ret = responseString(p); break;
+ case RIL_REQUEST_GET_IMEISV: ret = responseString(p); break;
+ case RIL_REQUEST_ANSWER: ret = responseVoid(p); break;
+ case RIL_REQUEST_DEACTIVATE_DEFAULT_PDP: ret = responseVoid(p); break;
+ case RIL_REQUEST_QUERY_FACILITY_LOCK: ret = responseInts(p); break;
+ case RIL_REQUEST_SET_FACILITY_LOCK: ret = responseVoid(p); break;
+ case RIL_REQUEST_CHANGE_BARRING_PASSWORD: ret = responseVoid(p); break;
+ case RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE: ret = responseInts(p); break;
+ case RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC: ret = responseVoid(p); break;
+ case RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL: ret = responseVoid(p); break;
+ case RIL_REQUEST_QUERY_AVAILABLE_NETWORKS : ret = responseNetworkInfos(p); break;
+ case RIL_REQUEST_DTMF_START: ret = responseVoid(p); break;
+ case RIL_REQUEST_DTMF_STOP: ret = responseVoid(p); break;
+ case RIL_REQUEST_BASEBAND_VERSION: ret = responseString(p); break;
+ case RIL_REQUEST_SEPARATE_CONNECTION: ret = responseVoid(p); break;
+ case RIL_REQUEST_SET_MUTE: ret =responseVoid(p); break;
+ case RIL_REQUEST_GET_MUTE: ret = responseInts(p); break;
+ case RIL_REQUEST_QUERY_CLIP: ret = responseInts(p); break;
+ case RIL_REQUEST_LAST_PDP_FAIL_CAUSE: ret = responseInts(p); break;
+ case RIL_REQUEST_PDP_CONTEXT_LIST: ret = responseContextList(p); break;
+ case RIL_REQUEST_RESET_RADIO: ret = responseVoid(p); break;
+ case RIL_REQUEST_OEM_HOOK_RAW: ret = responseRaw(p); break;
+ case RIL_REQUEST_OEM_HOOK_STRINGS: ret = responseStrings(p); break;
+ case RIL_REQUEST_SCREEN_STATE: ret = responseVoid(p); break;
+ case RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION: ret = responseVoid(p); break;
+ case RIL_REQUEST_WRITE_SMS_TO_SIM: ret = responseInts(p); break;
+ case RIL_REQUEST_DELETE_SMS_ON_SIM: ret = responseVoid(p); break;
+ case RIL_REQUEST_SET_BAND_MODE: ret = responseVoid(p); break;
+ case RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE: ret = responseInts(p); break;
+ case RIL_REQUEST_STK_GET_PROFILE: ret = responseString(p); break;
+ case RIL_REQUEST_STK_SET_PROFILE: ret = responseVoid(p); break;
+ case RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND: ret = responseString(p); break;
+ case RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE: ret = responseVoid(p); break;
+ case RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM: ret = responseInts(p); break;
+ case RIL_REQUEST_EXPLICIT_CALL_TRANSFER: ret = responseVoid(p); break;
+ case RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE: ret = responseVoid(p); break;
+ case RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE: ret = responseInts(p); break;
+ case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS: ret = responseStrings(p); break;
+ case RIL_REQUEST_SET_LOCATION_UPDATES: ret = responseVoid(p); break;
+
+ default:
+ throw new RuntimeException("Unrecognized solicited response: " + rr.mRequest);
+ //break;
+ }} catch (Throwable tr) {
+ // Exceptions here usually mean invalid RIL responses
+
+ Log.w(LOG_TAG, rr.serialString() + "< "
+ + requestToString(rr.mRequest) + " exception, possible invalid RIL response", tr);
+
+ if (rr.mResult != null) {
+ AsyncResult.forMessage(rr.mResult, null, tr);
+ rr.mResult.sendToTarget();
+ }
+ rr.release();
+ return;
+ }
+
+ if (RILJ_LOG) riljLog(rr.serialString() + "< " + requestToString(rr.mRequest)
+ + " " + retToString(rr.mRequest, ret));
+
+ if (rr.mResult != null) {
+ AsyncResult.forMessage(rr.mResult, ret, null);
+ rr.mResult.sendToTarget();
+ }
+
+ rr.release();
+ }
+
+ private String
+ retToString(int req, Object ret)
+ {
+ if (ret == null) return "";
+ switch (req) {
+ // Don't log these return values, for privacy's sake.
+ case RIL_REQUEST_GET_IMSI:
+ case RIL_REQUEST_GET_IMEI:
+ case RIL_REQUEST_GET_IMEISV:
+ return "";
+ }
+
+ StringBuilder sb;
+ String s;
+ int length;
+ if (ret instanceof int[]){
+ int[] intArray = (int[]) ret;
+ length = intArray.length;
+ sb = new StringBuilder("{");
+ if (length > 0) {
+ int i = 0;
+ sb.append(intArray[i++]);
+ while ( i < length) {
+ sb.append(", ").append(intArray[i++]);
+ }
+ }
+ sb.append("}");
+ s = sb.toString();
+ } else if (ret instanceof String[]) {
+ String[] strings = (String[]) ret;
+ length = strings.length;
+ sb = new StringBuilder("{");
+ if (length > 0) {
+ int i = 0;
+ sb.append(strings[i++]);
+ while ( i < length) {
+ sb.append(", ").append(strings[i++]);
+ }
+ }
+ sb.append("}");
+ s = sb.toString();
+ }else if (req == RIL_REQUEST_GET_CURRENT_CALLS) {
+ ArrayList<DriverCall> calls = (ArrayList<DriverCall>) ret;
+ sb = new StringBuilder(" ");
+ for (DriverCall dc : calls) {
+ sb.append("[").append(dc).append("] ");
+ }
+ s = sb.toString();
+ } else {
+ s = ret.toString();
+ }
+ return s;
+ }
+
+ private void
+ processUnsolicited (Parcel p)
+ {
+ int response;
+ Object ret;
+
+ response = p.readInt();
+
+ try {switch(response) {
+/*
+ cat libs/telephony/ril_unsol_commands.h \
+ | egrep "^ *{RIL_" \
+ | sed -re 's/\{([^,]+),[^,]+,([^}]+).+/case \1: \2(rr, p); break;/'
+*/
+
+ case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED: ret = responseVoid(p); break;
+ case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED: ret = responseVoid(p); break;
+ case RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED: ret = responseVoid(p); break;
+ case RIL_UNSOL_RESPONSE_NEW_SMS: ret = responseString(p); break;
+ case RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT: ret = responseString(p); break;
+ case RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM: ret = responseInts(p); break;
+ case RIL_UNSOL_ON_USSD: ret = responseStrings(p); break;
+ case RIL_UNSOL_NITZ_TIME_RECEIVED: ret = responseString(p); break;
+ case RIL_UNSOL_SIGNAL_STRENGTH: ret = responseInts(p); break;
+ case RIL_UNSOL_PDP_CONTEXT_LIST_CHANGED: ret = responseContextList(p);break;
+ case RIL_UNSOL_SUPP_SVC_NOTIFICATION: ret = responseSuppServiceNotification(p); break;
+ case RIL_UNSOL_STK_SESSION_END: ret = responseVoid(p); break;
+ case RIL_UNSOL_STK_PROACTIVE_COMMAND: ret = responseString(p); break;
+ case RIL_UNSOL_STK_EVENT_NOTIFY: ret = responseString(p); break;
+ case RIL_UNSOL_STK_CALL_SETUP: ret = responseInts(p); break;
+ case RIL_UNSOL_SIM_SMS_STORAGE_FULL: ret = responseVoid(p); break;
+ case RIL_UNSOL_SIM_REFRESH: ret = responseInts(p); break;
+ case RIL_UNSOL_CALL_RING: ret = responseVoid(p); break;
+ default:
+ throw new RuntimeException("Unrecognized unsol response: " + response);
+ //break; (implied)
+ }} catch (Throwable tr) {
+ Log.e(LOG_TAG, "Exception processing unsol response: "
+ + response, tr);
+ return;
+ }
+
+ switch(response) {
+ case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED:
+ /* has bonus radio state int */
+ setRadioStateFromRILInt(p.readInt());
+
+ if (RILJ_LOG) riljLog("[UNSL]< RADIO_STATE_CHANGED " +mState);
+ break;
+ case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED:
+ if (RILJ_LOG) riljLog("[UNSL]< CALL_STATE_CHANGED");
+
+ mCallStateRegistrants
+ .notifyRegistrants(new AsyncResult(null, null, null));
+ break;
+ case RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED:
+ if (RILJ_LOG) riljLog("[UNSL]< NETWORK_STATE_CHANGED");
+
+ mNetworkStateRegistrants
+ .notifyRegistrants(new AsyncResult(null, null, null));
+ break;
+ case RIL_UNSOL_RESPONSE_NEW_SMS: {
+ if (RILJ_LOG) riljLog("[UNSL]< NEW_SMS");
+
+ // FIXME this should move up a layer
+ String a[] = new String[2];
+
+ a[1] = (String)ret;
+
+ SmsMessage sms;
+
+ sms = SmsMessage.newFromCMT(a);
+ if (mSMSRegistrant != null) {
+ mSMSRegistrant
+ .notifyRegistrant(new AsyncResult(null, sms, null));
+ }
+ break;
+ }
+ case RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT:
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog("[UNSL]< NEW_SMS_STATUS_REPORT " + ret);
+ }
+
+ if (mSmsStatusRegistrant != null) {
+ mSmsStatusRegistrant.notifyRegistrant(
+ new AsyncResult(null, ret, null));
+ }
+ break;
+ case RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM:
+ if (RILJ_LOG) riljLog("[UNSL]< NEW_SMS_ON_SIM" + retToString(response, ret));
+
+ int[] smsIndex = (int[])ret;
+
+ if(smsIndex.length == 1) {
+ if (mSmsOnSimRegistrant != null) {
+ mSmsOnSimRegistrant.
+ notifyRegistrant(new AsyncResult(null, smsIndex, null));
+ }
+ } else {
+ if (RILJ_LOG) riljLog(" NEW_SMS_ON_SIM ERROR with wrong length "
+ + smsIndex.length);
+ }
+ break;
+ case RIL_UNSOL_ON_USSD:
+ String[] resp = (String[])ret;
+
+ if (resp.length < 2) {
+ resp = new String[2];
+ resp[0] = ((String[])ret)[0];
+ resp[1] = null;
+ }
+ if (RILJ_LOG) riljLog("[UNSL]< ON_USSD " + resp[0]);
+ if (mUSSDRegistrant != null) {
+ mUSSDRegistrant.notifyRegistrant(
+ new AsyncResult (null, resp, null));
+ }
+ break;
+ case RIL_UNSOL_NITZ_TIME_RECEIVED:
+ if (RILJ_LOG) riljLog("[UNSL]< NITZ_TIME_RECEIVED " + retToString(response, ret));
+
+ // has bonus int containing time_t that the NITZ
+ // time was received
+ int nitzReceiveTime = p.readInt();
+
+ Object[] result = new Object[2];
+
+ result[0] = ret;
+ result[1] = Integer.valueOf(nitzReceiveTime);
+
+ if (mNITZTimeRegistrant != null) {
+
+ mNITZTimeRegistrant
+ .notifyRegistrant(new AsyncResult (null, result, null));
+ } else {
+ // in case NITZ time registrant isnt registered yet
+ mLastNITZTimeInfo = result;
+ }
+ break;
+
+ case RIL_UNSOL_SIGNAL_STRENGTH:
+ // Note this is set to "verbose" because it happens
+ // frequently
+ if (Config.LOGV) Log.v(LOG_TAG, "[UNSL]< SIGNAL_STRENGTH "
+ + retToString(response, ret));
+
+ if (mSignalStrengthRegistrant != null) {
+ mSignalStrengthRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+ case RIL_UNSOL_PDP_CONTEXT_LIST_CHANGED:
+ if (RILJ_LOG) riljLog("[UNSL]< PDP_CONTEXT_CHANGED " + retToString(response, ret));
+
+ mPDPRegistrants
+ .notifyRegistrants(new AsyncResult(null, ret, null));
+ break;
+
+ case RIL_UNSOL_SUPP_SVC_NOTIFICATION:
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog("[UNSL]< SUPP_SVC_NOTIFICATION "
+ + retToString(response, ret));
+ }
+
+ if (mSsnRegistrant != null) {
+ mSsnRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_STK_SESSION_END:
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog("[UNSL]< STK_SESSION_END");
+ }
+
+ if (mStkSessionEndRegistrant != null) {
+ mStkSessionEndRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_STK_PROACTIVE_COMMAND:
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog("[UNSL]< STK_PROACTIVE_COMMAND "
+ + retToString(response, ret));
+ }
+
+ if (mStkProCmdRegistrant != null) {
+ mStkProCmdRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_STK_EVENT_NOTIFY:
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog("[UNSL]< STK_EVENT_NOTIFY "
+ + retToString(response, ret));
+ }
+
+ if (mStkEventRegistrant != null) {
+ mStkEventRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_STK_CALL_SETUP:
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog("[UNSL]< STK_CALL_SETUP "
+ + retToString(response, ret));
+ }
+
+ if (mStkCallSetUpRegistrant != null) {
+ mStkCallSetUpRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_SIM_SMS_STORAGE_FULL:
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog("[UNSL]< SIM_SMS_STORAGE_FULL");
+ }
+
+ if (mSimSmsFullRegistrant != null) {
+ mSimSmsFullRegistrant.notifyRegistrant();
+ }
+ break;
+
+ case RIL_UNSOL_SIM_REFRESH:
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog("[UNSL]< SIM_REFRESH " + retToString(response, ret));
+ }
+
+ if (mSimRefreshRegistrant != null) {
+ mSimRefreshRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_CALL_RING:
+ if (Config.LOGD) {
+ if (RILJ_LOG) riljLog("[UNSL]< CALL_RING ");
+ }
+
+ if (mRingRegistrant != null) {
+ mRingRegistrant.notifyRegistrant();
+ }
+ break;
+ }
+ }
+
+ private Object
+ responseInts(Parcel p)
+ {
+ int numInts;
+ int response[];
+
+ numInts = p.readInt();
+
+ response = new int[numInts];
+
+ for (int i = 0 ; i < numInts ; i++) {
+ response[i] = p.readInt();
+ }
+
+ return response;
+ }
+
+
+ private Object
+ responseVoid(Parcel p)
+ {
+ return null;
+ }
+
+ private Object
+ responseCallForward(Parcel p)
+ {
+ int numInfos;
+ CallForwardInfo infos[];
+
+ numInfos = p.readInt();
+
+ infos = new CallForwardInfo[numInfos];
+
+ for (int i = 0 ; i < numInfos ; i++) {
+ infos[i] = new CallForwardInfo();
+
+ infos[i].status = p.readInt();
+ infos[i].reason = p.readInt();
+ infos[i].serviceClass = p.readInt();
+ infos[i].toa = p.readInt();
+ infos[i].number = p.readString();
+ infos[i].timeSeconds = p.readInt();
+ }
+
+ return infos;
+ }
+
+ private Object
+ responseSuppServiceNotification(Parcel p)
+ {
+ SuppServiceNotification notification = new SuppServiceNotification();
+
+ notification.notificationType = p.readInt();
+ notification.code = p.readInt();
+ notification.index = p.readInt();
+ notification.type = p.readInt();
+ notification.number = p.readString();
+
+ return notification;
+ }
+
+ private Object
+ responseString(Parcel p)
+ {
+ String response;
+
+ response = p.readString();
+
+ return response;
+ }
+
+ private Object
+ responseStrings(Parcel p)
+ {
+ int num;
+ String response[];
+
+ response = p.readStringArray();
+
+ if (false) {
+ num = p.readInt();
+
+ response = new String[num];
+ for (int i = 0; i < num; i++) {
+ response[i] = p.readString();
+ }
+ }
+
+ return response;
+ }
+
+ private Object
+ responseRaw(Parcel p)
+ {
+ int num;
+ byte response[];
+
+ response = p.createByteArray();
+
+ return response;
+ }
+
+ private Object
+ responseSMS(Parcel p)
+ {
+ int messageRef;
+ String ackPDU;
+
+ messageRef = p.readInt();
+ ackPDU = p.readString();
+
+ SmsResponse response = new SmsResponse(messageRef, ackPDU);
+
+ return response;
+ }
+
+
+ private Object
+ responseSIM_IO(Parcel p)
+ {
+ int sw1, sw2;
+ byte data[] = null;
+ Message ret;
+
+ sw1 = p.readInt();
+ sw2 = p.readInt();
+
+ String s = p.readString();
+
+ return new SimIoResult(sw1, sw2, s);
+ }
+
+ private Object
+ responseSimStatus(Parcel p)
+ {
+ int status;
+
+ status = ((int[])responseInts(p))[0];
+ switch (status){
+ case RIL_SIM_ABSENT: return SimStatus.SIM_ABSENT;
+ case RIL_SIM_NOT_READY: return SimStatus.SIM_NOT_READY;
+ case RIL_SIM_READY: return SimStatus.SIM_READY;
+ case RIL_SIM_PIN: return SimStatus.SIM_PIN;
+ case RIL_SIM_PUK: return SimStatus.SIM_PUK;
+ case RIL_SIM_NETWORK_PERSONALIZATION:
+ return SimStatus.SIM_NETWORK_PERSONALIZATION;
+ default:
+ throw new RuntimeException ("Invalid RIL_REQUEST_GET_SIM_STATUS result: " + status);
+ }
+ }
+
+
+ private Object
+ responseCallList(Parcel p)
+ {
+ int num;
+ ArrayList<DriverCall> response;
+ DriverCall dc;
+
+ num = p.readInt();
+ response = new ArrayList<DriverCall>(num);
+
+ for (int i = 0 ; i < num ; i++) {
+ dc = new DriverCall();
+
+ dc.state = DriverCall.stateFromCLCC(p.readInt());
+ dc.index = p.readInt();
+ dc.TOA = p.readInt();
+ dc.isMpty = (0 != p.readInt());
+ dc.isMT = (0 != p.readInt());
+ dc.als = p.readInt();
+ dc.isVoice = (0 == p.readInt()) ? false : true;
+ dc.number = p.readString();
+
+ // Make sure there's a leading + on addresses with a TOA
+ // of 145
+
+ dc.number = PhoneNumberUtils.stringFromStringAndTOA(
+ dc.number, dc.TOA);
+
+ response.add(dc);
+ }
+
+ Collections.sort(response);
+
+ return response;
+ }
+
+ private Object
+ responseContextList(Parcel p)
+ {
+ int num;
+ ArrayList<PDPContextState> response;
+
+ num = p.readInt();
+ response = new ArrayList<PDPContextState>(num);
+
+ for (int i = 0; i < num; i++) {
+ PDPContextState pdp = new PDPContextState();
+
+ pdp.cid = p.readInt();
+ pdp.active = p.readInt() == 0 ? false : true;
+ pdp.type = p.readString();
+ pdp.apn = p.readString();
+ pdp.address = p.readString();
+
+ response.add(pdp);
+ }
+
+ return response;
+ }
+
+ private Object
+ responseNetworkInfos(Parcel p)
+ {
+ String strings[] = (String [])responseStrings(p);
+ ArrayList<NetworkInfo> ret;
+
+ if (strings.length % 4 != 0) {
+ throw new RuntimeException(
+ "RIL_REQUEST_QUERY_AVAILABLE_NETWORKS: invalid response. Got "
+ + strings.length + " strings, expected multible of 4");
+ }
+
+ ret = new ArrayList<NetworkInfo>(strings.length / 4);
+
+ for (int i = 0 ; i < strings.length ; i += 4) {
+ ret.add (
+ new NetworkInfo(
+ strings[i+0],
+ strings[i+1],
+ strings[i+2],
+ strings[i+3]));
+ }
+
+ return ret;
+ }
+
+
+ static String
+ requestToString(int request)
+ {
+/*
+ cat libs/telephony/ril_commands.h \
+ | egrep "^ *{RIL_" \
+ | sed -re 's/\{RIL_([^,]+),[^,]+,([^}]+).+/case RIL_\1: return "\1";/'
+*/
+ switch(request) {
+ case RIL_REQUEST_GET_SIM_STATUS: return "GET_SIM_STATUS";
+ case RIL_REQUEST_ENTER_SIM_PIN: return "ENTER_SIM_PIN";
+ case RIL_REQUEST_ENTER_SIM_PUK: return "ENTER_SIM_PUK";
+ case RIL_REQUEST_ENTER_SIM_PIN2: return "ENTER_SIM_PIN2";
+ case RIL_REQUEST_ENTER_SIM_PUK2: return "ENTER_SIM_PUK2";
+ case RIL_REQUEST_CHANGE_SIM_PIN: return "CHANGE_SIM_PIN";
+ case RIL_REQUEST_CHANGE_SIM_PIN2: return "CHANGE_SIM_PIN2";
+ case RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION: return "ENTER_NETWORK_DEPERSONALIZATION";
+ case RIL_REQUEST_GET_CURRENT_CALLS: return "GET_CURRENT_CALLS";
+ case RIL_REQUEST_DIAL: return "DIAL";
+ case RIL_REQUEST_GET_IMSI: return "GET_IMSI";
+ case RIL_REQUEST_HANGUP: return "HANGUP";
+ case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND: return "HANGUP_WAITING_OR_BACKGROUND";
+ case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND: return "HANGUP_FOREGROUND_RESUME_BACKGROUND";
+ case RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE: return "RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE";
+ case RIL_REQUEST_CONFERENCE: return "CONFERENCE";
+ case RIL_REQUEST_UDUB: return "UDUB";
+ case RIL_REQUEST_LAST_CALL_FAIL_CAUSE: return "LAST_CALL_FAIL_CAUSE";
+ case RIL_REQUEST_SIGNAL_STRENGTH: return "SIGNAL_STRENGTH";
+ case RIL_REQUEST_REGISTRATION_STATE: return "REGISTRATION_STATE";
+ case RIL_REQUEST_GPRS_REGISTRATION_STATE: return "GPRS_REGISTRATION_STATE";
+ case RIL_REQUEST_OPERATOR: return "OPERATOR";
+ case RIL_REQUEST_RADIO_POWER: return "RADIO_POWER";
+ case RIL_REQUEST_DTMF: return "DTMF";
+ case RIL_REQUEST_SEND_SMS: return "SEND_SMS";
+ case RIL_REQUEST_SEND_SMS_EXPECT_MORE: return "SEND_SMS_EXPECT_MORE";
+ case RIL_REQUEST_SETUP_DEFAULT_PDP: return "SETUP_DEFAULT_PDP";
+ case RIL_REQUEST_SIM_IO: return "SIM_IO";
+ case RIL_REQUEST_SEND_USSD: return "SEND_USSD";
+ case RIL_REQUEST_CANCEL_USSD: return "CANCEL_USSD";
+ case RIL_REQUEST_GET_CLIR: return "GET_CLIR";
+ case RIL_REQUEST_SET_CLIR: return "SET_CLIR";
+ case RIL_REQUEST_QUERY_CALL_FORWARD_STATUS: return "QUERY_CALL_FORWARD_STATUS";
+ case RIL_REQUEST_SET_CALL_FORWARD: return "SET_CALL_FORWARD";
+ case RIL_REQUEST_QUERY_CALL_WAITING: return "QUERY_CALL_WAITING";
+ case RIL_REQUEST_SET_CALL_WAITING: return "SET_CALL_WAITING";
+ case RIL_REQUEST_SMS_ACKNOWLEDGE: return "SMS_ACKNOWLEDGE";
+ case RIL_REQUEST_GET_IMEI: return "GET_IMEI";
+ case RIL_REQUEST_GET_IMEISV: return "GET_IMEISV";
+ case RIL_REQUEST_ANSWER: return "ANSWER";
+ case RIL_REQUEST_DEACTIVATE_DEFAULT_PDP: return "DEACTIVATE_DEFAULT_PDP";
+ case RIL_REQUEST_QUERY_FACILITY_LOCK: return "QUERY_FACILITY_LOCK";
+ case RIL_REQUEST_SET_FACILITY_LOCK: return "SET_FACILITY_LOCK";
+ case RIL_REQUEST_CHANGE_BARRING_PASSWORD: return "CHANGE_BARRING_PASSWORD";
+ case RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE: return "QUERY_NETWORK_SELECTION_MODE";
+ case RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC: return "SET_NETWORK_SELECTION_AUTOMATIC";
+ case RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL: return "SET_NETWORK_SELECTION_MANUAL";
+ case RIL_REQUEST_QUERY_AVAILABLE_NETWORKS : return "QUERY_AVAILABLE_NETWORKS ";
+ case RIL_REQUEST_DTMF_START: return "DTMF_START";
+ case RIL_REQUEST_DTMF_STOP: return "DTMF_STOP";
+ case RIL_REQUEST_BASEBAND_VERSION: return "BASEBAND_VERSION";
+ case RIL_REQUEST_SEPARATE_CONNECTION: return "SEPARATE_CONNECTION";
+ case RIL_REQUEST_SET_MUTE: return "SET_MUTE";
+ case RIL_REQUEST_GET_MUTE: return "GET_MUTE";
+ case RIL_REQUEST_QUERY_CLIP: return "QUERY_CLIP";
+ case RIL_REQUEST_LAST_PDP_FAIL_CAUSE: return "LAST_PDP_FAIL_CAUSE";
+ case RIL_REQUEST_PDP_CONTEXT_LIST: return "PDP_CONTEXT_LIST";
+ case RIL_REQUEST_RESET_RADIO: return "RESET_RADIO";
+ case RIL_REQUEST_OEM_HOOK_RAW: return "OEM_HOOK_RAW";
+ case RIL_REQUEST_OEM_HOOK_STRINGS: return "OEM_HOOK_STRINGS";
+ case RIL_REQUEST_SCREEN_STATE: return "SCREEN_STATE";
+ case RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION: return "SET_SUPP_SVC_NOTIFICATION";
+ case RIL_REQUEST_WRITE_SMS_TO_SIM: return "WRITE_SMS_TO_SIM";
+ case RIL_REQUEST_DELETE_SMS_ON_SIM: return "DELETE_SMS_ON_SIM";
+ case RIL_REQUEST_SET_BAND_MODE: return "SET_BAND_MODE";
+ case RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE: return "QUERY_AVAILABLE_BAND_MODE";
+ case RIL_REQUEST_STK_GET_PROFILE: return "RIL_REQUEST_STK_GET_PROFILE";
+ case RIL_REQUEST_STK_SET_PROFILE: return "RIL_REQUEST_STK_SET_PROFILE";
+ case RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND: return "RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND";
+ case RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE: return "RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE";
+ case RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM: return "RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM";
+ case RIL_REQUEST_EXPLICIT_CALL_TRANSFER: return "RIL_REQUEST_EXPLICIT_CALL_TRANSFER";
+ case RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE: return "RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE";
+ case RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE: return "RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE";
+ case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS: return "RIL_REQUEST_GET_NEIGHBORING_CELL_IDS";
+ case RIL_REQUEST_SET_LOCATION_UPDATES: return "RIL_REQUEST_SET_LOCATION_UPDATES";
+ default: return "<unknown request>";
+ }
+ }
+
+ private void riljLog(String msg) {
+ Log.d(LOG_TAG, msg);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/RILConstants.java b/telephony/java/com/android/internal/telephony/gsm/RILConstants.java
new file mode 100644
index 0000000..0a8e192
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/RILConstants.java
@@ -0,0 +1,150 @@
+/*
+ * 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.internal.telephony.gsm;
+
+
+/**
+ * {@hide}
+ */
+interface RILConstants
+{
+ // From the top of ril.cpp
+ int RIL_ERRNO_INVALID_RESPONSE = -1;
+
+ // from RIL_Errno
+ int SUCCESS = 0;
+ int RADIO_NOT_AVAILABLE = 1; /* If radio did not start or is resetting */
+ int GENERIC_FAILURE = 2;
+ int PASSWORD_INCORRECT = 3; /* for PIN/PIN2 methods only! */
+ int SIM_PIN2 = 4; /* Operation requires SIM PIN2 to be entered */
+ int SIM_PUK2 = 5; /* Operation requires SIM PIN2 to be entered */
+ int REQUEST_NOT_SUPPORTED = 6;
+ int REQUEST_CANCELLED = 7;
+ int OP_NOT_ALLOWED_DURING_VOICE_CALL = 8; /* data operation is not allowed during voice call in class C */
+ int OP_NOT_ALLOWED_BEFORE_REG_NW = 9; /* request is not allowed before device registers to network */
+ int SMS_SEND_FAIL_RETRY = 10; /* send sms fail and need retry */
+/*
+cat include/telephony/ril.h | \
+ egrep '^#define' | \
+ sed -re 's/^#define +([^ ]+)* +([^ ]+)/ int \1 = \2;/' \
+ >>java/android/com.android.internal.telephony/gsm/RILConstants.java
+*/
+
+
+ int RIL_SIM_ABSENT = 0;
+ int RIL_SIM_NOT_READY = 1;
+ int RIL_SIM_READY = 2;
+ int RIL_SIM_PIN = 3;
+ int RIL_SIM_PUK = 4;
+ int RIL_SIM_NETWORK_PERSONALIZATION = 5;
+ int RIL_REQUEST_GET_SIM_STATUS = 1;
+ int RIL_REQUEST_ENTER_SIM_PIN = 2;
+ int RIL_REQUEST_ENTER_SIM_PUK = 3;
+ int RIL_REQUEST_ENTER_SIM_PIN2 = 4;
+ int RIL_REQUEST_ENTER_SIM_PUK2 = 5;
+ int RIL_REQUEST_CHANGE_SIM_PIN = 6;
+ int RIL_REQUEST_CHANGE_SIM_PIN2 = 7;
+ int RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION = 8;
+ int RIL_REQUEST_GET_CURRENT_CALLS = 9;
+ int RIL_REQUEST_DIAL = 10;
+ int RIL_REQUEST_GET_IMSI = 11;
+ int RIL_REQUEST_HANGUP = 12;
+ int RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND = 13;
+ int RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND = 14;
+ int RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE = 15;
+ int RIL_REQUEST_CONFERENCE = 16;
+ int RIL_REQUEST_UDUB = 17;
+ int RIL_REQUEST_LAST_CALL_FAIL_CAUSE = 18;
+ int RIL_REQUEST_SIGNAL_STRENGTH = 19;
+ int RIL_REQUEST_REGISTRATION_STATE = 20;
+ int RIL_REQUEST_GPRS_REGISTRATION_STATE = 21;
+ int RIL_REQUEST_OPERATOR = 22;
+ int RIL_REQUEST_RADIO_POWER = 23;
+ int RIL_REQUEST_DTMF = 24;
+ int RIL_REQUEST_SEND_SMS = 25;
+ int RIL_REQUEST_SEND_SMS_EXPECT_MORE = 26;
+ int RIL_REQUEST_SETUP_DEFAULT_PDP = 27;
+ int RIL_REQUEST_SIM_IO = 28;
+ int RIL_REQUEST_SEND_USSD = 29;
+ int RIL_REQUEST_CANCEL_USSD = 30;
+ int RIL_REQUEST_GET_CLIR = 31;
+ int RIL_REQUEST_SET_CLIR = 32;
+ int RIL_REQUEST_QUERY_CALL_FORWARD_STATUS = 33;
+ int RIL_REQUEST_SET_CALL_FORWARD = 34;
+ int RIL_REQUEST_QUERY_CALL_WAITING = 35;
+ int RIL_REQUEST_SET_CALL_WAITING = 36;
+ int RIL_REQUEST_SMS_ACKNOWLEDGE = 37;
+ int RIL_REQUEST_GET_IMEI = 38;
+ int RIL_REQUEST_GET_IMEISV = 39;
+ int RIL_REQUEST_ANSWER = 40;
+ int RIL_REQUEST_DEACTIVATE_DEFAULT_PDP = 41;
+ int RIL_REQUEST_QUERY_FACILITY_LOCK = 42;
+ int RIL_REQUEST_SET_FACILITY_LOCK = 43;
+ int RIL_REQUEST_CHANGE_BARRING_PASSWORD = 44;
+ int RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE = 45;
+ int RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC = 46;
+ int RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL = 47;
+ int RIL_REQUEST_QUERY_AVAILABLE_NETWORKS = 48;
+ int RIL_REQUEST_DTMF_START = 49;
+ int RIL_REQUEST_DTMF_STOP = 50;
+ int RIL_REQUEST_BASEBAND_VERSION = 51;
+ int RIL_REQUEST_SEPARATE_CONNECTION = 52;
+ int RIL_REQUEST_SET_MUTE = 53;
+ int RIL_REQUEST_GET_MUTE = 54;
+ int RIL_REQUEST_QUERY_CLIP = 55;
+ int RIL_REQUEST_LAST_PDP_FAIL_CAUSE = 56;
+ int RIL_REQUEST_PDP_CONTEXT_LIST = 57;
+ int RIL_REQUEST_RESET_RADIO = 58;
+ int RIL_REQUEST_OEM_HOOK_RAW = 59;
+ int RIL_REQUEST_OEM_HOOK_STRINGS = 60;
+ int RIL_REQUEST_SCREEN_STATE = 61;
+ int RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION = 62;
+ int RIL_REQUEST_WRITE_SMS_TO_SIM = 63;
+ int RIL_REQUEST_DELETE_SMS_ON_SIM = 64;
+ int RIL_REQUEST_SET_BAND_MODE = 65;
+ int RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE = 66;
+ int RIL_REQUEST_STK_GET_PROFILE = 67;
+ int RIL_REQUEST_STK_SET_PROFILE = 68;
+ int RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND = 69;
+ int RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE = 70;
+ int RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM = 71;
+ int RIL_REQUEST_EXPLICIT_CALL_TRANSFER = 72;
+ int RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE = 73;
+ int RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE = 74;
+ int RIL_REQUEST_GET_NEIGHBORING_CELL_IDS = 75;
+ int RIL_REQUEST_SET_LOCATION_UPDATES = 76;
+ int RIL_UNSOL_RESPONSE_BASE = 1000;
+ int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000;
+ int RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED = 1001;
+ int RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED = 1002;
+ int RIL_UNSOL_RESPONSE_NEW_SMS = 1003;
+ int RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT = 1004;
+ int RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM = 1005;
+ int RIL_UNSOL_ON_USSD = 1006;
+ int RIL_UNSOL_ON_USSD_REQUEST = 1007;
+ int RIL_UNSOL_NITZ_TIME_RECEIVED = 1008;
+ int RIL_UNSOL_SIGNAL_STRENGTH = 1009;
+ int RIL_UNSOL_PDP_CONTEXT_LIST_CHANGED = 1010;
+ int RIL_UNSOL_SUPP_SVC_NOTIFICATION = 1011;
+ int RIL_UNSOL_STK_SESSION_END = 1012;
+ int RIL_UNSOL_STK_PROACTIVE_COMMAND = 1013;
+ int RIL_UNSOL_STK_EVENT_NOTIFY = 1014;
+ int RIL_UNSOL_STK_CALL_SETUP = 1015;
+ int RIL_UNSOL_SIM_SMS_STORAGE_FULL = 1016;
+ int RIL_UNSOL_SIM_REFRESH = 1017;
+ int RIL_UNSOL_CALL_RING = 1018;
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java b/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
new file mode 100644
index 0000000..81fc657
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
@@ -0,0 +1,521 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import com.android.internal.telephony.*;
+import com.android.internal.telephony.gsm.stk.ImageDescriptor;
+import android.os.*;
+import android.os.AsyncResult;
+import android.os.RegistrantList;
+import android.os.Registrant;
+import android.util.Log;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public final class SIMFileHandler extends Handler
+{
+ static final String LOG_TAG = "GSM";
+
+ //from TS 11.11 9.1 or elsewhere
+ static private final int COMMAND_READ_BINARY = 0xb0;
+ static private final int COMMAND_UPDATE_BINARY = 0xd6;
+ static private final int COMMAND_READ_RECORD = 0xb2;
+ static private final int COMMAND_UPDATE_RECORD = 0xdc;
+ static private final int COMMAND_SEEK = 0xa2;
+ static private final int COMMAND_GET_RESPONSE = 0xc0;
+
+ // from TS 11.11 9.2.5
+ static private final int READ_RECORD_MODE_ABSOLUTE = 4;
+
+ //***** types of files TS 11.11 9.3
+ static private final int EF_TYPE_TRANSPARENT = 0;
+ static private final int EF_TYPE_LINEAR_FIXED = 1;
+ static private final int EF_TYPE_CYCLIC = 3;
+
+ //***** types of files TS 11.11 9.3
+ static private final int TYPE_RFU = 0;
+ static private final int TYPE_MF = 1;
+ static private final int TYPE_DF = 2;
+ static private final int TYPE_EF = 4;
+
+ // size of GET_RESPONSE for EF
+ static private final int GET_RESPONSE_EF_SIZE_BYTES = 15;
+
+ // Byte order received in response to COMMAND_GET_RESPONSE
+ // Refer TS 51.011 Section 9.2.1
+ static private final int RESPONSE_DATA_RFU_1 = 0;
+ static private final int RESPONSE_DATA_RFU_2 = 1;
+
+ static private final int RESPONSE_DATA_FILE_SIZE_1 = 2;
+ static private final int RESPONSE_DATA_FILE_SIZE_2 = 3;
+
+ static private final int RESPONSE_DATA_FILE_ID_1 = 4;
+ static private final int RESPONSE_DATA_FILE_ID_2 = 5;
+ static private final int RESPONSE_DATA_FILE_TYPE = 6;
+ static private final int RESPONSE_DATA_RFU_3 = 7;
+ static private final int RESPONSE_DATA_ACCESS_CONDITION_1 = 8;
+ static private final int RESPONSE_DATA_ACCESS_CONDITION_2 = 9;
+ static private final int RESPONSE_DATA_ACCESS_CONDITION_3 = 10;
+ static private final int RESPONSE_DATA_FILE_STATUS = 11;
+ static private final int RESPONSE_DATA_LENGTH = 12;
+ static private final int RESPONSE_DATA_STRUCTURE = 13;
+ static private final int RESPONSE_DATA_RECORD_LENGTH = 14;
+
+
+ //***** Instance Variables
+ GSMPhone phone;
+
+ //***** Events
+
+ /** Finished retrieving size of transparent EF; start loading. */
+ static private final int EVENT_GET_BINARY_SIZE_DONE = 4;
+ /** Finished loading contents of transparent EF; post result. */
+ static private final int EVENT_READ_BINARY_DONE = 5;
+ /** Finished retrieving size of records for linear-fixed EF; now load. */
+ static private final int EVENT_GET_RECORD_SIZE_DONE = 6;
+ /** Finished loading single record from a linear-fixed EF; post result. */
+ static private final int EVENT_READ_RECORD_DONE = 7;
+ /** Finished retrieving record size; post result. */
+ static private final int EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE = 8;
+ /** Finished retrieving image instance record; post result. */
+ static private final int EVENT_READ_IMG_DONE = 9;
+ /** Finished retrieving icon data; post result. */
+ static private final int EVENT_READ_ICON_DONE = 10;
+
+ //***** Inner Classes
+
+ static class LoadLinearFixedContext
+ {
+
+ int efid;
+ int recordNum, recordSize, countRecords;
+ boolean loadAll;
+
+ Message onLoaded;
+
+ ArrayList<byte[]> results;
+
+ LoadLinearFixedContext(int efid, int recordNum, Message onLoaded)
+ {
+ this.efid = efid;
+ this.recordNum = recordNum;
+ this.onLoaded = onLoaded;
+ this.loadAll = false;
+ }
+
+ LoadLinearFixedContext(int efid, Message onLoaded)
+ {
+ this.efid = efid;
+ this.recordNum = 1;
+ this.loadAll = true;
+ this.onLoaded = onLoaded;
+ }
+
+ }
+
+
+ //***** Constructor
+
+ SIMFileHandler(GSMPhone phone)
+ {
+ this.phone = phone;
+ }
+
+ //***** Public Methods
+
+ /**
+ * Load a record from a SIM Linear Fixed EF
+ *
+ * @param fileid EF id
+ * @param recordNum 1-based (not 0-based) record number
+ * @param onLoaded
+ *
+ * ((AsyncResult)(onLoaded.obj)).result is the byte[]
+ *
+ */
+ void loadEFLinearFixed(int fileid, int recordNum, Message onLoaded)
+ {
+ Message response
+ = obtainMessage(EVENT_GET_RECORD_SIZE_DONE,
+ new LoadLinearFixedContext(fileid, recordNum, onLoaded));
+
+ phone.mCM.simIO(COMMAND_GET_RESPONSE, fileid, null,
+ 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response);
+ }
+
+ /**
+ * Load a image instance record from a SIM Linear Fixed EF-IMG
+ *
+ * @param recordNum 1-based (not 0-based) record number
+ * @param onLoaded
+ *
+ * ((AsyncResult)(onLoaded.obj)).result is the byte[]
+ *
+ */
+ public void loadEFImgLinearFixed(int recordNum, Message onLoaded) {
+ Message response = obtainMessage(EVENT_READ_IMG_DONE,
+ new LoadLinearFixedContext(SimConstants.EF_IMG, recordNum,
+ onLoaded));
+
+ phone.mCM.simIO(COMMAND_GET_RESPONSE, SimConstants.EF_IMG, "img",
+ recordNum, READ_RECORD_MODE_ABSOLUTE,
+ ImageDescriptor.ID_LENGTH, null, null, response);
+ }
+
+ /**
+ * get record size for a linear fixed EF
+ *
+ * @param fileid EF id
+ * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the recordSize[]
+ * int[0] is the record length int[1] is the total length of the EF
+ * file int[3] is the number of records in the EF file So int[0] *
+ * int[3] = int[1]
+ */
+ void getEFLinearRecordSize(int fileid, Message onLoaded)
+ {
+ Message response
+ = obtainMessage(EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE,
+ new LoadLinearFixedContext(fileid, onLoaded));
+ phone.mCM.simIO(COMMAND_GET_RESPONSE, fileid, null,
+ 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response);
+ }
+
+ /**
+ * Load all records from a SIM Linear Fixed EF
+ *
+ * @param fileid EF id
+ * @param onLoaded
+ *
+ * ((AsyncResult)(onLoaded.obj)).result is an ArrayList<byte[]>
+ *
+ */
+ void loadEFLinearFixedAll(int fileid, Message onLoaded)
+ {
+ Message response = obtainMessage(EVENT_GET_RECORD_SIZE_DONE,
+ new LoadLinearFixedContext(fileid,onLoaded));
+
+ phone.mCM.simIO(COMMAND_GET_RESPONSE, fileid, null,
+ 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response);
+ }
+
+ /**
+ * Load a SIM Transparent EF
+ *
+ * @param fileid EF id
+ * @param onLoaded
+ *
+ * ((AsyncResult)(onLoaded.obj)).result is the byte[]
+ *
+ */
+
+ void loadEFTransparent(int fileid, Message onLoaded)
+ {
+ Message response = obtainMessage(EVENT_GET_BINARY_SIZE_DONE,
+ fileid, 0, onLoaded);
+
+ phone.mCM.simIO(COMMAND_GET_RESPONSE, fileid, null,
+ 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response);
+ }
+
+ /**
+ * Load a SIM Transparent EF-IMG. Used right after loadEFImgLinearFixed to
+ * retrive STK's icon data.
+ *
+ * @param fileid EF id
+ * @param onLoaded
+ *
+ * ((AsyncResult)(onLoaded.obj)).result is the byte[]
+ *
+ */
+ public void loadEFImgTransparent(int fileid, int highOffset, int lowOffset,
+ int length, Message onLoaded) {
+ Message response = obtainMessage(EVENT_READ_ICON_DONE, fileid, 0,
+ onLoaded);
+
+ phone.mCM.simIO(COMMAND_READ_BINARY, fileid, "img", highOffset, lowOffset,
+ length, null, null, response);
+ }
+
+ /**
+ * Update a record in a linear fixed EF
+ * @param fileid EF id
+ * @param recordNum 1-based (not 0-based) record number
+ * @param data must be exactly as long as the record in the EF
+ * @param pin2 for CHV2 operations, otherwist must be null
+ * @param onComplete onComplete.obj will be an AsyncResult
+ * onComplete.obj.userObj will be a SimIoResult on success
+ */
+ void updateEFLinearFixed(int fileid, int recordNum, byte[] data,
+ String pin2, Message onComplete)
+ {
+ phone.mCM.simIO(COMMAND_UPDATE_RECORD, fileid, null,
+ recordNum, READ_RECORD_MODE_ABSOLUTE, data.length,
+ SimUtils.bytesToHexString(data), pin2, onComplete);
+ }
+
+ /**
+ * Update a transparent EF
+ * @param fileid EF id
+ * @param data must be exactly as long as the EF
+ */
+ void updateEFTransparent(int fileid, byte[] data, Message onComplete)
+ {
+ phone.mCM.simIO(COMMAND_UPDATE_BINARY, fileid, null,
+ 0, 0, data.length,
+ SimUtils.bytesToHexString(data), null, onComplete);
+ }
+
+ //***** Overridden from Handler
+
+ public void handleMessage(Message msg)
+ {
+ AsyncResult ar;
+ SimIoResult result;
+ Message response = null;
+ String str;
+ LoadLinearFixedContext lc;
+
+ SimException simException;
+ byte data[];
+ int size;
+ int fileid;
+ int recordNum;
+ int recordSize[];
+
+ try {
+ switch (msg.what) {
+ case EVENT_READ_IMG_DONE:
+ ar = (AsyncResult) msg.obj;
+ lc = (LoadLinearFixedContext) ar.userObj;
+ result = (SimIoResult) ar.result;
+ response = lc.onLoaded;
+
+ simException = result.getException();
+ if (simException != null) {
+ sendResult(response, result.payload, ar.exception);
+ }
+ break;
+ case EVENT_READ_ICON_DONE:
+ ar = (AsyncResult) msg.obj;
+ response = (Message) ar.userObj;
+ result = (SimIoResult) ar.result;
+
+ simException = result.getException();
+ if (simException != null) {
+ sendResult(response, result.payload, ar.exception);
+ }
+ break;
+ case EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE:
+ ar = (AsyncResult)msg.obj;
+ lc = (LoadLinearFixedContext) ar.userObj;
+ result = (SimIoResult) ar.result;
+ response = lc.onLoaded;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ simException = result.getException();
+ if (simException != null) {
+ sendResult(response, null, simException);
+ break;
+ }
+
+ data = result.payload;
+
+ if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE] ||
+ EF_TYPE_LINEAR_FIXED != data[RESPONSE_DATA_STRUCTURE]) {
+ throw new SimFileTypeMismatch();
+ }
+
+ recordSize = new int[3];
+ recordSize[0] = data[RESPONSE_DATA_RECORD_LENGTH] & 0xFF;
+ recordSize[1] = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8)
+ + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff);
+ recordSize[2] = recordSize[1] / recordSize[0];
+
+ sendResult(response, recordSize, null);
+ break;
+ case EVENT_GET_RECORD_SIZE_DONE:
+ ar = (AsyncResult)msg.obj;
+ lc = (LoadLinearFixedContext) ar.userObj;
+ result = (SimIoResult) ar.result;
+ response = lc.onLoaded;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ simException = result.getException();
+
+ if (simException != null) {
+ sendResult(response, null, simException);
+ break;
+ }
+
+ data = result.payload;
+ fileid = lc.efid;
+ recordNum = lc.recordNum;
+
+ if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE]) {
+ throw new SimFileTypeMismatch();
+ }
+
+ if (EF_TYPE_LINEAR_FIXED != data[RESPONSE_DATA_STRUCTURE]) {
+ throw new SimFileTypeMismatch();
+ }
+
+ lc.recordSize = data[RESPONSE_DATA_RECORD_LENGTH] & 0xFF;
+
+ size = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8)
+ + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff);
+
+ lc.countRecords = size / lc.recordSize;
+
+ if (lc.loadAll) {
+ lc.results = new ArrayList<byte[]>(lc.countRecords);
+ }
+
+ phone.mCM.simIO(COMMAND_READ_RECORD, lc.efid, null,
+ lc.recordNum,
+ READ_RECORD_MODE_ABSOLUTE,
+ lc.recordSize, null, null,
+ obtainMessage(EVENT_READ_RECORD_DONE, lc));
+ break;
+ case EVENT_GET_BINARY_SIZE_DONE:
+ ar = (AsyncResult)msg.obj;
+ response = (Message) ar.userObj;
+ result = (SimIoResult) ar.result;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ simException = result.getException();
+
+ if (simException != null) {
+ sendResult(response, null, simException);
+ break;
+ }
+
+ data = result.payload;
+
+ fileid = msg.arg1;
+
+ if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE]) {
+ throw new SimFileTypeMismatch();
+ }
+
+ if (EF_TYPE_TRANSPARENT != data[RESPONSE_DATA_STRUCTURE]) {
+ throw new SimFileTypeMismatch();
+ }
+
+ size = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8)
+ + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff);
+
+ phone.mCM.simIO(COMMAND_READ_BINARY, fileid, null,
+ 0, 0, size, null, null,
+ obtainMessage(EVENT_READ_BINARY_DONE,
+ fileid, 0, response));
+ break;
+
+ case EVENT_READ_RECORD_DONE:
+
+ ar = (AsyncResult)msg.obj;
+ lc = (LoadLinearFixedContext) ar.userObj;
+ result = (SimIoResult) ar.result;
+ response = lc.onLoaded;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ simException = result.getException();
+
+ if (simException != null) {
+ sendResult(response, null, simException);
+ break;
+ }
+
+ if (!lc.loadAll) {
+ sendResult(response, result.payload, null);
+ } else {
+ lc.results.add(result.payload);
+
+ lc.recordNum++;
+
+ if (lc.recordNum > lc.countRecords) {
+ sendResult(response, lc.results, null);
+ } else {
+ phone.mCM.simIO(COMMAND_READ_RECORD, lc.efid, null,
+ lc.recordNum,
+ READ_RECORD_MODE_ABSOLUTE,
+ lc.recordSize, null, null,
+ obtainMessage(EVENT_READ_RECORD_DONE, lc));
+ }
+ }
+
+ break;
+
+ case EVENT_READ_BINARY_DONE:
+ ar = (AsyncResult)msg.obj;
+ response = (Message) ar.userObj;
+ result = (SimIoResult) ar.result;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ simException = result.getException();
+
+ if (simException != null) {
+ sendResult(response, null, simException);
+ break;
+ }
+
+ sendResult(response, result.payload, null);
+ break;
+
+ }} catch (Exception exc) {
+ if (response != null) {
+ sendResult(response, null, exc);
+ } else {
+ Log.e(LOG_TAG, "uncaught exception", exc);
+ }
+ }
+ }
+
+ //***** Private Methods
+
+ private void sendResult(Message response, Object result, Throwable ex)
+ {
+ if (response == null) {
+ return;
+ }
+
+ AsyncResult.forMessage(response, result, ex);
+
+ response.sendToTarget();
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
new file mode 100644
index 0000000..ebbf501
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
@@ -0,0 +1,1523 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.os.AsyncResult;
+import android.os.RegistrantList;
+import android.os.Registrant;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.gsm.SmsMessage;
+import android.util.Log;
+import java.util.ArrayList;
+
+import static com.android.internal.telephony.TelephonyProperties.*;
+import com.android.internal.telephony.SimCard;
+
+/**
+ * {@hide}
+ */
+public final class SIMRecords extends Handler implements SimConstants
+{
+ static final String LOG_TAG = "GSM";
+
+ private static final boolean CRASH_RIL = false;
+
+ private static final boolean DBG = true;
+
+ //***** Instance Variables
+
+ GSMPhone phone;
+ RegistrantList recordsLoadedRegistrants = new RegistrantList();
+
+ int recordsToLoad; // number of pending load requests
+
+ AdnRecordCache adnCache;
+
+ VoiceMailConstants mVmConfig;
+
+ //***** Cached SIM State; cleared on channel close
+
+ boolean recordsRequested = false; // true if we've made requests for the sim records
+
+ String imsi;
+ String iccid;
+ String msisdn = null; // My mobile number
+ String msisdnTag = null;
+ String voiceMailNum = null;
+ String voiceMailTag = null;
+ String newVoiceMailNum = null;
+ String newVoiceMailTag = null;
+ boolean isVoiceMailFixed = false;
+ int countVoiceMessages = 0;
+ boolean callForwardingEnabled;
+ int mncLength = 0; // 0 is used to indicate that the value
+ // is not initialized
+ int mailboxIndex = 0; // 0 is no mailbox dailing number associated
+
+ /**
+ * Sates only used by getSpnFsm FSM
+ */
+ private Get_Spn_Fsm_State spnState;
+
+ /** CPHS service information (See CPHS 4.2 B.3.1.1)
+ * It will be set in onSimReady if reading GET_CPHS_INFO successfully
+ * mCphsInfo[0] is CPHS Phase
+ * mCphsInfo[1] and mCphsInfo[2] is CPHS Service Table
+ */
+ private byte[] mCphsInfo = null;
+
+ byte[] efMWIS = null;
+ byte[] efCPHS_MWI =null;
+ byte[] mEfCff = null;
+ byte[] mEfCfis = null;
+
+
+ String spn;
+ int spnDisplayCondition;
+ // Numeric network codes listed in TS 51.011 EF[SPDI]
+ ArrayList<String> spdiNetworks = null;
+
+ String pnnHomeName = null;
+
+ //***** Constants
+
+ // Bitmasks for SPN display rules.
+ static final int SPN_RULE_SHOW_SPN = 0x01;
+ static final int SPN_RULE_SHOW_PLMN = 0x02;
+
+ // From TS 51.011 EF[SPDI] section
+ static final int TAG_SPDI_PLMN_LIST = 0x80;
+
+ // Full Name IEI from TS 24.008
+ static final int TAG_FULL_NETWORK_NAME = 0x43;
+
+ // Short Name IEI from TS 24.008
+ static final int TAG_SHORT_NETWORK_NAME = 0x45;
+
+ // active CFF from CPHS 4.2 B.4.5
+ static final int CFF_UNCONDITIONAL_ACTIVE = 0x0a;
+ static final int CFF_UNCONDITIONAL_DEACTIVE = 0x05;
+ static final int CFF_LINE1_MASK = 0x0f;
+ static final int CFF_LINE1_RESET = 0xf0;
+
+ // CPHS Service Table (See CPHS 4.2 B.3.1)
+ private static final int CPHS_SST_MBN_MASK = 0x30;
+ private static final int CPHS_SST_MBN_ENABLED = 0x30;
+
+ //***** Event Constants
+
+ private static final int EVENT_SIM_READY = 1;
+ private static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 2;
+ private static final int EVENT_GET_IMSI_DONE = 3;
+ private static final int EVENT_GET_ICCID_DONE = 4;
+ private static final int EVENT_GET_MBI_DONE = 5;
+ private static final int EVENT_GET_MBDN_DONE = 6;
+ private static final int EVENT_GET_MWIS_DONE = 7;
+ private static final int EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE = 8;
+ private static final int EVENT_GET_AD_DONE = 9; // Admin data on SIM
+ private static final int EVENT_GET_MSISDN_DONE = 10;
+ private static final int EVENT_GET_CPHS_MAILBOX_DONE = 11;
+ private static final int EVENT_GET_SPN_DONE = 12;
+ private static final int EVENT_GET_SPDI_DONE = 13;
+ private static final int EVENT_UPDATE_DONE = 14;
+ private static final int EVENT_GET_PNN_DONE = 15;
+ private static final int EVENT_GET_SST_DONE = 17;
+ private static final int EVENT_GET_ALL_SMS_DONE = 18;
+ private static final int EVENT_MARK_SMS_READ_DONE = 19;
+ private static final int EVENT_SET_MBDN_DONE = 20;
+ private static final int EVENT_SMS_ON_SIM = 21;
+ private static final int EVENT_GET_SMS_DONE = 22;
+ private static final int EVENT_GET_CFF_DONE = 24;
+ private static final int EVENT_SET_CPHS_MAILBOX_DONE = 25;
+ private static final int EVENT_GET_INFO_CPHS_DONE = 26;
+ private static final int EVENT_SET_MSISDN_DONE = 30;
+ private static final int EVENT_SIM_REFRESH = 31;
+ private static final int EVENT_GET_CFIS_DONE = 32;
+
+ //***** Constructor
+
+ SIMRecords(GSMPhone phone)
+ {
+ this.phone = phone;
+
+ adnCache = new AdnRecordCache(phone);
+
+ mVmConfig = new VoiceMailConstants();
+
+ recordsRequested = false; // No load request is made till SIM ready
+
+ // recordsToLoad is set to 0 because no requests are made yet
+ recordsToLoad = 0;
+
+
+ phone.mCM.registerForSIMReady(this, EVENT_SIM_READY, null);
+ phone.mCM.registerForOffOrNotAvailable(
+ this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+ phone.mCM.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null);
+ phone.mCM.setOnSimRefresh(this, EVENT_SIM_REFRESH, null);
+
+ // Start off by setting empty state
+ onRadioOffOrNotAvailable();
+
+ }
+
+ AdnRecordCache getAdnCache() {
+ return adnCache;
+ }
+
+ private void onRadioOffOrNotAvailable()
+ {
+ imsi = null;
+ msisdn = null;
+ voiceMailNum = null;
+ countVoiceMessages = 0;
+ mncLength = 0;
+ iccid = null;
+ spn = null;
+ // -1 means no EF_SPN found; treat accordingly.
+ spnDisplayCondition = -1;
+ efMWIS = null;
+ efCPHS_MWI = null;
+ spn = null;
+ spdiNetworks = null;
+ pnnHomeName = null;
+
+ adnCache.reset();
+
+ phone.setSystemProperty(PROPERTY_LINE1_VOICE_MAIL_WAITING, null);
+ phone.setSystemProperty(PROPERTY_SIM_OPERATOR_NUMERIC, null);
+ phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ALPHA, null);
+ phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ISO_COUNTRY, null);
+
+ // recordsRequested is set to false indicating that the SIM
+ // read requests made so far are not valid. This is set to
+ // true only when fresh set of read requests are made.
+ recordsRequested = false;
+ }
+
+
+ //***** Public Methods
+ public void registerForRecordsLoaded(Handler h, int what, Object obj)
+ {
+ Registrant r = new Registrant(h, what, obj);
+ recordsLoadedRegistrants.add(r);
+
+ if (recordsToLoad == 0 && recordsRequested == true) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+
+ /** Returns null if SIM is not yet ready */
+ public String getIMSI()
+ {
+ return imsi;
+ }
+
+ public String getMsisdnNumber()
+ {
+ return msisdn;
+ }
+
+ /**
+ * Set subscriber number to SIM record
+ *
+ * The subscriber number is stored in EF_MSISDN (TS 51.011)
+ *
+ * When the operation is complete, onComplete will be sent to its handler
+ *
+ * @param alphaTag alpha-tagging of the dailing nubmer (up to 10 characters)
+ * @param number dailing nubmer (up to 20 digits)
+ * if the number starts with '+', then set to international TOA
+ * @param onComplete
+ * onComplete.obj will be an AsyncResult
+ * ((AsyncResult)onComplete.obj).exception == null on success
+ * ((AsyncResult)onComplete.obj).exception != null on fail
+ */
+ public void setMsisdnNumber(String alphaTag, String number,
+ Message onComplete) {
+
+ msisdn = number;
+ msisdnTag = alphaTag;
+
+ if(DBG) log("Set MSISDN: " + msisdnTag +" " + msisdn);
+
+
+ AdnRecord adn = new AdnRecord(msisdnTag, msisdn);
+
+ new AdnRecordLoader(phone).updateEF(adn, EF_MSISDN, EF_EXT1, 1, null,
+ obtainMessage(EVENT_SET_MSISDN_DONE, onComplete));
+ }
+
+ public String getMsisdnAlphaTag() {
+ return msisdnTag;
+ }
+
+ public String getVoiceMailNumber()
+ {
+ return voiceMailNum;
+ }
+
+ /**
+ * Return Service Provider Name stored in SIM
+ * @return null if SIM is not yet ready
+ */
+ public String getServiceProvideName()
+ {
+ return spn;
+ }
+
+ /**
+ * Set voice mail number to SIM record
+ *
+ * The voice mail number can be stored either in EF_MBDN (TS 51.011) or
+ * EF_MAILBOX_CPHS (CPHS 4.2)
+ *
+ * If EF_MBDN is available, store the voice mail number to EF_MBDN
+ *
+ * If EF_MAILBOX_CPHS is enabled, store the voice mail number to EF_CHPS
+ *
+ * So the voice mail number will be stored in both EFs if both are available
+ *
+ * Return error only if both EF_MBDN and EF_MAILBOX_CPHS fail.
+ *
+ * When the operation is complete, onComplete will be sent to its handler
+ *
+ * @param alphaTag alpha-tagging of the dailing nubmer (upto 10 characters)
+ * @param voiceNumber dailing nubmer (upto 20 digits)
+ * if the number is start with '+', then set to international TOA
+ * @param onComplete
+ * onComplete.obj will be an AsyncResult
+ * ((AsyncResult)onComplete.obj).exception == null on success
+ * ((AsyncResult)onComplete.obj).exception != null on fail
+ */
+ public void setVoiceMailNumber(String alphaTag, String voiceNumber,
+ Message onComplete) {
+ if (isVoiceMailFixed) {
+ AsyncResult.forMessage((onComplete)).exception =
+ new SimException("Voicemail number is fixed by operator");
+ onComplete.sendToTarget();
+ return;
+ }
+
+ newVoiceMailNum = voiceNumber;
+ newVoiceMailTag = alphaTag;
+
+ AdnRecord adn = new AdnRecord(newVoiceMailTag, newVoiceMailNum);
+
+ if (mailboxIndex != 0 && mailboxIndex != 0xff) {
+
+ new AdnRecordLoader(phone).updateEF(adn, EF_MBDN, EF_EXT6,
+ mailboxIndex, null,
+ obtainMessage(EVENT_SET_MBDN_DONE, onComplete));
+
+ } else if (isCphsMailboxEnabled()) {
+
+ new AdnRecordLoader(phone).updateEF(adn, EF_MAILBOX_CPHS,
+ EF_EXT1, 1, null,
+ obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, onComplete));
+
+ } else {
+ AsyncResult.forMessage((onComplete)).exception =
+ new SimException("Update SIM voice mailbox error");
+ onComplete.sendToTarget();
+ }
+ }
+
+ public String getVoiceMailAlphaTag()
+ {
+ return voiceMailTag;
+ }
+
+ /**
+ * Sets the SIM voice message waiting indicator records
+ * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported
+ * @param countWaiting The number of messages waiting, if known. Use
+ * -1 to indicate that an unknown number of
+ * messages are waiting
+ */
+ public void
+ setVoiceMessageWaiting(int line, int countWaiting)
+ {
+ if (line != 1) {
+ // only profile 1 is supported
+ return;
+ }
+
+ // range check
+ if (countWaiting < 0) {
+ countWaiting = -1;
+ } else if (countWaiting > 0xff) {
+ // TS 23.040 9.2.3.24.2
+ // "The value 255 shall be taken to mean 255 or greater"
+ countWaiting = 0xff;
+ }
+
+ countVoiceMessages = countWaiting;
+
+ phone.setSystemProperty(PROPERTY_LINE1_VOICE_MAIL_WAITING,
+ (countVoiceMessages != 0) ? "true" : "false");
+
+ phone.notifyMessageWaitingIndicator();
+
+ try {
+ if (efMWIS != null) {
+ // TS 51.011 10.3.45
+
+ // lsb of byte 0 is 'voicemail' status
+ efMWIS[0] = (byte)((efMWIS[0] & 0xfe)
+ | (countVoiceMessages == 0 ? 0 : 1));
+
+ // byte 1 is the number of voice messages waiting
+ if (countWaiting < 0) {
+ // The spec does not define what this should be
+ // if we don't know the count
+ efMWIS[1] = 0;
+ } else {
+ efMWIS[1] = (byte) countWaiting;
+ }
+
+ phone.mSIMFileHandler.updateEFLinearFixed(
+ EF_MWIS, 1, efMWIS, null,
+ obtainMessage (EVENT_UPDATE_DONE, EF_MWIS));
+ }
+
+ if (efCPHS_MWI != null) {
+ // Refer CPHS4_2.WW6 B4.2.3
+ efCPHS_MWI[0] = (byte)((efCPHS_MWI[0] & 0xf0)
+ | (countVoiceMessages == 0 ? 0x5 : 0xa));
+
+ phone.mSIMFileHandler.updateEFTransparent(
+ EF_VOICE_MAIL_INDICATOR_CPHS, efCPHS_MWI,
+ obtainMessage (EVENT_UPDATE_DONE, EF_VOICE_MAIL_INDICATOR_CPHS));
+ }
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ Log.w(LOG_TAG,
+ "Error saving voice mail state to SIM. Probably malformed SIM record", ex);
+ }
+ }
+
+ /** @return true if there are messages waiting, false otherwise. */
+ public boolean getVoiceMessageWaiting()
+ {
+ return countVoiceMessages != 0;
+ }
+
+ /**
+ * Returns number of voice messages waiting, if available
+ * If not available (eg, on an older CPHS SIM) -1 is returned if
+ * getVoiceMessageWaiting() is true
+ */
+ public int getCountVoiceMessages()
+ {
+ return countVoiceMessages;
+ }
+
+ public boolean getVoiceCallForwardingFlag() {
+ return callForwardingEnabled;
+ }
+
+ public void setVoiceCallForwardingFlag(int line, boolean enable) {
+
+ if (line != 1) return; // only line 1 is supported
+
+ callForwardingEnabled = enable;
+
+ phone.setSystemProperty(PROPERTY_LINE1_VOICE_CALL_FORWARDING,
+ (callForwardingEnabled ? "true" : "false"));
+
+ phone.notifyCallForwardingIndicator();
+
+ try {
+ if (mEfCfis != null) {
+ // lsb is of byte 1 is voice status
+ if (enable) {
+ mEfCfis[1] |= 1;
+ } else {
+ mEfCfis[1] &= 0xfe;
+ }
+
+ // TODO: Should really update other fields in EF_CFIS, eg,
+ // dialing number. We don't read or use it right now.
+
+ phone.mSIMFileHandler.updateEFLinearFixed(
+ EF_CFIS, 1, mEfCfis, null,
+ obtainMessage (EVENT_UPDATE_DONE, EF_CFIS));
+ }
+
+ if (mEfCff != null) {
+ if (enable) {
+ mEfCff[0] = (byte) ((mEfCff[0] & CFF_LINE1_RESET)
+ | CFF_UNCONDITIONAL_ACTIVE);
+ } else {
+ mEfCff[0] = (byte) ((mEfCff[0] & CFF_LINE1_RESET)
+ | CFF_UNCONDITIONAL_DEACTIVE);
+ }
+
+ phone.mSIMFileHandler.updateEFTransparent(
+ EF_CFF_CPHS, mEfCff,
+ obtainMessage (EVENT_UPDATE_DONE, EF_CFF_CPHS));
+ }
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ Log.w(LOG_TAG,
+ "Error saving call fowarding flag to SIM. "
+ + "Probably malformed SIM record", ex);
+
+ }
+ }
+
+ /**
+ * Called by STK Service when REFRESH is received.
+ * @param fileChanged indicates whether any files changed
+ * @param fileList if non-null, a list of EF files that changed
+ */
+ public void onRefresh(boolean fileChanged, int[] fileList) {
+ if (fileChanged) {
+ // A future optimization would be to inspect fileList and
+ // only reload those files that we care about. For now,
+ // just re-fetch all SIM records that we cache.
+ fetchSimRecords();
+ }
+ }
+
+ /** Returns the 5 or 6 digit MCC/MNC of the operator that
+ * provided the SIM card. Returns null of SIM is not yet ready
+ */
+ String getSIMOperatorNumeric()
+ {
+ if (imsi == null) {
+ return null;
+ }
+
+ if (mncLength != 0) {
+ // Length = length of MCC + length of MNC
+ // length of mcc = 3 (TS 23.003 Section 2.2)
+ return imsi.substring(0, 3 + mncLength);
+ }
+
+ // Guess the MNC length based on the MCC if we don't
+ // have a valid value in ef[ad]
+
+ int mcc;
+
+ mcc = Integer.parseInt(imsi.substring(0,3));
+
+ return imsi.substring(0, 3 + MccTable.smallestDigitsMccForMnc(mcc));
+ }
+
+ boolean getRecordsLoaded()
+ {
+ if (recordsToLoad == 0 && recordsRequested == true) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ //***** Overridden from Handler
+ public void handleMessage(Message msg)
+ {
+ AsyncResult ar;
+ AdnRecord adn;
+
+ byte data[];
+
+ boolean isRecordLoadResponse = false;
+
+ try { switch (msg.what) {
+ case EVENT_SIM_READY:
+ onSimReady();
+ break;
+
+ case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
+ onRadioOffOrNotAvailable();
+ break;
+
+ /* IO events */
+ case EVENT_GET_IMSI_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+ Log.e(LOG_TAG, "Exception querying IMSI", ar.exception);
+ break;
+ }
+
+ imsi = (String) ar.result;
+
+ // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
+ // than 15 (and usually 15).
+ if (imsi != null && (imsi.length() < 6 || imsi.length() > 15)) {
+ Log.e(LOG_TAG, "invalid IMSI " + imsi);
+ imsi = null;
+ }
+
+ Log.d(LOG_TAG, "IMSI: " + imsi.substring(0, 6) + "xxxxxxxxx");
+ phone.mSimCard.updateImsiConfiguration(imsi);
+ phone.mSimCard.broadcastSimStateChangedIntent(
+ SimCard.INTENT_VALUE_SIM_IMSI, null);
+ break;
+
+ case EVENT_GET_MBI_DONE:
+ boolean isValidMbdn;
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[]) ar.result;
+
+ isValidMbdn = false;
+ if (ar.exception == null) {
+ // Refer TS 51.011 Section 10.3.44 for content details
+ Log.d(LOG_TAG, "EF_MBI: " +
+ SimUtils.bytesToHexString(data));
+
+ // Voice mail record number stored first
+ mailboxIndex = (int)data[0] & 0xff;
+
+ // check if dailing numbe id valid
+ if (mailboxIndex != 0 && mailboxIndex != 0xff) {
+ Log.d(LOG_TAG, "Got valid mailbox number for MBDN");
+ isValidMbdn = true;
+ }
+ }
+
+ // one more record to load
+ recordsToLoad += 1;
+
+ if (isValidMbdn) {
+ // Note: MBDN was not included in NUM_OF_SIM_RECORDS_LOADED
+ new AdnRecordLoader(phone).loadFromEF(EF_MBDN, EF_EXT6,
+ mailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
+ } else {
+ // If this EF not present, try mailbox as in CPHS standard
+ // CPHS (CPHS4_2.WW6) is a european standard.
+ new AdnRecordLoader(phone).loadFromEF(EF_MAILBOX_CPHS,
+ EF_EXT1, 1,
+ obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
+ }
+
+ break;
+ case EVENT_GET_CPHS_MAILBOX_DONE:
+ case EVENT_GET_MBDN_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+
+ Log.d(LOG_TAG, "Invalid or missing EF"
+ + ((msg.what == EVENT_GET_CPHS_MAILBOX_DONE) ? "[MAILBOX]" : "[MBDN]"));
+
+ // Bug #645770 fall back to CPHS
+ // FIXME should use SST to decide
+
+ if (msg.what == EVENT_GET_MBDN_DONE) {
+ //load CPHS on fail...
+ // FIXME right now, only load line1's CPHS voice mail entry
+
+ recordsToLoad += 1;
+ new AdnRecordLoader(phone).loadFromEF(
+ EF_MAILBOX_CPHS, EF_EXT1, 1,
+ obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
+ }
+ break;
+ }
+
+ adn = (AdnRecord)ar.result;
+
+ Log.d(LOG_TAG, "VM: " + adn + ((msg.what == EVENT_GET_CPHS_MAILBOX_DONE) ? " EF[MAILBOX]" : " EF[MBDN]"));
+
+ if (adn.isEmpty() && msg.what == EVENT_GET_MBDN_DONE) {
+ // Bug #645770 fall back to CPHS
+ // FIXME should use SST to decide
+ // FIXME right now, only load line1's CPHS voice mail entry
+ recordsToLoad += 1;
+ new AdnRecordLoader(phone).loadFromEF(
+ EF_MAILBOX_CPHS, EF_EXT1, 1,
+ obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
+
+ break;
+ }
+
+ voiceMailNum = adn.getNumber();
+ voiceMailTag = adn.getAlphaTag();
+ break;
+
+ case EVENT_GET_MSISDN_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+ Log.d(LOG_TAG, "Invalid or missing EF[MSISDN]");
+ break;
+ }
+
+ adn = (AdnRecord)ar.result;
+
+ msisdn = adn.getNumber();
+ msisdnTag = adn.getAlphaTag();
+
+ Log.d(LOG_TAG, "MSISDN: " + msisdn);
+ break;
+
+ case EVENT_SET_MSISDN_DONE:
+ isRecordLoadResponse = false;
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.userObj != null) {
+ AsyncResult.forMessage(((Message) ar.userObj)).exception
+ = ar.exception;
+ ((Message) ar.userObj).sendToTarget();
+ }
+ break;
+
+ case EVENT_GET_MWIS_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ Log.d(LOG_TAG, "EF_MWIS: " +
+ SimUtils.bytesToHexString(data));
+
+ efMWIS = data;
+
+ if ((data[0] & 0xff) == 0xff) {
+ Log.d(LOG_TAG, "SIMRecords: Uninitialized record MWIS");
+ break;
+ }
+
+ // Refer TS 51.011 Section 10.3.45 for the content description
+ boolean voiceMailWaiting = ((data[0] & 0x01) != 0);
+ countVoiceMessages = data[1] & 0xff;
+
+ if (voiceMailWaiting && countVoiceMessages == 0) {
+ // Unknown count = -1
+ countVoiceMessages = -1;
+ }
+
+ phone.setSystemProperty(PROPERTY_LINE1_VOICE_MAIL_WAITING,
+ voiceMailWaiting ? "true" : "false");
+ phone.notifyMessageWaitingIndicator();
+ break;
+
+ case EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ efCPHS_MWI = data;
+
+ // Use this data if the EF[MWIS] exists and
+ // has been loaded
+
+ if (efMWIS == null) {
+ int indicator = (int)(data[0] & 0xf);
+
+ // Refer CPHS4_2.WW6 B4.2.3
+ if (indicator == 0xA) {
+ // Unknown count = -1
+ countVoiceMessages = -1;
+ } else if (indicator == 0x5) {
+ countVoiceMessages = 0;
+ }
+
+ phone.setSystemProperty(PROPERTY_LINE1_VOICE_MAIL_WAITING,
+ countVoiceMessages != 0
+ ? "true" : "false");
+ phone.notifyMessageWaitingIndicator();
+ }
+ break;
+
+ case EVENT_GET_ICCID_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ iccid = SimUtils.bcdToString(data, 0, data.length);
+
+ Log.d(LOG_TAG, "iccid: " + iccid);
+
+ break;
+
+
+ case EVENT_GET_AD_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ Log.d(LOG_TAG, "EF_AD: " +
+ SimUtils.bytesToHexString(data));
+
+ if (data.length < 3) {
+ Log.d(LOG_TAG, "SIMRecords: Corrupt AD data on SIM");
+ break;
+ }
+
+ if (data.length == 3) {
+ Log.d(LOG_TAG, "SIMRecords: MNC length not present in EF_AD");
+ break;
+ }
+
+ mncLength = (int)data[3] & 0xf;
+
+ if (mncLength == 0xf) {
+ // Resetting mncLength to 0 to indicate that it is not
+ // initialised
+ mncLength = 0;
+
+ Log.d(LOG_TAG, "SIMRecords: MNC length not present in EF_AD");
+ break;
+ }
+
+ break;
+
+ case EVENT_GET_SPN_DONE:
+ isRecordLoadResponse = true;
+ ar = (AsyncResult) msg.obj;
+ getSpnFsm(false, ar);
+ break;
+
+ case EVENT_GET_CFF_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult) msg.obj;
+ data = (byte[]) ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ Log.d(LOG_TAG, "EF_CFF_CPHS: " +
+ SimUtils.bytesToHexString(data));
+ mEfCff = data;
+
+ if (mEfCfis == null) {
+ callForwardingEnabled =
+ ((data[0] & CFF_LINE1_MASK) == CFF_UNCONDITIONAL_ACTIVE);
+
+ phone.setSystemProperty(PROPERTY_LINE1_VOICE_CALL_FORWARDING,
+ (callForwardingEnabled ? "true" : "false"));
+
+ phone.notifyCallForwardingIndicator();
+ }
+ break;
+
+ case EVENT_GET_SPDI_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ parseEfSpdi(data);
+ break;
+
+ case EVENT_UPDATE_DONE:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception != null) {
+ Log.i(LOG_TAG, "SIMRecords update failed", ar.exception);
+ }
+ break;
+
+ case EVENT_GET_PNN_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ SimTlv tlv = new SimTlv(data, 0, data.length);
+
+ for ( ; tlv.isValidObject() ; tlv.nextObject()) {
+ if (tlv.getTag() == TAG_FULL_NETWORK_NAME) {
+ pnnHomeName
+ = SimUtils.networkNameToString(
+ tlv.getData(), 0, tlv.getData().length);
+ break;
+ }
+ }
+ break;
+
+ case EVENT_GET_ALL_SMS_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception != null)
+ break;
+
+ handleSmses((ArrayList) ar.result);
+ break;
+
+ case EVENT_MARK_SMS_READ_DONE:
+ Log.i("ENF", "marked read: sms " + msg.arg1);
+ break;
+
+
+ case EVENT_SMS_ON_SIM:
+ isRecordLoadResponse = false;
+
+ ar = (AsyncResult)msg.obj;
+
+ int[] index = (int[])ar.result;
+
+ if (ar.exception != null || index.length != 1) {
+ Log.e(LOG_TAG, "[SIMRecords] Error on SMS_ON_SIM with exp "
+ + ar.exception + " length " + index.length);
+ } else {
+ Log.d(LOG_TAG, "READ EF_SMS RECORD index=" + index[0]);
+ phone.mSIMFileHandler.loadEFLinearFixed(EF_SMS,index[0],obtainMessage(EVENT_GET_SMS_DONE));
+ }
+ break;
+
+ case EVENT_GET_SMS_DONE:
+ isRecordLoadResponse = false;
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ handleSms((byte[])ar.result);
+ } else {
+ Log.e(LOG_TAG, "[SIMRecords] Error on GET_SMS with exp "
+ + ar.exception);
+ }
+ break;
+ case EVENT_GET_SST_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ //Log.d(LOG_TAG, "SST: " + SimUtils.bytesToHexString(data));
+ break;
+
+ case EVENT_GET_INFO_CPHS_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ mCphsInfo = (byte[])ar.result;
+
+ if (DBG) log("iCPHS: " + SimUtils.bytesToHexString(mCphsInfo));
+ break;
+
+ case EVENT_SET_MBDN_DONE:
+ isRecordLoadResponse = false;
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception == null) {
+ voiceMailNum = newVoiceMailNum;
+ voiceMailTag = newVoiceMailTag;
+ }
+
+ if (isCphsMailboxEnabled()) {
+ adn = new AdnRecord(voiceMailTag, voiceMailNum);
+ Message onCphsCompleted = (Message) ar.userObj;
+
+ /* write to cphs mailbox whenever it is available but
+ * we only need notify caller once if both updating are
+ * successful.
+ *
+ * so if set_mbdn successful, notify caller here and set
+ * onCphsCompleted to null
+ */
+ if (ar.exception == null && ar.userObj != null) {
+ AsyncResult.forMessage(((Message) ar.userObj)).exception
+ = null;
+ ((Message) ar.userObj).sendToTarget();
+
+ if (DBG) log("Callback with MBDN successful.");
+
+ onCphsCompleted = null;
+ }
+
+ new AdnRecordLoader(phone).
+ updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null,
+ obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE,
+ onCphsCompleted));
+ } else {
+ if (ar.userObj != null) {
+ AsyncResult.forMessage(((Message) ar.userObj)).exception
+ = ar.exception;
+ ((Message) ar.userObj).sendToTarget();
+ }
+ }
+ break;
+ case EVENT_SET_CPHS_MAILBOX_DONE:
+ isRecordLoadResponse = false;
+ ar = (AsyncResult)msg.obj;
+ if(ar.exception == null) {
+ voiceMailNum = newVoiceMailNum;
+ voiceMailTag = newVoiceMailTag;
+ } else {
+ if (DBG) log("Set CPHS MailBox with exception: "
+ + ar.exception);
+ }
+ if (ar.userObj != null) {
+ if (DBG) log("Callback with CPHS MB successful.");
+ AsyncResult.forMessage(((Message) ar.userObj)).exception
+ = ar.exception;
+ ((Message) ar.userObj).sendToTarget();
+ }
+ break;
+ case EVENT_SIM_REFRESH:
+ isRecordLoadResponse = false;
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ handleSimRefresh((int[])(ar.result));
+ }
+ break;
+ case EVENT_GET_CFIS_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ Log.d(LOG_TAG, "EF_CFIS: " +
+ SimUtils.bytesToHexString(data));
+
+ mEfCfis = data;
+
+ // Refer TS 51.011 Section 10.3.46 for the content description
+ callForwardingEnabled = ((data[1] & 0x01) != 0);
+
+ phone.setSystemProperty(PROPERTY_LINE1_VOICE_CALL_FORWARDING,
+ (callForwardingEnabled ? "true" : "false"));
+
+ phone.notifyCallForwardingIndicator();
+ break;
+
+ }}catch (RuntimeException exc) {
+ // I don't want these exceptions to be fatal
+ Log.w(LOG_TAG, "Exception parsing SIM record", exc);
+ } finally {
+ // Count up record load responses even if they are fails
+ if (isRecordLoadResponse) {
+ onRecordLoaded();
+ }
+ }
+ }
+
+ private void handleFileUpdate(int efid) {
+ switch(efid) {
+ case EF_MBDN:
+ recordsToLoad++;
+ new AdnRecordLoader(phone).loadFromEF(EF_MBDN, EF_EXT6,
+ mailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
+ break;
+ case EF_MAILBOX_CPHS:
+ recordsToLoad++;
+ new AdnRecordLoader(phone).loadFromEF(EF_MAILBOX_CPHS, EF_EXT1,
+ 1, obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
+ break;
+ default:
+ // For now, fetch all records if this is not a
+ // voicemail number.
+ // TODO: Handle other cases, instead of fetching all.
+ adnCache.reset();
+ fetchSimRecords();
+ break;
+ }
+ }
+
+ private void handleSimRefresh(int[] result) {
+ if (result == null || result.length == 0) {
+ return;
+ }
+
+ switch ((result[0])) {
+ case CommandsInterface.SIM_REFRESH_FILE_UPDATED:
+ // result[1] contains the EFID of the updated file.
+ int efid = result[1];
+ handleFileUpdate(efid);
+ break;
+ case CommandsInterface.SIM_REFRESH_INIT:
+ // need to reload all files (that we care about)
+ fetchSimRecords();
+ break;
+ case CommandsInterface.SIM_REFRESH_RESET:
+ phone.mCM.setRadioPower(false, null);
+ /* Note: no need to call setRadioPower(true). Assuming the desired
+ * radio power state is still ON (as tracked by ServiceStateTracker),
+ * ServiceStateTracker will call setRadioPower when it receives the
+ * RADIO_STATE_CHANGED notification for the power off. And if the
+ * desired power state has changed in the interim, we don't want to
+ * override it with an unconditional power on.
+ */
+ break;
+ default:
+ // unknown refresh operation
+ break;
+ }
+ }
+
+ private void handleSms(byte[] ba)
+ {
+ if (ba[0] != 0)
+ Log.d("ENF", "status : " + ba[0]);
+
+ // 3GPP TS 51.011 v5.0.0 (20011-12) 10.5.3
+ // 3 == "received by MS from network; message to be read"
+ if (ba[0] == 3) {
+ int n = ba.length;
+
+ // Note: Data may include trailing FF's. That's OK; message
+ // should still parse correctly.
+ byte[] nba = new byte[n - 1];
+ System.arraycopy(ba, 1, nba, 0, n - 1);
+
+ String pdu = SimUtils.bytesToHexString(nba);
+ // XXX first line is bogus
+ SmsMessage message = SmsMessage.newFromCMT(
+ new String[] { "", pdu });
+
+ Log.i("ENF", "message from " +
+ message.getOriginatingAddress());
+ Log.i("ENF", "message text " +
+ message.getMessageBody());
+
+ phone.mSMS.dispatchMessage(message);
+ }
+ }
+
+
+ private void handleSmses(ArrayList messages) {
+ int count = messages.size();
+
+ for (int i = 0; i < count; i++) {
+ byte[] ba = (byte[]) messages.get(i);
+
+ if (ba[0] != 0)
+ Log.i("ENF", "status " + i + ": " + ba[0]);
+
+ // 3GPP TS 51.011 v5.0.0 (20011-12) 10.5.3
+ // 3 == "received by MS from network; message to be read"
+
+ if (ba[0] == 3) {
+ int n = ba.length;
+
+ // Note: Data may include trailing FF's. That's OK; message
+ // should still parse correctly.
+ byte[] nba = new byte[n - 1];
+ System.arraycopy(ba, 1, nba, 0, n - 1);
+
+ String pdu = SimUtils.bytesToHexString(nba);
+ // XXX first line is bogus
+ SmsMessage message = SmsMessage.newFromCMT(
+ new String[] { "", pdu });
+
+ Log.i("ENF", "message from " +
+ message.getOriginatingAddress());
+ Log.i("ENF", "message text " +
+ message.getMessageBody());
+
+ phone.mSMS.dispatchMessage(message);
+
+ // 3GPP TS 51.011 v5.0.0 (20011-12) 10.5.3
+ // 1 == "received by MS from network; message read"
+
+ ba[0] = 1;
+
+ if (false) { // XXX writing seems to crash RdoServD
+ phone.mSIMFileHandler.updateEFLinearFixed(EF_SMS, i, ba, null,
+ obtainMessage(EVENT_MARK_SMS_READ_DONE, i));
+ }
+ }
+ }
+ }
+
+
+ //***** Private Methods
+
+ private void onRecordLoaded()
+ {
+ // One record loaded successfully or failed, In either case
+ // we need to update the recordsToLoad count
+ recordsToLoad -= 1;
+
+ if (recordsToLoad == 0 && recordsRequested == true) {
+ onAllRecordsLoaded();
+ } else if (recordsToLoad < 0) {
+ Log.e(LOG_TAG, "SIMRecords: recordsToLoad <0, programmer error suspected");
+ recordsToLoad = 0;
+ }
+ }
+
+ private void onAllRecordsLoaded()
+ {
+ Log.d(LOG_TAG, "SIMRecords: record load complete");
+
+ // Some fields require more than one SIM record to set
+
+ phone.setSystemProperty(PROPERTY_SIM_OPERATOR_NUMERIC,
+ getSIMOperatorNumeric());
+
+ if (imsi != null) {
+ phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ISO_COUNTRY,
+ MccTable.countryCodeForMcc(
+ Integer.parseInt(imsi.substring(0,3))));
+ }
+ else {
+ Log.e("SIM", "[SIMRecords] onAllRecordsLoaded: imsi is NULL!");
+ }
+
+ setVoiceMailByCountry(getSIMOperatorNumeric());
+
+ recordsLoadedRegistrants.notifyRegistrants(
+ new AsyncResult(null, null, null));
+ phone.mSimCard.broadcastSimStateChangedIntent(
+ SimCard.INTENT_VALUE_SIM_LOADED, null);
+ }
+
+ private void setVoiceMailByCountry (String spn) {
+ if (mVmConfig.containsCarrier(spn)) {
+ isVoiceMailFixed = true;
+ voiceMailNum = mVmConfig.getVoiceMailNumber(spn);
+ voiceMailTag = mVmConfig.getVoiceMailTag(spn);
+ }
+ }
+
+ private void onSimReady() {
+ /* broadcast intent SIM_READY here so that we can make sure
+ READY is sent before IMSI ready
+ */
+ phone.mSimCard.broadcastSimStateChangedIntent(
+ SimCard.INTENT_VALUE_SIM_READY, null);
+
+ fetchSimRecords();
+ }
+
+ private void fetchSimRecords() {
+ recordsRequested = true;
+
+ Log.v(LOG_TAG, "SIMRecords:fetchSimRecords " + recordsToLoad);
+
+ phone.mCM.getIMSI(obtainMessage(EVENT_GET_IMSI_DONE));
+ recordsToLoad++;
+
+ phone.mSIMFileHandler.loadEFTransparent(EF_ICCID,
+ obtainMessage(EVENT_GET_ICCID_DONE));
+ recordsToLoad++;
+
+ // FIXME should examine EF[MSISDN]'s capability configuration
+ // to determine which is the voice/data/fax line
+ new AdnRecordLoader(phone).loadFromEF(EF_MSISDN, EF_EXT1, 1,
+ obtainMessage(EVENT_GET_MSISDN_DONE));
+ recordsToLoad++;
+
+ // Record number is subscriber profile
+ phone.mSIMFileHandler.loadEFLinearFixed(EF_MBI, 1,
+ obtainMessage(EVENT_GET_MBI_DONE));
+ recordsToLoad++;
+
+ phone.mSIMFileHandler.loadEFTransparent(EF_AD,
+ obtainMessage(EVENT_GET_AD_DONE));
+ recordsToLoad++;
+
+ // Record number is subscriber profile
+ phone.mSIMFileHandler.loadEFLinearFixed(EF_MWIS, 1,
+ obtainMessage(EVENT_GET_MWIS_DONE));
+ recordsToLoad++;
+
+
+ // Also load CPHS-style voice mail indicator, which stores
+ // the same info as EF[MWIS]. If both exist, both are updated
+ // but the EF[MWIS] data is preferred
+ // Please note this must be loaded after EF[MWIS]
+ phone.mSIMFileHandler.loadEFTransparent(
+ EF_VOICE_MAIL_INDICATOR_CPHS,
+ obtainMessage(EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE));
+ recordsToLoad++;
+
+ // Same goes for Call Forward Status indicator: fetch both
+ // EF[CFIS] and CPHS-EF, with EF[CFIS] preferred.
+ phone.mSIMFileHandler.loadEFLinearFixed(EF_CFIS, 1, obtainMessage(EVENT_GET_CFIS_DONE));
+ recordsToLoad++;
+ phone.mSIMFileHandler.loadEFTransparent(EF_CFF_CPHS,
+ obtainMessage(EVENT_GET_CFF_DONE));
+ recordsToLoad++;
+
+
+ getSpnFsm(true, null);
+
+ phone.mSIMFileHandler.loadEFTransparent(EF_SPDI,
+ obtainMessage(EVENT_GET_SPDI_DONE));
+ recordsToLoad++;
+
+ phone.mSIMFileHandler.loadEFLinearFixed(EF_PNN, 1,
+ obtainMessage(EVENT_GET_PNN_DONE));
+ recordsToLoad++;
+
+ phone.mSIMFileHandler.loadEFTransparent(EF_SST,
+ obtainMessage(EVENT_GET_SST_DONE));
+ recordsToLoad++;
+
+ phone.mSIMFileHandler.loadEFTransparent(EF_INFO_CPHS,
+ obtainMessage(EVENT_GET_INFO_CPHS_DONE));
+ recordsToLoad++;
+
+ // XXX should seek instead of examining them all
+ if (false) { // XXX
+ phone.mSIMFileHandler.loadEFLinearFixedAll(EF_SMS,
+ obtainMessage(EVENT_GET_ALL_SMS_DONE));
+ recordsToLoad++;
+ }
+
+ if (CRASH_RIL) {
+ String sms = "0107912160130310f20404d0110041007030208054832b0120ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
+ byte[] ba = SimUtils.hexStringToBytes(sms);
+
+ phone.mSIMFileHandler.updateEFLinearFixed(EF_SMS, 1, ba, null,
+ obtainMessage(EVENT_MARK_SMS_READ_DONE, 1));
+ }
+ }
+
+ /**
+ * Returns the SpnDisplayRule based on settings on the SIM and the
+ * specified plmn (currently-registered PLMN). See TS 22.101 Annex A
+ * and TS 51.011 10.3.11 for details.
+ *
+ * If the SPN is not found on the SIM, the rule is always PLMN_ONLY.
+ */
+ int getDisplayRule(String plmn) {
+ int rule;
+ if (spn == null || spnDisplayCondition == -1) {
+ // EF_SPN was not found on the SIM, or not yet loaded. Just show ONS.
+ rule = SPN_RULE_SHOW_PLMN;
+ } else if (isOnMatchingPlmn(plmn)) {
+ rule = SPN_RULE_SHOW_SPN;
+ if ((spnDisplayCondition & 0x01) == 0x01) {
+ // ONS required when registered to HPLMN or PLMN in EF_SPDI
+ rule |= SPN_RULE_SHOW_PLMN;
+ }
+ } else {
+ rule = SPN_RULE_SHOW_PLMN;
+ if ((spnDisplayCondition & 0x02) == 0x00) {
+ // SPN required if not registered to HPLMN or PLMN in EF_SPDI
+ rule |= SPN_RULE_SHOW_SPN;
+ }
+ }
+ return rule;
+ }
+
+ /**
+ * Checks if plmn is HPLMN or on the spdiNetworks list.
+ */
+ private boolean isOnMatchingPlmn(String plmn) {
+ if (plmn == null) return false;
+
+ if (plmn.equals(getSIMOperatorNumeric())) {
+ return true;
+ }
+
+ if (spdiNetworks != null) {
+ for (String spdiNet : spdiNetworks) {
+ if (plmn.equals(spdiNet)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * States of Get SPN Finite State Machine which only used by getSpnFsm()
+ */
+ private enum Get_Spn_Fsm_State {
+ IDLE, // No initialized
+ INIT, // Start FSM
+ READ_SPN_3GPP, // Load EF_SPN firstly
+ READ_SPN_CPHS, // Load EF_SPN_CPHS secondly
+ READ_SPN_SHORT_CPHS // Load EF_SPN_SHORT_CPHS last
+ }
+
+ /**
+ * Finite State Machine to load Service Provider Name , which can be stored
+ * in either EF_SPN (3GPP), EF_SPN_CPHS, or EF_SPN_SHORT_CPHS (CPHS4.2)
+ *
+ * After starting, FSM will search SPN EFs in order and stop after finding
+ * the first valid SPN
+ *
+ * @param start set true only for initialize loading
+ * @param ar the AsyncResult from loadEFTransparent
+ * ar.exception holds exception in error
+ * ar.result is byte[] for data in success
+ */
+ private void getSpnFsm(boolean start, AsyncResult ar) {
+ byte[] data;
+
+ if (start) {
+ spnState = Get_Spn_Fsm_State.INIT;
+ }
+
+ switch(spnState){
+ case INIT:
+ spn = null;
+
+ phone.mSIMFileHandler.loadEFTransparent( EF_SPN,
+ obtainMessage(EVENT_GET_SPN_DONE));
+ recordsToLoad++;
+
+ spnState = Get_Spn_Fsm_State.READ_SPN_3GPP;
+ break;
+ case READ_SPN_3GPP:
+ if (ar != null && ar.exception == null) {
+ data = (byte[]) ar.result;
+ spnDisplayCondition = 0xff & data[0];
+ spn = SimUtils.adnStringFieldToString(data, 1, data.length - 1);
+
+ if (DBG) log("Load EF_SPN: " + spn
+ + " spnDisplayCondition: " + spnDisplayCondition);
+ phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ALPHA, spn);
+
+ spnState = Get_Spn_Fsm_State.IDLE;
+ } else {
+ phone.mSIMFileHandler.loadEFTransparent( EF_SPN_CPHS,
+ obtainMessage(EVENT_GET_SPN_DONE));
+ recordsToLoad++;
+
+ spnState = Get_Spn_Fsm_State.READ_SPN_CPHS;
+
+ // See TS 51.011 10.3.11. Basically, default to
+ // show PLMN always, and SPN also if roaming.
+ spnDisplayCondition = -1;
+ }
+ break;
+ case READ_SPN_CPHS:
+ if (ar != null && ar.exception == null) {
+ data = (byte[]) ar.result;
+ spn = SimUtils.adnStringFieldToString(
+ data, 0, data.length - 1 );
+
+ if (DBG) log("Load EF_SPN_CPHS: " + spn);
+ phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ALPHA, spn);
+
+ spnState = Get_Spn_Fsm_State.IDLE;
+ } else {
+ phone.mSIMFileHandler.loadEFTransparent( EF_SPN_SHORT_CPHS,
+ obtainMessage(EVENT_GET_SPN_DONE));
+ recordsToLoad++;
+
+ spnState = Get_Spn_Fsm_State.READ_SPN_SHORT_CPHS;
+ }
+ break;
+ case READ_SPN_SHORT_CPHS:
+ if (ar != null && ar.exception == null) {
+ data = (byte[]) ar.result;
+ spn = SimUtils.adnStringFieldToString(
+ data, 0, data.length - 1);
+
+ if (DBG) log("Load EF_SPN_SHORT_CPHS: " + spn);
+ phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ALPHA, spn);
+ }else {
+ if (DBG) log("No SPN loaded in either CHPS or 3GPP");
+ }
+
+ spnState = Get_Spn_Fsm_State.IDLE;
+ break;
+ default:
+ spnState = Get_Spn_Fsm_State.IDLE;
+ }
+ }
+
+ /**
+ * Parse TS 51.011 EF[SPDI] record
+ * This record contains the list of numeric network IDs that
+ * are treated specially when determining SPN display
+ */
+ private void
+ parseEfSpdi(byte[] data)
+ {
+ SimTlv tlv = new SimTlv(data, 0, data.length);
+
+ byte[] plmnEntries = null;
+
+ // There should only be one TAG_SPDI_PLMN_LIST
+ for ( ; tlv.isValidObject() ; tlv.nextObject()) {
+ if (tlv.getTag() == TAG_SPDI_PLMN_LIST) {
+ plmnEntries = tlv.getData();
+ break;
+ }
+ }
+
+ if (plmnEntries == null) {
+ return;
+ }
+
+ spdiNetworks = new ArrayList<String>(plmnEntries.length / 3);
+
+ for (int i = 0 ; i + 2 < plmnEntries.length ; i += 3) {
+ String plmnCode;
+ plmnCode = SimUtils.bcdToString(plmnEntries, i, 3);
+
+ // Valid operator codes are 5 or 6 digits
+ if (plmnCode.length() >= 5) {
+ log("EF_SPDI network: " + plmnCode);
+ spdiNetworks.add(plmnCode);
+ }
+ }
+ }
+
+ /**
+ * check to see if Mailbox Number is allocated and activated in CPHS SST
+ */
+ private boolean isCphsMailboxEnabled() {
+ if (mCphsInfo == null) return false;
+ return ((mCphsInfo[1] & CPHS_SST_MBN_MASK) == CPHS_SST_MBN_ENABLED );
+ }
+
+ private void log(String s) {
+ Log.d(LOG_TAG, "[SIMRecords] " + s);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java
new file mode 100644
index 0000000..258c058
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java
@@ -0,0 +1,942 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.AlertDialog;
+import android.app.PendingIntent.CanceledException;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Telephony;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.gsm.SmsMessage;
+import android.telephony.gsm.SmsManager;
+import android.telephony.ServiceState;
+import android.util.Config;
+import com.android.internal.util.HexDump;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Random;
+
+import com.android.internal.R;
+
+final class SMSDispatcher extends Handler {
+ private static final String TAG = "GSM";
+
+ /** Default checking period for SMS sent without uesr permit */
+ private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000;
+
+ /** Default number of SMS sent in checking period without uesr permit */
+ private static final int DEFAULT_SMS_MAX_ALLOWED = 100;
+
+ /** Default timeout for SMS sent query */
+ private static final int DEFAULT_SMS_TIMOUEOUT = 6000;
+
+ private static final int WAP_PDU_TYPE_PUSH = 0x06;
+
+ private static final int WAP_PDU_TYPE_CONFIRMED_PUSH = 0x07;
+
+ private static final byte DRM_RIGHTS_XML = (byte)0xca;
+
+ private static final String DRM_RIGHTS_XML_MIME_TYPE = "application/vnd.oma.drm.rights+xml";
+
+ private static final byte DRM_RIGHTS_WBXML = (byte)0xcb;
+
+ private static final String DRM_RIGHTS_WBXML_MIME_TYPE =
+ "application/vnd.oma.drm.rights+wbxml";
+
+ private static final byte WAP_SI_MIME_PORT = (byte)0xae;
+
+ private static final String WAP_SI_MIME_TYPE = "application/vnd.wap.sic";
+
+ private static final byte WAP_SL_MIME_PORT = (byte)0xb0;
+
+ private static final String WAP_SL_MIME_TYPE = "application/vnd.wap.slc";
+
+ private static final byte WAP_CO_MIME_PORT = (byte)0xb2;
+
+ private static final String WAP_CO_MIME_TYPE = "application/vnd.wap.coc";
+
+ private static final int WAP_PDU_SHORT_LENGTH_MAX = 30;
+
+ private static final int WAP_PDU_LENGTH_QUOTE = 31;
+
+ private static final String MMS_MIME_TYPE = "application/vnd.wap.mms-message";
+
+ private static final String[] RAW_PROJECTION = new String[] {
+ "pdu",
+ "sequence",
+ };
+
+ static final int MAIL_SEND_SMS = 1;
+
+ static final int EVENT_NEW_SMS = 1;
+
+ static final int EVENT_SEND_SMS_COMPLETE = 2;
+
+ /** Retry sending a previously failed SMS message */
+ static final int EVENT_SEND_RETRY = 3;
+
+ /** Status report received */
+ static final int EVENT_NEW_SMS_STATUS_REPORT = 5;
+
+ /** SIM storage is full */
+ static final int EVENT_SIM_FULL = 6;
+
+ /** SMS confirm required */
+ static final int EVENT_POST_ALERT = 7;
+
+ /** Send the user confirmed SMS */
+ static final int EVENT_SEND_CONFIRMED_SMS = 8;
+
+ /** Alert is timeout */
+ static final int EVENT_ALERT_TIMEOUT = 9;
+
+ private final GSMPhone mPhone;
+
+ private final Context mContext;
+
+ private final ContentResolver mResolver;
+
+ private final CommandsInterface mCm;
+
+ private final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
+
+ /** Maximum number of times to retry sending a failed SMS. */
+ private static final int MAX_SEND_RETRIES = 3;
+ /** Delay before next send attempt on a failed SMS, in milliseconds. */
+ private static final int SEND_RETRY_DELAY = 2000; // ms
+
+ /**
+ * Message reference for a CONCATENATED_8_BIT_REFERENCE or
+ * CONCATENATED_16_BIT_REFERENCE message set. Should be
+ * incremented for each set of concatenated messages.
+ */
+ private static int sConcatenatedRef;
+
+ private SmsCounter mCounter;
+
+ private SmsTracker mSTracker;
+
+ /**
+ * Implement the per-application based SMS control, which only allows
+ * a limit on the number of SMS/MMS messages an app can send in checking
+ * period.
+ */
+ private class SmsCounter {
+ private int mCheckPeriod;
+ private int mMaxAllowed;
+ private HashMap<String, ArrayList<Long>> mSmsStamp;
+
+ /**
+ * Create SmsCounter
+ * @param mMax is the number of SMS allowed without user permit
+ * @param mPeriod is the checking period
+ */
+ SmsCounter(int mMax, int mPeriod) {
+ mMaxAllowed = mMax;
+ mCheckPeriod = mPeriod;
+ mSmsStamp = new HashMap<String, ArrayList<Long>> ();
+ }
+
+ boolean check(String appName) {
+ if (!mSmsStamp.containsKey(appName)) {
+ mSmsStamp.put(appName, new ArrayList<Long>());
+ }
+
+ return isUnderLimit(mSmsStamp.get(appName));
+ }
+
+ private boolean isUnderLimit(ArrayList<Long> sent) {
+ Long ct = System.currentTimeMillis();
+
+ Log.d(TAG, "SMS send size=" + sent.size() + "time=" + ct);
+
+ while (sent.size() > 0 && (ct - sent.get(0)) > mCheckPeriod ) {
+ sent.remove(0);
+ }
+
+ if (sent.size() < mMaxAllowed) {
+ sent.add(ct);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ SMSDispatcher(GSMPhone phone) {
+ mPhone = phone;
+ mContext = phone.getContext();
+ mResolver = mContext.getContentResolver();
+ mCm = phone.mCM;
+ mSTracker = null;
+ mCounter = new SmsCounter(DEFAULT_SMS_MAX_ALLOWED,
+ DEFAULT_SMS_CHECK_PERIOD);
+
+ mCm.setOnNewSMS(this, EVENT_NEW_SMS, null);
+ mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
+ mCm.setOnSimSmsFull(this, EVENT_SIM_FULL, null);
+
+ // Don't always start message ref at 0.
+ sConcatenatedRef = new Random().nextInt(256);
+ }
+
+ /* TODO: Need to figure out how to keep track of status report routing in a
+ * persistent manner. If the phone process restarts (reboot or crash),
+ * we will lose this list and any status reports that come in after
+ * will be dropped.
+ */
+ /** Sent messages awaiting a delivery status report. */
+ private final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>();
+
+ /**
+ * Handles events coming from the phone stack. Overridden from handler.
+ *
+ * @param msg the message to handle
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case EVENT_NEW_SMS:
+ // A new SMS has been received by the device
+ if (Config.LOGD) {
+ Log.d(TAG, "New SMS Message Received");
+ }
+
+ SmsMessage sms;
+
+ ar = (AsyncResult) msg.obj;
+
+ // FIXME unit test leaves cm == null. this should change
+ if (mCm != null) {
+ // FIXME only acknowledge on store
+ mCm.acknowledgeLastIncomingSMS(true, null);
+ }
+
+ if (ar.exception != null) {
+ Log.e(TAG, "Exception processing incoming SMS",
+ ar.exception);
+ return;
+ }
+
+ sms = (SmsMessage) ar.result;
+ dispatchMessage(sms);
+
+ break;
+
+ case EVENT_SEND_SMS_COMPLETE:
+ // An outbound SMS has been sucessfully transferred, or failed.
+ handleSendComplete((AsyncResult) msg.obj);
+ break;
+
+ case EVENT_SEND_RETRY:
+ sendSms((SmsTracker) msg.obj);
+ break;
+
+ case EVENT_NEW_SMS_STATUS_REPORT:
+ handleStatusReport((AsyncResult)msg.obj);
+ break;
+
+ case EVENT_SIM_FULL:
+ handleSimFull();
+ break;
+
+ case EVENT_POST_ALERT:
+ handleReachSentLimit((SmsTracker)(msg.obj));
+ break;
+
+ case EVENT_ALERT_TIMEOUT:
+ ((AlertDialog)(msg.obj)).dismiss();
+ msg.obj = null;
+ mSTracker = null;
+ break;
+
+ case EVENT_SEND_CONFIRMED_SMS:
+ if (mSTracker!=null) {
+ Log.d(TAG, "Ready to send SMS again.");
+ sendSms(mSTracker);
+ mSTracker = null;
+ }
+ break;
+ }
+ }
+
+ /**
+ * Called when SIM_FULL message is received from the RIL. Notifies interested
+ * parties that SIM storage for SMS messages is full.
+ */
+ private void handleSimFull() {
+ // broadcast SIM_FULL intent
+ Intent intent = new Intent(Intents.SIM_FULL_ACTION);
+ mPhone.getContext().sendBroadcast(intent, "android.permission.RECEIVE_SMS");
+ }
+
+ /**
+ * Called when a status report is received. This should correspond to
+ * a previously successful SEND.
+ *
+ * @param ar AsyncResult passed into the message handler. ar.result should
+ * be a String representing the status report PDU, as ASCII hex.
+ */
+ private void handleStatusReport(AsyncResult ar) {
+ String pduString = (String) ar.result;
+ SmsMessage sms = SmsMessage.newFromCDS(pduString);
+
+ if (sms != null) {
+ int messageRef = sms.messageRef;
+ for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
+ SmsTracker tracker = deliveryPendingList.get(i);
+ if (tracker.mMessageRef == messageRef) {
+ // Found it. Remove from list and broadcast.
+ deliveryPendingList.remove(i);
+ PendingIntent intent = tracker.mDeliveryIntent;
+ Intent fillIn = new Intent();
+ fillIn.putExtra("pdu", SimUtils.hexStringToBytes(pduString));
+ try {
+ intent.send(mContext, Activity.RESULT_OK, fillIn);
+ } catch (CanceledException ex) {}
+
+ // Only expect to see one tracker matching this messageref
+ break;
+ }
+ }
+ }
+
+ if (mCm != null) {
+ mCm.acknowledgeLastIncomingSMS(true, null);
+ }
+ }
+
+ /**
+ * Called when SMS send completes. Broadcasts a sentIntent on success.
+ * On failure, either sets up retries or broadcasts a sentIntent with
+ * the failure in the result code.
+ *
+ * @param ar AsyncResult passed into the message handler. ar.result should
+ * an SmsResponse instance if send was successful. ar.userObj
+ * should be an SmsTracker instance.
+ */
+ private void handleSendComplete(AsyncResult ar) {
+ SmsTracker tracker = (SmsTracker) ar.userObj;
+ PendingIntent sentIntent = tracker.mSentIntent;
+
+ if (ar.exception == null) {
+ if (Config.LOGD) {
+ Log.d(TAG, "SMS send complete. Broadcasting "
+ + "intent: " + sentIntent);
+ }
+
+ if (tracker.mDeliveryIntent != null) {
+ // Expecting a status report. Add it to the list.
+ int messageRef = ((SmsResponse)ar.result).messageRef;
+ tracker.mMessageRef = messageRef;
+ deliveryPendingList.add(tracker);
+ }
+
+ if (sentIntent != null) {
+ try {
+ sentIntent.send(Activity.RESULT_OK);
+ } catch (CanceledException ex) {}
+ }
+ } else {
+ if (Config.LOGD) {
+ Log.d(TAG, "SMS send failed");
+ }
+
+ int ss = mPhone.getServiceState().getState();
+
+ if (ss != ServiceState.STATE_IN_SERVICE) {
+ handleNotInService(ss, tracker);
+ } else if ((((CommandException)(ar.exception)).getCommandError()
+ == CommandException.Error.SMS_FAIL_RETRY) &&
+ tracker.mRetryCount < MAX_SEND_RETRIES) {
+ // Retry after a delay if needed.
+ // TODO: According to TS 23.040, 9.2.3.6, we should resend
+ // with the same TP-MR as the failed message, and
+ // TP-RD set to 1. However, we don't have a means of
+ // knowing the MR for the failed message (EF_SMSstatus
+ // may or may not have the MR corresponding to this
+ // message, depending on the failure). Also, in some
+ // implementations this retry is handled by the baseband.
+ tracker.mRetryCount++;
+ Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
+ sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
+ } else if (tracker.mSentIntent != null) {
+ // Done retrying; return an error to the app.
+ try {
+ tracker.mSentIntent.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+ } catch (CanceledException ex) {}
+ }
+ }
+ }
+
+ /**
+ * Handles outbound message when the phone is not in service.
+ *
+ * @param ss Current service state. Valid values are:
+ * OUT_OF_SERVICE
+ * EMERGENCY_ONLY
+ * POWER_OFF
+ * @param tracker An SmsTracker for the current message.
+ */
+ private void handleNotInService(int ss, SmsTracker tracker) {
+ if (tracker.mSentIntent != null) {
+ try {
+ if (ss == ServiceState.STATE_POWER_OFF) {
+ tracker.mSentIntent.send(SmsManager.RESULT_ERROR_RADIO_OFF);
+ } else {
+ tracker.mSentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
+ }
+ } catch (CanceledException ex) {}
+ }
+ }
+
+ /**
+ * Dispatches an incoming SMS messages.
+ *
+ * @param sms the incoming message from the phone
+ */
+ /* package */ void dispatchMessage(SmsMessage sms) {
+ boolean handled = false;
+
+ // Special case the message waiting indicator messages
+ if (sms.isMWISetMessage()) {
+ mPhone.updateMessageWaitingIndicator(true);
+
+ if (sms.isMwiDontStore()) {
+ handled = true;
+ }
+
+ if (Config.LOGD) {
+ Log.d(TAG,
+ "Received voice mail indicator set SMS shouldStore="
+ + !handled);
+ }
+ } else if (sms.isMWIClearMessage()) {
+ mPhone.updateMessageWaitingIndicator(false);
+
+ if (sms.isMwiDontStore()) {
+ handled = true;
+ }
+
+ if (Config.LOGD) {
+ Log.d(TAG,
+ "Received voice mail indicator clear SMS shouldStore="
+ + !handled);
+ }
+ }
+
+ if (handled) {
+ return;
+ }
+
+ // Parse the headers to see if this is partial, or port addressed
+ int referenceNumber = -1;
+ int count = 0;
+ int sequence = 0;
+ int destPort = -1;
+
+ SmsHeader header = sms.getUserDataHeader();
+ if (header != null) {
+ for (SmsHeader.Element element : header.getElements()) {
+ switch (element.getID()) {
+ case SmsHeader.CONCATENATED_8_BIT_REFERENCE: {
+ byte[] data = element.getData();
+
+ referenceNumber = data[0] & 0xff;
+ count = data[1] & 0xff;
+ sequence = data[2] & 0xff;
+
+ break;
+ }
+
+ case SmsHeader.CONCATENATED_16_BIT_REFERENCE: {
+ byte[] data = element.getData();
+
+ referenceNumber = (data[0] & 0xff) * 256 + (data[1] & 0xff);
+ count = data[2] & 0xff;
+ sequence = data[3] & 0xff;
+
+ break;
+ }
+
+ case SmsHeader.APPLICATION_PORT_ADDRESSING_16_BIT: {
+ byte[] data = element.getData();
+
+ destPort = (data[0] & 0xff) << 8;
+ destPort |= (data[1] & 0xff);
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (referenceNumber == -1) {
+ // notify everyone of the message if it isn't partial
+ byte[][] pdus = new byte[1][];
+ pdus[0] = sms.getPdu();
+
+ if (destPort != -1) {
+ if (destPort == SmsHeader.PORT_WAP_PUSH) {
+ dispatchWapPdu(sms.getUserData());
+ }
+ // The message was sent to a port, so concoct a URI for it
+ dispatchPortAddressedPdus(pdus, destPort);
+ } else {
+ // It's a normal message, dispatch it
+ dispatchPdus(pdus);
+ }
+ } else {
+ // Process the message part
+ processMessagePart(sms, referenceNumber, sequence, count, destPort);
+ }
+ }
+
+ /**
+ * If this is the last part send the parts out to the application, otherwise
+ * the part is stored for later processing.
+ */
+ private void processMessagePart(SmsMessage sms, int referenceNumber,
+ int sequence, int count, int destinationPort) {
+ // Lookup all other related parts
+ StringBuilder where = new StringBuilder("reference_number =");
+ where.append(referenceNumber);
+ where.append(" AND address = ?");
+ String[] whereArgs = new String[] {sms.getOriginatingAddress()};
+
+ byte[][] pdus = null;
+ Cursor cursor = null;
+ try {
+ cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
+ int cursorCount = cursor.getCount();
+ if (cursorCount != count - 1) {
+ // We don't have all the parts yet, store this one away
+ ContentValues values = new ContentValues();
+ values.put("date", new Long(sms.getTimestampMillis()));
+ values.put("pdu", HexDump.toHexString(sms.getPdu()));
+ values.put("address", sms.getOriginatingAddress());
+ values.put("reference_number", referenceNumber);
+ values.put("count", count);
+ values.put("sequence", sequence);
+ if (destinationPort != -1) {
+ values.put("destination_port", destinationPort);
+ }
+ mResolver.insert(mRawUri, values);
+
+ return;
+ }
+
+ // All the parts are in place, deal with them
+ int pduColumn = cursor.getColumnIndex("pdu");
+ int sequenceColumn = cursor.getColumnIndex("sequence");
+
+ pdus = new byte[count][];
+ for (int i = 0; i < cursorCount; i++) {
+ cursor.moveToNext();
+ int cursorSequence = (int)cursor.getLong(sequenceColumn);
+ pdus[cursorSequence - 1] = HexDump.hexStringToByteArray(
+ cursor.getString(pduColumn));
+ }
+ // This one isn't in the DB, so add it
+ pdus[sequence - 1] = sms.getPdu();
+
+ // Remove the parts from the database
+ mResolver.delete(mRawUri, where.toString(), whereArgs);
+ } catch (SQLException e) {
+ Log.e(TAG, "Can't access multipart SMS database", e);
+ return; // TODO: NACK the message or something, don't just discard.
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+
+ // Dispatch the PDUs to applications
+ switch (destinationPort) {
+ case SmsHeader.PORT_WAP_PUSH: {
+ // Build up the data stream
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ for (int i = 0; i < count; i++) {
+ SmsMessage msg = SmsMessage.createFromPdu(pdus[i]);
+ byte[] data = msg.getUserData();
+ output.write(data, 0, data.length);
+ }
+
+ // Handle the PUSH
+ dispatchWapPdu(output.toByteArray());
+ break;
+ }
+
+ case -1:
+ // The messages were not sent to a port
+ dispatchPdus(pdus);
+ break;
+
+ default:
+ // The messages were sent to a port, so concoct a URI for it
+ dispatchPortAddressedPdus(pdus, destinationPort);
+ break;
+ }
+ }
+
+ /**
+ * Dispatches standard PDUs to interested applications
+ *
+ * @param pdus The raw PDUs making up the message
+ */
+ private void dispatchPdus(byte[][] pdus) {
+ Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
+ intent.putExtra("pdus", pdus);
+ mPhone.getContext().sendBroadcast(
+ intent, "android.permission.RECEIVE_SMS");
+ }
+
+ /**
+ * Dispatches port addressed PDUs to interested applications
+ *
+ * @param pdus The raw PDUs making up the message
+ * @param port The destination port of the messages
+ */
+ private void dispatchPortAddressedPdus(byte[][] pdus, int port) {
+ Uri uri = Uri.parse("sms://localhost:" + port);
+ Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
+ intent.putExtra("pdus", pdus);
+ mPhone.getContext().sendBroadcast(
+ intent, "android.permission.RECEIVE_SMS");
+ }
+
+ /**
+ * Dispatches inbound messages that are in the WAP PDU format. See
+ * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format.
+ *
+ * @param pdu The WAP PDU, made up of one or more SMS PDUs
+ */
+ private void dispatchWapPdu(byte[] pdu) {
+ int index = 0;
+ int transactionId = pdu[index++] & 0xFF;
+ int pduType = pdu[index++] & 0xFF;
+ int headerLength = 0;
+
+ if ((pduType != WAP_PDU_TYPE_PUSH) &&
+ (pduType != WAP_PDU_TYPE_CONFIRMED_PUSH)) {
+ Log.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
+ return;
+ }
+
+ /**
+ * Parse HeaderLen(unsigned integer).
+ * From wap-230-wsp-20010705-a section 8.1.2
+ * The maximum size of a uintvar is 32 bits.
+ * So it will be encoded in no more than 5 octets.
+ */
+ int temp = 0;
+ do {
+ temp = pdu[index++];
+ headerLength = headerLength << 7;
+ headerLength |= temp & 0x7F;
+ } while ((temp & 0x80) != 0);
+
+ int headerStartIndex = index;
+
+ /**
+ * Parse Content-Type.
+ * From wap-230-wsp-20010705-a section 8.4.2.24
+ *
+ * Content-type-value = Constrained-media | Content-general-form
+ * Content-general-form = Value-length Media-type
+ * Media-type = (Well-known-media | Extension-Media) *(Parameter)
+ * Value-length = Short-length | (Length-quote Length)
+ * Short-length = <Any octet 0-30> (octet <= WAP_PDU_SHORT_LENGTH_MAX)
+ * Length-quote = <Octet 31> (WAP_PDU_LENGTH_QUOTE)
+ * Length = Uintvar-integer
+ */
+ // Parse Value-length.
+ if ((pdu[index] & 0xff) <= WAP_PDU_SHORT_LENGTH_MAX) {
+ // Short-length.
+ index++;
+ } else if (pdu[index] == WAP_PDU_LENGTH_QUOTE) {
+ // Skip Length-quote.
+ index++;
+ // Skip Length.
+ // Now we assume 8bit is enough to store the content-type length.
+ index++;
+ }
+ String mimeType;
+ switch (pdu[headerStartIndex])
+ {
+ case DRM_RIGHTS_XML:
+ mimeType = DRM_RIGHTS_XML_MIME_TYPE;
+ break;
+ case DRM_RIGHTS_WBXML:
+ mimeType = DRM_RIGHTS_WBXML_MIME_TYPE;
+ break;
+ case WAP_SI_MIME_PORT:
+ // application/vnd.wap.sic
+ mimeType = WAP_SI_MIME_TYPE;
+ break;
+ case WAP_SL_MIME_PORT:
+ mimeType = WAP_SL_MIME_TYPE;
+ break;
+ case WAP_CO_MIME_PORT:
+ mimeType = WAP_CO_MIME_TYPE;
+ break;
+ default:
+ int start = index;
+
+ // Skip text-string.
+ // Now we assume the mimetype is Extension-Media.
+ while (pdu[index++] != '\0') {
+ ;
+ }
+ mimeType = new String(pdu, start, index-start-1);
+ break;
+ }
+
+ // XXX Skip the remainder of the header for now
+ int dataIndex = headerStartIndex + headerLength;
+ byte[] data;
+ if (pdu[headerStartIndex] == WAP_CO_MIME_PORT)
+ {
+ // because SMSDispatcher can't parse push headers "Content-Location" and
+ // X-Wap-Content-URI, so pass the whole push to CO application.
+ data = pdu;
+ } else
+ {
+ data = new byte[pdu.length - dataIndex];
+ System.arraycopy(pdu, dataIndex, data, 0, data.length);
+ }
+
+ // Notify listeners about the WAP PUSH
+ Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION);
+ intent.setType(mimeType);
+ intent.putExtra("transactionId", transactionId);
+ intent.putExtra("pduType", pduType);
+ intent.putExtra("data", data);
+
+ if (mimeType.equals(MMS_MIME_TYPE)) {
+ mPhone.getContext().sendBroadcast(
+ intent, "android.permission.RECEIVE_MMS");
+ } else {
+ mPhone.getContext().sendBroadcast(
+ intent, "android.permission.RECEIVE_WAP_PUSH");
+ }
+ }
+
+ /**
+ * Send a multi-part text based SMS.
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applicaitons,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ */
+ void sendMultipartText(String destinationAddress, String scAddress, ArrayList<String> parts,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+
+ int ref = ++sConcatenatedRef & 0xff;
+
+ for (int i = 0, count = parts.size(); i < count; i++) {
+ // build SmsHeader
+ byte[] data = new byte[3];
+ data[0] = (byte) ref; // reference #, unique per message
+ data[1] = (byte) count; // total part count
+ data[2] = (byte) (i + 1); // 1-based sequence
+ SmsHeader header = new SmsHeader();
+ header.add(new SmsHeader.Element(SmsHeader.CONCATENATED_8_BIT_REFERENCE, data));
+ PendingIntent sentIntent = null;
+ PendingIntent deliveryIntent = null;
+
+ if (sentIntents != null && sentIntents.size() > i) {
+ sentIntent = sentIntents.get(i);
+ }
+ if (deliveryIntents != null && deliveryIntents.size() > i) {
+ deliveryIntent = deliveryIntents.get(i);
+ }
+
+ SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
+ parts.get(i), deliveryIntent != null, header.toByteArray());
+
+ sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
+ }
+ }
+
+ /**
+ * Send a SMS
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * defatult SMSC
+ * @param pdu the raw PDU to send
+ * @param sentIntent if not NULL this <code>Intent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applicaitons,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>Intent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ */
+ void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
+ PendingIntent deliveryIntent) {
+ if (pdu == null) {
+ if (sentIntent != null) {
+ try {
+ sentIntent.send(SmsManager.RESULT_ERROR_NULL_PDU);
+ } catch (CanceledException ex) {}
+ }
+ return;
+ }
+
+ HashMap<String, Object> map = new HashMap<String, Object>();
+ map.put("smsc", smsc);
+ map.put("pdu", pdu);
+
+ SmsTracker tracker = new SmsTracker(map, sentIntent,
+ deliveryIntent);
+ int ss = mPhone.getServiceState().getState();
+
+ if (ss != ServiceState.STATE_IN_SERVICE) {
+ handleNotInService(ss, tracker);
+ } else {
+ String appName = getAppNameByIntent(sentIntent);
+ if (mCounter.check(appName)) {
+ sendSms(tracker);
+ } else {
+ sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
+ }
+ }
+ }
+
+ /**
+ * Post an alert while SMS needs user confirm.
+ *
+ * An SmsTracker for the current message.
+ */
+ private void handleReachSentLimit(SmsTracker tracker) {
+
+ Resources r = Resources.getSystem();
+
+ String appName = getAppNameByIntent(tracker.mSentIntent);
+
+ AlertDialog d = new AlertDialog.Builder(mContext)
+ .setTitle(r.getString(R.string.sms_control_title))
+ .setMessage(appName + " " + r.getString(R.string.sms_control_message))
+ .setPositiveButton(r.getString(R.string.sms_control_yes), mListener)
+ .setNegativeButton(r.getString(R.string.sms_control_no), null)
+ .create();
+
+ d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ d.show();
+
+ mSTracker = tracker;
+ sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
+ DEFAULT_SMS_TIMOUEOUT);
+ }
+
+ private String getAppNameByIntent(PendingIntent intent) {
+ Resources r = Resources.getSystem();
+ return (intent != null) ? intent.getTargetPackage()
+ : r.getString(R.string.sms_control_default_app_name);
+ }
+
+ /**
+ * Send the message along to the radio.
+ *
+ * @param tracker holds the SMS message to send
+ */
+ private void sendSms(SmsTracker tracker) {
+ HashMap map = tracker.mData;
+
+ byte smsc[] = (byte[]) map.get("smsc");
+ byte pdu[] = (byte[]) map.get("pdu");
+
+ Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
+ mCm.sendSMS(SimUtils.bytesToHexString(smsc),
+ SimUtils.bytesToHexString(pdu), reply);
+ }
+
+ /**
+ * Keeps track of an SMS that has been sent to the RIL, until it it has
+ * successfully been sent, or we're done trying.
+ *
+ */
+ static class SmsTracker {
+ HashMap mData;
+ int mRetryCount;
+ int mMessageRef;
+
+ PendingIntent mSentIntent;
+ PendingIntent mDeliveryIntent;
+
+ SmsTracker(HashMap data, PendingIntent sentIntent,
+ PendingIntent deliveryIntent) {
+ mData = data;
+ mSentIntent = sentIntent;
+ mDeliveryIntent = deliveryIntent;
+ mRetryCount = 0;
+ }
+
+ }
+
+ private DialogInterface.OnClickListener mListener =
+ new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON1) {
+ Log.d(TAG, "click YES to send out sms");
+ sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS));
+ }
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java
new file mode 100644
index 0000000..1c337f9
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java
@@ -0,0 +1,1358 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ALPHA;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISMANUAL;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISROAMING;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_NUMERIC;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_SIM_OPERATOR_ALPHA;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_SIM_OPERATOR_NUMERIC;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.Phone;
+import android.app.AlarmManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Checkin;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.provider.Telephony.Intents;
+import android.telephony.gsm.GsmCellLocation;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TimeUtils;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * {@hide}
+ */
+final class ServiceStateTracker extends Handler
+{
+ /**
+ * The access technology currently in use:
+ * 0 = unknown
+ * 1 = GPRS only
+ * 2 = EDGE
+ * 3 = UMTS
+ */
+ static final int DATA_ACCESS_UNKNOWN = 0;
+ static final int DATA_ACCESS_GPRS = 1;
+ static final int DATA_ACCESS_EDGE = 2;
+ static final int DATA_ACCESS_UMTS = 3;
+
+ //***** Instance Variables
+
+ GSMPhone phone;
+ CommandsInterface cm;
+
+ ServiceState ss;
+ ServiceState newSS;
+ GsmCellLocation cellLoc;
+ GsmCellLocation newCellLoc;
+ int mPreferredNetworkType;
+
+ int rssi = 99; // signal strength 0-31, 99=unknown
+ // That's "received signal strength indication" fyi
+
+ int[] pollingContext; // Used as a unique identifier to
+ // track requests associated with a poll
+ // and ignore stale responses.
+ // The value is a count-down of expected responses
+ // in this pollingContext
+
+ boolean mDesiredPowerState;
+
+ boolean dontPollSignalStrength = false; // Default is to poll strength
+ // If we're getting unsolicited signal strength updates from the radio,
+ // set value to true and don't bother polling any more
+
+ private int gprsState = ServiceState.STATE_OUT_OF_SERVICE;
+ private int newGPRSState = ServiceState.STATE_OUT_OF_SERVICE;
+
+ /**
+ * The access technology currently in use: DATA_ACCESS_
+ */
+ private int networkType = 0;
+ private int newNetworkType = 0;
+ /* gsm roaming status solely based on TS 27.007 7.2 CREG */
+ private boolean mGsmRoaming = false;
+
+ private RegistrantList networkAttachedRegistrants = new RegistrantList();
+ private RegistrantList gprsAttachedRegistrants = new RegistrantList();
+ private RegistrantList gprsDetachedRegistrants = new RegistrantList();
+ private RegistrantList roamingOnRegistrants = new RegistrantList();
+ private RegistrantList roamingOffRegistrants = new RegistrantList();
+
+ // Sometimes we get the NITZ time before we know what country we are in.
+ // Keep the time zone information from the NITZ string so we can fix
+ // the time zone once know the country.
+ private boolean mNeedFixZone = false;
+ private int mZoneOffset;
+ private boolean mZoneDst;
+ private long mZoneTime;
+ private boolean mGotCountryCode = false;
+
+ String mSavedTimeZone;
+ long mSavedTime;
+ long mSavedAtTime;
+
+ // We can't register for SIM_RECORDS_LOADED immediately because the
+ // SIMRecords object may not be instantiated yet.
+ private boolean mNeedToRegForSimLoaded;
+
+ // Keep track of SPN display rules, so we only broadcast intent if something changes.
+ private String curSpn = null;
+ private String curPlmn = null;
+ private int curSpnRule = 0;
+
+ //***** Constants
+
+ static final boolean DBG = true;
+ static final String LOG_TAG = "GSM";
+
+ // signal strength poll rate
+ static final int POLL_PERIOD_MILLIS = 20 * 1000;
+
+ //***** Events
+
+ static final int EVENT_RADIO_STATE_CHANGED = 1;
+ static final int EVENT_NETWORK_STATE_CHANGED = 2;
+ static final int EVENT_GET_SIGNAL_STRENGTH = 3;
+ static final int EVENT_POLL_STATE_REGISTRATION = 4;
+ static final int EVENT_POLL_STATE_GPRS = 5;
+ static final int EVENT_POLL_STATE_OPERATOR = 6;
+ static final int EVENT_POLL_SIGNAL_STRENGTH = 10;
+ static final int EVENT_NITZ_TIME = 11;
+ static final int EVENT_SIGNAL_STRENGTH_UPDATE = 12;
+ static final int EVENT_RADIO_AVAILABLE = 13;
+ static final int EVENT_POLL_STATE_NETWORK_SELECTION_MODE = 14;
+ static final int EVENT_GET_LOC_DONE = 15;
+ static final int EVENT_SIM_RECORDS_LOADED = 16;
+ static final int EVENT_SIM_READY = 17;
+ static final int EVENT_LOCATION_UPDATES_ENABLED = 18;
+ static final int EVENT_GET_PREFERRED_NETWORK_TYPE = 19;
+ static final int EVENT_SET_PREFERRED_NETWORK_TYPE = 20;
+ static final int EVENT_RESET_PREFERRED_NETWORK_TYPE = 21;
+
+
+ //***** Time Zones
+
+ private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
+
+ // List of ISO codes for countries that can have an offset of GMT+0
+ // when not in daylight savings time. This ignores some small places
+ // such as the Canary Islands (Spain) and Danmarkshavn (Denmark).
+ // The list must be sorted by code.
+ private static final String[] GMT_COUNTRY_CODES = {
+ "bf", // Burkina Faso
+ "ci", // Cote d'Ivoire
+ "eh", // Western Sahara
+ "fo", // Faroe Islands, Denmark
+ "gh", // Ghana
+ "gm", // Gambia
+ "gn", // Guinea
+ "gw", // Guinea Bissau
+ "ie", // Ireland
+ "lr", // Liberia
+ "is", // Iceland
+ "ma", // Morocco
+ "ml", // Mali
+ "mr", // Mauritania
+ "pt", // Portugal
+ "sl", // Sierra Leone
+ "sn", // Senegal
+ "st", // Sao Tome and Principe
+ "tg", // Togo
+ "uk", // U.K
+ };
+
+ private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ Log.i("ServiceStateTracker", "Auto time state changed");
+ revertToNitz();
+ }
+ };
+
+
+ //***** Constructors
+
+ ServiceStateTracker(GSMPhone phone)
+ {
+ this.phone = phone;
+ cm = phone.mCM;
+ ss = new ServiceState();
+ newSS = new ServiceState();
+ cellLoc = new GsmCellLocation();
+ newCellLoc = new GsmCellLocation();
+
+ cm.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+ cm.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
+
+ cm.registerForNetworkStateChanged(this, EVENT_NETWORK_STATE_CHANGED, null);
+ cm.setOnNITZTime(this, EVENT_NITZ_TIME, null);
+ cm.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
+
+ cm.registerForSIMReady(this, EVENT_SIM_READY, null);
+
+ // system setting property AIRPLANE_MODE_ON is set in Settings.
+ int airplaneMode = Settings.System.getInt(
+ phone.getContext().getContentResolver(),
+ Settings.System.AIRPLANE_MODE_ON, 0);
+ mDesiredPowerState = ! (airplaneMode > 0);
+
+ ContentResolver cr = phone.getContext().getContentResolver();
+ cr.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.AUTO_TIME), true,
+ mAutoTimeObserver);
+ setRssiDefaultValues();
+ mNeedToRegForSimLoaded = true;
+ }
+
+ /**
+ * Registration point for transition into GPRS attached.
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ /*protected*/ void
+ registerForGprsAttached(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ gprsAttachedRegistrants.add(r);
+
+ if (gprsState == ServiceState.STATE_IN_SERVICE) {
+ r.notifyRegistrant();
+ }
+ }
+
+ void registerForNetworkAttach(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ networkAttachedRegistrants.add(r);
+
+ if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
+ r.notifyRegistrant();
+ }
+ }
+ /**
+ * Registration point for transition into GPRS detached.
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ /*protected*/ void
+ registerForGprsDetached(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ gprsDetachedRegistrants.add(r);
+
+ if (gprsState == ServiceState.STATE_OUT_OF_SERVICE) {
+ r.notifyRegistrant();
+ }
+ }
+
+ /**
+ * Registration point for combined roaming on
+ * combined roaming is true when roaming is true and ONS differs SPN
+ *
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ void registerForRoamingOn(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ roamingOnRegistrants.add(r);
+
+ if (ss.getRoaming()) {
+ r.notifyRegistrant();
+ }
+ }
+
+ /**
+ * Registration point for combined roaming off
+ * combined roaming is true when roaming is true and ONS differs SPN
+ *
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ void registerForRoamingOff(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ roamingOffRegistrants.add(r);
+
+ if (!ss.getRoaming()) {
+ r.notifyRegistrant();
+ }
+ }
+
+ /**
+ * Reregister network through toggle perferred network type
+ * This is a work aorund to deregister and register network since there is
+ * no ril api to set COPS=2 (deregister) only.
+ *
+ * @param onComplete is dispatched when this is complete. it will be
+ * an AsyncResult, and onComplete.obj.exception will be non-null
+ * on failure.
+ */
+ void reRegisterNetwork(Message onComplete) {
+ cm.getPreferredNetworkType(
+ obtainMessage(EVENT_GET_PREFERRED_NETWORK_TYPE, onComplete));
+ }
+
+ //***** Called from GSMPhone
+
+ public void
+ setRadioPower(boolean power)
+ {
+ mDesiredPowerState = power;
+
+ setPowerStateToDesired();
+ }
+
+ public void
+ getLacAndCid(Message onComplete) {
+ cm.getRegistrationState(obtainMessage(
+ EVENT_GET_LOC_DONE, onComplete));
+ }
+
+ /*package*/ void enableLocationUpdates() {
+ cm.setLocationUpdates(true, obtainMessage(EVENT_LOCATION_UPDATES_ENABLED));
+ }
+
+ /*package*/ void disableLocationUpdates() {
+ cm.setLocationUpdates(false, null);
+ }
+ //***** Overridden from Handler
+
+ public void
+ handleMessage (Message msg)
+ {
+ AsyncResult ar;
+ int[] ints;
+ String[] strings;
+ Message message;
+
+ switch (msg.what) {
+ case EVENT_RADIO_AVAILABLE:
+ //this is unnecessary
+ //setPowerStateToDesired();
+ break;
+
+ case EVENT_SIM_READY:
+ // The SIM is now ready i.e if it was locked
+ // it has been unlocked. At this stage, the radio is already
+ // powered on.
+ if (mNeedToRegForSimLoaded) {
+ phone.mSIMRecords.registerForRecordsLoaded(this,
+ EVENT_SIM_RECORDS_LOADED, null);
+ mNeedToRegForSimLoaded = false;
+ }
+ // restore the previous network selection.
+ phone.restoreSavedNetworkSelection(null);
+ pollState();
+ // Signal strength polling stops when radio is off
+ queueNextSignalStrengthPoll();
+ break;
+
+ case EVENT_RADIO_STATE_CHANGED:
+ // This will do nothing in the radio not
+ // available case
+ setPowerStateToDesired();
+ pollState();
+ break;
+
+ case EVENT_NETWORK_STATE_CHANGED:
+ pollState();
+ break;
+
+ case EVENT_GET_SIGNAL_STRENGTH:
+ // This callback is called when signal strength is polled
+ // all by itself
+
+ if (!(cm.getRadioState().isOn())) {
+ // Polling will continue when radio turns back on
+ return;
+ }
+ ar = (AsyncResult) msg.obj;
+ onSignalStrengthResult(ar);
+ queueNextSignalStrengthPoll();
+
+ break;
+
+ case EVENT_GET_LOC_DONE:
+ ar = (AsyncResult) msg.obj;
+
+ if (ar.exception == null) {
+ String states[] = (String[])ar.result;
+ int lac = -1;
+ int cid = -1;
+ if (states.length == 3) {
+ try {
+ if (states[1] != null && states[1].length() > 0) {
+ lac = Integer.parseInt(states[1], 16);
+ }
+ if (states[2] != null && states[2].length() > 0) {
+ cid = Integer.parseInt(states[2], 16);
+ }
+ } catch (NumberFormatException ex) {
+ Log.w(LOG_TAG, "error parsing location: " + ex);
+ }
+ }
+
+ // only update if lac or cid changed
+ if (cellLoc.getCid() != cid || cellLoc.getLac() != lac) {
+ cellLoc.setLacAndCid(lac, cid);
+ phone.notifyLocationChanged();
+ }
+ }
+
+ if (ar.userObj != null) {
+ AsyncResult.forMessage(((Message) ar.userObj)).exception
+ = ar.exception;
+ ((Message) ar.userObj).sendToTarget();
+ }
+ break;
+
+ case EVENT_POLL_STATE_REGISTRATION:
+ case EVENT_POLL_STATE_GPRS:
+ case EVENT_POLL_STATE_OPERATOR:
+ case EVENT_POLL_STATE_NETWORK_SELECTION_MODE:
+ ar = (AsyncResult) msg.obj;
+
+ handlePollStateResult(msg.what, ar);
+ break;
+
+ case EVENT_POLL_SIGNAL_STRENGTH:
+ // Just poll signal strength...not part of pollState()
+
+ cm.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+ break;
+
+ case EVENT_NITZ_TIME:
+ ar = (AsyncResult) msg.obj;
+
+ String nitzString = (String)((Object[])ar.result)[0];
+ int nitzReceiveTime = ((Integer)((Object[])ar.result)[1]).intValue();
+
+ setTimeFromNITZString(nitzString, nitzReceiveTime);
+ break;
+
+ case EVENT_SIGNAL_STRENGTH_UPDATE:
+ // This is a notification from
+ // CommandsInterface.setOnSignalStrengthUpdate
+
+ ar = (AsyncResult) msg.obj;
+
+ // The radio is telling us about signal strength changes
+ // we don't have to ask it
+ dontPollSignalStrength = true;
+
+ onSignalStrengthResult(ar);
+ break;
+
+ case EVENT_SIM_RECORDS_LOADED:
+ updateSpnDisplay();
+ break;
+
+ case EVENT_LOCATION_UPDATES_ENABLED:
+ ar = (AsyncResult) msg.obj;
+
+ if (ar.exception == null) {
+ getLacAndCid(null);
+ }
+ break;
+
+ case EVENT_SET_PREFERRED_NETWORK_TYPE:
+ ar = (AsyncResult) msg.obj;
+ // Don't care the result, only use for dereg network (COPS=2)
+ message = obtainMessage(EVENT_RESET_PREFERRED_NETWORK_TYPE, ar.userObj);
+ cm.setPreferredNetworkType(mPreferredNetworkType, message);
+ break;
+
+ case EVENT_RESET_PREFERRED_NETWORK_TYPE:
+ ar = (AsyncResult) msg.obj;
+ if (ar.userObj != null) {
+ AsyncResult.forMessage(((Message) ar.userObj)).exception
+ = ar.exception;
+ ((Message) ar.userObj).sendToTarget();
+ }
+ break;
+
+ case EVENT_GET_PREFERRED_NETWORK_TYPE:
+ ar = (AsyncResult) msg.obj;
+
+ if (ar.exception == null) {
+ mPreferredNetworkType = ((int[])ar.result)[0];
+ } else {
+ mPreferredNetworkType = Phone.NT_AUTO_TYPE;
+ }
+
+ message = obtainMessage(EVENT_SET_PREFERRED_NETWORK_TYPE, ar.userObj);
+ int toggledNetworkType =
+ (mPreferredNetworkType == Phone.NT_AUTO_TYPE) ?
+ Phone.NT_GSM_TYPE : Phone.NT_AUTO_TYPE;
+
+ cm.setPreferredNetworkType(toggledNetworkType, message);
+ break;
+
+ }
+ }
+
+ //***** Private Instance Methods
+
+ private void updateSpnDisplay() {
+ int rule = phone.mSIMRecords.getDisplayRule(ss.getOperatorNumeric());
+ String spn = phone.mSIMRecords.getServiceProvideName();
+ String plmn = ss.getOperatorAlphaLong();
+
+ if (rule != curSpnRule
+ || !TextUtils.equals(spn, curSpn)
+ || !TextUtils.equals(plmn, curPlmn)) {
+ boolean showSpn =
+ (rule & SIMRecords.SPN_RULE_SHOW_SPN) == SIMRecords.SPN_RULE_SHOW_SPN;
+ boolean showPlmn =
+ (rule & SIMRecords.SPN_RULE_SHOW_PLMN) == SIMRecords.SPN_RULE_SHOW_PLMN;
+ Intent intent = new Intent(Intents.SPN_STRINGS_UPDATED_ACTION);
+ intent.putExtra(Intents.EXTRA_SHOW_SPN, showSpn);
+ intent.putExtra(Intents.EXTRA_SPN, spn);
+ intent.putExtra(Intents.EXTRA_SHOW_PLMN, showPlmn);
+ intent.putExtra(Intents.EXTRA_PLMN, plmn);
+ phone.getContext().sendStickyBroadcast(intent);
+ }
+ curSpnRule = rule;
+ curSpn = spn;
+ curPlmn = plmn;
+ }
+
+ private void
+ setPowerStateToDesired()
+ {
+ // If we want it on and it's off, turn it on
+ if (mDesiredPowerState
+ && cm.getRadioState() == CommandsInterface.RadioState.RADIO_OFF
+ ) {
+ cm.setRadioPower(true, null);
+ } else if (!mDesiredPowerState && cm.getRadioState().isOn()) {
+ // If it's on and available and we want it off..
+ cm.setRadioPower(false, null);
+ } // Otherwise, we're in the desired state
+ }
+
+ /** Cancel a pending (if any) pollState() operation */
+ private void
+ cancelPollState()
+ {
+ // This will effectively cancel the rest of the poll requests
+ pollingContext = new int[1];
+ }
+
+ /**
+ * Handle the result of one of the pollState()-related requests
+ */
+
+ private void
+ handlePollStateResult (int what, AsyncResult ar)
+ {
+ int ints[];
+ String states[];
+
+ // Ignore stale requests from last poll
+ if (ar.userObj != pollingContext) return;
+
+ if (ar.exception != null) {
+ CommandException.Error err=null;
+
+ if (ar.exception instanceof CommandException) {
+ err = ((CommandException)(ar.exception)).getCommandError();
+ }
+
+ if (err == CommandException.Error.RADIO_NOT_AVAILABLE) {
+ // Radio has crashed or turned off
+ cancelPollState();
+ return;
+ }
+
+ if (!cm.getRadioState().isOn()) {
+ // Radio has crashed or turned off
+ cancelPollState();
+ return;
+ }
+
+ if (err != CommandException.Error.OP_NOT_ALLOWED_BEFORE_REG_NW &&
+ err != CommandException.Error.OP_NOT_ALLOWED_BEFORE_REG_NW) {
+ Log.e(LOG_TAG,
+ "RIL implementation has returned an error where it must succeed",
+ ar.exception);
+ }
+ } else try {
+ switch (what) {
+ case EVENT_POLL_STATE_REGISTRATION:
+ states = (String[])ar.result;
+ int lac = -1;
+ int cid = -1;
+ int regState = -1;
+ if (states.length > 0) {
+ try {
+ regState = Integer.parseInt(states[0]);
+ if (states.length == 3) {
+ if (states[1] != null && states[1].length() > 0) {
+ lac = Integer.parseInt(states[1], 16);
+ }
+ if (states[2] != null && states[2].length() > 0) {
+ cid = Integer.parseInt(states[2], 16);
+ }
+ }
+ } catch (NumberFormatException ex) {
+ Log.w(LOG_TAG, "error parsing RegistrationState: " + ex);
+ }
+ }
+
+ mGsmRoaming = regCodeIsRoaming(regState);
+ newSS.setState (regCodeToServiceState(regState));
+
+ // LAC and CID are -1 if not avail
+ newCellLoc.setLacAndCid(lac, cid);
+ break;
+
+ case EVENT_POLL_STATE_GPRS:
+ states = (String[])ar.result;
+
+ int type = 0;
+ regState = -1;
+ if (states.length > 0) {
+ try {
+ regState = Integer.parseInt(states[0]);
+
+ // states[3] (if present) is the current radio technology
+ if (states.length >= 4 && states[3] != null) {
+ type = Integer.parseInt(states[3]);
+ }
+ } catch (NumberFormatException ex) {
+ Log.w(LOG_TAG, "error parsing GprsRegistrationState: " + ex);
+ }
+ }
+ newGPRSState = regCodeToServiceState(regState);
+ newNetworkType = type;
+ break;
+
+ case EVENT_POLL_STATE_OPERATOR:
+ String opNames[] = (String[])ar.result;
+
+ if (opNames != null && opNames.length >= 3) {
+ newSS.setOperatorName (
+ opNames[0], opNames[1], opNames[2]);
+ }
+ break;
+
+ case EVENT_POLL_STATE_NETWORK_SELECTION_MODE:
+ ints = (int[])ar.result;
+ newSS.setIsManualSelection(ints[0] == 1);
+ break;
+ }
+
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "Exception while polling service state. "
+ + "Probably malformed RIL response.", ex);
+ }
+
+ pollingContext[0]--;
+
+ if (pollingContext[0] == 0) {
+ newSS.setRoaming(isRoamingBetweenOperators(mGsmRoaming, newSS));
+ pollStateDone();
+ }
+
+ }
+
+ private void
+ setRssiDefaultValues()
+ {
+ rssi = 99;
+ }
+
+ /**
+ * A complete "service state" from our perspective is
+ * composed of a handful of separate requests to the radio.
+ *
+ * We make all of these requests at once, but then abandon them
+ * and start over again if the radio notifies us that some
+ * event has changed
+ */
+
+ private void
+ pollState()
+ {
+ pollingContext = new int[1];
+ pollingContext[0] = 0;
+
+ switch (cm.getRadioState()) {
+ case RADIO_UNAVAILABLE:
+ newSS.setStateOutOfService();
+ newCellLoc.setStateInvalid();
+ setRssiDefaultValues();
+ mGotCountryCode = false;
+
+ pollStateDone();
+ break;
+
+
+ case RADIO_OFF:
+ newSS.setStateOff();
+ newCellLoc.setStateInvalid();
+ setRssiDefaultValues();
+ mGotCountryCode = false;
+
+ pollStateDone();
+ break;
+
+ default:
+ // Issue all poll-related commands at once
+ // then count down the responses, which
+ // are allowed to arrive out-of-order
+
+ pollingContext[0]++;
+ cm.getOperator(
+ obtainMessage(
+ EVENT_POLL_STATE_OPERATOR, pollingContext));
+
+ pollingContext[0]++;
+ cm.getGPRSRegistrationState(
+ obtainMessage(
+ EVENT_POLL_STATE_GPRS, pollingContext));
+
+ pollingContext[0]++;
+ cm.getRegistrationState(
+ obtainMessage(
+ EVENT_POLL_STATE_REGISTRATION, pollingContext));
+
+ pollingContext[0]++;
+ cm.getNetworkSelectionMode(
+ obtainMessage(
+ EVENT_POLL_STATE_NETWORK_SELECTION_MODE, pollingContext));
+ break;
+ }
+ }
+
+ private static String networkTypeToString(int type) {
+ String ret = "unknown";
+
+ switch (type) {
+ case DATA_ACCESS_GPRS:
+ ret = "GPRS";
+ break;
+ case DATA_ACCESS_EDGE:
+ ret = "EDGE";
+ break;
+ case DATA_ACCESS_UMTS:
+ ret = "UMTS";
+ break;
+ }
+
+ return ret;
+ }
+
+ private void
+ pollStateDone()
+ {
+ if (DBG) {
+ Log.d(LOG_TAG, "Poll ServiceState done: " +
+ " oldSS=[" + ss + "] newSS=[" + newSS +
+ "] oldGprs=" + gprsState + " newGprs=" + newGPRSState +
+ " oldType=" + networkTypeToString(networkType) +
+ " newType=" + networkTypeToString(newNetworkType));
+ }
+
+ boolean hasRegistered =
+ ss.getState() != ServiceState.STATE_IN_SERVICE
+ && newSS.getState() == ServiceState.STATE_IN_SERVICE;
+
+ boolean hasDeregistered =
+ ss.getState() == ServiceState.STATE_IN_SERVICE
+ && newSS.getState() != ServiceState.STATE_IN_SERVICE;
+
+ boolean hasGprsAttached =
+ gprsState != ServiceState.STATE_IN_SERVICE
+ && newGPRSState == ServiceState.STATE_IN_SERVICE;
+
+ boolean hasGprsDetached =
+ gprsState == ServiceState.STATE_IN_SERVICE
+ && newGPRSState != ServiceState.STATE_IN_SERVICE;
+
+ boolean hasNetworkTypeChanged = networkType != newNetworkType;
+
+ boolean hasChanged = !newSS.equals(ss);
+
+ boolean hasRoamingOn = !ss.getRoaming() && newSS.getRoaming();
+
+ boolean hasRoamingOff = ss.getRoaming() && !newSS.getRoaming();
+
+ boolean hasLocationChanged = !newCellLoc.equals(cellLoc);
+
+ ServiceState tss;
+ tss = ss;
+ ss = newSS;
+ newSS = tss;
+ // clean slate for next time
+ newSS.setStateOutOfService();
+
+ GsmCellLocation tcl = cellLoc;
+ cellLoc = newCellLoc;
+ newCellLoc = tcl;
+
+ gprsState = newGPRSState;
+ networkType = newNetworkType;
+
+ newSS.setStateOutOfService(); // clean slate for next time
+
+ if (hasNetworkTypeChanged) {
+ phone.setSystemProperty(PROPERTY_DATA_NETWORK_TYPE,
+ networkTypeToString(networkType));
+ }
+
+ if (hasRegistered) {
+ Checkin.updateStats(phone.getContext().getContentResolver(),
+ Checkin.Stats.Tag.PHONE_GSM_REGISTERED, 1, 0.0);
+ networkAttachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasChanged) {
+ String operatorNumeric;
+
+ phone.setSystemProperty(PROPERTY_OPERATOR_ALPHA,
+ ss.getOperatorAlphaLong());
+
+ operatorNumeric = ss.getOperatorNumeric();
+ phone.setSystemProperty(PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
+
+ if (operatorNumeric == null) {
+ phone.setSystemProperty(PROPERTY_OPERATOR_ISO_COUNTRY, "");
+ } else {
+ String iso = "";
+ try{
+ iso = MccTable.countryCodeForMcc(Integer.parseInt(
+ operatorNumeric.substring(0,3)));
+ } catch ( NumberFormatException ex){
+ Log.w(LOG_TAG, "countryCodeForMcc error" + ex);
+ } catch ( StringIndexOutOfBoundsException ex) {
+ Log.w(LOG_TAG, "countryCodeForMcc error" + ex);
+ }
+
+ phone.setSystemProperty(PROPERTY_OPERATOR_ISO_COUNTRY, iso);
+ mGotCountryCode = true;
+
+ if (mNeedFixZone) {
+ TimeZone zone = null;
+ // If the offset is (0, false) and the timezone property
+ // is set, use the timezone property rather than
+ // GMT.
+ String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
+ if ((mZoneOffset == 0) && (mZoneDst == false) &&
+ (zoneName != null) && (zoneName.length() > 0) &&
+ (Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)) {
+ zone = TimeZone.getDefault();
+ // For NITZ string without timezone,
+ // need adjust time to reflect default timezone setting
+ long tzOffset;
+ tzOffset = zone.getOffset(System.currentTimeMillis());
+ SystemClock.setCurrentTimeMillis(
+ System.currentTimeMillis() - tzOffset);
+ } else if (iso.equals("")){
+ // Country code not found. This is likely a test network.
+ // Get a TimeZone based only on the NITZ parameters (best guess).
+ zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
+ } else {
+ zone = TimeUtils.getTimeZone(mZoneOffset,
+ mZoneDst, mZoneTime, iso);
+ }
+
+ mNeedFixZone = false;
+
+ if (zone != null) {
+ Context context = phone.getContext();
+ if (getAutoTime()) {
+ AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ alarm.setTimeZone(zone.getID());
+ }
+ saveNitzTimeZone(zone.getID());
+ }
+ }
+ }
+
+ phone.setSystemProperty(PROPERTY_OPERATOR_ISROAMING,
+ ss.getRoaming() ? "true" : "false");
+ phone.setSystemProperty(PROPERTY_OPERATOR_ISMANUAL,
+ ss.getIsManualSelection() ? "true" : "false");
+
+ updateSpnDisplay();
+ phone.notifyServiceStateChanged(ss);
+ }
+
+ if (hasGprsAttached) {
+ gprsAttachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasGprsDetached) {
+ gprsDetachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasNetworkTypeChanged) {
+ phone.notifyDataConnection(null);
+ }
+
+ if (hasRoamingOn) {
+ roamingOnRegistrants.notifyRegistrants();
+ }
+
+ if (hasRoamingOff) {
+ roamingOffRegistrants.notifyRegistrants();
+ }
+
+ if (hasLocationChanged) {
+ phone.notifyLocationChanged();
+ }
+ }
+
+ /**
+ * Returns a TimeZone object based only on parameters from the NITZ string.
+ */
+ private TimeZone getNitzTimeZone(int offset, boolean dst, long when) {
+ TimeZone guess = findTimeZone(offset, dst, when);
+ if (guess == null) {
+ // Couldn't find a proper timezone. Perhaps the DST data is wrong.
+ guess = findTimeZone(offset, !dst, when);
+ }
+ if (DBG) {
+ Log.d(LOG_TAG, "getNitzTimeZone returning "
+ + (guess == null ? guess : guess.getID()));
+ }
+ return guess;
+ }
+
+ private TimeZone findTimeZone(int offset, boolean dst, long when) {
+ int rawOffset = offset;
+ if (dst) {
+ rawOffset -= 3600000;
+ }
+ String[] zones = TimeZone.getAvailableIDs(rawOffset);
+ TimeZone guess = null;
+ Date d = new Date(when);
+ for (String zone : zones) {
+ TimeZone tz = TimeZone.getTimeZone(zone);
+ if (tz.getOffset(when) == offset &&
+ tz.inDaylightTime(d) == dst) {
+ guess = tz;
+ break;
+ }
+ }
+
+ return guess;
+ }
+
+ private void
+ queueNextSignalStrengthPoll()
+ {
+ if (dontPollSignalStrength) {
+ // The radio is telling us about signal strength changes
+ // we don't have to ask it
+ return;
+ }
+
+ Message msg;
+
+ msg = obtainMessage();
+ msg.what = EVENT_POLL_SIGNAL_STRENGTH;
+
+ long nextTime;
+
+ // TODO Done't poll signal strength if screen is off
+ sendMessageDelayed(msg, POLL_PERIOD_MILLIS);
+ }
+
+ /**
+ * send signal-strength-changed notification if rssi changed
+ * Called both for solicited and unsolicited signal stength updates
+ */
+ private void
+ onSignalStrengthResult(AsyncResult ar)
+ {
+ int oldRSSI = rssi;
+
+ if (ar.exception != null) {
+ // 99 = unknown
+ // most likely radio is resetting/disconnected
+ rssi = 99;
+ } else {
+ int[] ints = (int[])ar.result;
+
+ // bug 658816 seems to be a case where the result is 0-length
+ if (ints.length != 0) {
+ rssi = ints[0];
+ } else {
+ Log.e(LOG_TAG, "Bogus signal strength response");
+ rssi = 99;
+ }
+ }
+
+ if (rssi != oldRSSI) {
+ phone.notifySignalStrength();
+ }
+ }
+
+ /** code is registration state 0-5 from TS 27.007 7.2 */
+ private int
+ regCodeToServiceState(int code)
+ {
+ switch (code) {
+ case 0:
+ case 2: // 2 is "searching"
+ case 3: // 3 is "registration denied"
+ case 4: // 4 is "unknown" no vaild in current baseband
+ return ServiceState.STATE_OUT_OF_SERVICE;
+
+ case 1:
+ return ServiceState.STATE_IN_SERVICE;
+
+ case 5:
+ // in service, roam
+ return ServiceState.STATE_IN_SERVICE;
+
+ default:
+ Log.w(LOG_TAG, "unexpected service state " + code);
+ return ServiceState.STATE_OUT_OF_SERVICE;
+ }
+ }
+
+
+ /**
+ * code is registration state 0-5 from TS 27.007 7.2
+ * returns true if registered roam, false otherwise
+ */
+ private boolean
+ regCodeIsRoaming (int code)
+ {
+ // 5 is "in service -- roam"
+ return 5 == code;
+ }
+
+ /**
+ * Set roaming state when gsmRoaming is true and, if operator mcc is the
+ * same as sim mcc, ons is different from spn
+ * @param gsmRoaming TS 27.007 7.2 CREG registered roaming
+ * @param s ServiceState hold current ons
+ * @return true for roaming state set
+ */
+ private
+ boolean isRoamingBetweenOperators(boolean gsmRoaming, ServiceState s) {
+ String spn = SystemProperties.get(PROPERTY_SIM_OPERATOR_ALPHA, "empty");
+
+ String onsl = s.getOperatorAlphaLong();
+ String onss = s.getOperatorAlphaShort();
+
+ boolean equalsOnsl = onsl != null && spn.equals(onsl);
+ boolean equalsOnss = onss != null && spn.equals(onss);
+
+ String simNumeric = SystemProperties.get(PROPERTY_SIM_OPERATOR_NUMERIC, "");
+ String operatorNumeric = s.getOperatorNumeric();
+
+ boolean equalsMcc = true;
+ try {
+ equalsMcc = simNumeric.substring(0, 3).
+ equals(operatorNumeric.substring(0, 3));
+ } catch (Exception e){
+ }
+
+ return gsmRoaming && !(equalsMcc && (equalsOnsl || equalsOnss));
+ }
+
+ private static
+ int twoDigitsAt(String s, int offset)
+ {
+ int a, b;
+
+ a = Character.digit(s.charAt(offset), 10);
+ b = Character.digit(s.charAt(offset+1), 10);
+
+ if (a < 0 || b < 0) {
+
+ throw new RuntimeException("invalid format");
+ }
+
+ return a*10 + b;
+ }
+
+ /**
+ * @return The current GPRS state. IN_SERVICE is the same as "attached"
+ * and OUT_OF_SERVICE is the same as detached.
+ */
+ /*package*/ int getCurrentGprsState() {
+ return gprsState;
+ }
+
+ /**
+ * @return true if phone is camping on a technology (eg UMTS)
+ * that could support voice and data simultaniously.
+ */
+ boolean isConcurrentVoiceAndData() {
+ return (networkType == DATA_ACCESS_UMTS);
+ }
+
+ /**
+ * Provides the name of the algorithmic time zone for the specified
+ * offset. Taken from TimeZone.java.
+ */
+ private static String displayNameFor(int off) {
+ off = off / 1000 / 60;
+
+ char[] buf = new char[9];
+ buf[0] = 'G';
+ buf[1] = 'M';
+ buf[2] = 'T';
+
+ if (off < 0) {
+ buf[3] = '-';
+ off = -off;
+ } else {
+ buf[3] = '+';
+ }
+
+ int hours = off / 60;
+ int minutes = off % 60;
+
+ buf[4] = (char) ('0' + hours / 10);
+ buf[5] = (char) ('0' + hours % 10);
+
+ buf[6] = ':';
+
+ buf[7] = (char) ('0' + minutes / 10);
+ buf[8] = (char) ('0' + minutes % 10);
+
+ return new String(buf);
+ }
+
+ /**
+ * nitzReceiveTime is time_t that the NITZ time was posted
+ */
+
+ private
+ void setTimeFromNITZString (String nitz, int nitzReceiveTime)
+ {
+ // "yy/mm/dd,hh:mm:ss(+/-)tz"
+ // tz is in number of quarter-hours
+
+ Log.i(LOG_TAG, "setTimeFromNITZString: " +
+ nitz + "," + nitzReceiveTime);
+
+ try {
+ /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
+ * offset as well (which we won't worry about until later) */
+ Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+
+ c.clear();
+ c.set(Calendar.DST_OFFSET, 0);
+
+ String[] nitzSubs = nitz.split("[/:,+-]");
+
+ int year = 2000 + Integer.parseInt(nitzSubs[0]);
+ c.set(Calendar.YEAR, year);
+
+ // month is 0 based!
+ int month = Integer.parseInt(nitzSubs[1]) - 1;
+ c.set(Calendar.MONTH, month);
+
+ int date = Integer.parseInt(nitzSubs[2]);
+ c.set(Calendar.DATE, date);
+
+ int hour = Integer.parseInt(nitzSubs[3]);
+ c.set(Calendar.HOUR, hour);
+
+ int minute = Integer.parseInt(nitzSubs[4]);
+ c.set(Calendar.MINUTE, minute);
+
+ int second = Integer.parseInt(nitzSubs[5]);
+ c.set(Calendar.SECOND, second);
+
+ boolean sign = (nitz.indexOf('-') == -1);
+
+ int tzOffset = Integer.parseInt(nitzSubs[6]);
+
+ int dst = (nitzSubs.length >= 8 ) ? Integer.parseInt(nitzSubs[7])
+ : 0;
+
+ // The zone offset received from NITZ is for current local time,
+ // so DST correction is already applied. Don't add it again.
+ //
+ // tzOffset += dst * 4;
+ //
+ // We could unapply it if we wanted the raw offset.
+
+ tzOffset = (sign ? 1 : -1) * tzOffset * 15 * 60 * 1000;
+
+ TimeZone zone = null;
+
+ // As a special extension, the Android emulator appends the name of
+ // the host computer's timezone to the nitz string. this is zoneinfo
+ // timezone name of the form Area!Location or Area!Location!SubLocation
+ // so we need to convert the ! into /
+ if (nitzSubs.length >= 9) {
+ String tzname = nitzSubs[8].replace('!','/');
+ zone = TimeZone.getTimeZone( tzname );
+ }
+
+ String iso = SystemProperties.get(PROPERTY_OPERATOR_ISO_COUNTRY);
+
+ if (zone == null) {
+
+ if (mGotCountryCode) {
+ if (iso != null && iso.length() > 0) {
+ zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
+ c.getTimeInMillis(),
+ iso);
+ } else {
+ // We don't have a valid iso country code. This is
+ // most likely because we're on a test network that's
+ // using a bogus MCC (eg, "001"), so get a TimeZone
+ // based only on the NITZ parameters.
+ zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis());
+ }
+ }
+ }
+
+ if (zone == null) {
+ // We got the time before the country, so we don't know
+ // how to identify the DST rules yet. Save the information
+ // and hope to fix it up later.
+
+ mNeedFixZone = true;
+ mZoneOffset = tzOffset;
+ mZoneDst = dst != 0;
+ mZoneTime = c.getTimeInMillis();
+ }
+
+ if (zone != null) {
+ Context context = phone.getContext();
+ if (getAutoTime()) {
+ AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ alarm.setTimeZone(zone.getID());
+ }
+ saveNitzTimeZone(zone.getID());
+ }
+
+ long millisSinceNitzReceived
+ = System.currentTimeMillis() - (nitzReceiveTime * 1000L);
+
+ if (millisSinceNitzReceived < 0) {
+ // Sanity check: something is wrong
+ Log.i(LOG_TAG, "NITZ: not setting time, clock has rolled "
+ + "backwards since NITZ time received, "
+ + nitz);
+ return;
+ }
+
+ if (millisSinceNitzReceived > (1000L * 1000L)) {
+ // If the time is this far off, something is wrong
+ Log.i(LOG_TAG, "NITZ: not setting time, more than 1000 seconds "
+ + " have elapsed since time received, "
+ + nitz);
+
+ return;
+ }
+
+ // Note: with range checks above, cast to int is safe
+ c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
+
+ String ignore = SystemProperties.get("gsm.ignore-nitz");
+ if (ignore != null && ignore.equals("yes")) {
+ Log.i(LOG_TAG,
+ "Not setting clock because gsm.ignore-nitz is set");
+ return;
+ }
+
+ if (getAutoTime()) {
+ Log.i(LOG_TAG, "Setting time of day to " + c.getTime()
+ + " NITZ receive delay(ms): " + millisSinceNitzReceived
+ + " gained(ms): "
+ + (c.getTimeInMillis() - System.currentTimeMillis())
+ + " from " + nitz);
+
+ SystemClock.setCurrentTimeMillis(c.getTimeInMillis());
+ }
+ SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
+ saveNitzTime(c.getTimeInMillis());
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "Parsing NITZ time " + nitz, ex);
+ }
+ }
+
+ private boolean getAutoTime() {
+ try {
+ return Settings.System.getInt(phone.getContext().getContentResolver(),
+ Settings.System.AUTO_TIME) > 0;
+ } catch (SettingNotFoundException snfe) {
+ return true;
+ }
+ }
+
+ private void saveNitzTimeZone(String zoneId) {
+ mSavedTimeZone = zoneId;
+ // Send out a sticky broadcast so the system can determine if
+ // the timezone was set by the carrier...
+ Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
+ intent.putExtra("time-zone", zoneId);
+ phone.getContext().sendStickyBroadcast(intent);
+ }
+
+ private void saveNitzTime(long time) {
+ mSavedTime = time;
+ mSavedAtTime = SystemClock.elapsedRealtime();
+ // Send out a sticky broadcast so the system can determine if
+ // the time was set by the carrier...
+ Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+ intent.putExtra("time", time);
+ phone.getContext().sendStickyBroadcast(intent);
+ }
+
+ private void revertToNitz() {
+ if (Settings.System.getInt(phone.getContext().getContentResolver(),
+ Settings.System.AUTO_TIME, 0) == 0) {
+ return;
+ }
+ Log.d(LOG_TAG, "Reverting to NITZ: tz='" + mSavedTimeZone
+ + "' mSavedTime=" + mSavedTime
+ + " mSavedAtTime=" + mSavedAtTime);
+ if (mSavedTimeZone != null && mSavedTime != 0 && mSavedAtTime != 0) {
+ AlarmManager alarm =
+ (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
+ alarm.setTimeZone(mSavedTimeZone);
+ SystemClock.setCurrentTimeMillis(mSavedTime
+ + (SystemClock.elapsedRealtime() - mSavedAtTime));
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimConstants.java b/telephony/java/com/android/internal/telephony/gsm/SimConstants.java
new file mode 100644
index 0000000..a7e3bbc
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimConstants.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.telephony.gsm;
+
+/**
+ * {@hide}
+ */
+public interface SimConstants {
+ // SIM file ids from TS 51.011
+ public static final int EF_ADN = 0x6F3A;
+ public static final int EF_FDN = 0x6F3B;
+ public static final int EF_SDN = 0x6F49;
+ public static final int EF_EXT1 = 0x6F4A;
+ public static final int EF_EXT2 = 0x6F4B;
+ public static final int EF_EXT3 = 0x6F4C;
+ public static final int EF_EXT6 = 0x6fc8; // Ext record for EF[MBDN]
+ public static final int EF_MWIS = 0x6FCA;
+ public static final int EF_MBDN = 0x6fc7;
+ public static final int EF_PNN = 0x6fc5;
+ public static final int EF_SPN = 0x6F46;
+ public static final int EF_SMS = 0x6F3C;
+ public static final int EF_ICCID = 0x2fe2;
+ public static final int EF_AD = 0x6FAD;
+ public static final int EF_MBI = 0x6fc9;
+ public static final int EF_MSISDN = 0x6f40;
+ public static final int EF_SPDI = 0x6fcd;
+ public static final int EF_SST = 0x6f38;
+ public static final int EF_CFIS = 0x6FCB;
+ public static final int EF_IMG = 0x4f20;
+
+ // SIM file ids from CPHS (phase 2, version 4.2) CPHS4_2.WW6
+ public static final int EF_MAILBOX_CPHS = 0x6F17;
+ public static final int EF_VOICE_MAIL_INDICATOR_CPHS = 0x6F11;
+ public static final int EF_CFF_CPHS = 0x6F13;
+ public static final int EF_SPN_CPHS = 0x6f14;
+ public static final int EF_SPN_SHORT_CPHS = 0x6f18;
+ public static final int EF_INFO_CPHS = 0x6f16;
+
+ // SMS record length from TS 51.011 10.5.3
+ static public final int SMS_RECORD_LENGTH = 176;
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimException.java b/telephony/java/com/android/internal/telephony/gsm/SimException.java
new file mode 100644
index 0000000..4a9d266
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.internal.telephony.gsm;
+
+/**
+ * {@hide}
+ */
+public class SimException extends Exception
+{
+ SimException()
+ {
+
+ }
+
+ SimException(String s)
+ {
+ super(s);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimFileNotFound.java b/telephony/java/com/android/internal/telephony/gsm/SimFileNotFound.java
new file mode 100644
index 0000000..982e2fd
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimFileNotFound.java
@@ -0,0 +1,38 @@
+/*
+ * 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.internal.telephony.gsm;
+
+/**
+ * {@hide}
+ */
+public class SimFileNotFound extends SimException
+{
+ SimFileNotFound()
+ {
+
+ }
+
+ SimFileNotFound(String s)
+ {
+ super(s);
+ }
+
+ SimFileNotFound(int ef)
+ {
+ super("SIM EF Not Found 0x" + Integer.toHexString(ef));
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimFileTypeMismatch.java b/telephony/java/com/android/internal/telephony/gsm/SimFileTypeMismatch.java
new file mode 100644
index 0000000..72790d0
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimFileTypeMismatch.java
@@ -0,0 +1,33 @@
+/*
+ * 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.internal.telephony.gsm;
+
+/**
+ * {@hide}
+ */
+public class SimFileTypeMismatch extends SimException
+{
+ SimFileTypeMismatch()
+ {
+
+ }
+
+ SimFileTypeMismatch(String s)
+ {
+ super(s);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimIoResult.java b/telephony/java/com/android/internal/telephony/gsm/SimIoResult.java
new file mode 100644
index 0000000..2c4da83
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimIoResult.java
@@ -0,0 +1,75 @@
+/*
+ * 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.internal.telephony.gsm;
+
+/**
+ * {@hide}
+ */
+public class
+SimIoResult
+{
+ int sw1;
+ int sw2;
+ byte[] payload;
+
+ public SimIoResult(int sw1, int sw2, byte[] payload)
+ {
+ this.sw1 = sw1;
+ this.sw2 = sw2;
+ this.payload = payload;
+ }
+
+ public SimIoResult(int sw1, int sw2, String hexString)
+ {
+ this(sw1, sw2, SimUtils.hexStringToBytes(hexString));
+ }
+
+ public String toString()
+ {
+ return "SimIoResponse sw1:0x" + Integer.toHexString(sw1) + " sw2:0x"
+ + Integer.toHexString(sw2);
+ }
+
+ /**
+ * true if this operation was successful
+ * See GSM 11.11 Section 9.4
+ * (the fun stuff is absent in 51.011)
+ */
+ public boolean success()
+ {
+ return sw1 == 0x90 || sw1 == 0x91 || sw1 == 0x9e || sw1 == 0x9f;
+ }
+
+ /**
+ * Returns exception on error or null if success
+ */
+ public SimException getException()
+ {
+ if (success()) return null;
+
+ switch (sw1) {
+ case 0x94:
+ if (sw2 == 0x08) {
+ return new SimFileTypeMismatch();
+ } else {
+ return new SimFileNotFound();
+ }
+ default:
+ return new SimException("sw1:" + sw1 + " sw2:" + sw2);
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
new file mode 100644
index 0000000..7cc9a80
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
@@ -0,0 +1,278 @@
+/*
+** Copyright 2007, 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.internal.telephony.gsm;
+
+import android.content.pm.PackageManager;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SimPhoneBookInterfaceManager to provide an inter-process communication to
+ * access ADN-like SIM records.
+ */
+public class SimPhoneBookInterfaceManager extends ISimPhoneBook.Stub {
+ static final String LOG_TAG = "GSM";
+ static final boolean DBG = false;
+
+ private GSMPhone phone;
+ private AdnRecordCache adnCache;
+ private final Object mLock = new Object();
+ private int recordSize[];
+ private boolean success;
+ private List<AdnRecord> records;
+
+ private static final boolean ALLOW_SIM_OP_IN_UI_THREAD = false;
+
+ private static final int EVENT_GET_SIZE_DONE = 1;
+ private static final int EVENT_LOAD_DONE = 2;
+ private static final int EVENT_UPDATE_DONE = 3;
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case EVENT_GET_SIZE_DONE:
+ ar = (AsyncResult) msg.obj;
+ synchronized (mLock) {
+ if (ar.exception == null) {
+ recordSize = (int[])ar.result;
+ // recordSize[0] is the record length
+ // recordSize[1] is the total length of the EF file
+ // recordSize[2] is the number of records in the EF file
+ log("GET_RECORD_SIZE Size " + recordSize[0] +
+ " total " + recordSize[1] +
+ " #record " + recordSize[2]);
+ mLock.notifyAll();
+ }
+ }
+ break;
+ case EVENT_UPDATE_DONE:
+ ar = (AsyncResult) msg.obj;
+ synchronized (mLock) {
+ success = (ar.exception == null);
+ mLock.notifyAll();
+ }
+ break;
+ case EVENT_LOAD_DONE:
+ ar = (AsyncResult)msg.obj;
+ synchronized (mLock) {
+ if (ar.exception == null) {
+ records = (List<AdnRecord>)
+ ((ArrayList<AdnRecord>) ar.result);
+ } else {
+ if(DBG) log("Cannot load ADN records");
+ if (records != null) {
+ records.clear();
+ }
+ }
+ mLock.notifyAll();
+ }
+ break;
+ }
+ }
+ };
+
+ public SimPhoneBookInterfaceManager(GSMPhone phone) {
+ this.phone = phone;
+ adnCache = phone.mSIMRecords.getAdnCache();
+ publish();
+ }
+
+ private void publish() {
+ ServiceManager.addService("simphonebook", this);
+ }
+
+ /**
+ * Replace oldAdn with newAdn in ADN-like record in EF
+ *
+ * getAdnRecordsInEf must be called at least once before this function,
+ * otherwise an error will be returned
+ * throws SecurityException if no WRITE_CONTACTS permission
+ *
+ * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
+ * @param oldTag adn tag to be replaced
+ * @param oldPhoneNumber adn number to be replaced
+ * Set both oldTag and oldPhoneNubmer to "" means to replace an
+ * empty record, aka, insert new record
+ * @param newTag adn tag to be stored
+ * @param newPhoneNumber adn number ot be stored
+ * Set both newTag and newPhoneNubmer to "" means to replace the old
+ * record with empty one, aka, delete old record
+ * @param pin2 required to update EF_FDN, otherwise must be null
+ * @return true for success
+ */
+ public boolean
+ updateAdnRecordsInEfBySearch (int efid,
+ String oldTag, String oldPhoneNumber,
+ String newTag, String newPhoneNumber, String pin2) {
+
+
+ if (phone.getContext().checkCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Requires android.permission.WRITE_CONTACTS permission");
+ }
+
+
+ if (DBG) log("updateAdnRecordsInEfBySearch: efid=" + efid +
+ " ("+ oldTag + "," + oldPhoneNumber + ")"+ "==>" +
+ " ("+ newTag + "," + newPhoneNumber + ")"+ " pin2=" + pin2);
+ synchronized(mLock) {
+ checkThread();
+ success = false;
+ Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
+ AdnRecord oldAdn = new AdnRecord(oldTag, oldPhoneNumber);
+ AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
+ adnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to update by search");
+ }
+ }
+ return success;
+ }
+
+ /**
+ * Update an ADN-like EF record by record index
+ *
+ * This is useful for iteration the whole ADN file, such as write the whole
+ * phone book or erase/format the whole phonebook
+ * throws SecurityException if no WRITE_CONTACTS permission
+ *
+ * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
+ * @param newTag adn tag to be stored
+ * @param newPhoneNumber adn number to be stored
+ * Set both newTag and newPhoneNubmer to "" means to replace the old
+ * record with empty one, aka, delete old record
+ * @param index is 1-based adn record index to be updated
+ * @param pin2 required to update EF_FDN, otherwise must be null
+ * @return true for success
+ */
+ public boolean
+ updateAdnRecordsInEfByIndex(int efid, String newTag,
+ String newPhoneNumber, int index, String pin2) {
+
+ if (phone.getContext().checkCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Requires android.permission.WRITE_CONTACTS permission");
+ }
+
+ if (DBG) log("updateAdnRecordsInEfByIndex: efid=" + efid +
+ " Index=" + index + " ==> " +
+ "("+ newTag + "," + newPhoneNumber + ")"+ " pin2=" + pin2);
+ synchronized(mLock) {
+ checkThread();
+ success = false;
+ Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
+ AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
+ adnCache.updateAdnByIndex(efid, newAdn, index, pin2, response);
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to update by index");
+ }
+ }
+ return success;
+ }
+
+ /**
+ * Get the capacity of records in efid
+ *
+ * @param efid the EF id of a ADN-like SIM
+ * @return int[3] array
+ * recordSizes[0] is the single record length
+ * recordSizes[1] is the total length of the EF file
+ * recordSizes[2] is the number of records in the EF file
+ */
+ public int[] getAdnRecordsSize(int efid) {
+ if (DBG) log("getAdnRecordsSize: efid=" + efid);
+ synchronized(mLock) {
+ checkThread();
+ recordSize = new int[3];
+ Message response = mHandler.obtainMessage(EVENT_GET_SIZE_DONE);
+ phone.mSIMFileHandler.getEFLinearRecordSize(efid, response);
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to load from the SIM");
+ }
+ }
+
+ return recordSize;
+ }
+
+ /**
+ * Loads the AdnRecords in efid and returns them as a
+ * List of AdnRecords
+ *
+ * throws SecurityException if no READ_CONTACTS permission
+ *
+ * @param efid the EF id of a ADN-like SIM
+ * @return List of AdnRecord
+ */
+ public List<AdnRecord> getAdnRecordsInEf(int efid) {
+
+ if (phone.getContext().checkCallingOrSelfPermission(
+ android.Manifest.permission.READ_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Requires android.permission.READ_CONTACTS permission");
+ }
+
+ if (DBG) log("getAdnRecordsInEF: efid=" + efid);
+
+ synchronized(mLock) {
+ checkThread();
+ Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
+ adnCache.requestLoadAllAdnLike(efid, response);
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to load from the SIM");
+ }
+ }
+ return records;
+ }
+
+ private void checkThread() {
+ if (!ALLOW_SIM_OP_IN_UI_THREAD) {
+ // Make sure this isn't the UI thread, since it will block
+ if (mHandler.getLooper().equals(Looper.myLooper())) {
+ Log.e(LOG_TAG, "query() called on the main UI thread!");
+ throw new IllegalStateException("You cannot call query on this provder from the main UI thread.");
+ }
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[SpbInterfaceManager] " + msg);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimProvider.java b/telephony/java/com/android/internal/telephony/gsm/SimProvider.java
new file mode 100644
index 0000000..cece4ba
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimProvider.java
@@ -0,0 +1,455 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.content.ContentProvider;
+import android.content.UriMatcher;
+import android.content.ContentValues;
+import com.android.internal.database.ArrayListCursor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemProperties;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+public class SimProvider extends ContentProvider {
+ private static final String TAG = "SimProvider";
+ private static final boolean DBG = false;
+
+
+ private static final String[] ADDRESS_BOOK_COLUMN_NAMES = new String[] {
+ "name",
+ "number"
+ };
+
+ private static final int ADN = 1;
+ private static final int FDN = 2;
+ private static final int SDN = 3;
+
+ private static final String STR_TAG = "tag";
+ private static final String STR_NUMBER = "number";
+ private static final String STR_PIN2 = "pin2";
+
+ private static final UriMatcher URL_MATCHER =
+ new UriMatcher(UriMatcher.NO_MATCH);
+
+ static {
+ URL_MATCHER.addURI("sim", "adn", ADN);
+ URL_MATCHER.addURI("sim", "fdn", FDN);
+ URL_MATCHER.addURI("sim", "sdn", SDN);
+ }
+
+
+ private boolean mSimulator;
+
+ @Override
+ public boolean onCreate() {
+ String device = SystemProperties.get("ro.product.device");
+ if (!TextUtils.isEmpty(device)) {
+ mSimulator = false;
+ } else {
+ // simulator
+ mSimulator = true;
+ }
+
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sort) {
+ ArrayList<ArrayList> results;
+
+ if (!mSimulator) {
+ switch (URL_MATCHER.match(url)) {
+ case ADN:
+ results = loadFromEf(SimConstants.EF_ADN);
+ break;
+
+ case FDN:
+ results = loadFromEf(SimConstants.EF_FDN);
+ break;
+
+ case SDN:
+ results = loadFromEf(SimConstants.EF_SDN);
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ } else {
+ // Fake up some data for the simulator
+ results = new ArrayList<ArrayList>(4);
+ ArrayList<String> contact;
+
+ contact = new ArrayList<String>();
+ contact.add("Ron Stevens/H");
+ contact.add("512-555-5038");
+ results.add(contact);
+
+ contact = new ArrayList<String>();
+ contact.add("Ron Stevens/M");
+ contact.add("512-555-8305");
+ results.add(contact);
+
+ contact = new ArrayList<String>();
+ contact.add("Melissa Owens");
+ contact.add("512-555-8305");
+ results.add(contact);
+
+ contact = new ArrayList<String>();
+ contact.add("Directory Assistence");
+ contact.add("411");
+ results.add(contact);
+ }
+
+ return new ArrayListCursor(ADDRESS_BOOK_COLUMN_NAMES, results);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ switch (URL_MATCHER.match(url)) {
+ case ADN:
+ case FDN:
+ case SDN:
+ return "vnd.android.cursor.dir/sim-contact";
+
+ default:
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ Uri resultUri;
+ int efType;
+ String pin2 = null;
+
+ if (DBG) log("insert");
+
+ int match = URL_MATCHER.match(url);
+ switch (match) {
+ case ADN:
+ efType = SimConstants.EF_ADN;
+ break;
+
+ case FDN:
+ efType = SimConstants.EF_FDN;
+ pin2 = initialValues.getAsString("pin2");
+ break;
+
+ default:
+ throw new UnsupportedOperationException(
+ "Cannot insert into URL: " + url);
+ }
+
+ String tag = initialValues.getAsString("tag");
+ String number = initialValues.getAsString("number");
+ boolean success = addSimRecordToEf(efType, tag, number, pin2);
+
+ if (!success) {
+ return null;
+ }
+
+ StringBuilder buf = new StringBuilder("content://im/");
+ switch (match) {
+ case ADN:
+ buf.append("adn/");
+ break;
+
+ case FDN:
+ buf.append("fdn/");
+ break;
+ }
+
+ // TODO: we need to find out the rowId for the newly added record
+ buf.append(0);
+
+ resultUri = Uri.parse(buf.toString());
+
+ /*
+ // notify interested parties that an insertion happened
+ getContext().getContentResolver().notifyInsert(
+ resultUri, rowID, null);
+ */
+
+ return resultUri;
+ }
+
+ private String normalizeValue(String inVal) {
+ int len = inVal.length();
+ String retVal = inVal;
+
+ if (inVal.charAt(0) == '\'' && inVal.charAt(len-1) == '\'') {
+ retVal = inVal.substring(1, len-1);
+ }
+
+ return retVal;
+ }
+
+ @Override
+ public int delete(Uri url, String where, String[] whereArgs) {
+ int efType;
+
+ if (DBG) log("delete");
+
+ int match = URL_MATCHER.match(url);
+ switch (match) {
+ case ADN:
+ efType = SimConstants.EF_ADN;
+ break;
+
+ case FDN:
+ efType = SimConstants.EF_FDN;
+ break;
+
+ default:
+ throw new UnsupportedOperationException(
+ "Cannot insert into URL: " + url);
+ }
+
+ // parse where clause
+ String tag = null;
+ String number = null;
+ String pin2 = null;
+
+ String[] tokens = where.split("AND");
+ int n = tokens.length;
+
+ while (--n >= 0) {
+ String param = tokens[n];
+ if (DBG) log("parsing '" + param + "'");
+
+ String[] pair = param.split("=");
+
+ if (pair.length != 2) {
+ Log.e(TAG, "resolve: bad whereClause parameter: " + param);
+ continue;
+ }
+
+ String key = pair[0].trim();
+ String val = pair[1].trim();
+
+ if (STR_TAG.equals(key)) {
+ tag = normalizeValue(val);
+ } else if (STR_NUMBER.equals(key)) {
+ number = normalizeValue(val);
+ } else if (STR_PIN2.equals(key)) {
+ pin2 = normalizeValue(val);
+ }
+ }
+
+ if (TextUtils.isEmpty(tag)) {
+ return 0;
+ }
+
+ if (efType == FDN && TextUtils.isEmpty(pin2)) {
+ return 0;
+ }
+
+ boolean success = deleteSimRecordFromEf(efType, tag, number, pin2);
+ if (!success) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ @Override
+ public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+ int efType;
+ String pin2 = null;
+
+ if (DBG) log("update");
+
+ int match = URL_MATCHER.match(url);
+ switch (match) {
+ case ADN:
+ efType = SimConstants.EF_ADN;
+ break;
+
+ case FDN:
+ efType = SimConstants.EF_FDN;
+ pin2 = values.getAsString("pin2");
+ break;
+
+ default:
+ throw new UnsupportedOperationException(
+ "Cannot insert into URL: " + url);
+ }
+
+ String tag = values.getAsString("tag");
+ String number = values.getAsString("number");
+ String newTag = values.getAsString("newTag");
+ String newNumber = values.getAsString("newNumber");
+
+ boolean success = updateSimRecordInEf(efType, tag, number,
+ newTag, newNumber, pin2);
+
+ if (!success) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ private ArrayList<ArrayList> loadFromEf(int efType) {
+ ArrayList<ArrayList> results = new ArrayList<ArrayList>();
+ List<AdnRecord> adnRecords = null;
+
+ if (DBG) log("loadFromEf: efType=" + efType);
+
+ try {
+ ISimPhoneBook simIpb = ISimPhoneBook.Stub.asInterface(
+ ServiceManager.getService("simphonebook"));
+ if (simIpb != null) {
+ adnRecords = simIpb.getAdnRecordsInEf(efType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ } catch (SecurityException ex) {
+ if (DBG) log(ex.toString());
+ }
+
+ if (adnRecords != null) {
+ // Load the results
+
+ int N = adnRecords.size();
+ if (DBG) log("adnRecords.size=" + N);
+ for (int i = 0; i < N ; i++) {
+ loadRecord(adnRecords.get(i), results);
+ }
+ } else {
+ // No results to load
+ Log.w(TAG, "Cannot load ADN records");
+ results.clear();
+ }
+ if (DBG) log("loadFromEf: return results");
+ return results;
+ }
+
+ private boolean
+ addSimRecordToEf(int efType, String name, String number, String pin2) {
+ if (DBG) log("addSimRecordToEf: efType=" + efType + ", name=" + name +
+ ", number=" + number);
+
+ boolean success = false;
+
+ // TODO: do we need to call getAdnRecordsInEf() before calling
+ // updateAdnRecordsInEfBySearch()? In any case, we will leave
+ // the UI level logic to fill that prereq if necessary. But
+ // hopefully, we can remove this requirement.
+ try {
+ ISimPhoneBook simIpb = ISimPhoneBook.Stub.asInterface(
+ ServiceManager.getService("simphonebook"));
+ if (simIpb != null) {
+ success = simIpb.updateAdnRecordsInEfBySearch(efType, "", "",
+ name, number, pin2);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ } catch (SecurityException ex) {
+ if (DBG) log(ex.toString());
+ }
+ if (DBG) log("addSimRecordToEf: " + success);
+ return success;
+ }
+
+ private boolean
+ updateSimRecordInEf(int efType, String oldName, String oldNumber,
+ String newName, String newNumber,String pin2) {
+ if (DBG) log("updateSimRecordInEf: efType=" + efType +
+ ", oldname=" + oldName + ", oldnumber=" + oldNumber +
+ ", newname=" + newName + ", newnumber=" + newNumber);
+ boolean success = false;
+
+ try {
+ ISimPhoneBook simIpb = ISimPhoneBook.Stub.asInterface(
+ ServiceManager.getService("simphonebook"));
+ if (simIpb != null) {
+ success = simIpb.updateAdnRecordsInEfBySearch(efType,
+ oldName, oldNumber, newName, newNumber, pin2);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ } catch (SecurityException ex) {
+ if (DBG) log(ex.toString());
+ }
+
+ if (DBG) log("updateSimRecordInEf: " + success);
+ return success;
+ }
+
+
+ private boolean deleteSimRecordFromEf(int efType,
+ String name, String number,
+ String pin2) {
+ if (DBG) log("deleteSimRecordFromEf: efType=" + efType +
+ ", name=" + name + ", number=" + number + ", pin2=" + pin2);
+
+ boolean success = false;
+
+ try {
+ ISimPhoneBook simIpb = ISimPhoneBook.Stub.asInterface(
+ ServiceManager.getService("simphonebook"));
+ if (simIpb != null) {
+ success = simIpb.updateAdnRecordsInEfBySearch(efType,
+ name, number, "", "", pin2);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ } catch (SecurityException ex) {
+ if (DBG) log(ex.toString());
+ }
+
+ if (DBG) log("deleteSimRecordFromEf: " + success);
+ return success;
+ }
+
+ /**
+ * Loads an AdnRecord into an ArrayList. Must be called with mLock held.
+ *
+ * @param record the ADN record to load from
+ * @param results the array list to put the results in
+ */
+ private void loadRecord(AdnRecord record,
+ ArrayList<ArrayList> results) {
+ if (!record.isEmpty()) {
+ ArrayList<String> contact = new ArrayList<String>(2);
+ String alphaTag = record.getAlphaTag();
+ String number = record.getNumber();
+
+ if (DBG) log("loadRecord: " + alphaTag + ", " + number);
+ contact.add(alphaTag);
+ contact.add(number);
+ results.add(contact);
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(TAG, "[SimProvider] " + msg);
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
new file mode 100644
index 0000000..c3df0d0
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -0,0 +1,302 @@
+/*
+** Copyright 2007, 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.internal.telephony.gsm;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.telephony.gsm.SmsManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SimSmsInterfaceManager to provide an inter-process communication to
+ * access Sms in Sim.
+ */
+public class SimSmsInterfaceManager extends ISms.Stub {
+ static final String LOG_TAG = "GSM";
+ static final boolean DBG = false;
+
+ private GSMPhone mPhone;
+ private final Object mLock = new Object();
+ private boolean mSuccess;
+ private List<SmsRawData> mSms;
+
+ private static final int EVENT_LOAD_DONE = 1;
+ private static final int EVENT_UPDATE_DONE = 2;
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case EVENT_UPDATE_DONE:
+ ar = (AsyncResult) msg.obj;
+ synchronized (mLock) {
+ mSuccess = (ar.exception == null);
+ mLock.notifyAll();
+ }
+ break;
+ case EVENT_LOAD_DONE:
+ ar = (AsyncResult)msg.obj;
+ synchronized (mLock) {
+ if (ar.exception == null) {
+ mSms = (List<SmsRawData>)
+ buildValidRawData((ArrayList<byte[]>) ar.result);
+ } else {
+ if(DBG) log("Cannot load Sms records");
+ if (mSms != null)
+ mSms.clear();
+ }
+ mLock.notifyAll();
+ }
+ break;
+ }
+ }
+ };
+
+ public SimSmsInterfaceManager(GSMPhone phone) {
+ this.mPhone = phone;
+ ServiceManager.addService("isms", this);
+ }
+
+ private void enforceReceiveAndSend(String message) {
+ Context context = mPhone.getContext();
+
+ context.enforceCallingPermission(
+ "android.permission.RECEIVE_SMS", message);
+ context.enforceCallingPermission(
+ "android.permission.SEND_SMS", message);
+ }
+
+ /**
+ * Update the specified message on the SIM.
+ *
+ * @param index record index of message to update
+ * @param status new message status (STATUS_ON_SIM_READ,
+ * STATUS_ON_SIM_UNREAD, STATUS_ON_SIM_SENT,
+ * STATUS_ON_SIM_UNSENT, STATUS_ON_SIM_FREE)
+ * @param pdu the raw PDU to store
+ * @return success or not
+ *
+ */
+ public boolean
+ updateMessageOnSimEf(int index, int status, byte[] pdu) {
+ if (DBG) log("updateMessageOnSimEf: index=" + index +
+ " status=" + status + " ==> " +
+ "("+ pdu + ")");
+ enforceReceiveAndSend("Updating message on SIM");
+ synchronized(mLock) {
+ mSuccess = false;
+ Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
+
+ if (status == SmsManager.STATUS_ON_SIM_FREE) {
+ // Special case FREE: call deleteSmsOnSim instead of
+ // manipulating the SIM record
+ mPhone.mCM.deleteSmsOnSim(index, response);
+ } else {
+ byte[] record = makeSmsRecordData(status, pdu);
+ mPhone.mSIMFileHandler.updateEFLinearFixed( SimConstants.EF_SMS,
+ index, record, null, response);
+ }
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to update by index");
+ }
+ }
+ return mSuccess;
+ }
+
+ /**
+ * Copy a raw SMS PDU to the SIM.
+ *
+ * @param pdu the raw PDU to store
+ * @param status message status (STATUS_ON_SIM_READ, STATUS_ON_SIM_UNREAD,
+ * STATUS_ON_SIM_SENT, STATUS_ON_SIM_UNSENT)
+ * @return success or not
+ *
+ */
+ public boolean copyMessageToSimEf(int status, byte[] pdu, byte[] smsc) {
+ if (DBG) log("copyMessageToSimEf: status=" + status + " ==> " +
+ "pdu=("+ pdu + "), smsm=(" + smsc +")");
+ enforceReceiveAndSend("Copying message to SIM");
+ synchronized(mLock) {
+ mSuccess = false;
+ Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
+
+ mPhone.mCM.writeSmsToSim(status, SimUtils.bytesToHexString(smsc),
+ SimUtils.bytesToHexString(pdu), response);
+
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to update by index");
+ }
+ }
+ return mSuccess;
+ }
+
+ /**
+ * Retrieves all messages currently stored on SIM.
+ *
+ * @return list of SmsRawData of all sms on SIM
+ */
+ public List<SmsRawData> getAllMessagesFromSimEf() {
+ if (DBG) log("getAllMessagesFromEF");
+
+ Context context = mPhone.getContext();
+
+ context.enforceCallingPermission(
+ "android.permission.RECEIVE_SMS",
+ "Reading messages from SIM");
+ synchronized(mLock) {
+ Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
+ mPhone.mSIMFileHandler.loadEFLinearFixedAll(SimConstants.EF_SMS,
+ response);
+
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to load from the SIM");
+ }
+ }
+ return mSms;
+ }
+
+ /**
+ * Send a Raw PDU SMS
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * defatult SMSC
+ * @param pdu the raw PDU to send
+ * @param sentIntent if not NULL this <code>Intent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * @param deliveryIntent if not NULL this <code>Intent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ */
+ public void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
+ PendingIntent deliveryIntent) {
+ Context context = mPhone.getContext();
+
+ context.enforceCallingPermission(
+ "android.permission.SEND_SMS",
+ "Sending SMS message");
+ if (DBG) log("sendRawPdu: smsc=" + smsc +
+ " pdu="+ pdu + " sentIntent" + sentIntent +
+ " deliveryIntent" + deliveryIntent);
+ mPhone.mSMS.sendRawPdu(smsc, pdu, sentIntent, deliveryIntent);
+ }
+
+ /**
+ * Send a multi-part text based SMS.
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ */
+ public void sendMultipartText(String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
+ Context context = mPhone.getContext();
+
+ context.enforceCallingPermission(
+ "android.permission.SEND_SMS",
+ "Sending SMS message");
+ if (DBG) log("sendMultipartText");
+ mPhone.mSMS.sendMultipartText(destinationAddress, scAddress, (ArrayList<String>) parts,
+ (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents);
+ }
+
+ /**
+ * Generates an EF_SMS record from status and raw PDU.
+ *
+ * @param status Message status. See TS 51.011 10.5.3.
+ * @param pdu Raw message PDU.
+ * @return byte array for the record.
+ */
+ private byte[] makeSmsRecordData(int status, byte[] pdu) {
+ byte[] data = new byte[SimConstants.SMS_RECORD_LENGTH];
+
+ // Status bits for this record. See TS 51.011 10.5.3
+ data[0] = (byte)(status & 7);
+
+ System.arraycopy(pdu, 0, data, 1, pdu.length);
+
+ // Pad out with 0xFF's.
+ for (int j = pdu.length+1; j < SimConstants.SMS_RECORD_LENGTH; j++) {
+ data[j] = -1;
+ }
+
+ return data;
+ }
+
+ /**
+ * create SmsRawData lists from all sms record byte[]
+ * Use null to indicate "free" record
+ *
+ * @param messages List of message records from EF_SMS.
+ * @return SmsRawData list of all in-used records
+ */
+ private ArrayList<SmsRawData> buildValidRawData(ArrayList<byte[]> messages) {
+ int count = messages.size();
+ ArrayList<SmsRawData> ret;
+
+ ret = new ArrayList<SmsRawData>(count);
+
+ for (int i = 0; i < count; i++) {
+ byte[] ba = messages.get(i);
+ if (ba[0] == 0) {
+ ret.add(null);
+ } else {
+ ret.add(new SmsRawData(messages.get(i)));
+ }
+ }
+
+ return ret;
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[SmsInterfaceManager] " + msg);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimTlv.java b/telephony/java/com/android/internal/telephony/gsm/SimTlv.java
new file mode 100644
index 0000000..00879ce
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimTlv.java
@@ -0,0 +1,129 @@
+/*
+ * 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.internal.telephony.gsm;
+
+/**
+ * SIM Tag-Length-Value record
+ * TS 102 223 Annex C
+ *
+ * {@hide}
+ *
+ */
+public class SimTlv
+{
+ //***** Private Instance Variables
+
+ byte record[];
+ int tlvOffset;
+ int tlvLength;
+ int curOffset;
+ int curDataOffset;
+ int curDataLength;
+ boolean hasValidTlvObject;
+
+ public SimTlv(byte[] record, int offset, int length)
+ {
+ this.record = record;
+
+ this.tlvOffset = offset;
+ this.tlvLength = length;
+ curOffset = offset;
+
+ hasValidTlvObject = parseCurrentTlvObject();
+ }
+
+ public boolean
+ nextObject()
+ {
+ if (!hasValidTlvObject) return false;
+
+ curOffset = curDataOffset + curDataLength;
+ hasValidTlvObject = parseCurrentTlvObject();
+ return hasValidTlvObject;
+ }
+
+ public boolean
+ isValidObject()
+ {
+ return hasValidTlvObject;
+ }
+
+ /**
+ * Returns the tag for the current TLV object
+ * Return 0 if !isValidObject()
+ * 0 and 0xff are invalid tag values
+ * valid tags range from 1 - 0xfe
+ */
+ public int
+ getTag()
+ {
+ if (!hasValidTlvObject) return 0;
+ return record[curOffset] & 0xff;
+ }
+
+ /**
+ * Returns data associated with current TLV object
+ * returns null if !isValidObject()
+ */
+
+ public byte[]
+ getData()
+ {
+ if (!hasValidTlvObject) return null;
+
+ byte[] ret = new byte[curDataLength];
+ System.arraycopy(record, curDataOffset, ret, 0, curDataLength);
+ return ret;
+ }
+
+ /**
+ * Updates curDataLength and curDataOffset
+ * @return false on invalid record, true on valid record
+ */
+
+ private boolean
+ parseCurrentTlvObject()
+ {
+ // 0x00 and 0xff are invalid tag values
+ if (record[curOffset] == 0 || (record[curOffset] & 0xff) == 0xff) {
+ return false;
+ }
+
+ try {
+ if ((record[curOffset + 1] & 0xff) < 0x80) {
+ // one byte length 0 - 0x7f
+ curDataLength = record[curOffset + 1] & 0xff;
+ curDataOffset = curOffset + 2;
+ } else if ((record[curOffset + 1] & 0xff) == 0x81) {
+ // two byte length 0x80 - 0xff
+ curDataLength = record[curOffset + 2] & 0xff;
+ curDataOffset = curOffset + 3;
+ } else {
+ return false;
+ }
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ return false;
+ }
+
+ if (curDataLength + curDataOffset > tlvOffset + tlvLength) {
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimUtils.java b/telephony/java/com/android/internal/telephony/gsm/SimUtils.java
new file mode 100644
index 0000000..5cd5a90
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SimUtils.java
@@ -0,0 +1,329 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import java.io.UnsupportedEncodingException;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.util.Log;
+
+/**
+ * Various methods, useful for dealing with SIM data.
+ */
+public class SimUtils
+{
+ static final String LOG_TAG="GSM";
+
+ /**
+ * Many fields in GSM SIM's are stored as nibble-swizzled BCD
+ *
+ * Assumes left-justified field that may be padded right with 0xf
+ * values.
+ *
+ * Stops on invalid BCD value, returning string so far
+ */
+ public static String
+ bcdToString(byte[] data, int offset, int length)
+ {
+ StringBuilder ret = new StringBuilder(length*2);
+
+ for (int i = offset ; i < offset + length ; i++) {
+ byte b;
+ int v;
+
+ v = data[i] & 0xf;
+ if (v > 9) break;
+ ret.append((char)('0' + v));
+
+ v = (data[i] >> 4) & 0xf;
+ if (v > 9) break;
+ ret.append((char)('0' + v));
+ }
+
+ return ret.toString();
+ }
+
+
+ /**
+ * Decodes a GSM-style BCD byte, returning an int ranging from 0-99.
+ *
+ * In GSM land, the least significant BCD digit is stored in the most
+ * significant nibble.
+ *
+ * Out-of-range digits are treated as 0 for the sake of the time stamp,
+ * because of this:
+ *
+ * TS 23.040 section 9.2.3.11
+ * "if the MS receives a non-integer value in the SCTS, it shall
+ * assume the digit is set to 0 but shall store the entire field
+ * exactly as received"
+ */
+ public static int
+ bcdByteToInt(byte b)
+ {
+ int ret = 0;
+
+ // treat out-of-range BCD values as 0
+ if ((b & 0xf0) <= 0x90) {
+ ret = (b >> 4) & 0xf;
+ }
+
+ if ((b & 0x0f) <= 0x09) {
+ ret += (b & 0xf) * 10;
+ }
+
+ return ret;
+ }
+
+
+ /**
+ * Decodes a string field that's formatted like the EF[ADN] alpha
+ * identifier
+ *
+ * From TS 51.011 10.5.1:
+ * Coding:
+ * this alpha tagging shall use either
+ * - the SMS default 7 bit coded alphabet as defined in
+ * TS 23.038 [12] with bit 8 set to 0. The alpha identifier
+ * shall be left justified. Unused bytes shall be set to 'FF'; or
+ * - one of the UCS2 coded options as defined in annex B.
+ *
+ * Annex B from TS 11.11 V8.13.0:
+ * 1) If the first octet in the alpha string is '80', then the
+ * remaining octets are 16 bit UCS2 characters ...
+ * 2) if the first octet in the alpha string is '81', then the
+ * second octet contains a value indicating the number of
+ * characters in the string, and the third octet contains an
+ * 8 bit number which defines bits 15 to 8 of a 16 bit
+ * base pointer, where bit 16 is set to zero and bits 7 to 1
+ * are also set to zero. These sixteen bits constitute a
+ * base pointer to a "half page" in the UCS2 code space, to be
+ * used with some or all of the remaining octets in the string.
+ * The fourth and subsequent octets contain codings as follows:
+ * If bit 8 of the octet is set to zero, the remaining 7 bits
+ * of the octet contain a GSM Default Alphabet character,
+ * whereas if bit 8 of the octet is set to one, then the
+ * remaining seven bits are an offset value added to the
+ * 16 bit base pointer defined earlier...
+ * 3) If the first octet of the alpha string is set to '82', then
+ * the second octet contains a value indicating the number of
+ * characters in the string, and the third and fourth octets
+ * contain a 16 bit number which defines the complete 16 bit
+ * base pointer to a "half page" in the UCS2 code space...
+ */
+ public static String
+ adnStringFieldToString(byte[] data, int offset, int length)
+ {
+ if (length >= 1) {
+ if (data[offset] == (byte) 0x80) {
+ int ucslen = (length - 1) / 2;
+ String ret = null;
+
+ try {
+ ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
+ } catch (UnsupportedEncodingException ex) {
+ Log.e(LOG_TAG, "implausible UnsupportedEncodingException",
+ ex);
+ }
+
+ if (ret != null) {
+ // trim off trailing FFFF characters
+
+ ucslen = ret.length();
+ while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF')
+ ucslen--;
+
+ return ret.substring(0, ucslen);
+ }
+ }
+ }
+
+ boolean isucs2 = false;
+ char base = '\0';
+ int len = 0;
+
+ if (length >= 3 && data[offset] == (byte) 0x81) {
+ len = data[offset + 1] & 0xFF;
+ if (len > length - 3)
+ len = length - 3;
+
+ base = (char) ((data[offset + 2] & 0xFF) << 7);
+ offset += 3;
+ isucs2 = true;
+ } else if (length >= 4 && data[offset] == (byte) 0x82) {
+ len = data[offset + 1] & 0xFF;
+ if (len > length - 4)
+ len = length - 4;
+
+ base = (char) (((data[offset + 2] & 0xFF) << 8) |
+ (data[offset + 3] & 0xFF));
+ offset += 4;
+ isucs2 = true;
+ }
+
+ if (isucs2) {
+ StringBuilder ret = new StringBuilder();
+
+ while (len > 0) {
+ // UCS2 subset case
+
+ if (data[offset] < 0) {
+ ret.append((char) (base + (data[offset] & 0x7F)));
+ offset++;
+ len--;
+ }
+
+ // GSM character set case
+
+ int count = 0;
+ while (count < len && data[offset + count] >= 0)
+ count++;
+
+ ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
+ offset, count));
+
+ offset += count;
+ len -= count;
+ }
+
+ return ret.toString();
+ }
+
+ return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length);
+ }
+
+ static int
+ hexCharToInt(char c)
+ {
+ if (c >= '0' && c <= '9') return (c - '0');
+ if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
+ if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+
+ throw new RuntimeException ("invalid hex char '" + c + "'");
+ }
+
+ /**
+ * Converts a hex String to a byte array.
+ *
+ * @param s A string of hexadecimal characters, must be an even number of
+ * chars long
+ *
+ * @return byte array representation
+ *
+ * @throws RuntimeException on invalid format
+ */
+ public static byte[]
+ hexStringToBytes(String s)
+ {
+ byte[] ret;
+
+ if (s == null) return null;
+
+ int sz = s.length();
+
+ ret = new byte[sz/2];
+
+ for (int i=0 ; i <sz ; i+=2) {
+ ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4)
+ | hexCharToInt(s.charAt(i+1)));
+ }
+
+ return ret;
+ }
+
+
+ /**
+ * Converts a byte array into a String hexidecimal characters
+ *
+ * null returns null
+ */
+ public static String
+ bytesToHexString(byte[] bytes)
+ {
+ if (bytes == null) return null;
+
+ StringBuilder ret = new StringBuilder(2*bytes.length);
+
+ for (int i = 0 ; i < bytes.length ; i++) {
+ int b;
+
+ b = 0x0f & (bytes[i] >> 4);
+
+ ret.append("0123456789abcdef".charAt(b));
+
+ b = 0x0f & bytes[i];
+
+ ret.append("0123456789abcdef".charAt(b));
+ }
+
+ return ret.toString();
+ }
+
+
+ /**
+ * Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string
+ * "offset" points to "octet 3", the coding scheme byte
+ * empty string returned on decode error
+ */
+ public static String
+ networkNameToString(byte[] data, int offset, int length)
+ {
+ String ret;
+
+ if ((data[offset] & 0x80) != 0x80 || length < 1) {
+ return "";
+ }
+
+ switch ((data[offset] >>> 4) & 0x7) {
+ case 0:
+ // SMS character set
+ int countSeptets;
+ int unusedBits = data[offset] & 7;
+ countSeptets = (((length - 1) * 8) - unusedBits) / 7 ;
+ ret = GsmAlphabet.gsm7BitPackedToString(
+ data, offset + 1, countSeptets);
+ break;
+ case 1:
+ // UCS2
+ try {
+ ret = new String(data,
+ offset + 1, length - 1, "utf-16");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Log.e(LOG_TAG,"implausible UnsupportedEncodingException", ex);
+ }
+ break;
+
+ // unsupported encoding
+ default:
+ ret = "";
+ break;
+ }
+
+ // "Add CI"
+ // "The MS should add the letters for the Country's Initials and
+ // a separator (e.g. a space) to the text string"
+
+ if ((data[offset] & 0x40) != 0) {
+ // FIXME(mkf) add country initials here
+
+ }
+
+ return ret;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsHeader.java
new file mode 100644
index 0000000..22366ec
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsHeader.java
@@ -0,0 +1,236 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import com.android.internal.util.HexDump;
+
+import java.util.ArrayList;
+
+/**
+ * This class represents a SMS user data header.
+ *
+ */
+public class SmsHeader
+{
+ /** See TS 23.040 9.2.3.24 for description of this element ID. */
+ public static final int CONCATENATED_8_BIT_REFERENCE = 0x00;
+ /** See TS 23.040 9.2.3.24 for description of this element ID. */
+ public static final int SPECIAL_SMS_MESSAGE_INDICATION = 0x01;
+ /** See TS 23.040 9.2.3.24 for description of this element ID. */
+ public static final int APPLICATION_PORT_ADDRESSING_8_BIT = 0x04;
+ /** See TS 23.040 9.2.3.24 for description of this element ID. */
+ public static final int APPLICATION_PORT_ADDRESSING_16_BIT= 0x05;
+ /** See TS 23.040 9.2.3.24 for description of this element ID. */
+ public static final int CONCATENATED_16_BIT_REFERENCE = 0x08;
+
+ public static final int PORT_WAP_PUSH = 2948;
+ public static final int PORT_WAP_WSP = 9200;
+
+ private byte[] m_data;
+ private ArrayList<Element> m_elements = new ArrayList<Element>();
+
+ /**
+ * Creates an SmsHeader object from raw user data header bytes.
+ *
+ * @param data is user data header bytes
+ * @return an SmsHeader object
+ */
+ public static SmsHeader parse(byte[] data)
+ {
+ SmsHeader header = new SmsHeader();
+ header.m_data = data;
+
+ int index = 0;
+ while (index < data.length)
+ {
+ int id = data[index++] & 0xff;
+ int length = data[index++] & 0xff;
+ byte[] elementData = new byte[length];
+ System.arraycopy(data, index, elementData, 0, length);
+ header.add(new Element(id, elementData));
+ index += length;
+ }
+
+ return header;
+ }
+
+ public SmsHeader()
+ {
+ }
+
+ /**
+ * Returns the list of SmsHeader Elements that make up the header.
+ *
+ * @return the list of SmsHeader Elements.
+ */
+ public ArrayList<Element> getElements()
+ {
+ return m_elements;
+ }
+
+ /**
+ * Add an element to the SmsHeader.
+ *
+ * @param element to add.
+ */
+ public void add(Element element)
+ {
+ m_elements.add(element);
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append("UDH LENGTH: " + m_data.length + " octets");
+ builder.append("UDH: ");
+ builder.append(HexDump.toHexString(m_data));
+ builder.append("\n");
+
+ for (Element e : getElements()) {
+ builder.append(" 0x" + HexDump.toHexString((byte)e.getID()) + " - ");
+ switch (e.getID())
+ {
+ case CONCATENATED_8_BIT_REFERENCE:
+ {
+ builder.append("Concatenated Short Message 8bit ref\n");
+ byte[] data = e.getData();
+ builder.append(" " + data.length + " (0x");
+ builder.append(HexDump.toHexString((byte)data.length)+") Bytes - Information Element\n");
+ builder.append(" " + data[0] + " : SM reference number\n");
+ builder.append(" " + data[1] + " : number of messages\n");
+ builder.append(" " + data[2] + " : this SM sequence number\n");
+ break;
+ }
+
+ case CONCATENATED_16_BIT_REFERENCE:
+ {
+ builder.append("Concatenated Short Message 16bit ref\n");
+ byte[] data = e.getData();
+ builder.append(" " + data.length + " (0x");
+ builder.append(HexDump.toHexString((byte)data.length)+") Bytes - Information Element\n");
+ builder.append(" " + (data[0] & 0xff) * 256 + (data[1] & 0xff) +
+ " : SM reference number\n");
+ builder.append(" " + data[2] + " : number of messages\n");
+ builder.append(" " + data[3] + " : this SM sequence number\n");
+ break;
+ }
+
+ case APPLICATION_PORT_ADDRESSING_16_BIT:
+ {
+ builder.append("Application port addressing 16bit\n");
+ byte[] data = e.getData();
+
+ builder.append(" " + data.length + " (0x");
+ builder.append(HexDump.toHexString((byte)data.length)+") Bytes - Information Element\n");
+
+ int source = (data[0] & 0xff) << 8;
+ source |= (data[1] & 0xff);
+ builder.append(" " + source + " : DESTINATION port\n");
+
+ int dest = (data[2] & 0xff) << 8;
+ dest |= (data[3] & 0xff);
+ builder.append(" " + dest + " : SOURCE port\n");
+ break;
+ }
+
+ default:
+ {
+ builder.append("Unknown element\n");
+ break;
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ private int calcSize() {
+ int size = 1; // +1 for the UDHL field
+ for (Element e : m_elements) {
+ size += e.getData().length;
+ size += 2; // 1 byte ID, 1 byte length
+ }
+
+ return size;
+ }
+
+ /**
+ * Converts SmsHeader object to a byte array as specified in TS 23.040 9.2.3.24.
+ * @return Byte array representing the SmsHeader
+ */
+ public byte[] toByteArray() {
+ if (m_elements.size() == 0) return null;
+
+ if (m_data == null) {
+ int size = calcSize();
+ int cur = 1;
+ m_data = new byte[size];
+
+ m_data[0] = (byte) (size-1); // UDHL does not include itself
+
+ for (Element e : m_elements) {
+ int length = e.getData().length;
+ m_data[cur++] = (byte) e.getID();
+ m_data[cur++] = (byte) length;
+ System.arraycopy(e.getData(), 0, m_data, cur, length);
+ cur += length;
+ }
+ }
+
+ return m_data;
+ }
+
+ /**
+ * A single Element in the SMS User Data Header.
+ *
+ * See TS 23.040 9.2.3.24.
+ *
+ */
+ public static class Element
+ {
+ private byte[] m_data;
+ private int m_id;
+
+ public Element(int id, byte[] data)
+ {
+ m_id = id;
+ m_data = data;
+ }
+
+ /**
+ * Returns the Information Element Identifier for this element.
+ *
+ * @return the IE identifier.
+ */
+ public int getID()
+ {
+ return m_id;
+ }
+
+ /**
+ * Returns the data portion of this element.
+ *
+ * @return element data.
+ */
+ public byte[] getData()
+ {
+ return m_data;
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsRawData.aidl b/telephony/java/com/android/internal/telephony/gsm/SmsRawData.aidl
new file mode 100644
index 0000000..6f1a46d
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsRawData.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2007, 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.internal.telephony.gsm;
+
+parcelable SmsRawData;
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsRawData.java b/telephony/java/com/android/internal/telephony/gsm/SmsRawData.java
new file mode 100644
index 0000000..a029d5c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsRawData.java
@@ -0,0 +1,62 @@
+/*
+** Copyright 2007, 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.internal.telephony.gsm;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * A parcelable holder class of byte[] for ISms aidl implementation
+ */
+public class SmsRawData implements Parcelable {
+ byte[] data;
+
+ //Static Methods
+ public static final Parcelable.Creator<SmsRawData> CREATOR
+ = new Parcelable.Creator<SmsRawData> (){
+ public SmsRawData createFromParcel(Parcel source) {
+ int size;
+ size = source.readInt();
+ byte[] data = new byte[size];
+ source.readByteArray(data);
+ return new SmsRawData(data);
+ }
+
+ public SmsRawData[] newArray(int size) {
+ return new SmsRawData[size];
+ }
+ };
+
+ // Constructor
+ public SmsRawData(byte[] data) {
+ this.data = data;
+ }
+
+ public byte[] getBytes() {
+ return data;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(data.length);
+ dest.writeByteArray(data);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsResponse.java b/telephony/java/com/android/internal/telephony/gsm/SmsResponse.java
new file mode 100644
index 0000000..c005b5f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsResponse.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony.gsm;
+
+/**
+ * Object returned by the RIL upon successful completion of sendSMS.
+ * Contains message reference and ackPdu.
+ *
+ */
+class SmsResponse {
+ /** Message reference of the just-sent SMS. */
+ int messageRef;
+ /** ackPdu for the just-sent SMS. */
+ String ackPdu;
+
+ SmsResponse(int messageRef, String ackPdu) {
+ this.messageRef = messageRef;
+ this.ackPdu = ackPdu;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SuppServiceNotification.java b/telephony/java/com/android/internal/telephony/gsm/SuppServiceNotification.java
new file mode 100644
index 0000000..11ad52d
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SuppServiceNotification.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony.gsm;
+
+import android.telephony.PhoneNumberUtils;
+
+/**
+ * Represents a Supplementary Service Notification received from the network.
+ *
+ * {@hide}
+ */
+public class SuppServiceNotification {
+ /** Type of notification: 0 = MO; 1 = MT */
+ public int notificationType;
+ /** TS 27.007 7.17 "code1" or "code2" */
+ public int code;
+ /** TS 27.007 7.17 "index" */
+ public int index;
+ /** TS 27.007 7.17 "type" (MT only) */
+ public int type;
+ /** TS 27.007 7.17 "number" (MT only) */
+ public String number;
+
+ static public final int MO_CODE_UNCONDITIONAL_CF_ACTIVE = 0;
+ static public final int MO_CODE_SOME_CF_ACTIVE = 1;
+ static public final int MO_CODE_CALL_FORWARDED = 2;
+ static public final int MO_CODE_CALL_IS_WAITING = 3;
+ static public final int MO_CODE_CUG_CALL = 4;
+ static public final int MO_CODE_OUTGOING_CALLS_BARRED = 5;
+ static public final int MO_CODE_INCOMING_CALLS_BARRED = 6;
+ static public final int MO_CODE_CLIR_SUPPRESSION_REJECTED = 7;
+ static public final int MO_CODE_CALL_DEFLECTED = 8;
+
+ static public final int MT_CODE_FORWARDED_CALL = 0;
+ static public final int MT_CODE_CUG_CALL = 1;
+ static public final int MT_CODE_CALL_ON_HOLD = 2;
+ static public final int MT_CODE_CALL_RETRIEVED = 3;
+ static public final int MT_CODE_MULTI_PARTY_CALL = 4;
+ static public final int MT_CODE_ON_HOLD_CALL_RELEASED = 5;
+ static public final int MT_CODE_FORWARD_CHECK_RECEIVED = 6;
+ static public final int MT_CODE_CALL_CONNECTING_ECT = 7;
+ static public final int MT_CODE_CALL_CONNECTED_ECT = 8;
+ static public final int MT_CODE_DEFLECTED_CALL = 9;
+ static public final int MT_CODE_ADDITIONAL_CALL_FORWARDED = 10;
+
+ public String toString()
+ {
+ return super.toString() + " mobile"
+ + (notificationType == 0 ? " originated " : " terminated ")
+ + " code: " + code
+ + " index: " + index
+ + " \""
+ + PhoneNumberUtils.stringFromStringAndTOA(number, type) + "\" ";
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/VoiceMailConstants.java b/telephony/java/com/android/internal/telephony/gsm/VoiceMailConstants.java
new file mode 100644
index 0000000..d4e1f72
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/VoiceMailConstants.java
@@ -0,0 +1,114 @@
+/*
+ * 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.internal.telephony.gsm;
+
+import android.os.Environment;
+import android.util.Xml;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.io.FileReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import com.android.internal.util.XmlUtils;
+
+/**
+ * {@hide}
+ */
+class VoiceMailConstants {
+ private HashMap<String, String[]> CarrierVmMap;
+
+
+ static final String LOG_TAG = "GSM";
+ static final String PARTNER_VOICEMAIL_PATH ="etc/voicemail-conf.xml";
+
+ static final int NAME = 0;
+ static final int NUMBER = 1;
+ static final int TAG = 2;
+ static final int SIZE = 3;
+
+ VoiceMailConstants () {
+ CarrierVmMap = new HashMap<String, String[]>();
+ loadVoiceMail();
+ }
+
+ boolean containsCarrier(String carrier) {
+ return CarrierVmMap.containsKey(carrier);
+ }
+
+ String getCarrierName(String carrier) {
+ String[] data = CarrierVmMap.get(carrier);
+ return data[NAME];
+ }
+
+ String getVoiceMailNumber(String carrier) {
+ String[] data = CarrierVmMap.get(carrier);
+ return data[NUMBER];
+ }
+
+ String getVoiceMailTag(String carrier) {
+ String[] data = CarrierVmMap.get(carrier);
+ return data[TAG];
+ }
+
+ private void loadVoiceMail() {
+ FileReader vmReader;
+
+ final File vmFile = new File(Environment.getRootDirectory(),
+ PARTNER_VOICEMAIL_PATH);
+
+ try {
+ vmReader = new FileReader(vmFile);
+ } catch (FileNotFoundException e) {
+ Log.w(LOG_TAG, "Can't open " +
+ Environment.getRootDirectory() + "/" + PARTNER_VOICEMAIL_PATH);
+ return;
+ }
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(vmReader);
+
+ XmlUtils.beginDocument(parser, "voicemail");
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ String name = parser.getName();
+ if (!"voicemail".equals(name)) {
+ break;
+ }
+
+ String[] data = new String[SIZE];
+ String numeric = parser.getAttributeValue(null, "numeric");
+ data[NAME] = parser.getAttributeValue(null, "carrier");
+ data[NUMBER] = parser.getAttributeValue(null, "vmnumber");
+ data[TAG] = parser.getAttributeValue(null, "vmtag");
+
+ CarrierVmMap.put(numeric, data);
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(LOG_TAG, "Exception in Voicemail parser " + e);
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Exception in Voicemail parser " + e);
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/package.html b/telephony/java/com/android/internal/telephony/gsm/package.html
new file mode 100755
index 0000000..4b1cf99
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides classes to control or read data from GSM phones.
+@hide
+</BODY>
+</HTML>
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/AppInterface.java b/telephony/java/com/android/internal/telephony/gsm/stk/AppInterface.java
new file mode 100644
index 0000000..a43e32f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/AppInterface.java
@@ -0,0 +1,257 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+/**
+ * Interface for communication from Apps to STK Service
+ *
+ * {@hide}
+ */
+public interface AppInterface {
+ /**
+ * STK state
+ */
+ public enum State {
+ /**
+ * Idle state
+ */
+ IDLE,
+ /**
+ * Idle but main menu exists. Menu selection should be done by calling {@code
+ * notifyMenuSelection()}.
+ */
+ MAIN_MENU,
+ /**
+ * Waiting for a key input. Key input should be notified by calling
+ * {@code notifyInkey()}.
+ */
+ GET_INKEY,
+ /**
+ * Waiting for a user input. Text input should be notified by calling
+ * {@code notifyInput()}.
+ */
+ GET_INPUT,
+ /**
+ * Waiting for a user selection. The selection should be notified by
+ * calling {@code notifySelectedItem()}.
+ */
+ SELECT_ITEM,
+ /**
+ * Waiting for the user to accept or reject a call. It should be
+ * notified by calling {@code acceptOrRejectCall()}.
+ */
+ CALL_SETUP,
+ /**
+ * Waiting for user to confirm Display Text message.
+ * notified by calling {@code notifyDisplayTextEnded()}.
+ */
+ DISPLAY_TEXT,
+ /**
+ * Waiting for user to confirm launching the browser.
+ * notified by calling {@code notifyLaunchedBrowserConfirmed()}.
+ */
+ LAUNCH_BROWSER,
+ /**
+ * Waiting for the application to play the requested tone.
+ */
+ PLAY_TONE
+ }
+
+ /**
+ * Sets the {@link CommandListener CommandListener} object that is used for
+ * notifying of proactive commands or events from the SIM/RIL.
+ *
+ * @param l CommandListener object that handles the proactive commands and
+ * events from the SIM/RIL.
+ */
+ void setCommandListener(CommandListener l);
+
+ /**
+ * Gets the current state of STK service.
+ * @return The current state.
+ */
+ State getState();
+
+ /**
+ * Gets the main menu that has been setup by the SIM.
+ *
+ * @return The main menu that has been setup by the SIM. It can be null.
+ */
+ Menu getCurrentMenu();
+
+ /**
+ * Notifies the SIM of the menu selection among a set of menu options
+ * supplied by the SIM using SET UP MENU.
+ *
+ * @param menuId ID of the selected menu item. It can be between 1 and
+ * 255.
+ * @param helpRequired True if just help information is requested on a menu
+ * item rather than menu selection. False if the menu item
+ * is actually selected.
+ */
+ void notifyMenuSelection(int menuId, boolean helpRequired);
+
+ /**
+ * Notifies the SIM that a user activity has occurred. It is actually sent
+ * to the SIM when it has registered to be notified of this event via SET
+ * UP EVENT LIST command.
+ */
+ void notifyUserActivity();
+
+ /**
+ * Notifies the SIM that the idle screen is available. It is actually sent
+ * to the SIM when it has registered to be notified of this event via SET
+ * UP EVENT LIST command.
+ */
+ void notifyIdleScreenAvailable();
+
+ /**
+ * Notifies the SIM that the currently used language has changed. It is
+ * actually sent to the SIM when it has registered to be notified of this
+ * event via SET UP EVENT LIST command.
+ *
+ * @param langCode Language code of the currently selected language.
+ * Language code is defined in ISO 639. This must be a
+ * string of two characters.
+ */
+ void notifyLanguageSelection(String langCode);
+
+ /**
+ * Notifies the SIM that the browser is terminated. It is actually sent to
+ * the SIM when it has registered to be notified of this event via SET UP
+ * EVENT LIST command.
+ *
+ * @param isErrorTermination True if the cause is "Error Termination",
+ * false if the cause is "User Termination".
+ */
+ void notifyBrowserTermination(boolean isErrorTermination);
+
+ /**
+ * Notifies the SIM about the launch browser confirmation. This method
+ * should be called only after the application gets notified by {@code
+ * CommandListener.onLaunchBrowser()} or inside that method.
+ *
+ * @param userConfirmed True if user choose to confirm browser launch,
+ * False if user choose not to confirm browser launch.
+ */
+ void notifyLaunchBrowser(boolean userConfirmed);
+
+ /**
+ * Notifies the SIM that a tone had been played. This method should be called
+ * only after the application gets notified by {@code
+ * CommandListener.onPlayTone()} or inside that method.
+ *
+ */
+ void notifyToneEnded();
+
+ /**
+ * Notifies the SIM that the user input a text. This method should be
+ * called only after the application gets notified by {@code
+ * CommandListener.onGetInput()} or inside that method.
+ *
+ * @param input The text string that the user has typed.
+ * @param helpRequired True if just help information is requested on a menu
+ * item rather than menu selection. False if the menu
+ * item is actually selected.
+ */
+ void notifyInput(String input, boolean helpRequired);
+
+ /**
+ * Notifies the SIM that the user input a key in Yes/No scenario.
+ * This method should be called only after the application gets notified by
+ * {@code CommandListener.onGetInkey()} or inside that method.
+ *
+ * @param yesNoResponse User's choice for Yes/No scenario.
+ * @param helpRequired True if just help information is requested on a menu
+ * item rather than menu selection. False if the menu
+ * item is actually selected.
+ */
+ void notifyInkey(boolean yesNoResponse, boolean helpRequired);
+
+ /**
+ * Notifies the SIM that the user input a key. This method should be called
+ * only after the application gets notified by {@code
+ * CommandListener.onGetInkey()} or inside that method.
+ *
+ * @param key The key that the user has typed. If the SIM required
+ * @param helpRequired True if just help information is requested on a menu
+ * item rather than menu selection. False if the menu
+ * item is actually selected.
+ */
+ void notifyInkey(char key, boolean helpRequired);
+
+ /**
+ * Notifies the SIM that no response was received from the user.
+ */
+ void notifyNoResponse();
+
+ /**
+ * Send terminal response for backward move in the proactive SIM session
+ * requested by the user
+ *
+ * Only available when responding following proactive commands
+ * DISPLAY_TEXT(0x21),
+ * GET_INKEY(0x22),
+ * GET_INPUT(0x23),
+ * SET_UP_MENU(0x25);
+ *
+ * @return true if stk can send backward move response
+ */
+ boolean backwardMove();
+
+ /**
+ * Send terminal response for proactive SIM session terminated by the user
+ *
+ * Only available when responding following proactive commands
+ * DISPLAY_TEXT(0x21),
+ * GET_INKEY(0x22),
+ * GET_INPUT(0x23),
+ * PLAY_TONE(0x20),
+ * SET_UP_MENU(0x25);
+ *
+ * @return true if stk can send terminate session response
+ */
+ boolean terminateSession();
+
+ /**
+ * Notifies the SIM that the user selected an item. This method should be
+ * called only after the application gets notified by {@code
+ * CommandListener.onSelectItem()} or inside that method.
+ *
+ * @param id The menu item that the user has selected.
+ * @param wantsHelp Indicates if the user requested help for the id item.
+ */
+ void notifySelectedItem(int id, boolean wantsHelp);
+
+ /**
+ * Notifies the SIM that No response was received from the user for display
+ * text message dialog.
+ *
+ * * @param terminationCode indication for display text termination. Uses
+ * {@code ResultCode } values.
+ */
+ public void notifyDisplayTextEnded(ResultCode terminationCode);
+
+ /**
+ * Notifies the SIM whether the user accepted the call or not. This method
+ * should be called only after the application gets notified by {@code
+ * CommandListener.onCallSetup()} or inside that method.
+ *
+ * @param accept True if the user has accepted the call, false if not.
+ */
+ void acceptOrRejectCall(boolean accept);
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/BerTlv.java b/telephony/java/com/android/internal/telephony/gsm/stk/BerTlv.java
new file mode 100644
index 0000000..f5268e5
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/BerTlv.java
@@ -0,0 +1,122 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import java.util.List;
+
+/**
+ * Class for representing BER-TLV objects.
+ *
+ * @see "ETSI TS 102 223 Annex C" for more information.
+ *
+ * {@hide}
+ */
+class BerTlv {
+ private int mTag = BER_UNKNOWN_TAG;
+ private List<ComprehensionTlv> mCompTlvs = null;
+
+ public static final int BER_UNKNOWN_TAG = 0x00;
+ public static final int BER_PROACTIVE_COMMAND_TAG = 0xd0;
+ public static final int BER_MENU_SELECTION_TAG = 0xd3;
+ public static final int BER_EVENT_DOWNLOAD_TAG = 0xd6;
+
+ private BerTlv(int tag, List<ComprehensionTlv> ctlvs) {
+ mTag = tag;
+ mCompTlvs = ctlvs;
+ }
+
+ /**
+ * Gets a list of ComprehensionTlv objects contained in this BER-TLV object.
+ *
+ * @return A list of COMPREHENSION-TLV object
+ */
+ public List<ComprehensionTlv> getComprehensionTlvs() {
+ return mCompTlvs;
+ }
+
+ /**
+ * Gets a tag id of the BER-TLV object.
+ *
+ * @return A tag integer.
+ */
+ public int getTag() {
+ return mTag;
+ }
+
+ /**
+ * Decodes a BER-TLV object from a byte array.
+ *
+ * @param data A byte array to decode from
+ * @return A BER-TLV object decoded
+ * @throws ResultException
+ */
+ public static BerTlv decode(byte[] data) throws ResultException {
+ int curIndex = 0;
+ int endIndex = data.length;
+ int tag, length = 0;
+
+ try {
+ /* tag */
+ tag = data[curIndex++] & 0xff;
+ if (tag != BER_PROACTIVE_COMMAND_TAG) {
+ // If the buffer doesn't contain proactive command tag, but
+ // start with a command details tlv object ==> skip the length
+ // parsing and look for tlv objects.
+ ComprehensionTlv ctlv = ComprehensionTlv.decode(data,
+ curIndex--);
+ if (ctlv.getTag() == ComprehensionTlvTag.COMMAND_DETAILS.value()) {
+ tag = BER_UNKNOWN_TAG;
+ curIndex--;
+ } else {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ } else {
+
+ /* length */
+ int temp = data[curIndex++] & 0xff;
+ if (temp < 0x80) {
+ length = temp;
+ } else if (temp == 0x81) {
+ temp = data[curIndex++] & 0xff;
+ if (temp < 0x80) {
+ throw new ResultException(
+ ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ length = temp;
+ } else {
+ throw new ResultException(
+ ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ }
+
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ } catch (ResultException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+
+ /* COMPREHENSION-TLVs */
+ if (endIndex - curIndex < length) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+
+ List<ComprehensionTlv> ctlvs = ComprehensionTlv.decodeMany(data,
+ curIndex);
+
+ return new BerTlv(tag, ctlvs);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/CommandListener.java b/telephony/java/com/android/internal/telephony/gsm/stk/CommandListener.java
new file mode 100644
index 0000000..5c9a3e9
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/CommandListener.java
@@ -0,0 +1,187 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import android.graphics.Bitmap;
+
+import java.util.BitSet;
+import java.util.List;
+
+
+/**
+ * Interface for command notification from STK Service to Apps
+ *
+ * {@hide}
+ */
+public interface CommandListener {
+
+ /**
+ * Call back function to be called when the session with the SIM ends.
+ * Application must go back to the main SIM Toolkit application screen when
+ * this is called.
+ */
+ void onSessionEnd();
+
+ /**
+ * Call back function to be called when the SIM wants a call to be set up.
+ * Application must call {@code AppInterface.acceptOrRejectCall()} after
+ * this method returns or inside this method.
+ *
+ * @param confirmMsg User confirmation phase message.
+ * @param textAttrs List of text attributes to be applied. Can be null.
+ * @param callMsg Call set up phase message.
+ */
+ void onCallSetup(String confirmMsg, List<TextAttribute> textAttrs,
+ String callMsg);
+
+ /**
+ * Call back function to be called for handling DISPLAY_TEXT proactive
+ * commands.
+ * @param text A text to be displayed
+ * @param textAttrs List of text attributes to be applied. Can be null.
+ * @param isHighPriority High priority
+ * @param userClear Wait for user to clear message if true, clear
+ * message after a delay if false.
+ */
+ void onDisplayText(String text, List<TextAttribute> textAttrs,
+ boolean isHighPriority, boolean userClear, boolean responseNeeded,
+ Bitmap icon);
+
+ /**
+ * Call back function to be called for handling SET_UP_MENU proactive
+ * commands. The menu can be retrieved by calling {@code
+ * AppInterface.getMainMenu}.
+ *
+ * @param menu application main menu.
+ */
+ void onSetUpMenu(Menu menu);
+
+ /**
+ * Call back function to be called for handling GET_INKEY proactive
+ * commands.
+ * Application must call {@code AppInterface.notifyInkey()} after this
+ * method returns or inside this method.
+ *
+ * @param text A text to be used as a prompt.
+ * @param textAttrs List of text attributes to be applied. Can be null.
+ * @param yesNo "Yes/No" response is requested if true. When this is
+ * true, {@code digitOnly} and {@code ucs2} are ignored.
+ * @param digitOnly Digits (0 to 9, *, # and +) only if true. Alphabet set
+ * if false.
+ * @param ucs2 UCS2 alphabet if true, SMS default alphabet if false.
+ * @param immediateResponse An immediate digit response (0 to 9, * and #)
+ * is required if true. User response shall be displayed
+ * and the terminal may allow alteration and/or
+ * confirmation if false.
+ * @param helpAvailable Help information available.
+ */
+ void onGetInkey(String text, List<TextAttribute> textAttrs, boolean yesNo, boolean digitOnly,
+ boolean ucs2, boolean immediateResponse, boolean helpAvailable);
+ /**
+ * Call back function to be called for handling GET_INPUT proactive
+ * commands. Application must call {@code AppInterface.notifyInput()} after
+ * this method returns or inside this method.
+ *
+ * @param text A text to be used as a prompt
+ * @param defaultText A text to be used as a default input
+ * @param minLen Mininum length of response (0 indicates there is no mininum
+ * length requirement).
+ * @param maxLen Maximum length of response (between 0 and 0xfe).
+ * @param noMaxLimit If true, there is no limit in maximum length of
+ * response.
+ * @param textAttrs List of text attributes to be applied. Can be null.
+ * @param digitOnly Digits (0 to 9, *, # and +) only if true. Alphabet set
+ * if false.
+ * @param ucs2 UCS2 alphabet if true, SMS default alphabet if false.
+ * @param echo Terminal may echo user input on the display if true. User
+ * input shall not be revealed in any way if false.
+ * @param helpAvailable Help information available.
+ */
+ void onGetInput(String text, String defaultText, int minLen, int maxLen,
+ boolean noMaxLimit, List<TextAttribute> textAttrs,
+ boolean digitOnly, boolean ucs2, boolean echo, boolean helpAvailable);
+
+ /**
+ * Call back function to be called for handling SELECT_ITEM proactive
+ * commands.
+ * Application must call {@code AppInterface.notifySelectedItem()} after
+ * this method returns or inside this method.
+ *
+ * @param menu Items menu.
+ * @param presentationType Presentation type of the choices.
+ */
+ void onSelectItem(Menu menu, PresentationType presentationType);
+
+ /**
+ * Call back function to be called for handling SET_UP_EVENT_LIST proactive
+ * commands.
+ * @param events BitSet object each bit of which represents an event
+ * that UICC wants the terminal to monitor.
+ * <ul>
+ * <li>0x00: MT call
+ * <li>0x01: Call connected
+ * <li>0x02: Call disconnected
+ * <li>0x03: Location status
+ * <li>0x04: User activity
+ * <li>0x05: Idle screen available
+ * <li>0x06: Card reader status
+ * <li>0x07: Language selection
+ * <li>0x08: Browser termination
+ * <li>0x09: Data available
+ * <li>0x0A: Channel status
+ * <li>0x0B: Access Technology Change
+ * <li>0x0C: Display parameters changed
+ * <li>0x0D: Local connection
+ * <li>0x0E: Network Search Mode Change
+ * <li>0x0F: Browsing status
+ * <li>0x10: Frames Information Change
+ * <li>0x11: reserved for 3GPP (I-WLAN Access Status)
+ * </ul>
+ * These values are defined in Service as UICC_EVENT_*.
+ * @throws ResultException must be BEYOND_TERMINAL_CAPABILITY
+ * if the ME is not able to successfully accept all events
+ */
+ void onSetUpEventList(BitSet events) throws ResultException;
+
+ /**
+ * Call back function to be called for handling LAUNCH_BROWSER proactive
+ * commands.
+ *
+ * @param useDefaultUrl If true, use the system default URL, otherwise use
+ * {@code url} as the URL.
+ * @param confirmMsg A text to be used as the user confirmation message. Can
+ * be null.
+ * @param confirmMsgAttrs List of text attributes to be applied to {code
+ * confirmMsgAttrs}. Can be null.
+ * @param mode Launch mode.
+ */
+ void onLaunchBrowser(String url, String confirmMsg,
+ List<TextAttribute> confirmMsgAttrs, LaunchBrowserMode mode);
+
+ /**
+ * Call back function to be called for handling PLAY_TONE proactive
+ * commands.
+ *
+ * @param tone Tone to be played
+ * @param text A text to be displayed. Can be null.
+ * @param textAttrs List of text attributes to be applied. Can be null.
+ * @param duration Time duration to play the tone.
+ * @throws ResultException
+ */
+ void onPlayTone(Tone tone, String text, List<TextAttribute> textAttrs,
+ Duration duration) throws ResultException;
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/CommandParams.java b/telephony/java/com/android/internal/telephony/gsm/stk/CommandParams.java
new file mode 100644
index 0000000..d39ad7b
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/CommandParams.java
@@ -0,0 +1,119 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import android.graphics.Bitmap;
+
+import java.util.List;
+
+/**
+ * Container class for proactive command parameters.
+ *
+ */
+class CommandParams {
+ public CtlvCommandDetails cmdDet;
+
+ CommandParams(CtlvCommandDetails cmdDet) {
+ this.cmdDet = cmdDet;
+ }
+}
+
+class CommonUIParams extends CommandParams {
+ String mText;
+ Bitmap mIcon;
+ boolean mIconSelfExplanatory;
+ TextAttribute mTextAttrs;
+
+ CommonUIParams(CtlvCommandDetails cmdDet, String text,
+ TextAttribute textAttrs) {
+ super(cmdDet);
+
+ mText = text;
+ mTextAttrs = textAttrs;
+ mIconSelfExplanatory = false;
+ mIcon = null;
+ }
+
+ void setIcon(Bitmap icon) {
+ mIcon = icon;
+ }
+
+ void setIconSelfExplanatory(boolean iconSelfExplanatory) {
+ mIconSelfExplanatory = iconSelfExplanatory;
+ }
+}
+
+class DisplayTextParams extends CommandParams {
+ String text = null;
+ Bitmap icon = null;
+ List<TextAttribute> textAttrs = null;
+ boolean immediateResponse = false;
+ boolean userClear = false;
+ boolean isHighPriority = false;
+
+ DisplayTextParams(CtlvCommandDetails cmdDet) {
+ super(cmdDet);
+ }
+}
+
+class GetInkeyParams extends CommandParams {
+ boolean isYesNo;
+ boolean isUcs2;
+
+ GetInkeyParams(CtlvCommandDetails cmdDet, boolean isYesNo,
+ boolean isUcs2) {
+ super(cmdDet);
+
+ this.isYesNo = isYesNo;
+ this.isUcs2 = isUcs2;
+ }
+}
+
+class GetInputParams extends CommandParams {
+ boolean isUcs2;
+ boolean isPacked;
+
+ GetInputParams(CtlvCommandDetails cmdDet, boolean isUcs2,
+ boolean isPacked) {
+ super(cmdDet);
+
+ this.isUcs2 = isUcs2;
+ this.isPacked = isPacked;
+ }
+}
+
+class SelectItemParams extends CommandParams {
+ Menu mMenu = null;
+ PresentationType mPresentationType;
+ int mIconLoadState = LOAD_NO_ICON;
+
+ // loading icons state parameters.
+ static final int LOAD_NO_ICON = 0;
+ static final int LOAD_TITLE_ICON = 1;
+ static final int LOAD_ITEMS_ICONS = 2;
+ static final int LOAD_TITLE_ITEMS_ICONS = 3;
+
+ SelectItemParams(CtlvCommandDetails cmdDet, Menu menu,
+ PresentationType presentationType, int iconLoadState) {
+ super(cmdDet);
+
+ mMenu = menu;
+ mPresentationType = presentationType;
+ mIconLoadState = iconLoadState;
+ }
+}
+
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/ComprehensionTlv.java b/telephony/java/com/android/internal/telephony/gsm/stk/ComprehensionTlv.java
new file mode 100644
index 0000000..3cf8ca6
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/ComprehensionTlv.java
@@ -0,0 +1,175 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Class for representing COMPREHENSION-TLV objects.
+ *
+ * @see "ETSI TS 101 220 subsection 7.1.1"
+ *
+ * {@hide}
+ */
+class ComprehensionTlv {
+ private int mTag;
+ private boolean mCr;
+ private int mLength;
+ private int mValueIndex;
+ private byte[] mRawValue;
+
+ /**
+ * Constructor. Private on purpose. Use
+ * {@link #decodeMany(byte[], int) decodeMany} or
+ * {@link #decode(byte[], int) decode} method.
+ *
+ * @param tag The tag for this object
+ * @param cr Comprehension Required flag
+ * @param length Length of the value
+ * @param data Byte array containing the value
+ * @param valueIndex Index in data at which the value starts
+ */
+ private ComprehensionTlv(int tag, boolean cr, int length, byte[] data,
+ int valueIndex) {
+ mTag = tag;
+ mCr = cr;
+ mLength = length;
+ mValueIndex = valueIndex;
+ mRawValue = data;
+ }
+
+ public int getTag() {
+ return mTag;
+ }
+
+ public boolean isComprehensionRequired() {
+ return mCr;
+ }
+
+ public int getLength() {
+ return mLength;
+ }
+
+ public int getValueIndex() {
+ return mValueIndex;
+ }
+
+ public byte[] getRawValue() {
+ return mRawValue;
+ }
+
+ /**
+ * Parses a list of COMPREHENSION-TLV objects from a byte array.
+ *
+ * @param data A byte array containing data to be parsed
+ * @param startIndex Index in data at which to start parsing
+ * @return A list of COMPREHENSION-TLV objects parsed
+ * @throws ResultException
+ */
+ public static List<ComprehensionTlv> decodeMany(byte[] data, int startIndex)
+ throws ResultException {
+ ArrayList<ComprehensionTlv> items = new ArrayList<ComprehensionTlv>();
+ int endIndex = data.length;
+ while (startIndex < endIndex) {
+ ComprehensionTlv ctlv = ComprehensionTlv.decode(data, startIndex);
+ items.add(ctlv);
+ startIndex = ctlv.mValueIndex + ctlv.mLength;
+ }
+
+ return items;
+ }
+
+ /**
+ * Parses an COMPREHENSION-TLV object from a byte array.
+ *
+ * @param data A byte array containing data to be parsed
+ * @param startIndex Index in data at which to start parsing
+ * @return A COMPREHENSION-TLV object parsed
+ * @throws ResultException
+ */
+ public static ComprehensionTlv decode(byte[] data, int startIndex)
+ throws ResultException {
+ try {
+ int curIndex = startIndex;
+ int endIndex = data.length;
+
+ /* tag */
+ int tag;
+ boolean cr; // Comprehension required flag
+ int temp = data[curIndex++] & 0xff;
+ switch (temp) {
+ case 0:
+ case 0xff:
+ case 0x80:
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+
+ case 0x7f: // tag is in three-byte format
+ tag = ((data[curIndex] & 0xff) << 8)
+ | (data[curIndex + 1] & 0xff);
+ cr = (tag & 0x8000) != 0;
+ tag &= ~0x8000;
+ curIndex += 2;
+ break;
+
+ default: // tag is in single-byte format
+ tag = temp;
+ cr = (tag & 0x80) != 0;
+ tag &= ~0x80;
+ break;
+ }
+
+ /* length */
+ int length;
+ temp = data[curIndex++] & 0xff;
+ if (temp < 0x80) {
+ length = temp;
+ } else if (temp == 0x81) {
+ length = data[curIndex++] & 0xff;
+ if (length < 0x80) {
+ throw new ResultException(
+ ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ } else if (temp == 0x82) {
+ length = ((data[curIndex] & 0xff) << 8)
+ | (data[curIndex + 1] & 0xff);
+ curIndex += 2;
+ if (length < 0x100) {
+ throw new ResultException(
+ ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ } else if (temp == 0x83) {
+ length = ((data[curIndex] & 0xff) << 16)
+ | ((data[curIndex + 1] & 0xff) << 8)
+ | (data[curIndex + 2] & 0xff);
+ curIndex += 3;
+ if (length < 0x10000) {
+ throw new ResultException(
+ ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ } else {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+
+ return new ComprehensionTlv(tag, cr, length, data, curIndex);
+
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/CtlvCommandDetails.java b/telephony/java/com/android/internal/telephony/gsm/stk/CtlvCommandDetails.java
new file mode 100644
index 0000000..6389537
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/CtlvCommandDetails.java
@@ -0,0 +1,29 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Class for Command Detailes object of proactive commands from SIM.
+ * {@hide}
+ */
+public class CtlvCommandDetails {
+ public boolean compRequired;
+ public int commandNumber;
+ public int typeOfCommand;
+ public int commandQualifier;
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/Duration.java b/telephony/java/com/android/internal/telephony/gsm/stk/Duration.java
new file mode 100644
index 0000000..9ca8fb5
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/Duration.java
@@ -0,0 +1,52 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Class for representing "Duration" object for STK.
+ *
+ * {@hide}
+ */
+public class Duration {
+ public int timeInterval;
+ public TimeUnit timeUnit;
+
+ public enum TimeUnit {
+ MINUTE(0x00),
+ SECOND(0x01),
+ TENTH_SECOND(0x02);
+
+ private int mValue;
+
+ TimeUnit(int value) {
+ mValue = value;
+ }
+
+ public int value() {
+ return mValue;
+ }
+ }
+
+ /**
+ * @param timeInterval Between 1 and 255 inclusive.
+ */
+ public Duration(int timeInterval, TimeUnit timeUnit) {
+ this.timeInterval = timeInterval;
+ this.timeUnit = timeUnit;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/FontSize.java b/telephony/java/com/android/internal/telephony/gsm/stk/FontSize.java
new file mode 100644
index 0000000..bd4f49f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/FontSize.java
@@ -0,0 +1,50 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Enumeration for representing text font size.
+ *
+ * {@hide}
+ */
+public enum FontSize {
+ NORMAL(0x0),
+ LARGE(0x1),
+ SMALL(0x2);
+
+ private int mValue;
+
+ FontSize(int value) {
+ mValue = value;
+ }
+
+ /**
+ * Create a FontSize object.
+ * @param value Integer value to be converted to a FontSize object.
+ * @return FontSize object whose value is {@code value}. If no
+ * FontSize object has that value, null is returned.
+ */
+ public static FontSize fromInt(int value) {
+ for (FontSize e : FontSize.values()) {
+ if (e.mValue == value) {
+ return e;
+ }
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/IconLoader.java b/telephony/java/com/android/internal/telephony/gsm/stk/IconLoader.java
new file mode 100644
index 0000000..ee91541
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/IconLoader.java
@@ -0,0 +1,343 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import com.android.internal.telephony.gsm.SIMFileHandler;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * Class for loading icons from the SIM card. Has two states: single, for loading
+ * one icon. Multi, for loading icons list.
+ *
+ */
+class IconLoader extends Handler {
+ // members
+ int mState = STATE_SINGLE_ICON;
+ ImageDescriptor mId = null;
+ Bitmap mCurrentIcon = null;
+ int mRecordNumber;
+ SIMFileHandler mSimFH = null;
+ Message mEndMsg = null;
+ byte[] mIconData = null;
+ // multi icons state members
+ int[] mRecordNumbers = null;
+ int mCurrentRecordIndex = 0;
+ Bitmap[] mIcons = null;
+
+ private static IconLoader sLoader = null;
+
+ // Loader state values.
+ private static final int STATE_SINGLE_ICON = 1;
+ private static final int STATE_MULTI_ICONS = 2;
+
+ // Finished loading single record from a linear-fixed EF-IMG.
+ private static final int EVENT_READ_EF_IMG_RECOED_DONE = 1;
+ // Finished loading single icon from a Transparent DF-Graphics.
+ private static final int EVENT_READ_ICON_DONE = 2;
+ // Finished loading single colour icon lookup table.
+ private static final int EVENT_READ_CLUT_DONE = 3;
+
+ // Color lookup table offset inside the EF.
+ private static final int CLUT_LOCATION_OFFSET = 4;
+ // CLUT entry size, {Red, Green, Black}
+ private static final int CLUT_ENTRY_SIZE = 3;
+
+ static private final String TAG = "STK IconLoader";
+
+ private IconLoader(Looper looper , SIMFileHandler fh) {
+ super(looper);
+ mSimFH = fh;
+ }
+
+ static IconLoader getInstance(Handler caller , SIMFileHandler fh) {
+ if (sLoader != null) {
+ return sLoader;
+ }
+ if (fh != null) {
+ HandlerThread thread = new HandlerThread("Stk Icon Laoder");
+ thread.start();
+ return new IconLoader(thread.getLooper(), fh);
+ }
+ return null;
+ }
+
+ void loadIcons(int[] recordNumbers, Message msg) {
+ if (recordNumbers == null || recordNumbers.length == 0 || msg == null) {
+ return;
+ }
+ mEndMsg = msg;
+ // initialize multi icons load variables.
+ mIcons = new Bitmap[recordNumbers.length];
+ mRecordNumbers = recordNumbers;
+ mCurrentRecordIndex = 0;
+ mState = STATE_MULTI_ICONS;
+ startLoadingIcon(recordNumbers[0]);
+ }
+
+ void loadIcon(int recordNumber, Message msg) {
+ if (msg == null) {
+ return;
+ }
+ mEndMsg = msg;
+ mState = STATE_SINGLE_ICON;
+ startLoadingIcon(recordNumber);
+ }
+
+ private void startLoadingIcon(int recordNumber) {
+ // Reset the load variables.
+ mId = null;
+ mIconData = null;
+ mCurrentIcon = null;
+ mRecordNumber = recordNumber;
+
+ // start the first phase ==> loading Image Descriptor.
+ readId();
+ }
+
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+
+ try {
+ switch (msg.what) {
+ case EVENT_READ_EF_IMG_RECOED_DONE:
+ ar = (AsyncResult) msg.obj;
+ if (handleImageDescriptor((byte[]) ar.result)) {
+ readIconData();
+ } else {
+ throw new Exception("Unable to parse image descriptor");
+ }
+ break;
+ case EVENT_READ_ICON_DONE:
+ ar = (AsyncResult) msg.obj;
+ byte[] rawData = ((byte[]) ar.result);
+ if (mId.codingScheme == ImageDescriptor.CODING_SCHEME_BASIC) {
+ mCurrentIcon = parseToBnW(rawData, rawData.length);
+ postIcon();
+ } else if (mId.codingScheme == ImageDescriptor.CODING_SCHEME_COLOUR) {
+ mIconData = rawData;
+ readClut();
+ }
+ break;
+ case EVENT_READ_CLUT_DONE:
+ ar = (AsyncResult) msg.obj;
+ byte [] clut = ((byte[]) ar.result);
+ mCurrentIcon = parseToRGB(mIconData, mIconData.length,
+ false, clut);
+ postIcon();
+ break;
+ }
+ } catch (Exception e) {
+ Log.d(TAG, "Icon load failed");
+ // post null icon back to the caller.
+ postIcon();
+ }
+ }
+
+ /**
+ * Handles Image descriptor parsing and required processing. This is the
+ * first step required to handle retrieving icons from the SIM.
+ *
+ * @param data byte [] containing Image Instance descriptor as defined in
+ * TS 51.011.
+ */
+ private boolean handleImageDescriptor(byte[] rawData) {
+ mId = ImageDescriptor.parse(rawData, 1);
+ if (mId == null) {
+ return false;
+ }
+ return true;
+ }
+
+ // Start reading colour lookup table from SIM card.
+ private void readClut() {
+ int length = mIconData[3] * CLUT_ENTRY_SIZE;
+ Message msg = this.obtainMessage(EVENT_READ_CLUT_DONE);
+ mSimFH.loadEFImgTransparent(mId.imageId,
+ mIconData[CLUT_LOCATION_OFFSET],
+ mIconData[CLUT_LOCATION_OFFSET + 1], length, msg);
+ }
+
+ // Start reading Image Descriptor from SIM card.
+ private void readId() {
+ Message msg = this.obtainMessage(EVENT_READ_EF_IMG_RECOED_DONE);
+ mSimFH.loadEFImgLinearFixed(mRecordNumber, msg);
+ }
+
+ // Start reading icon bytes array from SIM card.
+ private void readIconData() {
+ Message msg = this.obtainMessage(EVENT_READ_ICON_DONE);
+ mSimFH.loadEFImgTransparent(mId.imageId, 0, 0, mId.length ,msg);
+ }
+
+ // When all is done pass icon back to caller.
+ private void postIcon() {
+ if (mState == STATE_SINGLE_ICON) {
+ mEndMsg.obj = mCurrentIcon;
+ mEndMsg.sendToTarget();
+ } else if (mState == STATE_MULTI_ICONS) {
+ mIcons[mCurrentRecordIndex++] = mCurrentIcon;
+ // If not all icons were loaded, start loading the next one.
+ if (mCurrentRecordIndex < mRecordNumbers.length) {
+ startLoadingIcon(mRecordNumbers[mCurrentRecordIndex]);
+ } else {
+ mEndMsg.obj = mIcons;
+ mEndMsg.sendToTarget();
+ }
+ }
+ }
+
+ /**
+ * Convert a TS 131.102 image instance of code scheme '11' into Bitmap
+ * @param data The raw data
+ * @param length The length of image body
+ * @return The bitmap
+ */
+ public static Bitmap parseToBnW(byte[] data, int length){
+ int valueIndex = 0;
+ int width = data[valueIndex++] & 0xFF;
+ int height = data[valueIndex++] & 0xFF;
+ int numOfPixels = width*height;
+
+ int[] pixels = new int[numOfPixels];
+
+ int pixelIndex = 0;
+ int bitIndex = 7;
+ byte currentByte = 0x00;
+ while (pixelIndex < numOfPixels) {
+ // reassign data and index for every byte (8 bits).
+ if (pixelIndex % 8 == 0) {
+ currentByte = data[valueIndex++];
+ bitIndex = 7;
+ }
+ pixels[pixelIndex++] = bitToBnW((currentByte >> bitIndex-- ) & 0x01);
+ };
+
+ if (pixelIndex != numOfPixels) {
+ Log.e(TAG, "parse end and size error");
+ }
+ return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
+ }
+
+ /**
+ * Decode one bit to a black and white color:
+ * 0 is black
+ * 1 is white
+ * @param bit to decode
+ * @return RGB color
+ */
+ private static int bitToBnW(int bit){
+ if(bit == 1){
+ return Color.WHITE;
+ } else {
+ return Color.BLACK;
+ }
+ }
+
+ /**
+ * a TS 131.102 image instance of code scheme '11' into color Bitmap
+ *
+ * @param data The raw data
+ * @param length the length of image body
+ * @param transparency with or without transparency
+ * @param clut coulor lookup table
+ * @return The color bitmap
+ */
+ public static Bitmap parseToRGB(byte[] data, int length,
+ boolean transparency, byte[] clut) {
+ int valueIndex = 0;
+ int width = data[valueIndex++] & 0xFF;
+ int height = data[valueIndex++] & 0xFF;
+ int bitsPerImg = data[valueIndex++] & 0xFF;
+ int numOfClutEntries = data[valueIndex++] & 0xFF;
+
+ if (true == transparency) {
+ clut[numOfClutEntries - 1] = Color.TRANSPARENT;
+ }
+
+ int numOfPixels = width * height;
+ int[] pixels = new int[numOfPixels];
+
+ valueIndex = 6;
+ int pixelIndex = 0;
+ int bitsStartOffset = 8 - bitsPerImg;
+ int bitIndex = bitsStartOffset;
+ byte currentByte = data[valueIndex++];
+ int mask = getMask(bitsPerImg);
+ boolean bitsOverlaps = (8 % bitsPerImg == 0);
+ while (pixelIndex < numOfPixels) {
+ // reassign data and index for every byte (8 bits).
+ if (bitIndex < 0) {
+ currentByte = data[valueIndex++];
+ bitIndex = bitsOverlaps ? (bitsStartOffset) : (bitIndex * -1);
+ }
+ int clutEntry = ((currentByte >> bitIndex) & mask);
+ int clutIndex = clutEntry * CLUT_ENTRY_SIZE;
+ pixels[pixelIndex++] = Color.rgb(clut[clutIndex],
+ clut[clutIndex + 1], clut[clutIndex + 2]);
+ bitIndex -= bitsPerImg;
+ }
+
+ return Bitmap.createBitmap(pixels, width, height,
+ Bitmap.Config.ARGB_8888);
+ }
+
+ /**
+ * Calculate bit mask for a given number of bits. The mask should enable to
+ * make a bitwise and to the given number of bits.
+ * @param numOfBits number of bits to calculate mask for.
+ * @return bit mask
+ */
+ private static int getMask(int numOfBits) {
+ int mask = 0x00;
+
+ switch (numOfBits) {
+ case 1:
+ mask = 0x01;
+ break;
+ case 2:
+ mask = 0x03;
+ break;
+ case 3:
+ mask = 0x07;
+ break;
+ case 4:
+ mask = 0x0F;
+ break;
+ case 5:
+ mask = 0x1F;
+ break;
+ case 6:
+ mask = 0x3F;
+ break;
+ case 7:
+ mask = 0x7F;
+ break;
+ case 8:
+ mask = 0xFF;
+ break;
+ }
+ return mask;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/ImageDescriptor.java b/telephony/java/com/android/internal/telephony/gsm/stk/ImageDescriptor.java
new file mode 100644
index 0000000..e1a20f6
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/ImageDescriptor.java
@@ -0,0 +1,80 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+public class ImageDescriptor {
+ // members
+ int width;
+ int height;
+ int codingScheme;
+ int imageId;
+ int highOffset;
+ int lowOffset;
+ int length;
+
+ // constants
+ static final int CODING_SCHEME_BASIC = 0x11;
+ static final int CODING_SCHEME_COLOUR = 0x21;
+
+ public static final int ID_LENGTH = 9;
+
+ private static final String TAG = "ImageDescriptor";
+
+ ImageDescriptor() {
+ width = 0;
+ height = 0;
+ codingScheme = 0;
+ imageId = 0;
+ highOffset = 0;
+ lowOffset = 0;
+ length = 0;
+ }
+
+ /**
+ * Extract descriptor information about image instance.
+ *
+ * @param rawData
+ * @param valueIndex
+ * @return ImageDescriptor
+ */
+ static ImageDescriptor parse(byte[] rawData, int valueIndex) {
+ ImageDescriptor d = new ImageDescriptor();
+ try {
+ d.width = rawData[valueIndex++] & 0xff;
+ d.height = rawData[valueIndex++] & 0xff;
+ d.codingScheme = rawData[valueIndex++] & 0xff;
+
+ // parse image id
+ d.imageId = (rawData[valueIndex++] & 0xff) << 8;
+ d.imageId |= rawData[valueIndex++] & 0xff;
+ // parse offset
+ d.highOffset = (rawData[valueIndex++] & 0xff); // high byte offset
+ d.lowOffset = rawData[valueIndex++] & 0xff; // low byte offset
+
+ d.length = ((rawData[valueIndex++] & 0xff) << 8 | (rawData[valueIndex++] & 0xff));
+ } catch (IndexOutOfBoundsException e) {
+ Log.d(TAG, "failed parsing image descriptor");
+ d = null;
+ }
+ return d;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/Item.java b/telephony/java/com/android/internal/telephony/gsm/stk/Item.java
new file mode 100644
index 0000000..0122c86
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/Item.java
@@ -0,0 +1,69 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents an Item COMPREHENSION-TLV object.
+ *
+ * {@hide}
+ */
+public class Item implements Parcelable {
+ /** Identifier of the item. */
+ public int id;
+ /** Text string of the item. */
+ public String text;
+ /** Icon of the item */
+ public Bitmap icon;
+
+ public Item(int id, String text) {
+ this.id = id;
+ this.text = text;
+ this.icon = null;
+ }
+
+ public Item(Parcel in) {
+ id = in.readInt();
+ text = in.readString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(id);
+ dest.writeString(text);
+ }
+
+ public static final Parcelable.Creator<Item> CREATOR = new Parcelable.Creator<Item>() {
+ public Item createFromParcel(Parcel in) {
+ return new Item(in);
+ }
+
+ public Item[] newArray(int size) {
+ return new Item[size];
+ }
+ };
+
+ public String toString() {
+ return text;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/LaunchBrowserMode.java b/telephony/java/com/android/internal/telephony/gsm/stk/LaunchBrowserMode.java
new file mode 100644
index 0000000..302273c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/LaunchBrowserMode.java
@@ -0,0 +1,35 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Browser launch mode for LAUNCH BROWSER proactive command.
+ *
+ * {@hide}
+ */
+public enum LaunchBrowserMode {
+ /** Launch browser if not already launched. */
+ LAUNCH_IF_NOT_ALREADY_LAUNCHED,
+ /**
+ * Use the existing browser (the browser shall not use the active existing
+ * secured session).
+ */
+ USE_EXISTING_BROWSER,
+ /** Close the existing browser session and launch new browser session. */
+ LAUNCH_NEW_BROWSER;
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/Menu.java b/telephony/java/com/android/internal/telephony/gsm/stk/Menu.java
new file mode 100644
index 0000000..5f9de15
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/Menu.java
@@ -0,0 +1,99 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Menu implements Parcelable {
+ public List<Item> items;
+ public String title;
+ public List<TextAttribute> titleAttrs;
+ public Bitmap titleIcon;
+ public int defaultItem;
+ public boolean softKeyPreferred;
+ public boolean helpAvailable;
+ public boolean titleIconSelfExplanatory;
+ public boolean itemsIconSelfExplanatory;
+
+ public Menu() {
+ // Create an empty list.
+ this.items = new ArrayList<Item>();
+ this.title = null;
+ this.titleAttrs = null;
+ this.defaultItem = 0;
+ this.softKeyPreferred = false;
+ this.helpAvailable = false;
+ this.titleIconSelfExplanatory = false;
+ this.titleIcon = null;
+ }
+
+ public Menu(List<Item> items, String title, List<TextAttribute> titleAttrs,
+ boolean softKeyPreferred, boolean helpAvailable, int defaultItem) {
+ this.items = items;
+ this.title = title;
+ this.titleAttrs = titleAttrs;
+ this.defaultItem = defaultItem;
+ this.softKeyPreferred = softKeyPreferred;
+ this.helpAvailable = helpAvailable;
+ }
+
+ private Menu(Parcel in) {
+ title = in.readString();
+ // rebuild items list.
+ items = new ArrayList<Item>();
+ int size = in.readInt();
+ for (int i=0; i<size; i++) {
+ Item item = in.readParcelable(null);
+ items.add(item);
+ }
+ defaultItem = in.readInt();
+ softKeyPreferred = in.readInt() == 1 ? true : false;
+ helpAvailable = in.readInt() == 1 ? true : false;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(title);
+ // write items list to the parcel.
+ int size = items.size();
+ dest.writeInt(size);
+ for (int i=0; i<size; i++) {
+ dest.writeParcelable(items.get(i), flags);
+ }
+ dest.writeInt(defaultItem);
+ dest.writeInt(softKeyPreferred ? 1 : 0);
+ dest.writeInt(helpAvailable ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<Menu> CREATOR = new Parcelable.Creator<Menu>() {
+ public Menu createFromParcel(Parcel in) {
+ return new Menu(in);
+ }
+
+ public Menu[] newArray(int size) {
+ return new Menu[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/PresentationType.java b/telephony/java/com/android/internal/telephony/gsm/stk/PresentationType.java
new file mode 100644
index 0000000..71bdcdc
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/PresentationType.java
@@ -0,0 +1,32 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Presentation types for SELECT TYPE proactive command.
+ *
+ * {@hide}
+ */
+public enum PresentationType {
+ /** Presentation type is not specified */
+ NOT_SPECIFIED,
+ /** Presentation as a choice of data values */
+ DATA_VALUES,
+ /** Presentation as a choice of navigation options */
+ NAVIGATION_OPTIONS;
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/ResultCode.java b/telephony/java/com/android/internal/telephony/gsm/stk/ResultCode.java
new file mode 100644
index 0000000..6559c73
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/ResultCode.java
@@ -0,0 +1,177 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Enumeration for the return code in TERMINAL RESPONSE.
+ * To get the actual return code for each enum value, call {@link #code() code}
+ * method.
+ *
+ * {@hide}
+ */
+public enum ResultCode {
+
+ /*
+ * Results '0X' and '1X' indicate that the command has been performed.
+ */
+
+ /** Command performed successfully */
+ OK(0x00),
+
+ /** Command performed with partial comprehension */
+ PRFRMD_WITH_PARTIAL_COMPREHENSION(0x01),
+
+ /** Command performed, with missing information */
+ PRFRMD_WITH_MISSING_INFO(0x02),
+
+ /** REFRESH performed with additional EFs read */
+ PRFRMD_WITH_ADDITIONAL_EFS_READ(0x03),
+
+ /**
+ * Command performed successfully, but requested icon could not be
+ * displayed
+ */
+ PRFRMD_ICON_NOT_DISPLAYED(0x04),
+
+ /** Command performed, but modified by call control by NAA */
+ PRFRMD_MODIFIED_BY_NAA(0x05),
+
+ /** Command performed successfully, limited service */
+ PRFRMD_LIMITED_SERVICE(0x06),
+
+ /** Command performed with modification */
+ PRFRMD_WITH_MODIFICATION(0x07),
+
+ /** REFRESH performed but indicated NAA was not active */
+ PRFRMD_NAA_NOT_ACTIVE(0x08),
+
+ /** Command performed successfully, tone not played */
+ PRFRMD_TONE_NOT_PLAYED(0x09),
+
+ /** Proactive UICC session terminated by the user */
+ UICC_SESSION_TERM_BY_USER(0x10),
+
+ /** Backward move in the proactive UICC session requested by the user */
+ BACKWARD_MOVE_BY_USER(0x11),
+
+ /** No response from user */
+ NO_RESPONSE_FROM_USER(0x12),
+
+ /** Help information required by the user */
+ HELP_INFO_REQUIRED(0x13),
+
+ /** USSD or SS transaction terminated by the user */
+ USSD_SS_SESSION_TERM_BY_USER(0x14),
+
+
+ /*
+ * Results '2X' indicate to the UICC that it may be worth re-trying the
+ * command at a later opportunity.
+ */
+
+ /** Terminal currently unable to process command */
+ TERMINAL_CRNTLY_UNABLE_TO_PROCESS(0x20),
+
+ /** Network currently unable to process command */
+ NETWORK_CRNTLY_UNABLE_TO_PROCESS(0x21),
+
+ /** User did not accept the proactive command */
+ USER_NOT_ACCEPT(0x22),
+
+ /** User cleared down call before connection or network release */
+ USER_CLEAR_DOWN_CALL(0x23),
+
+ /** Action in contradiction with the current timer state */
+ CONTRADICTION_WITH_TIMER(0x24),
+
+ /** Interaction with call control by NAA, temporary problem */
+ NAA_CALL_CONTROL_TEMPORARY(0x25),
+
+ /** Launch browser generic error code */
+ LAUNCH_BROWSER_ERROR(0x26),
+
+ /** MMS temporary problem. */
+ MMS_TEMPORARY(0x27),
+
+
+ /*
+ * Results '3X' indicate that it is not worth the UICC re-trying with an
+ * identical command, as it will only get the same response. However, the
+ * decision to retry lies with the application.
+ */
+
+ /** Command beyond terminal's capabilities */
+ BEYOND_TERMINAL_CAPABILITY(0x30),
+
+ /** Command type not understood by terminal */
+ CMD_TYPE_NOT_UNDERSTOOD(0x31),
+
+ /** Command data not understood by terminal */
+ CMD_DATA_NOT_UNDERSTOOD(0x32),
+
+ /** Command number not known by terminal */
+ CMD_NUM_NOT_KNOWN(0x33),
+
+ /** SS Return Error */
+ SS_RETURN_ERROR(0x34),
+
+ /** SMS RP-ERROR */
+ SMS_RP_ERROR(0x35),
+
+ /** Error, required values are missing */
+ REQUIRED_VALUES_MISSING(0x36),
+
+ /** USSD Return Error */
+ USSD_RETURN_ERROR(0x37),
+
+ /** MultipleCard commands error */
+ MULTI_CARDS_CMD_ERROR(0x38),
+
+ /**
+ * Interaction with call control by USIM or MO short message control by
+ * USIM, permanent problem
+ */
+ USIM_CALL_CONTROL_PERMANENT(0x39),
+
+ /** Bearer Independent Protocol error */
+ BIP_ERROR(0x3a),
+
+ /** Access Technology unable to process command */
+ ACCESS_TECH_UNABLE_TO_PROCESS(0x3b),
+
+ /** Frames error */
+ FRAMES_ERROR(0x3c),
+
+ /** MMS Error */
+ MMS_ERROR(0x3d);
+
+
+ private int mCode;
+
+ ResultCode(int code) {
+ mCode = code;
+ }
+
+ /**
+ * Retrieves the actual result code that this object represents.
+ * @return Actual result code
+ */
+ public int code() {
+ return mCode;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/ResultException.java b/telephony/java/com/android/internal/telephony/gsm/stk/ResultException.java
new file mode 100644
index 0000000..2eb16c9
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/ResultException.java
@@ -0,0 +1,76 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Class for errors in the Result object.
+ *
+ * {@hide}
+ */
+public class ResultException extends StkException {
+ private ResultCode mResult;
+ private int mAdditionalInfo;
+
+ public ResultException(ResultCode result) {
+ super();
+
+ // ETSI TS 102 223, 8.12 -- For the general results '20', '21', '26',
+ // '38', '39', '3A', '3C', and '3D', it is mandatory for the terminal
+ // to provide a specific cause value as additional information.
+ switch (result) {
+ case TERMINAL_CRNTLY_UNABLE_TO_PROCESS: // 0x20
+ case NETWORK_CRNTLY_UNABLE_TO_PROCESS: // 0x21
+ case LAUNCH_BROWSER_ERROR: // 0x26
+ case MULTI_CARDS_CMD_ERROR: // 0x38
+ case USIM_CALL_CONTROL_PERMANENT: // 0x39
+ case BIP_ERROR: // 0x3a
+ case FRAMES_ERROR: // 0x3c
+ case MMS_ERROR: // 0x3d
+ throw new AssertionError(
+ "For result code, " + result +
+ ", additional information must be given!");
+ }
+
+ mResult = result;
+ mAdditionalInfo = -1;
+ }
+
+ public ResultException(ResultCode result, int additionalInfo) {
+ super();
+
+ if (additionalInfo < 0) {
+ throw new AssertionError(
+ "Additional info must be greater than zero!");
+ }
+
+ mResult = result;
+ mAdditionalInfo = additionalInfo;
+ }
+
+ public ResultCode result() {
+ return mResult;
+ }
+
+ public boolean hasAdditionalInfo() {
+ return mAdditionalInfo >= 0;
+ }
+
+ public int additionalInfo() {
+ return mAdditionalInfo;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/Service.java b/telephony/java/com/android/internal/telephony/gsm/stk/Service.java
new file mode 100644
index 0000000..e002202
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/Service.java
@@ -0,0 +1,2467 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+
+import com.android.internal.telephony.gsm.CommandsInterface;
+import com.android.internal.telephony.gsm.EncodeException;
+import com.android.internal.telephony.gsm.GsmAlphabet;
+import com.android.internal.telephony.gsm.GsmSimCard;
+import com.android.internal.telephony.gsm.SIMFileHandler;
+import com.android.internal.telephony.gsm.SIMRecords;
+import com.android.internal.telephony.gsm.SimUtils;
+import com.android.internal.telephony.gsm.stk.Duration.TimeUnit;
+
+import android.util.Config;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Enumeration for representing the tag value of COMPREHENSION-TLV objects. If
+ * you want to get the actual value, call {@link #value() value} method.
+ *
+ * {@hide}
+ */
+enum ComprehensionTlvTag {
+ COMMAND_DETAILS(0x01),
+ DEVICE_IDENTITIES(0x02),
+ RESULT(0x03),
+ DURATION(0x04),
+ ALPHA_ID(0x05),
+ USSD_STRING(0x0a),
+ TEXT_STRING(0x0d),
+ TONE(0x0e),
+ ITEM(0x0f),
+ ITEM_ID(0x10),
+ RESPONSE_LENGTH(0x11),
+ FILE_LIST(0x12),
+ HELP_REQUEST(0x15),
+ DEFAULT_TEXT(0x17),
+ EVENT_LIST(0x19),
+ ICON_ID(0x1e),
+ ITEM_ICON_ID_LIST(0x1f),
+ IMMEDIATE_RESPONSE(0x2b),
+ LANGUAGE(0x2d),
+ URL(0x31),
+ BROWSER_TERMINATION_CAUSE(0x34),
+ TEXT_ATTRIBUTE(0x50);
+
+ private int mValue;
+
+ ComprehensionTlvTag(int value) {
+ mValue = value;
+ }
+
+ /**
+ * Returns the actual value of this COMPREHENSION-TLV object.
+ *
+ * @return Actual tag value of this object
+ */
+ public int value() {
+ return mValue;
+ }
+}
+
+/**
+ * Enumeration for representing "Type of Command" of proactive commands. If you
+ * want to create a CommandType object, call the static method {@link
+ * #fromInt(int) fromInt}.
+ *
+ * {@hide}
+ */
+enum CommandType {
+ DISPLAY_TEXT(0x21),
+ GET_INKEY(0x22),
+ GET_INPUT(0x23),
+ LAUNCH_BROWSER(0x15),
+ PLAY_TONE(0x20),
+ REFRESH(0x01),
+ SELECT_ITEM(0x24),
+ SEND_SS(0x11),
+ SEND_USSD(0x12),
+ SEND_SMS(0x13),
+ SEND_DTMF(0x14),
+ SET_UP_EVENT_LIST(0x05),
+ SET_UP_IDLE_MODE_TEXT(0x28),
+ SET_UP_MENU(0x25),
+ SET_UP_CALL(0x10);
+
+ private int mValue;
+
+ CommandType(int value) {
+ mValue = value;
+ }
+
+ /**
+ * Create a CommandType object.
+ *
+ * @param value Integer value to be converted to a CommandType object.
+ * @return CommandType object whose "Type of Command" value is {@code
+ * value}. If no CommandType object has that value, null is
+ * returned.
+ */
+ public static CommandType fromInt(int value) {
+ for (CommandType e : CommandType.values()) {
+ if (e.mValue == value) {
+ return e;
+ }
+ }
+ return null;
+ }
+}
+
+/**
+ * Main class that implements SIM Toolkit Service.
+ *
+ * {@hide}
+ */
+public class Service extends Handler implements AppInterface {
+
+ // Service members.
+ private static Service sInstance;
+ private CommandsInterface mCmdIf;
+ private SIMRecords mSimRecords;
+ private Context mContext;
+ private GsmSimCard mSimCard;
+ private CommandListener mCmdListener;
+ private Object mCmdListenerLock = new Object();
+ private CommandParams mCmdParams = null;
+ private CommandParams mNextCmdParams = null;
+ private State mState = State.IDLE;
+ private Menu mMainMenu = null;
+ private String mServiceName = "";
+ private NotificationManager mNm = null;
+ private int mAppIndicator = APP_INDICATOR_PRE_BOOT;
+ private int mInstallIndicator = APP_INDICATOR_UNINSTALLED;
+ private IconLoader mIconLoader = null;
+
+ private static final String TAG = "STK";
+
+ // Service constants.
+ private static final int EVENT_SESSION_END = 1;
+ private static final int EVENT_PROACTIVE_COMMAND = 2;
+ private static final int EVENT_EVENT_NOTIFY = 3;
+ private static final int EVENT_CALL_SETUP = 4;
+ // Events to signal SIM presence or absent in the device.
+ private static final int EVENT_SIM_LOADED = 12;
+ private static final int EVENT_SIM_ABSENT = 13;
+ static final int EVENT_LOAD_ICON_DONE = 14;
+
+ private static final int DEV_ID_KEYPAD = 0x01;
+ private static final int DEV_ID_DISPLAY = 0x02;
+ private static final int DEV_ID_EARPIECE = 0x03;
+ private static final int DEV_ID_UICC = 0x81;
+ private static final int DEV_ID_TERMINAL = 0x82;
+ private static final int DEV_ID_NETWORK = 0x83;
+
+ // Event value for Event List COMPREHENSION-TLV object
+ public static final int UICC_EVENT_MT_CALL = 0x00;
+ public static final int UICC_EVENT_CALL_CONNECTED = 0x01;
+ public static final int UICC_EVENT_CALL_DISCONNECTED = 0x02;
+ public static final int UICC_EVENT_LOCATION_STATUS = 0x03;
+ public static final int UICC_EVENT_USER_ACTIVITY = 0x04;
+ public static final int UICC_EVENT_IDLE_SCREEN_AVAILABLE = 0x05;
+ public static final int UICC_EVENT_CARD_READER_STATUS = 0x06;
+ public static final int UICC_EVENT_LANGUAGE_SELECTION = 0x07;
+ public static final int UICC_EVENT_BROWSER_TERMINATION = 0x08;
+ public static final int UICC_EVENT_DATA_AVAILABLE = 0x09;
+ public static final int UICC_EVENT_CHANNEL_STATUS = 0x0a;
+ public static final int UICC_EVENT_ACCESS_TECH_CHANGE = 0x0b;
+ public static final int UICC_EVENT_DISPLAY_PARAMS_CHANGE = 0x0c;
+ public static final int UICC_EVENT_LOCAL_CONNECTION = 0x0d;
+ public static final int UICC_EVENT_NETWORK_SEARCH_MODE_CHANGE = 0x0e;
+ public static final int UICC_EVENT_BROWSING_STATUS = 0x0f;
+ public static final int UICC_EVENT_FRAMES_INFO_CHANGE = 0x10;
+ public static final int UICC_EVENT_I_WLAN_ACESS_STATUS = 0x11;
+
+ // Command Qualifier values
+ static final int REFRESH_NAA_INIT_AND_FULL_FILE_CHANGE = 0x00;
+ static final int REFRESH_NAA_INIT_AND_FILE_CHANGE = 0x02;
+ static final int REFRESH_NAA_INIT = 0x03;
+ static final int REFRESH_UICC_RESET = 0x04;
+
+ // GetInKey Yes/No response characters constants.
+ private static final byte GET_INKEY_YES = 0x01;
+ private static final byte GET_INKEY_NO = 0x00;
+
+ // Notification id used to display Idle Mode text in NotificationManager.
+ static final int STK_NOTIFICATION_ID = 333;
+
+ private static String APP_PACKAGE_NAME = "com.android.stk";
+ private static String APP_FULL_NAME = APP_PACKAGE_NAME + ".StkActivity";
+
+ // Application indicators constants
+ static final int APP_INDICATOR_PRE_BOOT = 0;
+ static final int APP_INDICATOR_UNINSTALLED = 1;
+ static final int APP_INDICATOR_INSTALLED_NORMAL = 2;
+ static final int APP_INDICATOR_INSTALLED_SPECIAL = 3;
+ private static final int APP_INDICATOR_LAUNCHED = 4;
+ // Use setAppIndication(APP_INSTALL_INDICATOR) to go back for the original
+ // install indication.
+ private static final int APP_INSTALL_INDICATOR = 5;
+
+ // Container class to hold temporary icon identifier TLV object info.
+ class IconId {
+ int recordNumber;
+ boolean selfExplanatory;
+ }
+
+ // Container class to hold temporary item icon identifier list TLV object info.
+ class ItemsIconId {
+ int [] recordNumbers;
+ boolean selfExplanatory;
+ }
+
+ /* Intentionally private for singleton */
+ private Service(CommandsInterface ci, SIMRecords sr, Context context,
+ SIMFileHandler fh, GsmSimCard sc) {
+ if (ci == null || sr == null || context == null || fh == null
+ || sc == null) {
+ throw new NullPointerException(
+ "Service: Input parameters must not be null");
+ }
+ mCmdIf = ci;
+ mContext = context;
+
+ mCmdIf.setOnStkSessionEnd(this, EVENT_SESSION_END, null);
+ mCmdIf.setOnStkProactiveCmd(this, EVENT_PROACTIVE_COMMAND, null);
+ mCmdIf.setOnStkEvent(this, EVENT_EVENT_NOTIFY, null);
+ mCmdIf.setOnStkCallSetUp(this, EVENT_CALL_SETUP, null);
+
+ mSimRecords = sr;
+
+ mSimCard = sc;
+ mNm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ mIconLoader = IconLoader.getInstance(this, fh);
+
+ // Register a receiver for install/unistall application.
+ StkAppStateReceiver receiver = new StkAppStateReceiver();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(StkAppInstaller.STK_APP_INSTALL_ACTION);
+ filter.addAction(StkAppInstaller.STK_APP_UNINSTALL_ACTION);
+ mContext.registerReceiver(receiver, filter);
+
+ // Register for SIM ready event.
+ mSimRecords.registerForRecordsLoaded(this, EVENT_SIM_LOADED, null);
+ mSimCard.registerForAbsent(this, EVENT_SIM_ABSENT, null);
+ }
+
+ /**
+ * Used for retrieving the only Service object in the system. There is only
+ * one Service object.
+ *
+ * @param ci CommandsInterface object
+ * @param sr SIMRecords object
+ * @return The only Service object in the system
+ */
+ public static Service getInstance(CommandsInterface ci, SIMRecords sr,
+ Context context, SIMFileHandler fh, GsmSimCard sc) {
+ if (sInstance == null) {
+ if (ci == null || sr == null || context == null || fh == null
+ || sc == null) {
+ return null;
+ }
+ sInstance = new Service(ci, sr, context, fh, sc);
+ }
+ return sInstance;
+ }
+
+ /**
+ * Used for retrieving the only Service object in the system. There is only
+ * one Service object.
+ *
+ * @return The only Service object in the system
+ */
+ public static Service getInstance() {
+ return getInstance(null, null, null, null, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setCommandListener(CommandListener l) {
+ synchronized (mCmdListenerLock) {
+ mCmdListener = l;
+ if (mCmdListener != null) {
+ setAppIndication(APP_INDICATOR_LAUNCHED);
+ } else {
+ setAppIndication(APP_INSTALL_INDICATOR);
+ }
+ }
+ }
+
+ synchronized void setAppIndication(int indication) {
+ switch(indication) {
+ case APP_INDICATOR_PRE_BOOT:
+ case APP_INDICATOR_UNINSTALLED:
+ case APP_INDICATOR_INSTALLED_NORMAL:
+ case APP_INDICATOR_INSTALLED_SPECIAL:
+ case APP_INDICATOR_LAUNCHED:
+ mAppIndicator = indication;
+ break;
+ case APP_INSTALL_INDICATOR:
+ mAppIndicator = mInstallIndicator;
+ break;
+ default:
+ throw new NullPointerException("Trying to set wrong app indication");
+ }
+ }
+
+ public synchronized int getAppIndication() {
+ return mAppIndicator;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public State getState() {
+ return mState;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyMenuSelection(int menuId, boolean helpRequired) {
+ if (mState != State.MAIN_MENU) {
+ return;
+ }
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ // tag
+ int tag = BerTlv.BER_MENU_SELECTION_TAG;
+ buf.write(tag);
+
+ // length
+ buf.write(0x00); // place holder
+
+ // device identities
+ tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
+ buf.write(tag);
+ buf.write(0x02); // length
+ buf.write(DEV_ID_KEYPAD); // source device id
+ buf.write(DEV_ID_UICC); // destination device id
+
+ // item identifier
+ tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
+ buf.write(tag);
+ buf.write(0x01); // length
+ buf.write(menuId); // menu identifier chosen
+
+ // help request
+ if (helpRequired) {
+ tag = ComprehensionTlvTag.HELP_REQUEST.value();
+ buf.write(tag);
+ buf.write(0x00); // length
+ }
+
+ byte[] rawData = buf.toByteArray();
+
+ // write real length
+ int len = rawData.length - 2; // minus (tag + length)
+ rawData[1] = (byte) len;
+
+ String hexString = SimUtils.bytesToHexString(rawData);
+
+ mCmdIf.sendEnvelope(hexString, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyUserActivity() {
+ eventDownload(UICC_EVENT_USER_ACTIVITY, DEV_ID_TERMINAL, DEV_ID_UICC,
+ null, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyDisplayTextEnded(ResultCode terminationCode) {
+ if (mState != State.DISPLAY_TEXT) {
+ return;
+ }
+ ResultCode rc = ResultCode.OK;
+
+ switch (terminationCode) {
+ case OK:
+ case BACKWARD_MOVE_BY_USER:
+ case NO_RESPONSE_FROM_USER:
+ rc = terminationCode;
+ break;
+ default:
+ Log.d(TAG, "Invalid termination code for Display Text");
+ return;
+ }
+ sendTerminalResponse(mCmdParams.cmdDet, rc, false, 0, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyToneEnded() {
+ if (mState != State.PLAY_TONE) {
+ return;
+ }
+
+ sendTerminalResponse(mCmdParams.cmdDet, ResultCode.OK, false, 0,
+ null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyIdleScreenAvailable() {
+ eventDownload(UICC_EVENT_IDLE_SCREEN_AVAILABLE, DEV_ID_DISPLAY,
+ DEV_ID_UICC, null, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyLanguageSelection(String langCode) {
+ assert langCode.length() == 2 : "Language code must be two characters";
+
+ byte[] lang = GsmAlphabet.stringToGsm8BitPacked(langCode);
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ // language
+ int tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value();
+ buf.write(tag);
+ buf.write(0x02); // length
+ buf.write(lang[0]);
+ buf.write(lang[1]);
+
+ byte[] info = buf.toByteArray();
+
+ eventDownload(UICC_EVENT_LANGUAGE_SELECTION, DEV_ID_TERMINAL,
+ DEV_ID_UICC, info, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyBrowserTermination(boolean isErrorTermination) {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ int cause = isErrorTermination ? 1 : 0;
+
+ // browser termination cause
+ int tag = 0x80 | ComprehensionTlvTag.BROWSER_TERMINATION_CAUSE.value();
+ buf.write(tag);
+ buf.write(0x01); // length
+ buf.write(cause);
+
+ byte[] info = buf.toByteArray();
+
+ eventDownload(UICC_EVENT_BROWSER_TERMINATION, DEV_ID_TERMINAL,
+ DEV_ID_UICC, info, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyLaunchBrowser(boolean userConfirmed) {
+
+ if (mState != State.LAUNCH_BROWSER) {
+ return;
+ }
+
+ ResultCode rc = userConfirmed ? ResultCode.OK
+ : ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS;
+ sendTerminalResponse(mCmdParams.cmdDet, rc, false, 0, null);
+ }
+
+ private void eventDownload(int event, int sourceId, int destinationId,
+ byte[] additionalInfo, boolean oneShot) {
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ // tag
+ int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG;
+ buf.write(tag);
+
+ // length
+ buf.write(0x00); // place holder, assume length < 128.
+
+ // event list
+ tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value();
+ buf.write(tag);
+ buf.write(0x01); // length
+ buf.write(event); // event value
+
+ // device identities
+ tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
+ buf.write(tag);
+ buf.write(0x02); // length
+ buf.write(sourceId); // source device id
+ buf.write(destinationId); // destination device id
+
+ // additional information
+ if (additionalInfo != null) {
+ for (byte b : additionalInfo) {
+ buf.write(b);
+ }
+ }
+
+ byte[] rawData = buf.toByteArray();
+
+ // write real length
+ int len = rawData.length - 2; // minus (tag + length)
+ rawData[1] = (byte) len;
+
+ String hexString = SimUtils.bytesToHexString(rawData);
+
+ mCmdIf.sendEnvelope(hexString, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyInkey(char key, boolean helpRequired) {
+ if (mState != State.GET_INKEY) {
+ return;
+ }
+
+ GetInkeyInputResponseData resp = null;
+ ResultCode result = ResultCode.OK;
+ GetInkeyParams request = (GetInkeyParams) mCmdParams;
+ if (helpRequired) {
+ result = ResultCode.HELP_INFO_REQUIRED;
+ } else {
+ resp = new GetInkeyInputResponseData(Character.toString(key),
+ request.isUcs2, false);
+ }
+
+ sendTerminalResponse(request.cmdDet, result, false, 0, resp);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyInkey(boolean yesNoResponse, boolean helpRequired) {
+ if (mState != State.GET_INKEY) {
+ return;
+ }
+
+ GetInkeyInputResponseData resp = null;
+ ResultCode result = ResultCode.OK;
+ GetInkeyParams cmdParams = (GetInkeyParams) mCmdParams;
+ if (!cmdParams.isYesNo) {
+ // Illegal use of this call.
+ return;
+ }
+ if (helpRequired) {
+ result = ResultCode.HELP_INFO_REQUIRED;
+ } else {
+ resp = new GetInkeyInputResponseData(yesNoResponse);
+ }
+
+ sendTerminalResponse(cmdParams.cmdDet, result, false, 0, resp);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyInput(String input, boolean helpRequired) {
+ if (mState != State.GET_INPUT) {
+ return;
+ }
+
+ GetInkeyInputResponseData resp = null;
+ GetInputParams cmdParams = (GetInputParams) mCmdParams;
+ ResultCode result = ResultCode.OK;
+
+ if (helpRequired) {
+ result = ResultCode.HELP_INFO_REQUIRED;
+ } else {
+ resp = new GetInkeyInputResponseData(input, cmdParams.isUcs2,
+ cmdParams.isPacked);
+ }
+ sendTerminalResponse(cmdParams.cmdDet, result, false, 0, resp);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifySelectedItem(int id, boolean helpRequired) {
+ if (mState != State.SELECT_ITEM) {
+ return;
+ }
+
+ SelectItemResponseData resp = new SelectItemResponseData(id);
+ ResultCode result = helpRequired ? ResultCode.HELP_INFO_REQUIRED
+ : ResultCode.OK;
+
+ sendTerminalResponse(mCmdParams.cmdDet, result, false, 0, resp);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyNoResponse() {
+ CtlvCommandDetails cmdDet = getCurrentCmdDet();
+ if (cmdDet == null) {
+ // Unable to continue;
+ return;
+ }
+ sendTerminalResponse(cmdDet, ResultCode.NO_RESPONSE_FROM_USER, false,
+ 0, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void acceptOrRejectCall(boolean accept) {
+ if (mState != State.CALL_SETUP) {
+ return;
+ }
+ mCmdIf.handleCallSetupRequestFromSim(accept, null);
+ }
+
+ /**
+ * Indicates if STK is supported by the SIM card.
+ */
+ public boolean isStkSupported() {
+ switch (getAppIndication()) {
+ case APP_INDICATOR_PRE_BOOT:
+ case APP_INDICATOR_UNINSTALLED:
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the unique service name for STK.
+ */
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case EVENT_SESSION_END:
+ ar = (AsyncResult) msg.obj;
+ handleSessionEnd(ar.result);
+ break;
+ case EVENT_PROACTIVE_COMMAND:
+ ar = (AsyncResult) msg.obj;
+ handleProactiveCommand((String) ar.result);
+ break;
+ case EVENT_EVENT_NOTIFY:
+ ar = (AsyncResult) msg.obj;
+ handleEventNotify((String) ar.result);
+ break;
+ case EVENT_CALL_SETUP:
+ mState = State.CALL_SETUP;
+ break;
+ case EVENT_SIM_LOADED:
+ case EVENT_SIM_ABSENT:
+ if (!isStkSupported()) {
+ setAppState(false);
+ }
+ break;
+ case EVENT_LOAD_ICON_DONE:
+ handleProactiveCommandIcons(msg.obj);
+ break;
+ default:
+ throw new AssertionError("Unrecognized STK command: " + msg.what);
+ }
+ }
+
+ /**
+ * Send terminal response for backward move in the proactive SIM session
+ * requested by the user
+ *
+ * Only available when responding following proactive commands
+ * DISPLAY_TEXT(0x21),
+ * GET_INKEY(0x22),
+ * GET_INPUT(0x23),
+ * SET_UP_MENU(0x25);
+ *
+ * @return true if stk can send backward move response
+ *
+ */
+ public boolean backwardMove() {
+ CtlvCommandDetails cmdDet = null;
+
+ cmdDet = getCurrentCmdDet();
+
+ if (cmdDet == null) {
+ return false;
+ }
+
+ sendTerminalResponse(cmdDet, ResultCode.BACKWARD_MOVE_BY_USER, false,
+ 0, null);
+ return true;
+ }
+
+ /**
+ * Send terminal response for proactive SIM session terminated by the user
+ *
+ * Only available when responding following proactive commands
+ * DISPLAY_TEXT(0x21),
+ * GET_INKEY(0x22),
+ * GET_INPUT(0x23),
+ * PLAY_TONE(0x20),
+ * SET_UP_MENU(0x25);
+ *
+ * @return true if stk can send terminate session response
+ */
+ public boolean terminateSession() {
+ CtlvCommandDetails cmdDet = null;
+
+ cmdDet = getCurrentCmdDet();
+
+ if (cmdDet == null) {
+ return false;
+ }
+
+ sendTerminalResponse(cmdDet, ResultCode.UICC_SESSION_TERM_BY_USER,
+ false, 0, null);
+ mState = State.MAIN_MENU;
+ return true;
+ }
+
+ private CtlvCommandDetails getCurrentCmdDet() {
+ CtlvCommandDetails cmdDet = null;
+
+ if (mCmdParams != null) {
+ cmdDet = mCmdParams.cmdDet;
+ }
+
+ return cmdDet;
+ }
+
+ /**
+ * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL.
+ *
+ * @param data Null object. Do not use this.
+ */
+ private void handleSessionEnd(Object data) {
+ if (Config.LOGD) {
+ Log.d(TAG, "handleSessionEnd begins");
+ }
+ switch (mInstallIndicator) {
+ case APP_INDICATOR_INSTALLED_NORMAL:
+ mState = State.MAIN_MENU;
+ break;
+ case APP_INDICATOR_INSTALLED_SPECIAL:
+ case APP_INDICATOR_UNINSTALLED:
+ mState = State.IDLE;
+ break;
+ default:
+ Log.d(TAG, "Can't set service state");
+ }
+ synchronized (mCmdListenerLock) {
+ if (mCmdListener != null) {
+ mCmdListener.onSessionEnd();
+ }
+ }
+ }
+
+ class CtlvDeviceIdentities {
+ public int sourceId;
+ public int destinationId;
+ }
+
+ abstract class ResponseData {
+ /**
+ * Format the data appropriate for TERMINAL RESPONSE and write it into
+ * the ByteArrayOutputStream object.
+ */
+ public abstract void format(ByteArrayOutputStream buf);
+ }
+
+ class GetInkeyInputResponseData extends ResponseData {
+ private boolean mIsUcs2;
+ private boolean mIsPacked;
+ private boolean mIsYesNo;
+ private boolean mYesNoResponse;
+ public String mInData;
+
+ public GetInkeyInputResponseData(String inData, boolean ucs2,
+ boolean packed) {
+ super();
+ this.mIsUcs2 = ucs2;
+ this.mIsPacked = packed;
+ this.mInData = inData;
+ this.mIsYesNo = false;
+ }
+
+ public GetInkeyInputResponseData(boolean yesNoResponse) {
+ super();
+ this.mIsUcs2 = false;
+ this.mIsPacked = false;
+ this.mInData = "";
+ this.mIsYesNo = true;
+ this.mYesNoResponse = yesNoResponse;
+ }
+
+ @Override
+ public void format(ByteArrayOutputStream buf) {
+ if (buf == null) {
+ return;
+ }
+
+ // Text string object
+ int tag = 0x80 | ComprehensionTlvTag.TEXT_STRING.value();
+ buf.write(tag); // tag
+
+ byte[] data;
+
+ if (mIsYesNo) {
+ data = new byte[1];
+ data[0] = mYesNoResponse ? GET_INKEY_YES : GET_INKEY_NO;
+ } else if (mInData != null && mInData.length() > 0) {
+ try {
+ if (mIsUcs2) {
+ data = mInData.getBytes("UTF-16");
+ } else if (mIsPacked) {
+ int size = mInData.length();
+
+ byte[] tempData = GsmAlphabet
+ .stringToGsm7BitPacked(mInData);
+ data = new byte[size];
+ // Since stringToGsm7BitPacked() set byte 0 in the
+ // returned byte array to the count of septets used...
+ // copy to a new array without byte 0.
+ System.arraycopy(tempData, 1, data, 0, size);
+ } else {
+ data = GsmAlphabet.stringToGsm8BitPacked(mInData);
+ }
+ } catch (UnsupportedEncodingException e) {
+ data = new byte[0];
+ } catch (EncodeException e) {
+ data = new byte[0];
+ }
+ } else {
+ data = new byte[0];
+ }
+
+ // length - one more for data coding scheme.
+ buf.write(data.length + 1);
+
+ // data coding scheme
+ if (mIsUcs2) {
+ buf.write(0x08); // UCS2
+ } else if (mIsPacked) {
+ buf.write(0x00); // 7 bit packed
+ } else {
+ buf.write(0x04); // 8 bit unpacked
+ }
+
+ for (byte b : data) {
+ buf.write(b);
+ }
+ }
+ }
+
+ class SelectItemResponseData extends ResponseData {
+ private int id;
+
+ public SelectItemResponseData(int id) {
+ super();
+ this.id = id;
+ }
+
+ @Override
+ public void format(ByteArrayOutputStream buf) {
+ // Item identifier object
+ int tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
+ buf.write(tag); // tag
+ buf.write(1); // length
+ buf.write(id); // identifier of item chosen
+ }
+ }
+
+ /**
+ * Handles RIL_UNSOL_STK_PROACTIVE_COMMAND unsolicited command from RIL.
+ * This method parses the data transmitted from the SIM card, and handles
+ * the command according to the "Type of Command". Each proactive command is
+ * handled by a corresponding handleXXX() method.
+ *
+ * @param data String containing SAT/USAT proactive command in hexadecimal
+ * format starting with command tag
+ */
+ private void handleProactiveCommand(String data) {
+ if (Config.LOGD) {
+ Log.d(TAG, "handleProactiveCommand begins");
+ }
+ // If commands arrives before the SIM loaded/SIM absent events have
+ // arrived post a message for a delayed processing in 2 seconds.
+ if (getAppIndication() == APP_INDICATOR_PRE_BOOT) {
+ Message installMsg = this.obtainMessage(EVENT_PROACTIVE_COMMAND);
+ AsyncResult.forMessage(installMsg, data, null);
+ sendMessageDelayed(installMsg, 2000);
+ return;
+ }
+
+ CtlvCommandDetails cmdDet = null;
+ try {
+ byte[] rawData = SimUtils.hexStringToBytes(data);
+ BerTlv berTlv = BerTlv.decode(rawData);
+
+ List<ComprehensionTlv> ctlvs = berTlv.getComprehensionTlvs();
+ cmdDet = retrieveCommandDetails(ctlvs);
+
+ CommandType cmdType = CommandType.fromInt(cmdDet.typeOfCommand);
+ if (cmdType == null) {
+ throw new ResultException(ResultCode.BEYOND_TERMINAL_CAPABILITY);
+ }
+
+ // SET UP MENU & SET up IDLE MODE TEXT commands should not trigger
+ // the special install & launch sequence.
+ if (cmdType != CommandType.SET_UP_MENU
+ && cmdType != CommandType.SET_UP_IDLE_MODE_TEXT) {
+ switch (getAppIndication()) {
+ case APP_INDICATOR_UNINSTALLED:
+ setAppState(true);
+ setAppIndication(APP_INDICATOR_INSTALLED_SPECIAL);
+ mInstallIndicator = APP_INDICATOR_INSTALLED_SPECIAL;
+ Message installMsg = this
+ .obtainMessage(EVENT_PROACTIVE_COMMAND);
+ AsyncResult.forMessage(installMsg, data, null);
+ sendMessageDelayed(installMsg, 20);
+ return;
+ case APP_INDICATOR_INSTALLED_SPECIAL:
+ case APP_INDICATOR_INSTALLED_NORMAL:
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName("com.android.stk",
+ "com.android.stk.StkActivity");
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ Message launchMsg = this
+ .obtainMessage(EVENT_PROACTIVE_COMMAND);
+ AsyncResult.forMessage(launchMsg, data, null);
+ sendMessageDelayed(launchMsg, 2000);
+ return;
+ }
+ }
+
+ CtlvDeviceIdentities devIds = retrieveDeviceIdentities(ctlvs);
+ boolean cmdPending = false;
+ boolean responseNeeded = false;
+
+ switch (cmdType) {
+ case DISPLAY_TEXT:
+ cmdPending = processDisplayText(cmdDet, devIds, ctlvs);
+ responseNeeded = true;
+ break;
+ case SET_UP_MENU:
+ cmdPending = processSetUpMenu(cmdDet, devIds, ctlvs);
+ responseNeeded = true;
+ break;
+ case SET_UP_IDLE_MODE_TEXT:
+ cmdPending = processSetUpIdleModeText(cmdDet, devIds, ctlvs);
+ responseNeeded = true;
+ break;
+ case GET_INKEY:
+ cmdPending = processGetInkey(cmdDet, devIds, ctlvs);
+ break;
+ case GET_INPUT:
+ cmdPending = processGetInput(cmdDet, devIds, ctlvs);
+ break;
+ case REFRESH:
+ processRefresh(cmdDet, devIds, ctlvs);
+ responseNeeded = true;
+ break;
+ case SELECT_ITEM:
+ cmdPending = processSelectItem(cmdDet, devIds, ctlvs);
+ break;
+ case LAUNCH_BROWSER:
+ cmdPending = processLaunchBrowser(cmdDet, devIds, ctlvs);
+ break;
+ case PLAY_TONE:
+ cmdPending = processPlayTone(cmdDet, devIds, ctlvs);
+ break;
+ default:
+ // This should never be reached!
+ throw new AssertionError(
+ "Add case statements for the newly added "
+ + "command types!");
+ }
+ if (!cmdPending) {
+ callStkApp(cmdType);
+ }
+ if (responseNeeded) {
+ sendTerminalResponse(cmdDet, ResultCode.OK, false, 0, null);
+ }
+ } catch (ResultException e) {
+ sendTerminalResponse(cmdDet, e.result(), e.hasAdditionalInfo(), e
+ .additionalInfo(), null);
+ }
+ }
+
+ private void handleProactiveCommandIcons(Object data) {
+ CommandType cmdType = CommandType
+ .fromInt(mNextCmdParams.cmdDet.typeOfCommand);
+ boolean needsResponse = false;
+ Bitmap[] icons = null;
+ int iconIndex = 0;
+
+ switch (cmdType) {
+ case SET_UP_IDLE_MODE_TEXT:
+ ((CommonUIParams) mNextCmdParams).mIcon = (Bitmap) (data);
+ callStkApp(CommandType.SET_UP_IDLE_MODE_TEXT);
+ break;
+ case DISPLAY_TEXT:
+ ((DisplayTextParams) mNextCmdParams).icon = (Bitmap) (data);
+ callStkApp(CommandType.DISPLAY_TEXT);
+ break;
+ case SELECT_ITEM:
+
+ SelectItemParams params = ((SelectItemParams) mNextCmdParams);
+ Menu menu = params.mMenu;
+ switch(params.mIconLoadState) {
+ case SelectItemParams.LOAD_TITLE_ICON:
+ menu.titleIcon = (Bitmap) data;
+ break;
+ case SelectItemParams.LOAD_ITEMS_ICONS:
+ icons = (Bitmap[]) data;
+ // set each item icon.
+ for (Item item : menu.items) {
+ item.icon = icons[iconIndex++];
+ }
+ break;
+ case SelectItemParams.LOAD_TITLE_ITEMS_ICONS:
+ icons = (Bitmap[]) data;
+ // set title icon
+ menu.titleIcon = icons[iconIndex++];
+ // set each item icon.
+ for (Item item : menu.items) {
+ item.icon = icons[iconIndex++];
+ }
+ }
+ callStkApp(CommandType.SELECT_ITEM);
+ break;
+ default:
+ // This should never be reached!
+ throw new AssertionError("Add case statements for the newly added "
+ + "command types!");
+ }
+ }
+
+ private void callStkApp(CommandType cmdType) {
+ boolean needsResponse = false;
+ mCmdParams = mNextCmdParams;
+
+ synchronized (mCmdListenerLock) {
+ switch (cmdType) {
+ case SET_UP_IDLE_MODE_TEXT:
+ if (mNm == null) {
+ break;
+ }
+ CommonUIParams i = (CommonUIParams) mCmdParams;
+ if (i.mText == null) {
+ mNm.cancel(STK_NOTIFICATION_ID);
+ } else {
+ Notification notification = new Notification();
+ RemoteViews contentView = new RemoteViews(
+ mContext.getPackageName(),
+ com.android.internal.R.layout.status_bar_latest_event_content);
+
+ // Set text and icon for the status bar.
+ notification.icon = com.android.internal.R.drawable.stat_notify_sim_toolkit;
+ notification.tickerText = i.mText;
+ notification.flags |= Notification.FLAG_NO_CLEAR;
+
+ // Set text and icon for the notification body.
+ if (!i.mIconSelfExplanatory) {
+ contentView.setTextViewText(
+ com.android.internal.R.id.text, i.mText);
+ }
+ if (i.mIcon != null) {
+ contentView.setImageViewBitmap(
+ com.android.internal.R.id.icon, i.mIcon);
+ } else {
+ contentView
+ .setImageViewResource(
+ com.android.internal.R.id.icon,
+ com.android.internal.R.drawable.stat_notify_sim_toolkit);
+ }
+ notification.contentView = contentView;
+
+ mNm.notify(STK_NOTIFICATION_ID, notification);
+ }
+ case SET_UP_MENU:
+ needsResponse = true;
+ break;
+ case SELECT_ITEM:
+ mState = State.SELECT_ITEM;
+ SelectItemParams s = (SelectItemParams) mCmdParams;
+ mCmdListener.onSelectItem(s.mMenu, s.mPresentationType);
+ needsResponse = false;
+ break;
+ case DISPLAY_TEXT:
+ mState = State.DISPLAY_TEXT;
+ DisplayTextParams d = (DisplayTextParams) mCmdParams;
+ mCmdListener.onDisplayText(d.text, d.textAttrs, d.isHighPriority,
+ d.userClear, !d.immediateResponse, d.icon);
+
+ needsResponse = d.immediateResponse;
+ break;
+ default:
+ // This should never be reached!
+ throw new AssertionError(
+ "Add case statements for the newly added "
+ + "command types!");
+ }
+ }
+
+ if (needsResponse) {
+ sendTerminalResponse(mCmdParams.cmdDet, ResultCode.OK, false, 0, null);
+ }
+ }
+
+ private void sendTerminalResponse(CtlvCommandDetails cmdDet,
+ ResultCode resultCode, boolean includeAdditionalInfo,
+ int additionalInfo, ResponseData resp) {
+
+ if (cmdDet == null) {
+ return;
+ }
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ // command details
+ int tag = ComprehensionTlvTag.COMMAND_DETAILS.value();
+ if (cmdDet.compRequired) {
+ tag |= 0x80;
+ }
+ buf.write(tag);
+ buf.write(0x03); // length
+ buf.write(cmdDet.commandNumber);
+ buf.write(cmdDet.typeOfCommand);
+ buf.write(cmdDet.commandQualifier);
+
+ // device identities
+ tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
+ buf.write(tag);
+ buf.write(0x02); // length
+ buf.write(DEV_ID_TERMINAL); // source device id
+ buf.write(DEV_ID_UICC); // destination device id
+
+ // result
+ tag = 0x80 | ComprehensionTlvTag.RESULT.value();
+ buf.write(tag);
+ int length = includeAdditionalInfo ? 2 : 1;
+ buf.write(length);
+ buf.write(resultCode.code());
+
+ // additional info
+ if (includeAdditionalInfo) {
+ buf.write(additionalInfo);
+ }
+
+ // Fill optional data for each corresponding command
+ if (resp != null) {
+ resp.format(buf);
+ }
+
+ byte[] rawData = buf.toByteArray();
+ String hexString = SimUtils.bytesToHexString(rawData);
+ if (Config.LOGD) {
+ Log.d(TAG, "TERMINAL RESPONSE: " + hexString);
+ }
+
+ mCmdIf.sendTerminalResponse(hexString, null);
+ }
+
+ /**
+ * Search for a COMPREHENSION-TLV object with the given tag from a list
+ *
+ * @param tag A tag to search for
+ * @param ctlvs List of ComprehensionTlv objects used to search in
+ *
+ * @return A ComprehensionTlv object that has the tag value of {@code tag}.
+ * If no object is found with the tag, null is returned.
+ */
+ private ComprehensionTlv searchForTag(ComprehensionTlvTag tag,
+ List<ComprehensionTlv> ctlvs) {
+ Iterator<ComprehensionTlv> iter = ctlvs.iterator();
+ return searchForNextTag(tag, iter);
+ }
+
+ /**
+ * Search for the next COMPREHENSION-TLV object with the given tag from a
+ * list iterated by {@code iter}. {@code iter} points to the object next to
+ * the found object when this method returns. Used for searching the same
+ * list for similar tags, usually item id.
+ *
+ * @param tag A tag to search for
+ * @param iter Iterator for ComprehensionTlv objects used for search
+ *
+ * @return A ComprehensionTlv object that has the tag value of {@code tag}.
+ * If no object is found with the tag, null is returned.
+ */
+ private ComprehensionTlv searchForNextTag(ComprehensionTlvTag tag,
+ Iterator<ComprehensionTlv> iter) {
+ int tagValue = tag.value();
+ while (iter.hasNext()) {
+ ComprehensionTlv ctlv = iter.next();
+ if (ctlv.getTag() == tagValue) {
+ return ctlv;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Search for a Command Details object from a list.
+ *
+ * @param ctlvs List of ComprehensionTlv objects used for search
+ * @return An CtlvCommandDetails object found from the objects. If no
+ * Command Details object is found, ResultException is thrown.
+ * @throws ResultException
+ */
+ private CtlvCommandDetails retrieveCommandDetails(
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+
+ ComprehensionTlv ctlv = searchForTag(
+ ComprehensionTlvTag.COMMAND_DETAILS, ctlvs);
+ if (ctlv != null) {
+ CtlvCommandDetails cmdDet = new CtlvCommandDetails();
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ try {
+ cmdDet.compRequired = ctlv.isComprehensionRequired();
+ cmdDet.commandNumber = rawValue[valueIndex] & 0xff;
+ cmdDet.typeOfCommand = rawValue[valueIndex + 1] & 0xff;
+ cmdDet.commandQualifier = rawValue[valueIndex + 2] & 0xff;
+ return cmdDet;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ } else {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+ }
+
+ /**
+ * Search for a Device Identities object from a list.
+ *
+ * @param ctlvs List of ComprehensionTlv objects used for search
+ * @return An CtlvDeviceIdentities object found from the objects. If no
+ * Command Details object is found, ResultException is thrown.
+ * @throws ResultException
+ */
+ private CtlvDeviceIdentities retrieveDeviceIdentities(
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+
+ ComprehensionTlv ctlv = searchForTag(
+ ComprehensionTlvTag.DEVICE_IDENTITIES, ctlvs);
+ if (ctlv != null) {
+ CtlvDeviceIdentities devIds = new CtlvDeviceIdentities();
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ try {
+ devIds.sourceId = rawValue[valueIndex] & 0xff;
+ devIds.destinationId = rawValue[valueIndex + 1] & 0xff;
+ return devIds;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+ } else {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+ }
+
+ /**
+ * Processes SETUP_CALL proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.
+ */
+ private void processSetupCall(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs) {
+ if (Config.LOGD) {
+ Log.d(TAG, "processSetupCall begins");
+ }
+
+ // User confirmation phase message.
+ String confirmMsg = null;
+ // Call set up phase message.
+ String callMsg = null;
+ List<TextAttribute> textAttrs = null;
+ Iterator<ComprehensionTlv> iter = ctlvs.iterator();
+ ComprehensionTlv ctlv = null;
+
+ try {
+ // get confirmation message string.
+ ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
+ if (ctlv != null) {
+ confirmMsg = retrieveAlphaId(ctlv);
+ } else {
+ // No message to show.
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+ // get call set up message string.
+ ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
+ if (ctlv != null) {
+ callMsg = retrieveAlphaId(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ textAttrs = retrieveTextAttribute(ctlv);
+ }
+ } catch (ResultException e) {
+ // Unable to process command. Send terminal response when service is
+ // in call state.
+ while (mState != State.CALL_SETUP) {
+ Thread.yield();
+ }
+ sendTerminalResponse(cmdDet, ResultCode.REQUIRED_VALUES_MISSING,
+ false, 0, null);
+ return;
+ }
+
+ synchronized (mCmdListenerLock) {
+ if (mCmdListener != null) {
+ mCmdListener.onCallSetup(confirmMsg, textAttrs, callMsg);
+ }
+ }
+ }
+
+ /**
+ * Processes DISPLAY_TEXT proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.
+ * @throws ResultException
+ */
+ private boolean processDisplayText(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processDisplayText begins");
+ }
+
+ DisplayTextParams params = new DisplayTextParams(cmdDet);
+ IconId iconId = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
+ ctlvs);
+ if (ctlv != null) {
+ params.text = retrieveTextString(ctlv);
+ }
+ // If the tlv object doesn't exist or the it is a null object reply
+ // with command not understood.
+ if (params.text == null) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ params.textAttrs = retrieveTextAttribute(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.IMMEDIATE_RESPONSE, ctlvs);
+ if (ctlv != null) {
+ params.immediateResponse = true;
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = retrieveIconId(ctlv);
+ }
+
+ // Parse command qualifier parameters.
+ params.isHighPriority = (cmdDet.commandQualifier & 0x01) != 0;
+ params.userClear = (cmdDet.commandQualifier & 0x80) != 0;
+
+ mNextCmdParams = params;
+
+ // If there's no icon to load call stk application.
+ if (iconId != null) {
+ mIconLoader.loadIcon(iconId.recordNumber, this
+ .obtainMessage(EVENT_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Processes SET_UP_MENU proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs Iterator for ComprehensionTlv objects following Command
+ * Details object and Device Identities object within the proactive
+ * command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.
+ * @throws ResultException
+ */
+ private boolean processSetUpMenu(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processSetUpMenu begins");
+ }
+
+ Menu menu = new Menu();
+ boolean first = true;
+ boolean removeExistingMenu = false;
+ Iterator<ComprehensionTlv> iter = ctlvs.iterator();
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
+ ctlvs);
+ if (ctlv != null) {
+ menu.title = retrieveAlphaId(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ menu.titleAttrs = retrieveTextAttribute(ctlv);
+ }
+
+ while (true) {
+ ctlv = searchForNextTag(ComprehensionTlvTag.ITEM, iter);
+ if (ctlv != null) {
+ Item item = retrieveItem(ctlv);
+ // If the first item is a "null" object, it means that
+ // the existing menu should be removed.
+ if (first && item == null) {
+ removeExistingMenu = true;
+ break;
+ }
+ menu.items.add(retrieveItem(ctlv));
+ first = false;
+ } else {
+ break;
+ }
+ }
+
+ // Extract command details.
+ menu.softKeyPreferred = (cmdDet.commandQualifier & 0x01) != 0;
+ menu.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
+
+ // We must have at least one menu item.
+ if (menu.items.size() == 0 && !removeExistingMenu) {
+ if (Config.LOGD) {
+ Log.d(TAG, "processSetUpMenu: Need at least one menu item");
+ }
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+ mCmdParams = new CommandParams(cmdDet);
+ if (removeExistingMenu) {
+ mState = State.IDLE;
+ mMainMenu = null;
+ setAppState(false);
+ } else {
+ Menu currentMenu = mMainMenu;
+ mState = State.MAIN_MENU;
+ mMainMenu = menu;
+ if (!isStkSupported()) {
+ setAppState(true);
+ setAppIndication(APP_INDICATOR_INSTALLED_NORMAL);
+ mInstallIndicator = APP_INDICATOR_INSTALLED_NORMAL;
+ }
+ }
+ return true;
+ }
+
+ private void setAppState(boolean installed) {
+ if (installed) {
+ StkAppInstaller.installApp(mContext);
+ } else {
+ setAppIndication(APP_INDICATOR_UNINSTALLED);
+ mInstallIndicator = APP_INDICATOR_UNINSTALLED;
+ StkAppInstaller.unInstallApp(mContext);
+ }
+ }
+
+ public Menu getCurrentMenu() {
+ Menu menu = null;
+ switch(mState) {
+ case MAIN_MENU:
+ menu = mMainMenu;
+ break;
+ case SELECT_ITEM:
+ menu = ((SelectItemParams) mCmdParams).mMenu;
+ break;
+ }
+ return menu;
+ }
+
+ /**
+ * Processes SET_UP_IDLE_MODE_TEXT proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.
+ * @throws ResultException
+ */
+ private boolean processSetUpIdleModeText(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processSetUpIdleModeText begins");
+ }
+
+ if (mNm == null) {
+ throw new ResultException(ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS);
+ }
+
+ String text = null;
+ IconId iconId = null;
+ List<TextAttribute> textAttrs = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
+ ctlvs);
+ if (ctlv != null) {
+ text = retrieveTextString(ctlv);
+ } else {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = retrieveIconId(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ textAttrs = retrieveTextAttribute(ctlv);
+ }
+
+ CommonUIParams params = new CommonUIParams(cmdDet, text, null);
+ mNextCmdParams = params;
+ if (iconId != null) {
+ params.mIconSelfExplanatory = iconId.selfExplanatory;
+ mIconLoader.loadIcon(iconId.recordNumber, this
+ .obtainMessage(EVENT_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Processes GET_INKEY proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.
+ * @throws ResultException
+ */
+ private boolean processGetInkey(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processGetInkey begins");
+ }
+
+ String text = null;
+ List<TextAttribute> textAttrs = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
+ ctlvs);
+ if (ctlv != null) {
+ text = retrieveTextString(ctlv);
+ } else {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ textAttrs = retrieveTextAttribute(ctlv);
+ }
+
+ boolean digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
+ boolean ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
+ boolean yesNo = (cmdDet.commandQualifier & 0x04) != 0;
+ boolean immediateResponse = (cmdDet.commandQualifier & 0x08) != 0;
+ boolean helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
+
+ synchronized (mCmdListenerLock) {
+ if (mCmdListener != null) {
+ mCmdParams = new GetInkeyParams(cmdDet, yesNo, ucs2);
+ mState = State.GET_INKEY;
+
+ mCmdListener.onGetInkey(text, textAttrs, yesNo, digitOnly,
+ ucs2, immediateResponse, helpAvailable);
+ return true;
+ } else {
+ // '0' means "No specific cause can be given"
+ throw new ResultException(
+ ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS, 0);
+ }
+ }
+ }
+
+ /**
+ * Processes GET_INPUT proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.
+ * @throws ResultException
+ */
+ private boolean processGetInput(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processGetInput begins");
+ }
+
+ String text = null;
+ String defaultText = null;
+ int minLen, maxLen;
+ List<TextAttribute> textAttrs = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
+ ctlvs);
+ if (ctlv != null) {
+ text = retrieveTextString(ctlv);
+ } else {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.RESPONSE_LENGTH, ctlvs);
+ if (ctlv != null) {
+ try {
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ minLen = rawValue[valueIndex] & 0xff;
+ maxLen = rawValue[valueIndex + 1] & 0xff;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ } else {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ textAttrs = retrieveTextAttribute(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.DEFAULT_TEXT, ctlvs);
+ if (ctlv != null) {
+ defaultText = retrieveTextString(ctlv);
+ }
+
+ boolean digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
+ boolean ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
+ boolean echo = (cmdDet.commandQualifier & 0x04) == 0;
+ boolean packed = (cmdDet.commandQualifier & 0x08) != 0;
+ boolean helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
+
+ synchronized (mCmdListenerLock) {
+ if (mCmdListener != null) {
+ mCmdParams = new GetInputParams(cmdDet, ucs2, packed);
+ mState = State.GET_INPUT;
+
+ boolean noMaxLimit = maxLen == 0xff;
+ mCmdListener.onGetInput(text, defaultText, minLen, maxLen,
+ noMaxLimit, textAttrs, digitOnly, ucs2, echo,
+ helpAvailable);
+ return true;
+ } else {
+ // '0' means "No specific cause can be given"
+ throw new ResultException(
+ ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS, 0);
+ }
+ }
+ }
+
+ /**
+ * Processes REFRESH proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @throws ResultException
+ */
+ private void processRefresh(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processRefresh begins");
+ }
+
+ // REFRESH proactive command is rerouted by the baseband and handled by
+ // the telephony layer. IDLE TEXT should be removed for a REFRESH command
+ // with "initialization" or "reset"
+
+ if (mNm == null) {
+ throw new ResultException(ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS);
+ }
+
+ boolean removeIdleText = false;
+
+ switch (cmdDet.commandQualifier) {
+ case REFRESH_NAA_INIT_AND_FULL_FILE_CHANGE:
+ case REFRESH_NAA_INIT_AND_FILE_CHANGE:
+ case REFRESH_NAA_INIT:
+ case REFRESH_UICC_RESET:
+ removeIdleText = true;
+ }
+ if (removeIdleText) {
+ mNm.cancel(STK_NOTIFICATION_ID);
+ }
+ }
+
+ /**
+ * Processes SELECT_ITEM proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.
+ * @throws ResultException
+ */
+ private boolean processSelectItem(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processSelectItem begins");
+ }
+
+ Menu menu = new Menu();
+ IconId titleIconId = null;
+ ItemsIconId itemsIconId = null;
+ int iconLoadState = SelectItemParams.LOAD_NO_ICON;
+ PresentationType presentType = PresentationType.NOT_SPECIFIED;
+ Iterator<ComprehensionTlv> iter = ctlvs.iterator();
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
+ ctlvs);
+ if (ctlv != null) {
+ menu.title = retrieveAlphaId(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ menu.titleAttrs = retrieveTextAttribute(ctlv);
+ }
+
+ while (true) {
+ ctlv = searchForNextTag(ComprehensionTlvTag.ITEM, iter);
+ if (ctlv != null) {
+ menu.items.add(retrieveItem(ctlv));
+ } else {
+ break;
+ }
+ }
+
+ // We must have at least one menu item.
+ if (menu.items.size() == 0) {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.ITEM_ID, ctlvs);
+ if (ctlv != null) {
+ // STK items are listed 1...n while list start at 0, need to
+ // subtract one.
+ menu.defaultItem = retrieveItemId(ctlv) - 1;
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconLoadState = SelectItemParams.LOAD_TITLE_ICON;
+ titleIconId = retrieveIconId(ctlv);
+ menu.titleIconSelfExplanatory = titleIconId.selfExplanatory;
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.ITEM_ICON_ID_LIST, ctlvs);
+ if (ctlv != null) {
+ if (iconLoadState == SelectItemParams.LOAD_TITLE_ICON) {
+ iconLoadState = SelectItemParams.LOAD_TITLE_ITEMS_ICONS;
+ } else {
+ iconLoadState = SelectItemParams.LOAD_ITEMS_ICONS;
+ }
+ itemsIconId = retrieveItemsIconId(ctlv);
+ menu.itemsIconSelfExplanatory = itemsIconId.selfExplanatory;
+ }
+
+ boolean presentTypeSpecified = (cmdDet.commandQualifier & 0x01) != 0;
+ if (presentTypeSpecified) {
+ if ((cmdDet.commandQualifier & 0x02) == 0) {
+ presentType = PresentationType.DATA_VALUES;
+ } else {
+ presentType = PresentationType.NAVIGATION_OPTIONS;
+ }
+ }
+ menu.softKeyPreferred = (cmdDet.commandQualifier & 0x04) != 0;
+ menu.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
+
+ mNextCmdParams = new SelectItemParams(cmdDet, menu, presentType,
+ iconLoadState);
+
+ // Load icons data if needed.
+ switch(iconLoadState) {
+ case SelectItemParams.LOAD_NO_ICON:
+ return false;
+ case SelectItemParams.LOAD_TITLE_ICON:
+ mIconLoader.loadIcon(titleIconId.recordNumber, this
+ .obtainMessage(EVENT_LOAD_ICON_DONE));
+ break;
+ case SelectItemParams.LOAD_ITEMS_ICONS:
+ mIconLoader.loadIcons(itemsIconId.recordNumbers, this
+ .obtainMessage(EVENT_LOAD_ICON_DONE));
+ break;
+ case SelectItemParams.LOAD_TITLE_ITEMS_ICONS:
+ // Create a new array for all the icons (title and items).
+ int[] recordNumbers = new int[itemsIconId.recordNumbers.length + 1];
+ recordNumbers[0] = titleIconId.recordNumber;
+ System.arraycopy(itemsIconId.recordNumbers, 0, recordNumbers, 1,
+ itemsIconId.recordNumbers.length);
+ mIconLoader.loadIcons(recordNumbers, this
+ .obtainMessage(EVENT_LOAD_ICON_DONE));
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Processes EVENT_NOTIFY message from baseband.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ */
+ synchronized private void processEventNotify(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs) {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processEventNotify begins");
+ }
+
+ String text = null;
+ List<TextAttribute> textAttrs = null;
+
+ try {
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
+ ctlvs);
+ if (ctlv != null) {
+ text = retrieveAlphaId(ctlv);
+ } else {
+ // No message to show.
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ textAttrs = retrieveTextAttribute(ctlv);
+ }
+ } catch (ResultException e) {
+ // Unable to process command.
+ return;
+ }
+
+ Toast toast = Toast.makeText(mContext.getApplicationContext(), text,
+ Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.BOTTOM, 0, 0);
+ toast.show();
+ }
+
+ /**
+ * Processes SET_UP_EVENT_LIST proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.
+ */
+ private boolean processSetUpEventList(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs) {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processSetUpEventList begins");
+ }
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.EVENT_LIST,
+ ctlvs);
+ if (ctlv != null) {
+ try {
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ int valueLen = ctlv.getLength();
+
+ } catch (IndexOutOfBoundsException e) {}
+ }
+ return true;
+ }
+
+ /**
+ * Processes LAUNCH_BROWSER proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.
+ * @throws ResultException
+ */
+ private boolean processLaunchBrowser(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processLaunchBrowser begins");
+ }
+
+ String url = null;
+ String confirmMsg = null;
+ List<TextAttribute> confirmMsgAttrs = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.URL, ctlvs);
+ if (ctlv != null) {
+ try {
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ int valueLen = ctlv.getLength();
+ if (valueLen > 0) {
+ url = GsmAlphabet.gsm8BitUnpackedToString(rawValue,
+ valueIndex, valueLen);
+ } else {
+ url = null;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
+ if (ctlv != null) {
+ confirmMsg = retrieveAlphaId(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ confirmMsgAttrs = retrieveTextAttribute(ctlv);
+ }
+
+ LaunchBrowserMode mode;
+ switch (cmdDet.commandQualifier) {
+ case 0x00:
+ default:
+ mode = LaunchBrowserMode.LAUNCH_IF_NOT_ALREADY_LAUNCHED;
+ break;
+ case 0x02:
+ mode = LaunchBrowserMode.USE_EXISTING_BROWSER;
+ break;
+ case 0x03:
+ mode = LaunchBrowserMode.LAUNCH_NEW_BROWSER;
+ break;
+ }
+
+ synchronized (mCmdListenerLock) {
+ if (mCmdListener != null) {
+ mCmdParams = new CommandParams(cmdDet);
+ mState = State.LAUNCH_BROWSER;
+
+ mCmdListener.onLaunchBrowser(url, confirmMsg, confirmMsgAttrs,
+ mode);
+ return true;
+ } else {
+ // '0' means "No specific cause can be given"
+ throw new ResultException(
+ ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS, 0);
+ }
+ }
+ }
+
+ /**
+ * Processes PLAY_TONE proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command
+ * object
+ * @param devIds Device Identities object retrieved from the proactive
+ * command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return true if the command is processing is pending and additional
+ * asynchronous processing is required.t
+ * @throws ResultException
+ */
+ private boolean processPlayTone(CtlvCommandDetails cmdDet,
+ CtlvDeviceIdentities devIds, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ if (Config.LOGD) {
+ Log.d(TAG, "processPlayTone begins");
+ }
+
+ Tone tone = null;
+ String text = null;
+ List<TextAttribute> textAttrs = null;
+ Duration duration = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TONE, ctlvs);
+ if (ctlv != null) {
+ // Nothing to do for null objects.
+ if (ctlv.getLength() > 0) {
+ try {
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ int toneVal = rawValue[valueIndex];
+ tone = Tone.fromInt(toneVal);
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(
+ ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ }
+ }
+ ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
+ if (ctlv != null) {
+ text = retrieveAlphaId(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.TEXT_ATTRIBUTE, ctlvs);
+ if (ctlv != null) {
+ textAttrs = retrieveTextAttribute(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
+ if (ctlv != null) {
+ duration = retrieveDuration(ctlv);
+ }
+
+ synchronized (mCmdListenerLock) {
+ if (mCmdListener != null) {
+ mState = State.PLAY_TONE;
+ mCmdParams = new CommandParams(cmdDet);
+ mCmdListener.onPlayTone(tone, text, textAttrs, duration);
+ return true;
+ } else {
+ // '0' means "No specific cause can be given"
+ throw new ResultException(
+ ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS, 0);
+ }
+ }
+ }
+
+ /**
+ * Retrieves text from the Text COMPREHENSION-TLV object, and decodes it
+ * into a {@link java.lang.String}.
+ *
+ * @param ctlv A Text COMPREHENSION-TLV object
+ * @return A {@link java.lang.String} object decoded from the Text object
+ * @throws ResultException
+ */
+ private String retrieveTextString(ComprehensionTlv ctlv)
+ throws ResultException {
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ byte codingScheme = 0x00;
+ String text = null;
+ int textLen = ctlv.getLength();
+
+ // In case the text length is 0, return a null string.
+ if (textLen == 0) {
+ return text;
+ } else {
+ // one byte is coding scheme
+ textLen -= 1;
+ }
+
+ try {
+ codingScheme = (byte) (rawValue[valueIndex] & 0x0c);
+
+ if (codingScheme == 0x00) { // GSM 7-bit packed
+ text = GsmAlphabet.gsm7BitPackedToString(rawValue,
+ valueIndex + 1, (textLen * 8) / 7);
+ } else if (codingScheme == 0x04) { // GSM 8-bit unpacked
+ text = GsmAlphabet.gsm8BitUnpackedToString(rawValue,
+ valueIndex + 1, textLen);
+ } else if (codingScheme == 0x08) { // UCS2
+ text = new String(rawValue, valueIndex + 1, textLen, "UTF-16");
+ } else {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+
+ return text;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ } catch (UnsupportedEncodingException e) {
+ // This should never happen.
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ }
+
+ /**
+ * Retrieves Duration information from the Duration COMPREHENSION-TLV
+ * object.
+ *
+ * @param ctlv A Text Attribute COMPREHENSION-TLV object
+ * @return A Duration object
+ * @throws ResultException
+ */
+ private Duration retrieveDuration(ComprehensionTlv ctlv)
+ throws ResultException {
+ int timeInterval = 0;
+ TimeUnit timeUnit = TimeUnit.SECOND;
+
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+
+ try {
+ timeUnit = TimeUnit.values()[(rawValue[valueIndex] & 0xff)];
+ timeInterval = rawValue[valueIndex + 1] & 0xff;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ return new Duration(timeInterval, timeUnit);
+ }
+
+ /**
+ * Retrieves Item information from the COMPREHENSION-TLV object.
+ *
+ * @param ctlv A Text Attribute COMPREHENSION-TLV object
+ * @return An Item
+ * @throws ResultException
+ */
+ private Item retrieveItem(ComprehensionTlv ctlv) throws ResultException {
+ Item item = null;
+
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ int length = ctlv.getLength();
+
+ if (length != 0) {
+ int textLen = length - 1;
+
+ try {
+ int id = rawValue[valueIndex] & 0xff;
+ String text = SimUtils.adnStringFieldToString(rawValue,
+ valueIndex + 1, textLen);
+ item = new Item(id, text);
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ }
+
+ return item;
+ }
+
+ /**
+ * Retrieves Item id information from the COMPREHENSION-TLV object.
+ *
+ * @param ctlv A Text Attribute COMPREHENSION-TLV object
+ * @return An Item id
+ * @throws ResultException
+ */
+ private int retrieveItemId(ComprehensionTlv ctlv) throws ResultException {
+ int id = 0;
+
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+
+ try {
+ id = rawValue[valueIndex] & 0xff;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+
+ return id;
+ }
+
+ /**
+ * Retrieves icon id from an Icon Identifier COMPREHENSION-TLV object
+ *
+ * @param ctlv An Icon Identifier COMPREHENSION-TLV object
+ * @return IconId instance
+ * @throws ResultException
+ */
+ private IconId retrieveIconId(ComprehensionTlv ctlv) throws ResultException {
+ IconId id = new IconId();
+
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ try {
+ id.selfExplanatory = (rawValue[valueIndex++] & 0xff) == 0x00;
+ id.recordNumber = rawValue[valueIndex] & 0xff;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+
+ return id;
+ }
+
+ /**
+ * Retrieves item icons id from an Icon Identifier List COMPREHENSION-TLV object
+ *
+ * @param ctlv An Item Icon List Identifier COMPREHENSION-TLV object
+ * @return ItemsIconId instance
+ * @throws ResultException
+ */
+ private ItemsIconId retrieveItemsIconId(ComprehensionTlv ctlv)
+ throws ResultException{
+ Log.d(TAG, "retrieveIconIdList:");
+ ItemsIconId id = new ItemsIconId();
+
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ int numOfItems = ctlv.getLength() - 1;
+ id.recordNumbers = new int[numOfItems];
+
+ try {
+ // get icon self-explanatory
+ id.selfExplanatory = (rawValue[valueIndex++] & 0xff) == 0x00;
+
+ for (int index = 0; index < numOfItems;) {
+ id.recordNumbers[index++] = rawValue[valueIndex++];
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ return id;
+ }
+
+ /**
+ * Retrieves text attribute information from the Text Attribute
+ * COMPREHENSION-TLV object.
+ *
+ * @param ctlv A Text Attribute COMPREHENSION-TLV object
+ * @return A list of TextAttribute objects
+ * @throws ResultException
+ */
+ private List<TextAttribute> retrieveTextAttribute(ComprehensionTlv ctlv)
+ throws ResultException {
+ ArrayList<TextAttribute> lst = new ArrayList<TextAttribute>();
+
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ int length = ctlv.getLength();
+
+ if (length != 0) {
+ // Each attribute is consisted of four bytes
+ int itemCount = length / 4;
+
+ try {
+ for (int i = 0; i < itemCount; i++, valueIndex += 4) {
+ int start = rawValue[valueIndex] & 0xff;
+ int textLength = rawValue[valueIndex + 1] & 0xff;
+ int format = rawValue[valueIndex + 2] & 0xff;
+ int colorValue = rawValue[valueIndex + 3] & 0xff;
+
+ int alignValue = format & 0x03;
+ TextAlignment align = TextAlignment.fromInt(alignValue);
+
+ int sizeValue = (format >> 2) & 0x03;
+ FontSize size = FontSize.fromInt(sizeValue);
+ if (size == null) {
+ // Font size value is not defined. Use default.
+ size = FontSize.NORMAL;
+ }
+
+ boolean bold = (format & 0x10) != 0;
+ boolean italic = (format & 0x20) != 0;
+ boolean underlined = (format & 0x40) != 0;
+ boolean strikeThrough = (format & 0x80) != 0;
+
+ TextColor color = TextColor.fromInt(colorValue);
+
+ TextAttribute attr = new TextAttribute(start, textLength,
+ align, size, bold, italic, underlined,
+ strikeThrough, color);
+ lst.add(attr);
+ }
+
+ return lst;
+
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves alpha identifier from an Alpha Identifier COMPREHENSION-TLV
+ * object.
+ *
+ * @param ctlv An Alpha Identifier COMPREHENSION-TLV object
+ * @return String corresponding to the alpha identifier
+ * @throws ResultException
+ */
+ private String retrieveAlphaId(ComprehensionTlv ctlv)
+ throws ResultException {
+
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ int length = ctlv.getLength();
+ if (length != 0) {
+ try {
+ return SimUtils.adnStringFieldToString(rawValue, valueIndex,
+ length);
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Handles RIL_UNSOL_STK_EVENT_NOTIFY unsolicited command from RIL.
+ *
+ * @param data String containing SAT/USAT commands or responses sent by ME
+ * to SIM or commands handled by ME, in hexadecimal format starting
+ * with first byte of response data or command tag
+ */
+ private void handleEventNotify(String data) {
+ if (Config.LOGD) {
+ Log.d(TAG, "handleEventNotify begins");
+ }
+ byte[] rawData = null;
+ BerTlv berTlv = null;
+ CtlvCommandDetails cmdDet = null;
+ CtlvDeviceIdentities devIds = null;
+ CommandType cmdType = null;
+
+ // Nothing to do for empty strings.
+ if (data.length() == 0) {
+ return;
+ }
+ rawData = SimUtils.hexStringToBytes(data);
+ try {
+ berTlv = BerTlv.decode(rawData);
+ } catch (ResultException e) {
+ // Can't parse command buffer.
+ return;
+ }
+
+ // Extract command details & Device identities tlv objects list.
+ List<ComprehensionTlv> ctlvs = berTlv.getComprehensionTlvs();
+ try {
+ cmdDet = retrieveCommandDetails(ctlvs);
+ devIds = retrieveDeviceIdentities(ctlvs);
+ } catch (ResultException e) {
+ if (Config.LOGD) {
+ Log.d(TAG, "invlaid command details/device identities");
+ }
+ return;
+ }
+
+ // Check to see if we support this command.
+ cmdType = CommandType.fromInt(cmdDet.typeOfCommand);
+ if (cmdType == null) {
+ return;
+ }
+
+ // There are two scenarios for EVENT_NOTIFY messages:
+ // 1. A proactive command which is partially handled by the baseband and
+ // requires UI processing from the application. This messages will be
+ // tagged with PROACTIVE COMMAND tag.
+ // 2. A notification for an action completed by the baseband. This
+ // messages will be tagged with UNKNOWN tag and the command type inside
+ // the Command details object should indicate which action was completed.
+ if (berTlv.getTag() == BerTlv.BER_PROACTIVE_COMMAND_TAG) {
+ switch (cmdType) {
+ case SEND_SS:
+ case SEND_USSD:
+ case SEND_SMS:
+ case SEND_DTMF:
+ processEventNotify(cmdDet, devIds, ctlvs);
+ break;
+ case SET_UP_EVENT_LIST:
+ processSetUpEventList(cmdDet, devIds, ctlvs);
+ break;
+ case SET_UP_CALL:
+ processSetupCall(cmdDet, devIds, ctlvs);
+ break;
+ default:
+ // nada
+ break;
+ }
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/StkAppInstaller.java b/telephony/java/com/android/internal/telephony/gsm/stk/StkAppInstaller.java
new file mode 100644
index 0000000..07e3e56
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/StkAppInstaller.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony.gsm.stk;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+/**
+ * Application installer for SIM Toolkit.
+ *
+ */
+public class StkAppInstaller {
+ // Application state actions: install, uninstall used by StkAppStateReceiver.
+ static final String STK_APP_INSTALL_ACTION = "com.android.stk.action.INSTALL";
+ static final String STK_APP_UNINSTALL_ACTION = "com.android.stk.action.UNINSTALL";
+
+ public static void installApp(Context context) {
+ setAppState(context, PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ }
+
+ public static void unInstallApp(Context context) {
+ setAppState(context, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ }
+
+ private static void setAppState(Context context, int state) {
+ if (context == null) {
+ return;
+ }
+ PackageManager pm = context.getPackageManager();
+ if (pm == null) {
+ return;
+ }
+ // check that STK app package is known to the PackageManager
+ ComponentName cName = new ComponentName("com.android.stk",
+ "com.android.stk.StkActivity");
+
+ try {
+ pm.setComponentEnabledSetting(cName, state,
+ PackageManager.DONT_KILL_APP);
+ } catch (Exception e) {
+ Log.w("StkAppInstaller", "Could not change STK app state");
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/StkAppStateReceiver.java b/telephony/java/com/android/internal/telephony/gsm/stk/StkAppStateReceiver.java
new file mode 100644
index 0000000..778ca2e
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/StkAppStateReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 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.internal.telephony.gsm.stk;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import com.android.internal.telephony.gsm.stk.Service;
+import android.util.Log;
+
+/**
+ * This class implements a Broadcast receiver. It waits for an intent sent by
+ * the STK service and install/uninstall the STK application. If no intent is
+ * received when the device finished booting, the application is then unistalled.
+ */
+public class StkAppStateReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "StkAppStateReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive");
+
+ String action = intent.getAction();
+ Service stkService = Service.getInstance();
+ if (stkService == null) {
+ return;
+ }
+ if (action.equals(StkAppInstaller.STK_APP_INSTALL_ACTION)) {
+ stkService.setAppIndication(Service.APP_INDICATOR_INSTALLED_NORMAL);
+ StkAppInstaller.installApp(context);
+ } else if (action.equals(StkAppInstaller.STK_APP_UNINSTALL_ACTION)) {
+ stkService.setAppIndication(Service.APP_INDICATOR_UNINSTALLED);
+ StkAppInstaller.unInstallApp(context);
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/StkException.java b/telephony/java/com/android/internal/telephony/gsm/stk/StkException.java
new file mode 100644
index 0000000..86de366
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/StkException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+import android.util.AndroidException;
+
+
+/**
+ * Base class for all the exceptions in STK service.
+ *
+ * {@hide}
+ */
+class StkException extends AndroidException {
+ public StkException() {
+ super();
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/TextAlignment.java b/telephony/java/com/android/internal/telephony/gsm/stk/TextAlignment.java
new file mode 100644
index 0000000..c5dd50e
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/TextAlignment.java
@@ -0,0 +1,52 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Enumeration for representing text alignment.
+ *
+ * {@hide}
+ */
+public enum TextAlignment {
+ LEFT(0x0),
+ CENTER(0x1),
+ RIGHT(0x2),
+ /** Language dependent (default) */
+ DEFAULT(0x3);
+
+ private int mValue;
+
+ TextAlignment(int value) {
+ mValue = value;
+ }
+
+ /**
+ * Create a TextAlignment object.
+ * @param value Integer value to be converted to a TextAlignment object.
+ * @return TextAlignment object whose value is {@code value}. If no
+ * TextAlignment object has that value, null is returned.
+ */
+ public static TextAlignment fromInt(int value) {
+ for (TextAlignment e : TextAlignment.values()) {
+ if (e.mValue == value) {
+ return e;
+ }
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/TextAttribute.java b/telephony/java/com/android/internal/telephony/gsm/stk/TextAttribute.java
new file mode 100644
index 0000000..ace4300
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/TextAttribute.java
@@ -0,0 +1,49 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Class for representing text attributes for SIM Toolkit.
+ *
+ * {@hide}
+ */
+public class TextAttribute {
+ public int start;
+ public int length;
+ public TextAlignment align;
+ public FontSize size;
+ public boolean bold;
+ public boolean italic;
+ public boolean underlined;
+ public boolean strikeThrough;
+ public TextColor color;
+
+ public TextAttribute(int start, int length, TextAlignment align,
+ FontSize size, boolean bold, boolean italic, boolean underlined,
+ boolean strikeThrough, TextColor color) {
+ this.start = start;
+ this.length = length;
+ this.align = align;
+ this.size = size;
+ this.bold = bold;
+ this.italic = italic;
+ this.underlined = underlined;
+ this.strikeThrough = strikeThrough;
+ this.color = color;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/TextColor.java b/telephony/java/com/android/internal/telephony/gsm/stk/TextColor.java
new file mode 100644
index 0000000..126fc62
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/TextColor.java
@@ -0,0 +1,63 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Enumeration for representing text color.
+ *
+ * {@hide}
+ */
+public enum TextColor {
+ BLACK(0x0),
+ DARK_GRAY(0x1),
+ DARK_RED(0x2),
+ DARK_YELLOW(0x3),
+ DARK_GREEN(0x4),
+ DARK_CYAN(0x5),
+ DARK_BLUE(0x6),
+ DARK_MAGENTA(0x7),
+ GRAY(0x8),
+ WHITE(0x9),
+ BRIGHT_RED(0xa),
+ BRIGHT_YELLOW(0xb),
+ BRIGHT_GREEN(0xc),
+ BRIGHT_CYAN(0xd),
+ BRIGHT_BLUE(0xe),
+ BRIGHT_MAGENTA(0xf);
+
+ private int mValue;
+
+ TextColor(int value) {
+ mValue = value;
+ }
+
+ /**
+ * Create a TextColor object.
+ * @param value Integer value to be converted to a TextColor object.
+ * @return TextColor object whose value is {@code value}. If no TextColor
+ * object has that value, null is returned.
+ */
+ public static TextColor fromInt(int value) {
+ for (TextColor e : TextColor.values()) {
+ if (e.mValue == value) {
+ return e;
+ }
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/Tone.java b/telephony/java/com/android/internal/telephony/gsm/stk/Tone.java
new file mode 100644
index 0000000..c96f164
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/Tone.java
@@ -0,0 +1,166 @@
+/*
+ * 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.internal.telephony.gsm.stk;
+
+
+/**
+ * Enumeration for representing the tone values for use with PLAY TONE
+ * proactive commands.
+ *
+ * {@hide}
+ */
+public enum Tone {
+ // Standard supervisory tones
+
+ /**
+ * Dial tone.
+ */
+ DIAL(0x01),
+
+ /**
+ * Called subscriber busy.
+ */
+ BUSY(0x02),
+
+ /**
+ * Congestion.
+ */
+ CONGESTION(0x03),
+
+ /**
+ * Radio path acknowledge.
+ */
+ RADIO_PATH_ACK(0x04),
+
+ /**
+ * Radio path not available / Call dropped.
+ */
+ RADIO_PATH_NOT_AVAILABLE(0x05),
+
+ /**
+ * Error/Special information.
+ */
+ ERROR_SPECIAL_INFO(0x06),
+
+ /**
+ * Call waiting tone.
+ */
+ CALL_WAITING(0x07),
+
+ /**
+ * Ringing tone.
+ */
+ RINGING(0x08),
+
+ // Terminal proprietary tones
+
+ /**
+ * General beep.
+ */
+ GENERAL_BEEP(0x10),
+
+ /**
+ * Positive acknowledgement tone.
+ */
+ POSITIVE_ACK(0x11),
+
+ /**
+ * Negative acknowledgement tone.
+ */
+ NEGATIVE_ACK(0x12),
+
+ /**
+ * Ringing tone as selected by the user for incoming speech call.
+ */
+ INCOMING_SPEECH_CALL(0x13),
+
+ /**
+ * Alert tone as selected by the user for incoming SMS.
+ */
+ INCOMING_SMS(0x14),
+
+ /**
+ * Critical alert.
+ * This tone is to be used in critical situations. The terminal shall make
+ * every effort to alert the user when this tone is indicated independent
+ * from the volume setting in the terminal.
+ */
+ CRITICAL_ALERT(0x15),
+
+ /**
+ * Vibrate only, if available.
+ */
+ VIBRATE_ONLY(0x20),
+
+ // Themed tones
+
+ /**
+ * Happy tone.
+ */
+ HAPPY(0x30),
+
+ /**
+ * Sad tone.
+ */
+ SAD(0x31),
+
+ /**
+ * Urgent action tone.
+ */
+ URGENT(0x32),
+
+ /**
+ * Question tone.
+ */
+ QUESTION(0x33),
+
+ /**
+ * Message received tone.
+ */
+ MESSAGE_RECEIVED(0x34),
+
+ // Melody tones
+ MELODY_1(0x40),
+ MELODY_2(0x41),
+ MELODY_3(0x42),
+ MELODY_4(0x43),
+ MELODY_5(0x44),
+ MELODY_6(0x45),
+ MELODY_7(0x46),
+ MELODY_8(0x47);
+
+ private int mValue;
+
+ Tone(int value) {
+ mValue = value;
+ }
+
+ /**
+ * Create a Tone object.
+ * @param value Integer value to be converted to a Tone object.
+ * @return Tone object whose value is {@code value}. If no Tone object has
+ * that value, null is returned.
+ */
+ public static Tone fromInt(int value) {
+ for (Tone e : Tone.values()) {
+ if (e.mValue == value) {
+ return e;
+ }
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/package.html b/telephony/java/com/android/internal/telephony/gsm/stk/package.html
new file mode 100644
index 0000000..c285b57
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/stk/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Provides classes for SIM Toolkit Service.
+</BODY>
+</HTML>
diff --git a/telephony/java/com/android/internal/telephony/package.html b/telephony/java/com/android/internal/telephony/package.html
new file mode 100644
index 0000000..6377c18
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+{@hide}
+</body>
+</html>
diff --git a/telephony/java/com/android/internal/telephony/test/ModelInterpreter.java b/telephony/java/com/android/internal/telephony/test/ModelInterpreter.java
new file mode 100644
index 0000000..6af5d87
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/test/ModelInterpreter.java
@@ -0,0 +1,750 @@
+/*
+ * 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.internal.telephony.test;
+
+import com.android.internal.os.HandlerThread;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import android.os.*;
+import android.util.Log;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.InetSocketAddress;
+import java.util.List;
+
+// Also in ATChannel.java
+class LineReader
+{
+ /**
+ * Not threadsafe
+ * Assumes input is ASCII
+ */
+
+ //***** Constants
+
+ // For what it's worth, this is also the size of an
+ // OMAP CSMI mailbox
+ static final int BUFFER_SIZE = 0x1000;
+
+ // just to prevent constant allocations
+ byte buffer[] = new byte[BUFFER_SIZE];
+
+ //***** Instance Variables
+
+ InputStream inStream;
+
+ LineReader (InputStream s)
+ {
+ inStream = s;
+ }
+
+ String
+ getNextLine()
+ {
+ return getNextLine(false);
+ }
+
+ String
+ getNextLineCtrlZ()
+ {
+ return getNextLine(true);
+ }
+
+ /**
+ * Note: doesn't return the last incomplete line read on EOF, since
+ * it doesn't typically matter anyway
+ *
+ * Returns NULL on EOF
+ */
+
+ String
+ getNextLine(boolean ctrlZ)
+ {
+ int i = 0;
+
+ try {
+ for (;;) {
+ int result;
+
+ result = inStream.read();
+
+ if (result < 0) {
+ return null;
+ }
+
+ if (ctrlZ && result == 0x1a) {
+ break;
+ } else if (result == '\r' || result == '\n') {
+ if (i == 0) {
+ // Skip leading cr/lf
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ buffer[i++] = (byte)result;
+ }
+ } catch (IOException ex) {
+ return null;
+ } catch (IndexOutOfBoundsException ex) {
+ System.err.println("ATChannel: buffer overflow");
+ }
+
+ try {
+ return new String(buffer, 0, i, "US-ASCII");
+ } catch (UnsupportedEncodingException ex) {
+ System.err.println("ATChannel: implausable UnsupportedEncodingException");
+ return null;
+ }
+ }
+}
+
+
+
+class InterpreterEx extends Exception
+{
+ public
+ InterpreterEx (String result)
+ {
+ this.result = result;
+ }
+
+ String result;
+}
+
+public class ModelInterpreter
+ implements Runnable, HandlerInterface, SimulatedRadioControl
+{
+ static final int MAX_CALLS = 6;
+
+ /** number of msec between dialing -> alerting and alerting->active */
+ static final int CONNECTING_PAUSE_MSEC = 5 * 100;
+
+ static final String LOG_TAG = "ModelInterpreter";
+
+ //***** Instance Variables
+
+ InputStream in;
+ OutputStream out;
+ LineReader lineReader;
+ ServerSocket ss;
+
+ private String finalResponse;
+
+ SimulatedGsmCallState simulatedCallState;
+
+ HandlerThread handlerThread;
+
+ int pausedResponseCount;
+ Object pausedResponseMonitor = new Object();
+
+ //***** Events
+
+ static final int PROGRESS_CALL_STATE = 1;
+
+ //***** Constructor
+
+ public
+ ModelInterpreter (InputStream in, OutputStream out)
+ {
+ this.in = in;
+ this.out = out;
+
+ init();
+ }
+
+ public
+ ModelInterpreter (InetSocketAddress sa) throws java.io.IOException
+ {
+ ss = new ServerSocket();
+
+ ss.setReuseAddress(true);
+ ss.bind(sa);
+
+ init();
+ }
+
+ private void
+ init()
+ {
+ new Thread(this, "ModelInterpreter").start();
+
+ handlerThread
+ = new HandlerThread(this,
+ new Runnable() {
+ public void run() {
+ simulatedCallState = new SimulatedGsmCallState();
+ }
+ },
+ "ModelInterpreter");
+ }
+
+ //***** Runnable Implementation
+
+ public void run()
+ {
+ for (;;) {
+ if (ss != null) {
+ Socket s;
+
+ try {
+ s = ss.accept();
+ } catch (java.io.IOException ex) {
+ Log.w(LOG_TAG,
+ "IOException on socket.accept(); stopping", ex);
+ return;
+ }
+
+ try {
+ in = s.getInputStream();
+ out = s.getOutputStream();
+ } catch (java.io.IOException ex) {
+ Log.w(LOG_TAG,
+ "IOException on accepted socket(); re-listening", ex);
+ continue;
+ }
+
+ Log.i(LOG_TAG, "New connection accepted");
+ }
+
+
+ lineReader = new LineReader (in);
+
+ println ("Welcome");
+
+ for (;;) {
+ String line;
+
+ line = lineReader.getNextLine();
+
+ //System.out.println("MI<< " + line);
+
+ if (line == null) {
+ break;
+ }
+
+ synchronized(pausedResponseMonitor) {
+ while (pausedResponseCount > 0) {
+ try {
+ pausedResponseMonitor.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+
+ synchronized (this) {
+ try {
+ finalResponse = "OK";
+ processLine(line);
+ println(finalResponse);
+ } catch (InterpreterEx ex) {
+ println(ex.result);
+ } catch (RuntimeException ex) {
+ ex.printStackTrace();
+ println("ERROR");
+ }
+ }
+ }
+
+ Log.i(LOG_TAG, "Disconnected");
+
+ if (ss == null) {
+ // no reconnect in this case
+ break;
+ }
+ }
+ }
+
+ //***** HandlerInterface Implementation
+
+ public void
+ handleMessage(Message msg)
+ {
+
+ }
+
+
+ //***** Instance Methods
+
+ /** Start the simulated phone ringing */
+ public void
+ triggerRing(String number)
+ {
+ synchronized (this) {
+ boolean success;
+
+ success = simulatedCallState.triggerRing(number);
+
+ if (success) {
+ println ("RING");
+ }
+ }
+ }
+
+ /** If a call is DIALING or ALERTING, progress it to the next state */
+ public void
+ progressConnectingCallState()
+ {
+ simulatedCallState.progressConnectingCallState();
+ }
+
+
+ /** If a call is DIALING or ALERTING, progress it all the way to ACTIVE */
+ public void
+ progressConnectingToActive()
+ {
+ simulatedCallState.progressConnectingToActive();
+ }
+
+ /** automatically progress mobile originated calls to ACTIVE.
+ * default to true
+ */
+ public void
+ setAutoProgressConnectingCall(boolean b)
+ {
+ simulatedCallState.setAutoProgressConnectingCall(b);
+ }
+
+ public void
+ setNextDialFailImmediately(boolean b)
+ {
+ simulatedCallState.setNextDialFailImmediately(b);
+ }
+
+ public void setNextCallFailCause(int gsmCause)
+ {
+ //FIXME implement
+ }
+
+
+ /** hangup ringing, dialing, or actuve calls */
+ public void
+ triggerHangupForeground()
+ {
+ boolean success;
+
+ success = simulatedCallState.triggerHangupForeground();
+
+ if (success) {
+ println ("NO CARRIER");
+ }
+ }
+
+ /** hangup holding calls */
+ public void
+ triggerHangupBackground()
+ {
+ boolean success;
+
+ success = simulatedCallState.triggerHangupBackground();
+
+ if (success) {
+ println ("NO CARRIER");
+ }
+ }
+
+ /** hangup all */
+
+ public void
+ triggerHangupAll()
+ {
+ boolean success;
+
+ success = simulatedCallState.triggerHangupAll();
+
+ if (success) {
+ println ("NO CARRIER");
+ }
+ }
+
+ public void
+ sendUnsolicited (String unsol)
+ {
+ synchronized (this) {
+ println(unsol);
+ }
+ }
+
+ public void triggerSsn(int a, int b) {}
+ public void triggerIncomingUssd(String statusCode, String message) {}
+
+ public void
+ triggerIncomingSMS(String message)
+ {
+/**************
+ StringBuilder pdu = new StringBuilder();
+
+ pdu.append ("00"); //SMSC address - 0 bytes
+
+ pdu.append ("04"); // Message type indicator
+
+ // source address: +18005551212
+ pdu.append("918100551521F0");
+
+ // protocol ID and data coding scheme
+ pdu.append("0000");
+
+ Calendar c = Calendar.getInstance();
+
+ pdu.append (c.
+
+
+
+ synchronized (this) {
+ println("+CMT: ,1\r" + pdu.toString());
+ }
+
+**************/
+ }
+
+ public void
+ pauseResponses()
+ {
+ synchronized(pausedResponseMonitor) {
+ pausedResponseCount++;
+ }
+ }
+
+ public void
+ resumeResponses()
+ {
+ synchronized(pausedResponseMonitor) {
+ pausedResponseCount--;
+
+ if (pausedResponseCount == 0) {
+ pausedResponseMonitor.notifyAll();
+ }
+ }
+ }
+
+ //***** Private Instance Methods
+
+ private void
+ onAnswer() throws InterpreterEx
+ {
+ boolean success;
+
+ success = simulatedCallState.onAnswer();
+
+ if (!success) {
+ throw new InterpreterEx("ERROR");
+ }
+ }
+
+ private void
+ onHangup() throws InterpreterEx
+ {
+ boolean success = false;
+
+ success = simulatedCallState.onAnswer();
+
+ if (!success) {
+ throw new InterpreterEx("ERROR");
+ }
+
+ finalResponse = "NO CARRIER";
+ }
+
+ private void
+ onCHLD(String command) throws InterpreterEx
+ {
+ // command starts with "+CHLD="
+ char c0;
+ char c1 = 0;
+ boolean success;
+
+ c0 = command.charAt(6);
+
+ if (command.length() >= 8) {
+ c1 = command.charAt(7);
+ }
+
+ success = simulatedCallState.onChld(c0, c1);
+
+ if (!success) {
+ throw new InterpreterEx("ERROR");
+ }
+ }
+
+ private void
+ releaseHeldOrUDUB() throws InterpreterEx
+ {
+ boolean success;
+
+ success = simulatedCallState.releaseHeldOrUDUB();
+
+ if (!success) {
+ throw new InterpreterEx("ERROR");
+ }
+ }
+
+ private void
+ releaseActiveAcceptHeldOrWaiting() throws InterpreterEx
+ {
+ boolean success;
+
+ success = simulatedCallState.releaseActiveAcceptHeldOrWaiting();
+
+ if (!success) {
+ throw new InterpreterEx("ERROR");
+ }
+ }
+
+ private void
+ switchActiveAndHeldOrWaiting() throws InterpreterEx
+ {
+ boolean success;
+
+ success = simulatedCallState.switchActiveAndHeldOrWaiting();
+
+ if (!success) {
+ throw new InterpreterEx("ERROR");
+ }
+ }
+
+ private void
+ separateCall(int index) throws InterpreterEx
+ {
+ boolean success;
+
+ success = simulatedCallState.separateCall(index);
+
+ if (!success) {
+ throw new InterpreterEx("ERROR");
+ }
+ }
+
+ private void
+ conference() throws InterpreterEx
+ {
+ boolean success;
+
+ success = simulatedCallState.conference();
+
+ if (!success) {
+ throw new InterpreterEx("ERROR");
+ }
+ }
+
+ private void
+ onDial(String command) throws InterpreterEx
+ {
+ boolean success;
+
+ success = simulatedCallState.onDial(command.substring(1));
+
+ if (!success) {
+ throw new InterpreterEx("ERROR");
+ }
+ }
+
+ private void
+ onCLCC() throws InterpreterEx
+ {
+ List<String> lines;
+
+ lines = simulatedCallState.getClccLines();
+
+ for (int i = 0, s = lines.size() ; i < s ; i++) {
+ println (lines.get(i));
+ }
+ }
+
+ private void
+ onSMSSend(String command) throws InterpreterEx
+ {
+ String pdu;
+
+ print ("> ");
+ pdu = lineReader.getNextLineCtrlZ();
+
+ println("+CMGS: 1");
+ }
+
+ void
+ processLine (String line) throws InterpreterEx
+ {
+ String[] commands;
+
+ commands = splitCommands(line);
+
+ for (int i = 0; i < commands.length ; i++) {
+ String command = commands[i];
+
+ if (command.equals("A")) {
+ onAnswer();
+ } else if (command.equals("H")) {
+ onHangup();
+ } else if (command.startsWith("+CHLD=")) {
+ onCHLD(command);
+ } else if (command.equals("+CLCC")) {
+ onCLCC();
+ } else if (command.startsWith("D")) {
+ onDial(command);
+ } else if (command.startsWith("+CMGS=")) {
+ onSMSSend(command);
+ } else {
+ boolean found = false;
+
+ for (int j = 0; j < sDefaultResponses.length ; j++) {
+ if (command.equals(sDefaultResponses[j][0])) {
+ String r = sDefaultResponses[j][1];
+ if (r != null) {
+ println(r);
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ throw new InterpreterEx ("ERROR");
+ }
+ }
+ }
+ }
+
+
+ String[]
+ splitCommands(String line) throws InterpreterEx
+ {
+ if (!line.startsWith ("AT")) {
+ throw new InterpreterEx("ERROR");
+ }
+
+ if (line.length() == 2) {
+ // Just AT by itself
+ return new String[0];
+ }
+
+ String ret[] = new String[1];
+
+ //TODO fix case here too
+ ret[0] = line.substring(2);
+
+ return ret;
+/****
+ try {
+ // i = 2 to skip over AT
+ for (int i = 2, s = line.length() ; i < s ; i++) {
+ // r"|([A-RT-Z]\d?)" # Normal commands eg ATA or I0
+ // r"|(&[A-Z]\d*)" # & commands eg &C
+ // r"|(S\d+(=\d+)?)" # S registers
+ // r"((\+|%)\w+(\?|=([^;]+(;|$)))?)" # extended command eg +CREG=2
+
+
+ }
+ } catch (StringIndexOutOfBoundsException ex) {
+ throw new InterpreterEx ("ERROR");
+ }
+***/
+ }
+
+ void
+ println (String s)
+ {
+ synchronized(this) {
+ try {
+ byte[] bytes = s.getBytes("US-ASCII");
+
+ //System.out.println("MI>> " + s);
+
+ out.write(bytes);
+ out.write('\r');
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ void
+ print (String s)
+ {
+ synchronized(this) {
+ try {
+ byte[] bytes = s.getBytes("US-ASCII");
+
+ //System.out.println("MI>> " + s + " (no <cr>)");
+
+ out.write(bytes);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+
+ public void
+ shutdown()
+ {
+ handlerThread.getHandler().getLooper().quit();
+
+ try {
+ in.close();
+ } catch (IOException ex) {
+ }
+ try {
+ out.close();
+ } catch (IOException ex) {
+ }
+ }
+
+
+ static final String [][] sDefaultResponses = {
+ {"E0Q0V1", null},
+ {"+CMEE=2", null},
+ {"+CREG=2", null},
+ {"+CGREG=2", null},
+ {"+CCWA=1", null},
+ {"+COPS=0", null},
+ {"+CFUN=1", null},
+ {"+CGMI", "+CGMI: Android Model AT Interpreter\r"},
+ {"+CGMM", "+CGMM: Android Model AT Interpreter\r"},
+ {"+CGMR", "+CGMR: 1.0\r"},
+ {"+CGSN", "000000000000000\r"},
+ {"+CIMI", "320720000000000\r"},
+ {"+CSCS=?", "+CSCS: (\"HEX\",\"UCS2\")\r"},
+ {"+CFUN?", "+CFUN: 1\r"},
+ {"+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?",
+ "+COPS: 0,0,\"Android\"\r"
+ + "+COPS: 0,1,\"Android\"\r"
+ + "+COPS: 0,2,\"310995\"\r"},
+ {"+CREG?", "+CREG: 2,5, \"0113\", \"6614\"\r"},
+ {"+CGREG?", "+CGREG: 2,0\r"},
+ {"+CSQ", "+CSQ: 16,99\r"},
+ {"+CNMI?", "+CNMI: 1,2,2,1,1\r"},
+ {"+CLIR?", "+CLIR: 1,3\r"},
+ {"%CPVWI=2", "%CPVWI: 0\r"},
+ {"+CUSD=1,\"#646#\"", "+CUSD=0,\"You have used 23 minutes\"\r"},
+ {"+CRSM=176,12258,0,0,10", "+CRSM: 144,0,981062200050259429F6\r"},
+ {"+CRSM=192,12258,0,0,15", "+CRSM: 144,0,0000000A2FE204000FF55501020000\r"},
+
+ /* EF[ADN] */
+ {"+CRSM=192,28474,0,0,15", "+CRSM: 144,0,0000005a6f3a040011f5220102011e\r"},
+ {"+CRSM=178,28474,1,4,30", "+CRSM: 144,0,437573746f6d65722043617265ffffff07818100398799f7ffffffffffff\r"},
+ {"+CRSM=178,28474,2,4,30", "+CRSM: 144,0,566f696365204d61696cffffffffffff07918150367742f3ffffffffffff\r"},
+ {"+CRSM=178,28474,3,4,30", "+CRSM: 144,0,4164676a6dffffffffffffffffffffff0b918188551512c221436587ff01\r"},
+ {"+CRSM=178,28474,4,4,30", "+CRSM: 144,0,810101c1ffffffffffffffffffffffff068114455245f8ffffffffffffff\r"},
+ /* EF[EXT1] */
+ {"+CRSM=192,28490,0,0,15", "+CRSM: 144,0,000000416f4a040011f5550102010d\r"},
+ {"+CRSM=178,28490,1,4,13", "+CRSM: 144,0,0206092143658709ffffffffff\r"}
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
new file mode 100644
index 0000000..47fac46
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -0,0 +1,1364 @@
+/*
+ * 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.internal.telephony.test;
+
+import com.android.internal.os.HandlerThread;
+import android.os.AsyncResult;
+import android.os.HandlerInterface;
+import android.os.Message;
+import com.android.internal.telephony.ATParseEx;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.gsm.BaseCommands;
+import com.android.internal.telephony.gsm.BaseCommands;
+import com.android.internal.telephony.gsm.CallFailCause;
+import com.android.internal.telephony.gsm.CommandException;
+import com.android.internal.telephony.gsm.CommandsInterface;
+import com.android.internal.telephony.gsm.DriverCall;
+import com.android.internal.telephony.gsm.PDPContextState;
+import com.android.internal.telephony.gsm.stk.CtlvCommandDetails;
+import com.android.internal.telephony.gsm.stk.ResultCode;
+import com.android.internal.telephony.gsm.SuppServiceNotification;
+import android.util.Log;
+import java.util.ArrayList;
+
+public final class SimulatedCommands extends BaseCommands
+ implements CommandsInterface, HandlerInterface, SimulatedRadioControl
+{
+ private final static String LOG_TAG = "SIM";
+
+ private enum SimLockState {
+ NONE,
+ REQUIRE_PIN,
+ REQUIRE_PUK,
+ SIM_PERM_LOCKED
+ };
+
+ private enum SimFdnState {
+ NONE,
+ REQUIRE_PIN2,
+ REQUIRE_PUK2,
+ SIM_PERM_LOCKED
+ };
+
+ private final static SimLockState INITIAL_LOCK_STATE = SimLockState.NONE;
+ private final static String DEFAULT_SIM_PIN_CODE = "1234";
+ private final static String SIM_PUK_CODE = "12345678";
+ private final static SimFdnState INITIAL_FDN_STATE = SimFdnState.NONE;
+ private final static String DEFAULT_SIM_PIN2_CODE = "5678";
+ private final static String SIM_PUK2_CODE = "87654321";
+
+ //***** Instance Variables
+
+ SimulatedGsmCallState simulatedCallState;
+ HandlerThread handlerThread;
+ SimLockState mSimLockedState;
+ boolean mSimLockEnabled;
+ int mPinUnlockAttempts;
+ int mPukUnlockAttempts;
+ String mPinCode;
+ SimFdnState mSimFdnEnabledState;
+ boolean mSimFdnEnabled;
+ int mPin2UnlockAttempts;
+ int mPuk2UnlockAttempts;
+ int mNetworkType;
+ String mPin2Code;
+ boolean mSsnNotifyOn = false;
+
+ int pausedResponseCount;
+ ArrayList<Message> pausedResponses = new ArrayList<Message>();
+
+ int nextCallFailCause = CallFailCause.NORMAL_CLEARING;
+
+ //***** Constructor
+
+ public
+ SimulatedCommands() {
+ super(null); // Don't log statistics
+ handlerThread
+ = new HandlerThread(this,
+ new Runnable() {
+ public void run() {
+ simulatedCallState = new SimulatedGsmCallState();
+ }
+ },
+ "SimulatedCommands");
+
+ setRadioState(RadioState.RADIO_OFF);
+ mSimLockedState = INITIAL_LOCK_STATE;
+ mSimLockEnabled = (mSimLockedState != SimLockState.NONE);
+ mPinCode = DEFAULT_SIM_PIN_CODE;
+ mSimFdnEnabledState = INITIAL_FDN_STATE;
+ mSimFdnEnabled = (mSimFdnEnabledState != SimFdnState.NONE);
+ mPin2Code = DEFAULT_SIM_PIN2_CODE;
+ }
+
+ //***** CommandsInterface implementation
+
+ public void getSimStatus(Message result)
+ {
+ switch (mState) {
+ case SIM_READY:
+ resultSuccess(result, SimStatus.SIM_READY);
+ break;
+
+ case SIM_LOCKED_OR_ABSENT:
+ returnSimLockedStatus(result);
+ break;
+
+ default:
+ resultSuccess(result, SimStatus.SIM_NOT_READY);
+ break;
+ }
+ }
+
+ private void returnSimLockedStatus(Message result) {
+ switch (mSimLockedState) {
+ case REQUIRE_PIN:
+ Log.i(LOG_TAG, "[SimCmd] returnSimLockedStatus: SIM_PIN");
+ resultSuccess(result, SimStatus.SIM_PIN);
+ break;
+
+ case REQUIRE_PUK:
+ Log.i(LOG_TAG, "[SimCmd] returnSimLockedStatus: SIM_PUK");
+ resultSuccess(result, SimStatus.SIM_PUK);
+ break;
+
+ default:
+ Log.i(LOG_TAG,
+ "[SimCmd] returnSimLockedStatus: mSimLockedState==NONE !?");
+ break;
+ }
+ }
+
+ public void supplySimPin(String pin, Message result) {
+ if (mSimLockedState != SimLockState.REQUIRE_PIN) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPin: wrong state, state=" +
+ mSimLockedState);
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ return;
+ }
+
+ if (pin != null && pin.equals(mPinCode)) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPin: success!");
+ setRadioState(RadioState.SIM_READY);
+ mPinUnlockAttempts = 0;
+ mSimLockedState = SimLockState.NONE;
+
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ mPinUnlockAttempts ++;
+
+ Log.i(LOG_TAG, "[SimCmd] supplySimPin: failed! attempt=" +
+ mPinUnlockAttempts);
+ if (mPinUnlockAttempts >= 3) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPin: set state to REQUIRE_PUK");
+ mSimLockedState = SimLockState.REQUIRE_PUK;
+ }
+
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ }
+ }
+
+ public void supplySimPuk(String puk, String newPin, Message result) {
+ if (mSimLockedState != SimLockState.REQUIRE_PUK) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPuk: wrong state, state=" +
+ mSimLockedState);
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ return;
+ }
+
+ if (puk != null && puk.equals(SIM_PUK_CODE)) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPuk: success!");
+ setRadioState(RadioState.SIM_READY);
+ mSimLockedState = SimLockState.NONE;
+ mPukUnlockAttempts = 0;
+
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ mPukUnlockAttempts ++;
+
+ Log.i(LOG_TAG, "[SimCmd] supplySimPuk: failed! attempt=" +
+ mPukUnlockAttempts);
+ if (mPukUnlockAttempts >= 10) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPuk: set state to SIM_PERM_LOCKED");
+ mSimLockedState = SimLockState.SIM_PERM_LOCKED;
+ }
+
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ }
+ }
+
+ public void supplySimPin2(String pin2, Message result) {
+ if (mSimFdnEnabledState != SimFdnState.REQUIRE_PIN2) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPin2: wrong state, state=" +
+ mSimFdnEnabledState);
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ return;
+ }
+
+ if (pin2 != null && pin2.equals(mPin2Code)) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPin2: success!");
+ mPin2UnlockAttempts = 0;
+ mSimFdnEnabledState = SimFdnState.NONE;
+
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ mPin2UnlockAttempts ++;
+
+ Log.i(LOG_TAG, "[SimCmd] supplySimPin2: failed! attempt=" +
+ mPin2UnlockAttempts);
+ if (mPin2UnlockAttempts >= 3) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPin2: set state to REQUIRE_PUK2");
+ mSimFdnEnabledState = SimFdnState.REQUIRE_PUK2;
+ }
+
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ }
+ }
+
+ public void supplySimPuk2(String puk2, String newPin2, Message result) {
+ if (mSimFdnEnabledState != SimFdnState.REQUIRE_PUK2) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPuk2: wrong state, state=" +
+ mSimLockedState);
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ return;
+ }
+
+ if (puk2 != null && puk2.equals(SIM_PUK2_CODE)) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPuk2: success!");
+ mSimFdnEnabledState = SimFdnState.NONE;
+ mPuk2UnlockAttempts = 0;
+
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ mPuk2UnlockAttempts ++;
+
+ Log.i(LOG_TAG, "[SimCmd] supplySimPuk2: failed! attempt=" +
+ mPuk2UnlockAttempts);
+ if (mPuk2UnlockAttempts >= 10) {
+ Log.i(LOG_TAG, "[SimCmd] supplySimPuk2: set state to SIM_PERM_LOCKED");
+ mSimFdnEnabledState = SimFdnState.SIM_PERM_LOCKED;
+ }
+
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ }
+ }
+
+ public void changeSimPin(String oldPin, String newPin, Message result) {
+ if (oldPin != null && oldPin.equals(mPinCode)) {
+ mPinCode = newPin;
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ Log.i(LOG_TAG, "[SimCmd] changeSimPin: pin failed!");
+
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ }
+ }
+
+ public void changeSimPin2(String oldPin2, String newPin2, Message result) {
+ if (oldPin2 != null && oldPin2.equals(mPin2Code)) {
+ mPin2Code = newPin2;
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ Log.i(LOG_TAG, "[SimCmd] changeSimPin: pin2 failed!");
+
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ }
+ }
+
+ public void
+ changeBarringPassword(String facility, String oldPwd, String newPwd, Message result)
+ {
+ unimplemented(result);
+ }
+
+ public void
+ setSuppServiceNotifications(boolean enable, Message result)
+ {
+ resultSuccess(result, null);
+
+ if (enable && mSsnNotifyOn) {
+ Log.w(LOG_TAG, "Supp Service Notifications already enabled!");
+ }
+
+ mSsnNotifyOn = enable;
+ }
+
+ /**
+ * (AsyncResult)response.obj).result will be an Integer representing
+ * the sum of enabled serivice classes (sum of SERVICE_CLASS_*)
+ *
+ * @param facility one of CB_FACILTY_*
+ * @param pin password or "" if not required
+ * @param serviceClass is a sum of SERVICE_CLASS_*
+ */
+
+ public void queryFacilityLock (String facility, String pin,
+ int serviceClass, Message result) {
+ if (facility != null &&
+ facility.equals(CommandsInterface.CB_FACILITY_BA_SIM)) {
+ if (result != null) {
+ int[] r = new int[1];
+ r[0] = (mSimLockEnabled ? 1 : 0);
+ Log.i(LOG_TAG, "[SimCmd] queryFacilityLock: SIM is " +
+ (r[0] == 0 ? "unlocked" : "locked"));
+ AsyncResult.forMessage(result, r, null);
+ result.sendToTarget();
+ }
+ return;
+ } else if (facility != null &&
+ facility.equals(CommandsInterface.CB_FACILITY_BA_FD)) {
+ if (result != null) {
+ int[] r = new int[1];
+ r[0] = (mSimFdnEnabled ? 1 : 0);
+ Log.i(LOG_TAG, "[SimCmd] queryFacilityLock: FDN is " +
+ (r[0] == 0 ? "disabled" : "enabled"));
+ AsyncResult.forMessage(result, r, null);
+ result.sendToTarget();
+ }
+ return;
+ }
+
+ unimplemented(result);
+ }
+
+ /**
+ * @param facility one of CB_FACILTY_*
+ * @param lockEnabled true if SIM lock is enabled
+ * @param pin the SIM pin or "" if not required
+ * @param serviceClass is a sum of SERVICE_CLASS_*
+ */
+ public void setFacilityLock (String facility, boolean lockEnabled,
+ String pin, int serviceClass,
+ Message result) {
+ if (facility != null &&
+ facility.equals(CommandsInterface.CB_FACILITY_BA_SIM)) {
+ if (pin != null && pin.equals(mPinCode)) {
+ Log.i(LOG_TAG, "[SimCmd] setFacilityLock: pin is valid");
+ mSimLockEnabled = lockEnabled;
+
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ Log.i(LOG_TAG, "[SimCmd] setFacilityLock: pin failed!");
+
+ CommandException ex = new CommandException(
+ CommandException.Error.GENERIC_FAILURE);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ }
+
+ return;
+ } else if (facility != null &&
+ facility.equals(CommandsInterface.CB_FACILITY_BA_FD)) {
+ if (pin != null && pin.equals(mPin2Code)) {
+ Log.i(LOG_TAG, "[SimCmd] setFacilityLock: pin2 is valid");
+ mSimFdnEnabled = lockEnabled;
+
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ Log.i(LOG_TAG, "[SimCmd] setFacilityLock: pin2 failed!");
+
+ CommandException ex = new CommandException(
+ CommandException.Error.GENERIC_FAILURE);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ unimplemented(result);
+ }
+
+ public void supplyNetworkDepersonalization(String netpin, Message result) {
+ unimplemented(result);
+ }
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result contains a List of DriverCall
+ * The ar.result List is sorted by DriverCall.index
+ */
+ public void getCurrentCalls (Message result)
+ {
+ if (mState == RadioState.SIM_READY) {
+ //Log.i("GSM", "[SimCmds] getCurrentCalls");
+ resultSuccess(result, simulatedCallState.getDriverCalls());
+ } else {
+ //Log.i("GSM", "[SimCmds] getCurrentCalls: SIM not ready!");
+ resultFail(result,
+ new CommandException(
+ CommandException.Error.RADIO_NOT_AVAILABLE));
+ }
+ }
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result contains a List of PDPContextState
+ */
+ public void getPDPContextList(Message result)
+ {
+ resultSuccess(result, new ArrayList<PDPContextState>(0));
+ }
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ *
+ * CLIR_DEFAULT == on "use subscription default value"
+ * CLIR_SUPPRESSION == on "CLIR suppression" (allow CLI presentation)
+ * CLIR_INVOCATION == on "CLIR invocation" (restrict CLI presentation)
+ */
+ public void dial (String address, int clirMode, Message result)
+ {
+ simulatedCallState.onDial(address);
+
+ resultSuccess(result, null);
+ }
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is String containing IMSI on success
+ */
+ public void getIMSI(Message result)
+ {
+ resultSuccess(result, "012345678901234");
+ }
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is String containing IMEI on success
+ */
+ public void getIMEI(Message result)
+ {
+ resultSuccess(result, "012345678901234");
+ }
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is String containing IMEISV on success
+ */
+ public void getIMEISV(Message result)
+ {
+ resultSuccess(result, "99");
+ }
+
+ /**
+ * Hang up one individual connection.
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ *
+ * 3GPP 22.030 6.5.5
+ * "Releases a specific active call X"
+ */
+ public void hangupConnection (int gsmIndex, Message result)
+ {
+ boolean success;
+
+ success = simulatedCallState.onChld('1', (char)('0'+gsmIndex));
+
+ if (!success){
+ Log.i("GSM", "[SimCmd] hangupConnection: resultFail");
+ resultFail(result, new RuntimeException("Hangup Error"));
+ } else {
+ Log.i("GSM", "[SimCmd] hangupConnection: resultSuccess");
+ resultSuccess(result, null);
+ }
+ }
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Releases all held calls or sets User Determined User Busy (UDUB)
+ * for a waiting call."
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void hangupWaitingOrBackground (Message result)
+ {
+ boolean success;
+
+ success = simulatedCallState.onChld('0', '\0');
+
+ if (!success){
+ resultFail(result, new RuntimeException("Hangup Error"));
+ } else {
+ resultSuccess(result, null);
+ }
+ }
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Releases all active calls (if any exist) and accepts
+ * the other (held or waiting) call."
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void hangupForegroundResumeBackground (Message result)
+ {
+ boolean success;
+
+ success = simulatedCallState.onChld('1', '\0');
+
+ if (!success){
+ resultFail(result, new RuntimeException("Hangup Error"));
+ } else {
+ resultSuccess(result, null);
+ }
+ }
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Places all active calls (if any exist) on hold and accepts
+ * the other (held or waiting) call."
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void switchWaitingOrHoldingAndActive (Message result)
+ {
+ boolean success;
+
+ success = simulatedCallState.onChld('2', '\0');
+
+ if (!success){
+ resultFail(result, new RuntimeException("Hangup Error"));
+ } else {
+ resultSuccess(result, null);
+ }
+ }
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Adds a held call to the conversation"
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void conference (Message result)
+ {
+ boolean success;
+
+ success = simulatedCallState.onChld('3', '\0');
+
+ if (!success){
+ resultFail(result, new RuntimeException("Hangup Error"));
+ } else {
+ resultSuccess(result, null);
+ }
+ }
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Connects the two calls and disconnects the subscriber from both calls"
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void explicitCallTransfer (Message result)
+ {
+ boolean success;
+
+ success = simulatedCallState.onChld('4', '\0');
+
+ if (!success){
+ resultFail(result, new RuntimeException("Hangup Error"));
+ } else {
+ resultSuccess(result, null);
+ }
+ }
+
+ /**
+ * 3GPP 22.030 6.5.5
+ * "Places all active calls on hold except call X with which
+ * communication shall be supported."
+ */
+ public void separateConnection (int gsmIndex, Message result)
+ {
+ boolean success;
+
+ char ch = (char)(gsmIndex + '0');
+ success = simulatedCallState.onChld('2', ch);
+
+ if (!success){
+ resultFail(result, new RuntimeException("Hangup Error"));
+ } else {
+ resultSuccess(result, null);
+ }
+ }
+
+ /**
+ *
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void acceptCall (Message result)
+ {
+ boolean success;
+
+ success = simulatedCallState.onAnswer();
+
+ if (!success){
+ resultFail(result, new RuntimeException("Hangup Error"));
+ } else {
+ resultSuccess(result, null);
+ }
+ }
+
+ /**
+ * also known as UDUB
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void rejectCall (Message result)
+ {
+ boolean success;
+
+ success = simulatedCallState.onChld('0', '\0');
+
+ if (!success){
+ resultFail(result, new RuntimeException("Hangup Error"));
+ } else {
+ resultSuccess(result, null);
+ }
+ }
+
+ /**
+ * cause code returned as Integer in Message.obj.response
+ * Returns integer cause code defined in TS 24.008
+ * Annex H or closest approximation.
+ * Most significant codes:
+ * - Any defined in 22.001 F.4 (for generating busy/congestion)
+ * - Cause 68: ACM >= ACMMax
+ */
+ public void getLastCallFailCause (Message result)
+ {
+ int[] ret = new int[1];
+
+ ret[0] = nextCallFailCause;
+ resultSuccess(result, ret);
+ }
+
+ public void
+ getLastPdpFailCause (Message result)
+ {
+ unimplemented(result);
+ }
+
+ public void setMute (boolean enableMute, Message result) {unimplemented(result);}
+
+ public void getMute (Message result) {unimplemented(result);}
+
+ /**
+ * response.obj is an AsyncResult
+ * response.obj.result is an int[2]
+ * response.obj.result[0] is received signal strength (0-31, 99)
+ * response.obj.result[1] is bit error rate (0-7, 99)
+ * as defined in TS 27.007 8.5
+ */
+ public void getSignalStrength (Message result)
+ {
+ int ret[] = new int[2];
+
+ ret[0] = 23;
+ ret[1] = 0;
+
+ resultSuccess(result, ret);
+ }
+
+ /**
+ * Assign a specified band for RF configuration.
+ *
+ * @param bandMode one of BM_*_BAND
+ * @param result is callback message
+ */
+ public void setBandMode (int bandMode, Message result) {
+ resultSuccess(result, null);
+ }
+
+ /**
+ * Query the list of band mode supported by RF.
+ *
+ * @param result is callback message
+ * ((AsyncResult)response.obj).result is an int[] with every
+ * element representing one avialable BM_*_BAND
+ */
+ public void queryAvailableBandMode (Message result) {
+ int ret[] = new int [4];
+
+ ret[0] = 4;
+ ret[1] = Phone.BM_US_BAND;
+ ret[2] = Phone.BM_JPN_BAND;
+ ret[3] = Phone.BM_AUS_BAND;
+
+ resultSuccess(result, ret);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendTerminalResponse(String contents, Message response) {
+ resultSuccess(response, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendEnvelope(String contents, Message response) {
+ resultSuccess(response, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleCallSetupRequestFromSim(
+ boolean accept, Message response) {
+ resultSuccess(response, null);
+ }
+
+ /**
+ * response.obj.result is an String[3]
+ * response.obj.result[0] is registration state 0-5 from TS 27.007 7.2
+ * response.obj.result[1] is LAC if registered or NULL if not
+ * response.obj.result[2] is CID if registered or NULL if not
+ * valid LAC are 0x0000 - 0xffff
+ * valid CID are 0x00000000 - 0xffffffff
+ *
+ * Please note that registration state 4 ("unknown") is treated
+ * as "out of service" above
+ */
+ public void getRegistrationState (Message result)
+ {
+ String ret[] = new String[3];
+
+ ret[0] = "5"; // registered roam
+ ret[1] = null;
+ ret[2] = null;
+
+ resultSuccess(result, ret);
+ }
+
+ /**
+ * response.obj.result is an String[4]
+ * response.obj.result[0] is registration state 0-5 from TS 27.007 7.2
+ * response.obj.result[1] is LAC if registered or NULL if not
+ * response.obj.result[2] is CID if registered or NULL if not
+ * response.obj.result[3] indicates the available radio technology, where:
+ * 0 == unknown
+ * 1 == GPRS only
+ * 2 == EDGE
+ * 3 == UMTS
+ *
+ * valid LAC are 0x0000 - 0xffff
+ * valid CID are 0x00000000 - 0xffffffff
+ *
+ * Please note that registration state 4 ("unknown") is treated
+ * as "out of service" in the Android telephony system
+ */
+ public void getGPRSRegistrationState (Message result)
+ {
+ String ret[] = new String[4];
+
+ ret[0] = "5"; // registered roam
+ ret[1] = null;
+ ret[2] = null;
+ ret[3] = "2";
+
+ resultSuccess(result, ret);
+ }
+
+ /**
+ * response.obj.result is a String[3]
+ * response.obj.result[0] is long alpha or null if unregistered
+ * response.obj.result[1] is short alpha or null if unregistered
+ * response.obj.result[2] is numeric or null if unregistered
+ */
+ public void getOperator(Message result)
+ {
+ String[] ret = new String[3];
+
+ ret[0] = "El Telco Loco";
+ ret[1] = "Telco Loco";
+ ret[2] = "001001";
+
+ resultSuccess(result, ret);
+ }
+
+ /**
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void sendDtmf(char c, Message result)
+ {
+ resultSuccess(result, null);
+ }
+
+ /**
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void startDtmf(char c, Message result)
+ {
+ resultSuccess(result, null);
+ }
+
+ /**
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void stopDtmf(Message result)
+ {
+ resultSuccess(result, null);
+ }
+
+ /**
+ * smscPDU is smsc address in PDU form GSM BCD format prefixed
+ * by a length byte (as expected by TS 27.005) or NULL for default SMSC
+ * pdu is SMS in PDU format as an ASCII hex string
+ * less the SMSC address
+ */
+ public void sendSMS (String smscPDU, String pdu, Message result) {unimplemented(result);}
+
+ public void deleteSmsOnSim(int index, Message response) {
+ Log.d(LOG_TAG, "Delete message at index " + index);
+ unimplemented(response);
+ }
+
+ public void writeSmsToSim(int status, String smsc, String pdu, Message response) {
+ Log.d(LOG_TAG, "Write SMS to SIM with status " + status);
+ unimplemented(response);
+ }
+
+
+ public void setupDefaultPDP(String apn, String user, String password, Message result) {
+ unimplemented(result);
+ }
+
+ public void deactivateDefaultPDP(int cid, Message result) {unimplemented(result);}
+
+ public void setPreferredNetworkType(int networkType , Message result) {
+ mNetworkType = networkType;
+ resultSuccess(result, null);
+ }
+
+ public void getPreferredNetworkType(Message result) {
+ int ret[] = new int[1];
+
+ ret[0] = mNetworkType;
+ resultSuccess(result, ret);
+ }
+
+ public void getNeighboringCids(Message result) {
+ int ret[] = new int[7];
+
+ ret[0] = 6;
+ for (int i = 1; i<7; i++) {
+ ret[i] = i;
+ }
+ resultSuccess(result, ret);
+ }
+
+ public void setLocationUpdates(boolean enable, Message response) {
+ unimplemented(response);
+ }
+
+ private boolean isSimLocked() {
+ if (mSimLockedState != SimLockState.NONE) {
+ return true;
+ }
+ return false;
+ }
+
+ public void setRadioPower(boolean on, Message result)
+ {
+ if(on) {
+ if (isSimLocked()) {
+ Log.i("SIM", "[SimCmd] setRadioPower: SIM locked! state=" +
+ mSimLockedState);
+ setRadioState(RadioState.SIM_LOCKED_OR_ABSENT);
+ }
+ else {
+ setRadioState(RadioState.SIM_READY);
+ }
+ } else {
+ setRadioState(RadioState.RADIO_OFF);
+ }
+ }
+
+
+ public void acknowledgeLastIncomingSMS(boolean success, Message result) {
+ unimplemented(result);
+ }
+
+ /**
+ * parameters equivilient to 27.007 AT+CRSM command
+ * response.obj will be an AsyncResult
+ * response.obj.userObj will be a SimIoResult on success
+ */
+ public void simIO (int command, int fileid, String path, int p1, int p2,
+ int p3, String data, String pin2, Message result) {
+ unimplemented(result);
+ }
+
+ /**
+ * (AsyncResult)response.obj).result is an int[] with element [0] set to
+ * 1 for "CLIP is provisioned", and 0 for "CLIP is not provisioned".
+ *
+ * @param response is callback message
+ */
+ public void queryCLIP(Message response) { unimplemented(response); }
+
+
+ /**
+ * response.obj will be a an int[2]
+ *
+ * response.obj[0] will be TS 27.007 +CLIR parameter 'n'
+ * 0 presentation indicator is used according to the subscription of the CLIR service
+ * 1 CLIR invocation
+ * 2 CLIR suppression
+ *
+ * response.obj[1] will be TS 27.007 +CLIR parameter 'm'
+ * 0 CLIR not provisioned
+ * 1 CLIR provisioned in permanent mode
+ * 2 unknown (e.g. no network, etc.)
+ * 3 CLIR temporary mode presentation restricted
+ * 4 CLIR temporary mode presentation allowed
+ */
+
+ public void getCLIR(Message result) {unimplemented(result);}
+
+ /**
+ * clirMode is one of the CLIR_* constants above
+ *
+ * response.obj is null
+ */
+
+ public void setCLIR(int clirMode, Message result) {unimplemented(result);}
+
+ /**
+ * (AsyncResult)response.obj).result is an int[] with element [0] set to
+ * 0 for disabled, 1 for enabled.
+ *
+ * @param serviceClass is a sum of SERVICE_CLASS_*
+ * @param response is callback message
+ */
+
+ public void queryCallWaiting(int serviceClass, Message response)
+ {
+ unimplemented(response);
+ }
+
+ /**
+ * @param enable is true to enable, false to disable
+ * @param serviceClass is a sum of SERVICE_CLASS_*
+ * @param response is callback message
+ */
+
+ public void setCallWaiting(boolean enable, int serviceClass,
+ Message response)
+ {
+ unimplemented(response);
+ }
+
+ /**
+ * @param action is one of CF_ACTION_*
+ * @param cfReason is one of CF_REASON_*
+ * @param serviceClass is a sum of SERVICE_CLASSS_*
+ */
+ public void setCallForward(int action, int cfReason, int serviceClass,
+ String number, int timeSeconds, Message result) {unimplemented(result);}
+
+ /**
+ * cfReason is one of CF_REASON_*
+ *
+ * ((AsyncResult)response.obj).result will be an array of
+ * CallForwardInfo's
+ *
+ * An array of length 0 means "disabled for all codes"
+ */
+ public void queryCallForwardStatus(int cfReason, int serviceClass,
+ String number, Message result) {unimplemented(result);}
+
+ public void setNetworkSelectionModeAutomatic(Message result) {unimplemented(result);}
+
+ public void setNetworkSelectionModeManual(String operatorNumeric, Message result) {unimplemented(result);}
+
+ /**
+ * Queries whether the current network selection mode is automatic
+ * or manual
+ *
+ * ((AsyncResult)response.obj).result is an int[] with element [0] being
+ * a 0 for automatic selection and a 1 for manual selection
+ */
+
+ public void getNetworkSelectionMode(Message result)
+ {
+ int ret[] = new int[1];
+
+ ret[0] = 0;
+ resultSuccess(result, ret);
+ }
+
+ /**
+ * Queries the currently available networks
+ *
+ * ((AsyncResult)response.obj).result is a List of NetworkInfo objects
+ */
+ public void getAvailableNetworks(Message result) {unimplemented(result);}
+
+ public void getBasebandVersion (Message result)
+ {
+ resultSuccess(result, "SimulatedCommands");
+ }
+
+ /**
+ * Simulates an incoming USSD message
+ * @param statusCode Status code string. See <code>setOnUSSD</code>
+ * in CommandsInterface.java
+ * @param message Message text to send or null if none
+ */
+ public void triggerIncomingUssd(String statusCode, String message) {
+ if (mUSSDRegistrant != null) {
+ String[] result = {statusCode, message};
+ mUSSDRegistrant.notifyResult(result);
+ }
+ }
+
+
+ public void sendUSSD (String ussdString, Message result) {
+
+ // We simulate this particular sequence
+ if (ussdString.equals("#646#")) {
+ resultSuccess(result, null);
+
+ // 0 == USSD-Notify
+ triggerIncomingUssd("0", "You have NNN minutes remaining.");
+ } else {
+ resultSuccess(result, null);
+
+ triggerIncomingUssd("0", "All Done");
+ }
+ }
+
+ // inherited javadoc suffices
+ public void cancelPendingUssd (Message response) {
+ resultSuccess(response, null);
+ }
+
+
+ public void resetRadio(Message result)
+ {
+ unimplemented(result);
+ }
+
+ public void invokeOemRilRequestRaw(byte[] data, Message response)
+ {
+ // Just echo back data
+ if (response != null) {
+ AsyncResult.forMessage(response).result = data;
+ response.sendToTarget();
+ }
+ }
+
+ public void invokeOemRilRequestStrings(String[] strings, Message response)
+ {
+ // Just echo back data
+ if (response != null) {
+ AsyncResult.forMessage(response).result = strings;
+ response.sendToTarget();
+ }
+ }
+
+ //***** SimulatedRadioControl
+
+
+ /** Start the simulated phone ringing */
+ public void
+ triggerRing(String number)
+ {
+ simulatedCallState.triggerRing(number);
+ mCallStateRegistrants.notifyRegistrants();
+ }
+
+ public void
+ progressConnectingCallState()
+ {
+ simulatedCallState.progressConnectingCallState();
+ mCallStateRegistrants.notifyRegistrants();
+ }
+
+ /** If a call is DIALING or ALERTING, progress it all the way to ACTIVE */
+ public void
+ progressConnectingToActive()
+ {
+ simulatedCallState.progressConnectingToActive();
+ mCallStateRegistrants.notifyRegistrants();
+ }
+
+ /** automatically progress mobile originated calls to ACTIVE.
+ * default to true
+ */
+ public void
+ setAutoProgressConnectingCall(boolean b)
+ {
+ simulatedCallState.setAutoProgressConnectingCall(b);
+ }
+
+ public void
+ setNextDialFailImmediately(boolean b)
+ {
+ simulatedCallState.setNextDialFailImmediately(b);
+ }
+
+ public void
+ setNextCallFailCause(int gsmCause)
+ {
+ nextCallFailCause = gsmCause;
+ }
+
+ public void
+ triggerHangupForeground()
+ {
+ simulatedCallState.triggerHangupForeground();
+ mCallStateRegistrants.notifyRegistrants();
+ }
+
+ /** hangup holding calls */
+ public void
+ triggerHangupBackground()
+ {
+ simulatedCallState.triggerHangupBackground();
+ mCallStateRegistrants.notifyRegistrants();
+ }
+
+ public void triggerSsn(int type, int code)
+ {
+ SuppServiceNotification not = new SuppServiceNotification();
+ not.notificationType = type;
+ not.code = code;
+ mSsnRegistrant.notifyRegistrant(new AsyncResult(null, not, null));
+ }
+
+ public void
+ shutdown()
+ {
+ setRadioState(RadioState.RADIO_UNAVAILABLE);
+ handlerThread.getHandler().getLooper().quit();
+ }
+
+ /** hangup all */
+
+ public void
+ triggerHangupAll()
+ {
+ simulatedCallState.triggerHangupAll();
+ mCallStateRegistrants.notifyRegistrants();
+ }
+
+ public void
+ triggerIncomingSMS(String message)
+ {
+ //TODO
+ }
+
+ public void
+ pauseResponses()
+ {
+ pausedResponseCount++;
+ }
+
+ public void
+ resumeResponses()
+ {
+ pausedResponseCount--;
+
+ if (pausedResponseCount == 0) {
+ for (int i = 0, s = pausedResponses.size(); i < s ; i++) {
+ pausedResponses.get(i).sendToTarget();
+ }
+ pausedResponses.clear();
+ } else {
+ Log.e("GSM", "SimulatedCommands.resumeResponses < 0");
+ }
+ }
+
+ //***** HandlerInterface implementation
+
+ public void
+ handleMessage(Message msg)
+ {
+
+ }
+
+ //***** Private Methods
+
+ private void unimplemented(Message result)
+ {
+ if (result != null) {
+ AsyncResult.forMessage(result).exception
+ = new RuntimeException("Unimplemented");
+
+ if (pausedResponseCount > 0) {
+ pausedResponses.add(result);
+ } else {
+ result.sendToTarget();
+ }
+ }
+ }
+
+ private void resultSuccess(Message result, Object ret)
+ {
+ if (result != null) {
+ AsyncResult.forMessage(result).result = ret;
+ if (pausedResponseCount > 0) {
+ pausedResponses.add(result);
+ } else {
+ result.sendToTarget();
+ }
+ }
+ }
+
+ private void resultFail(Message result, Throwable tr)
+ {
+ if (result != null) {
+ AsyncResult.forMessage(result).exception = tr;
+ if (pausedResponseCount > 0) {
+ pausedResponses.add(result);
+ } else {
+ result.sendToTarget();
+ }
+ }
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedGsmCallState.java b/telephony/java/com/android/internal/telephony/test/SimulatedGsmCallState.java
new file mode 100644
index 0000000..bbc2bba
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/test/SimulatedGsmCallState.java
@@ -0,0 +1,837 @@
+/*
+ * 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.internal.telephony.test;
+
+import android.os.Message;
+import android.os.Handler;
+import android.telephony.PhoneNumberUtils;
+import com.android.internal.telephony.ATParseEx;
+import com.android.internal.telephony.gsm.DriverCall;
+import java.util.List;
+import java.util.ArrayList;
+
+import android.util.Log;
+
+class CallInfo
+{
+ enum State {
+ ACTIVE(0),
+ HOLDING(1),
+ DIALING(2), // MO call only
+ ALERTING(3), // MO call only
+ INCOMING(4), // MT call only
+ WAITING(5); // MT call only
+
+ State (int value) {this.value = value;}
+
+ private final int value;
+ public int value() {return value;};
+ };
+
+ boolean isMT;
+ State state;
+ boolean isMpty;
+ String number;
+ int TOA;
+
+ CallInfo (boolean isMT, State state, boolean isMpty, String number)
+ {
+ this.isMT = isMT;
+ this.state = state;
+ this.isMpty = isMpty;
+ this.number = number;
+
+ if (number.length() > 0 && number.charAt(0) == '+') {
+ TOA = PhoneNumberUtils.TOA_International;
+ } else {
+ TOA = PhoneNumberUtils.TOA_Unknown;
+ }
+ }
+
+ static CallInfo
+ createOutgoingCall(String number)
+ {
+ return new CallInfo (false, State.DIALING, false, number);
+ }
+
+ static CallInfo
+ createIncomingCall(String number)
+ {
+ return new CallInfo (true, State.INCOMING, false, number);
+ }
+
+ String
+ toCLCCLine(int index)
+ {
+ return
+ "+CLCC: "
+ + index + "," + (isMT ? "1" : "0") +","
+ + state.value() + ",0," + (isMpty ? "1" : "0")
+ + ",\"" + number + "\"," + TOA;
+ }
+
+ DriverCall
+ toDriverCall(int index)
+ {
+ DriverCall ret;
+
+ ret = new DriverCall();
+
+ ret.index = index;
+ ret.isMT = isMT;
+
+ try {
+ ret.state = DriverCall.stateFromCLCC(state.value());
+ } catch (ATParseEx ex) {
+ throw new RuntimeException("should never happen", ex);
+ }
+
+ ret.isMpty = isMpty;
+ ret.number = number;
+ ret.TOA = TOA;
+ ret.isVoice = true;
+ ret.als = 0;
+
+ return ret;
+ }
+
+
+ boolean
+ isActiveOrHeld()
+ {
+ return state == State.ACTIVE || state == State.HOLDING;
+ }
+
+ boolean
+ isConnecting()
+ {
+ return state == State.DIALING || state == State.ALERTING;
+ }
+
+ boolean
+ isRinging()
+ {
+ return state == State.INCOMING || state == State.WAITING;
+ }
+
+}
+
+class InvalidStateEx extends Exception
+{
+ InvalidStateEx()
+ {
+
+ }
+}
+
+
+class SimulatedGsmCallState extends Handler
+{
+ //***** Instance Variables
+
+ CallInfo calls[] = new CallInfo[MAX_CALLS];
+
+ private boolean autoProgressConnecting = true;
+ private boolean nextDialFailImmediately;
+
+
+ //***** Event Constants
+
+ static final int EVENT_PROGRESS_CALL_STATE = 1;
+
+ //***** Constants
+
+ static final int MAX_CALLS = 7;
+ /** number of msec between dialing -> alerting and alerting->active */
+ static final int CONNECTING_PAUSE_MSEC = 5 * 100;
+
+
+ //***** Overridden from Handler
+
+ public void
+ handleMessage(Message msg)
+ {
+ synchronized(this) { switch (msg.what) {
+ // PLEASE REMEMBER
+ // calls may have hung up by the time delayed events happen
+
+ case EVENT_PROGRESS_CALL_STATE:
+ progressConnectingCallState();
+ break;
+ }}
+ }
+
+ //***** Public Methods
+
+ /**
+ * Start the simulated phone ringing
+ * true if succeeded, false if failed
+ */
+ public boolean
+ triggerRing(String number)
+ {
+ synchronized (this) {
+ int empty = -1;
+ boolean isCallWaiting = false;
+
+ // ensure there aren't already calls INCOMING or WAITING
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (call == null && empty < 0) {
+ empty = i;
+ } else if (call != null
+ && (call.state == CallInfo.State.INCOMING
+ || call.state == CallInfo.State.WAITING)
+ ) {
+ Log.w("ModelInterpreter",
+ "triggerRing failed; phone already ringing");
+ return false;
+ } else if (call != null) {
+ isCallWaiting = true;
+ }
+ }
+
+ if (empty < 0 ) {
+ Log.w("ModelInterpreter", "triggerRing failed; all full");
+ return false;
+ }
+
+ calls[empty] = CallInfo.createIncomingCall(
+ PhoneNumberUtils.extractNetworkPortion(number));
+
+ if (isCallWaiting) {
+ calls[empty].state = CallInfo.State.WAITING;
+ }
+
+ }
+ return true;
+ }
+
+ /** If a call is DIALING or ALERTING, progress it to the next state */
+ public void
+ progressConnectingCallState()
+ {
+ synchronized (this) {
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (call != null && call.state == CallInfo.State.DIALING) {
+ call.state = CallInfo.State.ALERTING;
+
+ if (autoProgressConnecting) {
+ sendMessageDelayed(
+ obtainMessage(EVENT_PROGRESS_CALL_STATE, call),
+ CONNECTING_PAUSE_MSEC);
+ }
+ break;
+ } else if (call != null
+ && call.state == CallInfo.State.ALERTING
+ ) {
+ call.state = CallInfo.State.ACTIVE;
+ break;
+ }
+ }
+ }
+ }
+
+ /** If a call is DIALING or ALERTING, progress it all the way to ACTIVE */
+ public void
+ progressConnectingToActive()
+ {
+ synchronized (this) {
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (call != null && (call.state == CallInfo.State.DIALING
+ || call.state == CallInfo.State.ALERTING)
+ ) {
+ call.state = CallInfo.State.ACTIVE;
+ break;
+ }
+ }
+ }
+ }
+
+ /** automatically progress mobile originated calls to ACTIVE.
+ * default to true
+ */
+ public void
+ setAutoProgressConnectingCall(boolean b)
+ {
+ autoProgressConnecting = b;
+ }
+
+ public void
+ setNextDialFailImmediately(boolean b)
+ {
+ nextDialFailImmediately = b;
+ }
+
+ /**
+ * hangup ringing, dialing, or active calls
+ * returns true if call was hung up, false if not
+ */
+ public boolean
+ triggerHangupForeground()
+ {
+ synchronized (this) {
+ boolean found;
+
+ found = false;
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (call != null
+ && (call.state == CallInfo.State.INCOMING
+ || call.state == CallInfo.State.WAITING)
+ ) {
+ calls[i] = null;
+ found = true;
+ }
+ }
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (call != null
+ && (call.state == CallInfo.State.DIALING
+ || call.state == CallInfo.State.ACTIVE
+ || call.state == CallInfo.State.ALERTING)
+ ) {
+ calls[i] = null;
+ found = true;
+ }
+ }
+ return found;
+ }
+ }
+
+ /**
+ * hangup holding calls
+ * returns true if call was hung up, false if not
+ */
+ public boolean
+ triggerHangupBackground()
+ {
+ synchronized (this) {
+ boolean found = false;
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (call != null && call.state == CallInfo.State.HOLDING) {
+ calls[i] = null;
+ found = true;
+ }
+ }
+
+ return found;
+ }
+ }
+
+ /**
+ * hangup all
+ * returns true if call was hung up, false if not
+ */
+ public boolean
+ triggerHangupAll()
+ {
+ synchronized(this) {
+ boolean found = false;
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (calls[i] != null) {
+ found = true;
+ }
+
+ calls[i] = null;
+ }
+
+ return found;
+ }
+ }
+
+ public boolean
+ onAnswer()
+ {
+ synchronized (this) {
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (call != null
+ && (call.state == CallInfo.State.INCOMING
+ || call.state == CallInfo.State.WAITING)
+ ) {
+ return switchActiveAndHeldOrWaiting();
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public boolean
+ onHangup()
+ {
+ boolean found = false;
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (call != null && call.state != CallInfo.State.WAITING) {
+ calls[i] = null;
+ found = true;
+ }
+ }
+
+ return found;
+ }
+
+ public boolean
+ onChld(char c0, char c1)
+ {
+ boolean ret;
+ int callIndex = 0;
+
+ if (c1 != 0) {
+ callIndex = c1 - '1';
+
+ if (callIndex < 0 || callIndex >= calls.length) {
+ return false;
+ }
+ }
+
+ switch (c0) {
+ case '0':
+ ret = releaseHeldOrUDUB();
+ break;
+ case '1':
+ if (c1 <= 0) {
+ ret = releaseActiveAcceptHeldOrWaiting();
+ } else {
+ if (calls[callIndex] == null) {
+ ret = false;
+ } else {
+ calls[callIndex] = null;
+ ret = true;
+ }
+ }
+ break;
+ case '2':
+ if (c1 <= 0) {
+ ret = switchActiveAndHeldOrWaiting();
+ } else {
+ ret = separateCall(callIndex);
+ }
+ break;
+ case '3':
+ ret = conference();
+ break;
+ case '4':
+ ret = explicitCallTransfer();
+ break;
+ case '5':
+ if (true) { //just so javac doesnt complain about break
+ //CCBS not impled
+ ret = false;
+ }
+ break;
+ default:
+ ret = false;
+
+ }
+
+ return ret;
+ }
+
+ public boolean
+ releaseHeldOrUDUB() {
+ boolean found = false;
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null && c.isRinging()) {
+ found = true;
+ calls[i] = null;
+ break;
+ }
+ }
+
+ if (!found) {
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null && c.state == CallInfo.State.HOLDING) {
+ found = true;
+ calls[i] = null;
+ // don't stop...there may be more than one
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ public boolean
+ releaseActiveAcceptHeldOrWaiting()
+ {
+ boolean foundHeld = false;
+ boolean foundActive = false;
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null && c.state == CallInfo.State.ACTIVE) {
+ calls[i] = null;
+ foundActive = true;
+ }
+ }
+
+ if (!foundActive) {
+ // FIXME this may not actually be how most basebands react
+ // CHLD=1 may not hang up dialing/alerting calls
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null
+ && (c.state == CallInfo.State.DIALING
+ || c.state == CallInfo.State.ALERTING)
+ ) {
+ calls[i] = null;
+ foundActive = true;
+ }
+ }
+ }
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null && c.state == CallInfo.State.HOLDING) {
+ c.state = CallInfo.State.ACTIVE;
+ foundHeld = true;
+ }
+ }
+
+ if (foundHeld) {
+ return true;
+ }
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null && c.isRinging()) {
+ c.state = CallInfo.State.ACTIVE;
+ return true;
+ }
+ }
+
+ return true;
+ }
+
+ public boolean
+ switchActiveAndHeldOrWaiting()
+ {
+ boolean hasHeld = false;
+
+ // first, are there held calls?
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null && c.state == CallInfo.State.HOLDING) {
+ hasHeld = true;
+ break;
+ }
+ }
+
+ // Now, switch
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null) {
+ if (c.state == CallInfo.State.ACTIVE) {
+ c.state = CallInfo.State.HOLDING;
+ } else if (c.state == CallInfo.State.HOLDING) {
+ c.state = CallInfo.State.ACTIVE;
+ } else if (!hasHeld && c.isRinging()) {
+ c.state = CallInfo.State.ACTIVE;
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ public boolean
+ separateCall(int index)
+ {
+ try {
+ CallInfo c;
+
+ c = calls[index];
+
+ if (c == null || c.isConnecting() || countActiveLines() != 1) {
+ return false;
+ }
+
+ c.state = CallInfo.State.ACTIVE;
+ c.isMpty = false;
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ int countHeld=0, lastHeld=0;
+
+ if (i != index) {
+ CallInfo cb = calls[i];
+
+ if (cb != null && cb.state == CallInfo.State.ACTIVE) {
+ cb.state = CallInfo.State.HOLDING;
+ countHeld++;
+ lastHeld = i;
+ }
+ }
+
+ if (countHeld == 1) {
+ // if there's only one left, clear the MPTY flag
+ calls[lastHeld].isMpty = false;
+ }
+ }
+
+ return true;
+ } catch (InvalidStateEx ex) {
+ return false;
+ }
+ }
+
+
+
+ public boolean
+ conference()
+ {
+ int countCalls = 0;
+
+ // if there's connecting calls, we can't do this yet
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null) {
+ countCalls++;
+
+ if (c.isConnecting()) {
+ return false;
+ }
+ }
+ }
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null) {
+ c.state = CallInfo.State.ACTIVE;
+ if (countCalls > 0) {
+ c.isMpty = true;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public boolean
+ explicitCallTransfer()
+ {
+ int countCalls = 0;
+
+ // if there's connecting calls, we can't do this yet
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null) {
+ countCalls++;
+
+ if (c.isConnecting()) {
+ return false;
+ }
+ }
+ }
+
+ // disconnect the subscriber from both calls
+ return triggerHangupAll();
+ }
+
+ public boolean
+ onDial(String address)
+ {
+ CallInfo call;
+ int freeSlot = -1;
+
+ Log.d("GSM", "SC> dial '" + address + "'");
+
+ if (nextDialFailImmediately) {
+ nextDialFailImmediately = false;
+
+ Log.d("GSM", "SC< dial fail (per request)");
+ return false;
+ }
+
+ String phNum = PhoneNumberUtils.extractNetworkPortion(address);
+
+ if (phNum.length() == 0) {
+ Log.d("GSM", "SC< dial fail (invalid ph num)");
+ return false;
+ }
+
+ // Ignore setting up GPRS
+ if (phNum.startsWith("*99") && phNum.endsWith("#")) {
+ Log.d("GSM", "SC< dial ignored (gprs)");
+ return true;
+ }
+
+ // There can be at most 1 active "line" when we initiate
+ // a new call
+ try {
+ if (countActiveLines() > 1) {
+ Log.d("GSM", "SC< dial fail (invalid call state)");
+ return false;
+ }
+ } catch (InvalidStateEx ex) {
+ Log.d("GSM", "SC< dial fail (invalid call state)");
+ return false;
+ }
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ if (freeSlot < 0 && calls[i] == null) {
+ freeSlot = i;
+ }
+
+ if (calls[i] != null && !calls[i].isActiveOrHeld()) {
+ // Can't make outgoing calls when there is a ringing or
+ // connecting outgoing call
+ Log.d("GSM", "SC< dial fail (invalid call state)");
+ return false;
+ } else if (calls[i] != null && calls[i].state == CallInfo.State.ACTIVE) {
+ // All active calls behome held
+ calls[i].state = CallInfo.State.HOLDING;
+ }
+ }
+
+ if (freeSlot < 0) {
+ Log.d("GSM", "SC< dial fail (invalid call state)");
+ return false;
+ }
+
+ calls[freeSlot] = CallInfo.createOutgoingCall(phNum);
+
+ if (autoProgressConnecting) {
+ sendMessageDelayed(
+ obtainMessage(EVENT_PROGRESS_CALL_STATE, calls[freeSlot]),
+ CONNECTING_PAUSE_MSEC);
+ }
+
+ Log.d("GSM", "SC< dial (slot = " + freeSlot + ")");
+
+ return true;
+ }
+
+ public List<DriverCall>
+ getDriverCalls()
+ {
+ ArrayList<DriverCall> ret = new ArrayList<DriverCall>(calls.length);
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null) {
+ DriverCall dc;
+
+ dc = c.toDriverCall(i + 1);
+ ret.add(dc);
+ }
+ }
+
+ Log.d("GSM", "SC< getDriverCalls " + ret);
+
+ return ret;
+ }
+
+ public List<String>
+ getClccLines()
+ {
+ ArrayList<String> ret = new ArrayList<String>(calls.length);
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo c = calls[i];
+
+ if (c != null) {
+ ret.add((c.toCLCCLine(i + 1)));
+ }
+ }
+
+ return ret;
+ }
+
+ private int
+ countActiveLines() throws InvalidStateEx
+ {
+ boolean hasMpty = false;
+ boolean hasHeld = false;
+ boolean hasActive = false;
+ boolean hasConnecting = false;
+ boolean hasRinging = false;
+ boolean mptyIsHeld = false;
+
+ for (int i = 0 ; i < calls.length ; i++) {
+ CallInfo call = calls[i];
+
+ if (call != null) {
+ if (!hasMpty && call.isMpty) {
+ mptyIsHeld = call.state == CallInfo.State.HOLDING;
+ } else if (call.isMpty && mptyIsHeld
+ && call.state == CallInfo.State.ACTIVE
+ ) {
+ Log.e("ModelInterpreter", "Invalid state");
+ throw new InvalidStateEx();
+ } else if (!call.isMpty && hasMpty && mptyIsHeld
+ && call.state == CallInfo.State.HOLDING
+ ) {
+ Log.e("ModelInterpreter", "Invalid state");
+ throw new InvalidStateEx();
+ }
+
+ hasMpty |= call.isMpty;
+ hasHeld |= call.state == CallInfo.State.HOLDING;
+ hasActive |= call.state == CallInfo.State.ACTIVE;
+ hasConnecting |= call.isConnecting();
+ hasRinging |= call.isRinging();
+ }
+ }
+
+ int ret = 0;
+
+ if (hasHeld) ret++;
+ if (hasActive) ret++;
+ if (hasConnecting) ret++;
+ if (hasRinging) ret++;
+
+ return ret;
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedRadioControl.java b/telephony/java/com/android/internal/telephony/test/SimulatedRadioControl.java
new file mode 100644
index 0000000..9e1a7c5
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/test/SimulatedRadioControl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.internal.telephony.test;
+
+public interface SimulatedRadioControl
+{
+ public void triggerRing(String number);
+
+ public void progressConnectingCallState();
+
+ public void progressConnectingToActive();
+
+ public void setAutoProgressConnectingCall(boolean b);
+
+ public void setNextDialFailImmediately(boolean b);
+
+ public void setNextCallFailCause(int gsmCause);
+
+ public void triggerHangupForeground();
+
+ public void triggerHangupBackground();
+
+ public void triggerHangupAll();
+
+ public void triggerIncomingSMS(String message);
+
+ public void shutdown();
+
+ /** Pause responses to async requests until (ref-counted) resumeResponses() */
+ public void pauseResponses();
+
+ /** see pauseResponses */
+ public void resumeResponses();
+
+ public void triggerSsn(int type, int code);
+
+ /** Generates an incoming USSD message. */
+ public void triggerIncomingUssd(String statusCode, String message);
+}
diff --git a/telephony/java/com/android/internal/telephony/test/package.html b/telephony/java/com/android/internal/telephony/test/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/test/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>