diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
tree | 35051494d2af230dce54d6b31c6af8fc24091316 /telephony | |
download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 |
Initial Contribution
Diffstat (limited to 'telephony')
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: [<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 & 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> — One of <code>"CONNECTED"</code> + * <code>"CONNECTING"</code> or <code>"DISCONNNECTED"</code></li> + * <li><em>reason</em> — 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<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> |