summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk34
-rw-r--r--CleanSpec.mk45
-rw-r--r--mockril/Android.mk30
-rw-r--r--mockril/src/com/android/internal/telephony/mockril/MockRilController.java217
-rw-r--r--src/java/android/provider/Telephony.java1993
-rw-r--r--src/java/android/telephony/CellBroadcastMessage.java422
-rw-r--r--src/java/android/telephony/SmsCbCmasInfo.java308
-rw-r--r--src/java/android/telephony/SmsCbEtwsInfo.java206
-rw-r--r--src/java/android/telephony/SmsCbLocation.java202
-rw-r--r--src/java/android/telephony/SmsCbMessage.java382
-rw-r--r--src/java/android/telephony/SmsManager.java522
-rw-r--r--src/java/android/telephony/SmsMessage.java688
-rw-r--r--src/java/android/telephony/gsm/SmsManager.java261
-rw-r--r--src/java/android/telephony/gsm/SmsMessage.java628
-rw-r--r--src/java/com/android/internal/telephony/ATParseEx.java35
-rw-r--r--src/java/com/android/internal/telephony/ATResponseParser.java186
-rw-r--r--src/java/com/android/internal/telephony/AdnRecord.java321
-rw-r--r--src/java/com/android/internal/telephony/AdnRecordCache.java364
-rw-r--r--src/java/com/android/internal/telephony/AdnRecordLoader.java284
-rw-r--r--src/java/com/android/internal/telephony/ApnContext.java246
-rwxr-xr-xsrc/java/com/android/internal/telephony/ApnSetting.java200
-rw-r--r--src/java/com/android/internal/telephony/BaseCommands.java647
-rw-r--r--src/java/com/android/internal/telephony/Call.java255
-rw-r--r--src/java/com/android/internal/telephony/CallForwardInfo.java42
-rw-r--r--src/java/com/android/internal/telephony/CallManager.java1855
-rw-r--r--src/java/com/android/internal/telephony/CallStateException.java34
-rw-r--r--src/java/com/android/internal/telephony/CallTracker.java182
-rw-r--r--src/java/com/android/internal/telephony/CommandException.java98
-rw-r--r--src/java/com/android/internal/telephony/CommandsInterface.java1579
-rw-r--r--src/java/com/android/internal/telephony/Connection.java309
-rw-r--r--src/java/com/android/internal/telephony/DataCallState.java245
-rw-r--r--src/java/com/android/internal/telephony/DataConnection.java1286
-rw-r--r--src/java/com/android/internal/telephony/DataConnectionAc.java591
-rw-r--r--src/java/com/android/internal/telephony/DataConnectionTracker.java1203
-rw-r--r--src/java/com/android/internal/telephony/DebugService.java108
-rw-r--r--src/java/com/android/internal/telephony/DefaultPhoneNotifier.java281
-rw-r--r--src/java/com/android/internal/telephony/DriverCall.java162
-rw-r--r--src/java/com/android/internal/telephony/EventLogTags.logtags73
-rw-r--r--src/java/com/android/internal/telephony/IIccPhoneBook.aidl101
-rw-r--r--src/java/com/android/internal/telephony/ISms.aidl201
-rw-r--r--src/java/com/android/internal/telephony/IccCard.java958
-rw-r--r--src/java/com/android/internal/telephony/IccCardApplication.java226
-rw-r--r--src/java/com/android/internal/telephony/IccCardStatus.java188
-rw-r--r--src/java/com/android/internal/telephony/IccConstants.java90
-rw-r--r--src/java/com/android/internal/telephony/IccException.java30
-rw-r--r--src/java/com/android/internal/telephony/IccFileHandler.java551
-rw-r--r--src/java/com/android/internal/telephony/IccFileNotFound.java34
-rw-r--r--src/java/com/android/internal/telephony/IccFileTypeMismatch.java30
-rw-r--r--src/java/com/android/internal/telephony/IccIoResult.java70
-rw-r--r--src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java289
-rw-r--r--src/java/com/android/internal/telephony/IccPhoneBookInterfaceManagerProxy.java75
-rw-r--r--src/java/com/android/internal/telephony/IccProvider.java431
-rw-r--r--src/java/com/android/internal/telephony/IccRecords.java422
-rw-r--r--src/java/com/android/internal/telephony/IccRefreshResponse.java42
-rw-r--r--src/java/com/android/internal/telephony/IccServiceTable.java81
-rw-r--r--src/java/com/android/internal/telephony/IccSmsInterfaceManager.java217
-rw-r--r--src/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java88
-rw-r--r--src/java/com/android/internal/telephony/IccUtils.java530
-rw-r--r--src/java/com/android/internal/telephony/IccVmFixedException.java32
-rw-r--r--src/java/com/android/internal/telephony/IccVmNotSupportedException.java32
-rw-r--r--src/java/com/android/internal/telephony/IntRangeManager.java576
-rw-r--r--src/java/com/android/internal/telephony/MccTable.java572
-rw-r--r--src/java/com/android/internal/telephony/MmiCode.java62
-rw-r--r--src/java/com/android/internal/telephony/OperatorInfo.java149
-rw-r--r--src/java/com/android/internal/telephony/Phone.java1695
-rw-r--r--src/java/com/android/internal/telephony/PhoneBase.java1189
-rw-r--r--src/java/com/android/internal/telephony/PhoneFactory.java213
-rw-r--r--src/java/com/android/internal/telephony/PhoneNotifier.java50
-rw-r--r--src/java/com/android/internal/telephony/PhoneProxy.java972
-rw-r--r--src/java/com/android/internal/telephony/PhoneStateIntentReceiver.java203
-rwxr-xr-xsrc/java/com/android/internal/telephony/PhoneSubInfo.java206
-rwxr-xr-xsrc/java/com/android/internal/telephony/PhoneSubInfoProxy.java133
-rw-r--r--src/java/com/android/internal/telephony/RIL.java3861
-rw-r--r--src/java/com/android/internal/telephony/RestrictedState.java122
-rw-r--r--src/java/com/android/internal/telephony/RetryManager.java410
-rw-r--r--src/java/com/android/internal/telephony/SMSDispatcher.java1252
-rw-r--r--src/java/com/android/internal/telephony/ServiceStateTracker.java538
-rw-r--r--src/java/com/android/internal/telephony/SmsAddress.java65
-rw-r--r--src/java/com/android/internal/telephony/SmsHeader.java289
-rw-r--r--src/java/com/android/internal/telephony/SmsMessageBase.java350
-rw-r--r--src/java/com/android/internal/telephony/SmsRawData.java62
-rw-r--r--src/java/com/android/internal/telephony/SmsResponse.java48
-rw-r--r--src/java/com/android/internal/telephony/SmsStorageMonitor.java162
-rw-r--r--src/java/com/android/internal/telephony/SmsUsageMonitor.java476
-rw-r--r--src/java/com/android/internal/telephony/TelephonyCapabilities.java191
-rw-r--r--src/java/com/android/internal/telephony/UUSInfo.java100
-rw-r--r--src/java/com/android/internal/telephony/WapPushManagerParams.java70
-rwxr-xr-xsrc/java/com/android/internal/telephony/WapPushOverSms.java275
-rwxr-xr-xsrc/java/com/android/internal/telephony/WspTypeDecoder.java731
-rw-r--r--src/java/com/android/internal/telephony/cat/AppInterface.java96
-rw-r--r--src/java/com/android/internal/telephony/cat/BerTlv.java123
-rw-r--r--src/java/com/android/internal/telephony/cat/CatCmdMessage.java182
-rw-r--r--src/java/com/android/internal/telephony/cat/CatException.java31
-rw-r--r--src/java/com/android/internal/telephony/cat/CatLog.java41
-rw-r--r--src/java/com/android/internal/telephony/cat/CatResponseMessage.java54
-rw-r--r--src/java/com/android/internal/telephony/cat/CatService.java750
-rw-r--r--src/java/com/android/internal/telephony/cat/CommandDetails.java115
-rw-r--r--src/java/com/android/internal/telephony/cat/CommandParams.java199
-rw-r--r--src/java/com/android/internal/telephony/cat/CommandParamsFactory.java943
-rw-r--r--src/java/com/android/internal/telephony/cat/ComprehensionTlv.java203
-rw-r--r--src/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java74
-rw-r--r--src/java/com/android/internal/telephony/cat/Duration.java79
-rw-r--r--src/java/com/android/internal/telephony/cat/FontSize.java50
-rw-r--r--src/java/com/android/internal/telephony/cat/IconLoader.java362
-rw-r--r--src/java/com/android/internal/telephony/cat/ImageDescriptor.java77
-rw-r--r--src/java/com/android/internal/telephony/cat/Input.java101
-rw-r--r--src/java/com/android/internal/telephony/cat/Item.java71
-rw-r--r--src/java/com/android/internal/telephony/cat/LaunchBrowserMode.java35
-rw-r--r--src/java/com/android/internal/telephony/cat/Menu.java105
-rw-r--r--src/java/com/android/internal/telephony/cat/PresentationType.java32
-rw-r--r--src/java/com/android/internal/telephony/cat/ResponseData.java281
-rw-r--r--src/java/com/android/internal/telephony/cat/ResultCode.java186
-rw-r--r--src/java/com/android/internal/telephony/cat/ResultException.java97
-rw-r--r--src/java/com/android/internal/telephony/cat/RilMessageDecoder.java177
-rw-r--r--src/java/com/android/internal/telephony/cat/TextAlignment.java52
-rw-r--r--src/java/com/android/internal/telephony/cat/TextAttribute.java49
-rw-r--r--src/java/com/android/internal/telephony/cat/TextColor.java63
-rw-r--r--src/java/com/android/internal/telephony/cat/TextMessage.java71
-rw-r--r--src/java/com/android/internal/telephony/cat/Tone.java190
-rw-r--r--src/java/com/android/internal/telephony/cat/ToneSettings.java62
-rw-r--r--src/java/com/android/internal/telephony/cat/ValueParser.java341
-rw-r--r--src/java/com/android/internal/telephony/cat/package.html5
-rw-r--r--src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java274
-rwxr-xr-xsrc/java/com/android/internal/telephony/cdma/CDMAPhone.java1508
-rw-r--r--src/java/com/android/internal/telephony/cdma/CallFailCause.java59
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaCall.java208
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaCallTracker.java1163
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaCallWaitingNotification.java69
-rwxr-xr-xsrc/java/com/android/internal/telephony/cdma/CdmaConnection.java945
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaDataConnection.java124
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java1045
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaInformationRecords.java263
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java546
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java79
-rwxr-xr-xsrc/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java451
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java296
-rwxr-xr-xsrc/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java428
-rwxr-xr-xsrc/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java1752
-rw-r--r--src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java196
-rw-r--r--src/java/com/android/internal/telephony/cdma/EriInfo.java45
-rw-r--r--src/java/com/android/internal/telephony/cdma/EriManager.java492
-rw-r--r--src/java/com/android/internal/telephony/cdma/RuimFileHandler.java89
-rw-r--r--src/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java79
-rwxr-xr-xsrc/java/com/android/internal/telephony/cdma/RuimRecords.java462
-rw-r--r--src/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java222
-rw-r--r--src/java/com/android/internal/telephony/cdma/SignalToneUtil.java294
-rw-r--r--src/java/com/android/internal/telephony/cdma/SmsMessage.java998
-rw-r--r--src/java/com/android/internal/telephony/cdma/TtyIntent.java67
-rw-r--r--src/java/com/android/internal/telephony/cdma/package.html6
-rwxr-xr-xsrc/java/com/android/internal/telephony/cdma/sms/BearerData.java1938
-rw-r--r--src/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java228
-rw-r--r--src/java/com/android/internal/telephony/cdma/sms/CdmaSmsSubaddress.java27
-rw-r--r--src/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java130
-rw-r--r--src/java/com/android/internal/telephony/cdma/sms/UserData.java165
-rw-r--r--src/java/com/android/internal/telephony/cdma/sms/package.html6
-rw-r--r--src/java/com/android/internal/telephony/gsm/CallFailCause.java53
-rw-r--r--src/java/com/android/internal/telephony/gsm/GSMPhone.java1508
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmCall.java208
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmCallTracker.java952
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmConnection.java761
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmDataConnection.java165
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java2642
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmMmiCode.java1357
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java471
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java1727
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmSmsAddress.java148
-rw-r--r--src/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java287
-rw-r--r--src/java/com/android/internal/telephony/gsm/SIMFileHandler.java107
-rwxr-xr-xsrc/java/com/android/internal/telephony/gsm/SIMRecords.java1675
-rw-r--r--src/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java79
-rw-r--r--src/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java365
-rw-r--r--src/java/com/android/internal/telephony/gsm/SimTlv.java118
-rw-r--r--src/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java133
-rw-r--r--src/java/com/android/internal/telephony/gsm/SmsCbConstants.java125
-rw-r--r--src/java/com/android/internal/telephony/gsm/SmsCbHeader.java409
-rw-r--r--src/java/com/android/internal/telephony/gsm/SmsMessage.java1180
-rw-r--r--src/java/com/android/internal/telephony/gsm/SpnOverride.java94
-rw-r--r--src/java/com/android/internal/telephony/gsm/SuppServiceNotification.java70
-rw-r--r--src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java267
-rwxr-xr-xsrc/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java453
-rw-r--r--src/java/com/android/internal/telephony/gsm/UsimServiceTable.java142
-rw-r--r--src/java/com/android/internal/telephony/gsm/VoiceMailConstants.java115
-rwxr-xr-xsrc/java/com/android/internal/telephony/gsm/package.html6
-rw-r--r--src/java/com/android/internal/telephony/ims/IsimRecords.java44
-rw-r--r--src/java/com/android/internal/telephony/ims/IsimUiccRecords.java157
-rw-r--r--src/java/com/android/internal/telephony/package.html5
-rw-r--r--src/java/com/android/internal/telephony/sip/SipCallBase.java55
-rw-r--r--src/java/com/android/internal/telephony/sip/SipCommandInterface.java424
-rw-r--r--src/java/com/android/internal/telephony/sip/SipConnectionBase.java182
-rw-r--r--src/java/com/android/internal/telephony/sip/SipPhone.java939
-rwxr-xr-xsrc/java/com/android/internal/telephony/sip/SipPhoneBase.java465
-rw-r--r--src/java/com/android/internal/telephony/sip/SipPhoneFactory.java49
-rw-r--r--src/java/com/android/internal/telephony/test/ModelInterpreter.java741
-rw-r--r--src/java/com/android/internal/telephony/test/SimulatedCommands.java1525
-rw-r--r--src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java809
-rw-r--r--src/java/com/android/internal/telephony/test/SimulatedRadioControl.java53
-rwxr-xr-xsrc/java/com/android/internal/telephony/test/package.html5
-rw-r--r--src/java/com/android/internal/telephony/uicc/UiccController.java93
-rw-r--r--tests/telephonymockriltests/Android.mk14
-rw-r--r--tests/telephonymockriltests/AndroidManifest.xml40
-rw-r--r--tests/telephonymockriltests/src/com/android/telephonymockriltests/TelephonyMockTestRunner.java64
-rw-r--r--tests/telephonymockriltests/src/com/android/telephonymockriltests/functional/SimpleTestUsingMockRil.java63
-rw-r--r--tests/telephonytests/Android.mk14
-rw-r--r--tests/telephonytests/AndroidManifest.xml44
-rw-r--r--tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java93
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/ATResponseParserTest.java113
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java176
-rwxr-xr-xtests/telephonytests/src/com/android/internal/telephony/ApnSettingTest.java109
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/CallerInfoTest.java241
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java361
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java538
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/IccServiceTableTest.java85
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java374
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java75
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/NeighboringCellInfoTest.java79
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java657
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java292
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/SMSDispatcherTest.java105
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java106
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java60
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java91
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java605
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/TelephonyUtilsTest.java219
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/TestPhoneNotifier.java67
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/Wap230WspContentTypeTest.java853
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java746
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java887
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java1933
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/gsm/GSMTestHandler.java118
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java758
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java624
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java144
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/gsm/UsimServiceTableTest.java75
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java304
234 files changed, 88505 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..2487766
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2011 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ src/java/com/android/internal/telephony/ISms.aidl \
+ src/java/com/android/internal/telephony/IIccPhoneBook.aidl \
+ src/java/com/android/internal/telephony/EventLogTags.logtags \
+
+LOCAL_SRC_FILES += $(call all-java-files-under, src/java)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := telephony-common
+
+include $(BUILD_JAVA_LIBRARY)
+
+# Include subdirectory makefiles
+# ============================================================
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..9a63705
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,45 @@
+# Copyright (C) 2011 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/mockril/Android.mk b/mockril/Android.mk
new file mode 100644
index 0000000..95ae84c
--- /dev/null
+++ b/mockril/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2010 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.
+#
+#
+
+LOCAL_PATH:=$(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := core framework
+
+LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
+
+LOCAL_MODULE := mockrilcontroller
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/mockril/src/com/android/internal/telephony/mockril/MockRilController.java b/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
new file mode 100644
index 0000000..0e75c72
--- /dev/null
+++ b/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2010, 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.mockril;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.communication.MsgHeader;
+import com.android.internal.communication.Msg;
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.ril_proto.RilCtrlCmds;
+import com.android.internal.telephony.ril_proto.RilCmds;
+import com.google.protobuf.micro.MessageMicro;
+
+import java.io.IOException;
+
+/**
+ * Contain a list of commands to control Mock RIL. Before using these commands the devices
+ * needs to be set with Mock RIL. Refer to hardware/ril/mockril/README.txt for details.
+ *
+ */
+public class MockRilController {
+ private static final String TAG = "MockRILController";
+ private RilChannel mRilChannel = null;
+ private Msg mMessage = null;
+
+ public MockRilController() throws IOException {
+ mRilChannel = RilChannel.makeRilChannel();
+ }
+
+ /**
+ * Close the channel after the communication is done.
+ * This method has to be called after the test is finished.
+ */
+ public void closeChannel() {
+ mRilChannel.close();
+ }
+
+ /**
+ * Send commands and return true on success
+ * @param cmd for MsgHeader
+ * @param token for MsgHeader
+ * @param status for MsgHeader
+ * @param pbData for Msg data
+ * @return true if command is sent successfully, false if it fails
+ */
+ private boolean sendCtrlCommand(int cmd, long token, int status, MessageMicro pbData) {
+ try {
+ Msg.send(mRilChannel, cmd, token, status, pbData);
+ } catch (IOException e) {
+ Log.v(TAG, "send command : %d failed: " + e.getStackTrace());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get control response
+ * @return Msg if response is received, else return null.
+ */
+ private Msg getCtrlResponse() {
+ Msg response = null;
+ try {
+ response = Msg.recv(mRilChannel);
+ } catch (IOException e) {
+ Log.v(TAG, "receive response for getRadioState() error: " + e.getStackTrace());
+ return null;
+ }
+ return response;
+ }
+
+ /**
+ * @return the radio state if it is valid, otherwise return -1
+ */
+ public int getRadioState() {
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_GET_RADIO_STATE, 0, 0, null)) {
+ return -1;
+ }
+ Msg response = getCtrlResponse();
+ if (response == null) {
+ Log.v(TAG, "failed to get response");
+ return -1;
+ }
+ response.printHeader(TAG);
+ RilCtrlCmds.CtrlRspRadioState resp =
+ response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+ int state = resp.getState();
+ if ((state >= RilCmds.RADIOSTATE_OFF) && (state <= RilCmds.RADIOSTATE_NV_READY))
+ return state;
+ else
+ return -1;
+ }
+
+ /**
+ * Set the radio state of mock ril to the given state
+ * @param state for given radio state
+ * @return true if the state is set successful, false if it fails
+ */
+ public boolean setRadioState(int state) {
+ RilCtrlCmds.CtrlReqRadioState req = new RilCtrlCmds.CtrlReqRadioState();
+ if (state < 0 || state > RilCmds.RADIOSTATE_NV_READY) {
+ Log.v(TAG, "the give radio state is not valid.");
+ return false;
+ }
+ req.setState(state);
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, req)) {
+ Log.v(TAG, "send set radio state request failed.");
+ return false;
+ }
+ Msg response = getCtrlResponse();
+ if (response == null) {
+ Log.v(TAG, "failed to get response for setRadioState");
+ return false;
+ }
+ response.printHeader(TAG);
+ RilCtrlCmds.CtrlRspRadioState resp =
+ response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+ int curstate = resp.getState();
+ return curstate == state;
+ }
+
+ /**
+ * Start an incoming call for the given phone number
+ *
+ * @param phoneNumber is the number to show as incoming call
+ * @return true if the incoming call is started successfully, false if it fails.
+ */
+ public boolean startIncomingCall(String phoneNumber) {
+ RilCtrlCmds.CtrlReqSetMTCall req = new RilCtrlCmds.CtrlReqSetMTCall();
+
+ req.setPhoneNumber(phoneNumber);
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_MT_CALL, 0, 0, req)) {
+ Log.v(TAG, "send CMD_SET_MT_CALL request failed");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Hang up a connection remotelly for the given call fail cause
+ *
+ * @param connectionID is the connection to be hung up
+ * @param failCause is the call fail cause defined in ril.h
+ * @return true if the hangup is successful, false if it fails
+ */
+ public boolean hangupRemote(int connectionId, int failCause) {
+ RilCtrlCmds.CtrlHangupConnRemote req = new RilCtrlCmds.CtrlHangupConnRemote();
+ req.setConnectionId(connectionId);
+ req.setCallFailCause(failCause);
+
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_HANGUP_CONN_REMOTE, 0, 0, req)) {
+ Log.v(TAG, "send CTRL_CMD_HANGUP_CONN_REMOTE request failed");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set call transition flag to the Mock Ril
+ *
+ * @param flag is a boolean value for the call transiton flag
+ * true: call transition: dialing->alert, alert->active is controlled
+ * false: call transition is automatically handled by Mock Ril
+ * @return true if the request is successful, false if it failed to set the flag
+ */
+ public boolean setCallTransitionFlag(boolean flag) {
+ RilCtrlCmds.CtrlSetCallTransitionFlag req = new RilCtrlCmds.CtrlSetCallTransitionFlag();
+
+ req.setFlag(flag);
+
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_CALL_TRANSITION_FLAG, 0, 0, req)) {
+ Log.v(TAG, "send CTRL_CMD_SET_CALL_TRANSITION_FLAG request failed");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the dialing call to alert if the call transition flag is true
+ *
+ * @return true if the call transition is successful, false if it fails
+ */
+ public boolean setDialCallToAlert() {
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_CALL_ALERT, 0, 0, null)) {
+ Log.v(TAG, "send CTRL_CMD_SET_CALL_ALERT request failed");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the alert call to active if the call transition flag is true
+ *
+ * @return true if the call transition is successful, false if it fails
+ */
+ public boolean setAlertCallToActive() {
+ if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_CALL_ACTIVE, 0, 0, null)) {
+ Log.v(TAG, "send CTRL_CMD_SET_CALL_ACTIVE request failed");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/java/android/provider/Telephony.java b/src/java/android/provider/Telephony.java
new file mode 100644
index 0000000..dd8be66
--- /dev/null
+++ b/src/java/android/provider/Telephony.java
@@ -0,0 +1,1993 @@
+/*
+ * 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.provider;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SqliteWrapper;
+import android.net.Uri;
+import android.os.Environment;
+import android.telephony.SmsMessage;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Patterns;
+
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The Telephony provider contains data related to phone operation.
+ *
+ * @hide
+ */
+public final class Telephony {
+ private static final String TAG = "Telephony";
+ private static final boolean DEBUG = true;
+ private static final boolean LOCAL_LOGV = false;
+
+ // Constructor
+ public Telephony() {
+ }
+
+ /**
+ * Base columns for tables that contain text based SMSs.
+ */
+ public interface TextBasedSmsColumns {
+ /**
+ * The type of the message
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TYPE = "type";
+
+ public static final int MESSAGE_TYPE_ALL = 0;
+ public static final int MESSAGE_TYPE_INBOX = 1;
+ public static final int MESSAGE_TYPE_SENT = 2;
+ public static final int MESSAGE_TYPE_DRAFT = 3;
+ public static final int MESSAGE_TYPE_OUTBOX = 4;
+ public static final int MESSAGE_TYPE_FAILED = 5; // for failed outgoing messages
+ public static final int MESSAGE_TYPE_QUEUED = 6; // for messages to send later
+
+
+ /**
+ * The thread ID of the message
+ * <P>Type: INTEGER</P>
+ */
+ public static final String THREAD_ID = "thread_id";
+
+ /**
+ * The address of the other party
+ * <P>Type: TEXT</P>
+ */
+ public static final String ADDRESS = "address";
+
+ /**
+ * The person ID of the sender
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String PERSON_ID = "person";
+
+ /**
+ * The date the message was received
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = "date";
+
+ /**
+ * The date the message was sent
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
+ * Has the message been read
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String READ = "read";
+
+ /**
+ * Indicates whether this message has been seen by the user. The "seen" flag will be
+ * used to figure out whether we need to throw up a statusbar notification or not.
+ */
+ public static final String SEEN = "seen";
+
+ /**
+ * The TP-Status value for the message, or -1 if no status has
+ * been received
+ */
+ public static final String STATUS = "status";
+
+ public static final int STATUS_NONE = -1;
+ public static final int STATUS_COMPLETE = 0;
+ public static final int STATUS_PENDING = 32;
+ public static final int STATUS_FAILED = 64;
+
+ /**
+ * The subject of the message, if present
+ * <P>Type: TEXT</P>
+ */
+ public static final String SUBJECT = "subject";
+
+ /**
+ * The body of the message
+ * <P>Type: TEXT</P>
+ */
+ public static final String BODY = "body";
+
+ /**
+ * The id of the sender of the conversation, if present
+ * <P>Type: INTEGER (reference to item in content://contacts/people)</P>
+ */
+ public static final String PERSON = "person";
+
+ /**
+ * The protocol identifier code
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PROTOCOL = "protocol";
+
+ /**
+ * Whether the <code>TP-Reply-Path</code> bit was set on this message
+ * <P>Type: BOOLEAN</P>
+ */
+ public static final String REPLY_PATH_PRESENT = "reply_path_present";
+
+ /**
+ * The service center (SC) through which to send the message, if present
+ * <P>Type: TEXT</P>
+ */
+ public static final String SERVICE_CENTER = "service_center";
+
+ /**
+ * Has the message been locked?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String LOCKED = "locked";
+
+ /**
+ * Error code associated with sending or receiving this message
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_CODE = "error_code";
+
+ /**
+ * Meta data used externally.
+ * <P>Type: TEXT</P>
+ */
+ public static final String META_DATA = "meta_data";
+ }
+
+ /**
+ * Contains all text based SMS messages.
+ */
+ public static final class Sms implements BaseColumns, TextBasedSmsColumns {
+ public static final Cursor query(ContentResolver cr, String[] projection) {
+ return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+ }
+
+ public static final Cursor query(ContentResolver cr, String[] projection,
+ String where, String orderBy) {
+ return cr.query(CONTENT_URI, projection, where,
+ null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+ }
+
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://sms");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the given URI.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @return the URI for the new message
+ */
+ public static Uri addMessageToUri(ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport) {
+ return addMessageToUri(resolver, uri, address, body, subject,
+ date, read, deliveryReport, -1L);
+ }
+
+ /**
+ * Add an SMS to the given URI with thread_id specified.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @param threadId the thread_id of the message
+ * @return the URI for the new message
+ */
+ public static Uri addMessageToUri(ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport, long threadId) {
+ ContentValues values = new ContentValues(7);
+
+ values.put(ADDRESS, address);
+ if (date != null) {
+ values.put(DATE, date);
+ }
+ values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
+ values.put(SUBJECT, subject);
+ values.put(BODY, body);
+ if (deliveryReport) {
+ values.put(STATUS, STATUS_PENDING);
+ }
+ if (threadId != -1L) {
+ values.put(THREAD_ID, threadId);
+ }
+ return resolver.insert(uri, values);
+ }
+
+ /**
+ * Move a message to the given folder.
+ *
+ * @param context the context to use
+ * @param uri the message to move
+ * @param folder the folder to move to
+ * @return true if the operation succeeded
+ */
+ public static boolean moveMessageToFolder(Context context,
+ Uri uri, int folder, int error) {
+ if (uri == null) {
+ return false;
+ }
+
+ boolean markAsUnread = false;
+ boolean markAsRead = false;
+ switch(folder) {
+ case MESSAGE_TYPE_INBOX:
+ case MESSAGE_TYPE_DRAFT:
+ break;
+ case MESSAGE_TYPE_OUTBOX:
+ case MESSAGE_TYPE_SENT:
+ markAsRead = true;
+ break;
+ case MESSAGE_TYPE_FAILED:
+ case MESSAGE_TYPE_QUEUED:
+ markAsUnread = true;
+ break;
+ default:
+ return false;
+ }
+
+ ContentValues values = new ContentValues(3);
+
+ values.put(TYPE, folder);
+ if (markAsUnread) {
+ values.put(READ, Integer.valueOf(0));
+ } else if (markAsRead) {
+ values.put(READ, Integer.valueOf(1));
+ }
+ values.put(ERROR_CODE, error);
+
+ return 1 == SqliteWrapper.update(context, context.getContentResolver(),
+ uri, values, null, null);
+ }
+
+ /**
+ * Returns true iff the folder (message type) identifies an
+ * outgoing message.
+ */
+ public static boolean isOutgoingFolder(int messageType) {
+ return (messageType == MESSAGE_TYPE_FAILED)
+ || (messageType == MESSAGE_TYPE_OUTBOX)
+ || (messageType == MESSAGE_TYPE_SENT)
+ || (messageType == MESSAGE_TYPE_QUEUED);
+ }
+
+ /**
+ * Contains all text based SMS messages in the SMS app's inbox.
+ */
+ public static final class Inbox implements BaseColumns, TextBasedSmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://sms/inbox");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @return the URI for the new message
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date,
+ boolean read) {
+ return addMessageToUri(resolver, CONTENT_URI, address, body,
+ subject, date, read, false);
+ }
+ }
+
+ /**
+ * Contains all sent text based SMS messages in the SMS app's.
+ */
+ public static final class Sent implements BaseColumns, TextBasedSmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://sms/sent");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @return the URI for the new message
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(resolver, CONTENT_URI, address, body,
+ subject, date, true, false);
+ }
+ }
+
+ /**
+ * Contains all sent text based SMS messages in the SMS app's.
+ */
+ public static final class Draft implements BaseColumns, TextBasedSmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://sms/draft");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @return the URI for the new message
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(resolver, CONTENT_URI, address, body,
+ subject, date, true, false);
+ }
+
+ /**
+ * Save over an existing draft message.
+ *
+ * @param resolver the content resolver to use
+ * @param uri of existing message
+ * @param body the new body for the draft message
+ * @return true is successful, false otherwise
+ */
+ public static boolean saveMessage(ContentResolver resolver,
+ Uri uri, String body) {
+ ContentValues values = new ContentValues(2);
+ values.put(BODY, body);
+ values.put(DATE, System.currentTimeMillis());
+ return resolver.update(uri, values, null, null) == 1;
+ }
+ }
+
+ /**
+ * Contains all pending outgoing text based SMS messages.
+ */
+ public static final class Outbox implements BaseColumns, TextBasedSmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://sms/outbox");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the Out box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param deliveryReport whether a delivery report was requested for the message
+ * @return the URI for the new message
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date,
+ boolean deliveryReport, long threadId) {
+ return addMessageToUri(resolver, CONTENT_URI, address, body,
+ subject, date, true, deliveryReport, threadId);
+ }
+ }
+
+ /**
+ * Contains all sent text-based SMS messages in the SMS app's.
+ */
+ public static final class Conversations
+ implements BaseColumns, TextBasedSmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://sms/conversations");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * The first 45 characters of the body of the message
+ * <P>Type: TEXT</P>
+ */
+ public static final String SNIPPET = "snippet";
+
+ /**
+ * The number of messages in the conversation
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_COUNT = "msg_count";
+ }
+
+ /**
+ * Contains info about SMS related Intents that are broadcast.
+ */
+ public static final class Intents {
+ /**
+ * Set by BroadcastReceiver. Indicates the message was handled
+ * successfully.
+ */
+ public static final int RESULT_SMS_HANDLED = 1;
+
+ /**
+ * Set by BroadcastReceiver. Indicates a generic error while
+ * processing the message.
+ */
+ public static final int RESULT_SMS_GENERIC_ERROR = 2;
+
+ /**
+ * Set by BroadcastReceiver. Indicates insufficient memory to store
+ * the message.
+ */
+ public static final int RESULT_SMS_OUT_OF_MEMORY = 3;
+
+ /**
+ * Set by BroadcastReceiver. Indicates the message, while
+ * possibly valid, is of a format or encoding that is not
+ * supported.
+ */
+ public static final int RESULT_SMS_UNSUPPORTED = 4;
+
+ /**
+ * Broadcast Action: A new text based SMS message has been received
+ * by the device. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs
+ * that make up the message.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_RECEIVED";
+
+ /**
+ * Broadcast Action: A new data based SMS message has been received
+ * by the device. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
+ * that make up the message.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String DATA_SMS_RECEIVED_ACTION =
+ "android.intent.action.DATA_SMS_RECEIVED";
+
+ /**
+ * Broadcast Action: A new WAP PUSH message has been received by the
+ * device. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>transactionId (Integer)</em> - The WAP transaction ID</li>
+ * <li><em>pduType (Integer)</em> - The WAP PDU type</li>
+ * <li><em>header (byte[])</em> - The header of the message</li>
+ * <li><em>data (byte[])</em> - The data payload of the message</li>
+ * <li><em>contentTypeParameters (HashMap&lt;String,String&gt;)</em>
+ * - Any parameters associated with the content type
+ * (decoded from the WSP Content-Type header)</li>
+ * </ul>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>The contentTypeParameters extra value is map of content parameters keyed by
+ * their names.</p>
+ *
+ * <p>If any unassigned well-known parameters are encountered, the key of the map will
+ * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter. If
+ * a parameter has No-Value the value in the map will be null.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WAP_PUSH_RECEIVED_ACTION =
+ "android.provider.Telephony.WAP_PUSH_RECEIVED";
+
+ /**
+ * Broadcast Action: A new Cell Broadcast message has been received
+ * by the device. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>message</em> - An SmsCbMessage object containing the broadcast message
+ * data. This is not an emergency alert, so ETWS and CMAS data will be null.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_CB_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_CB_RECEIVED";
+
+ /**
+ * Broadcast Action: A new Emergency Broadcast message has been received
+ * by the device. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>message</em> - An SmsCbMessage object containing the broadcast message
+ * data, including ETWS or CMAS warning notification info if present.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_EMERGENCY_CB_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
+
+ /**
+ * Broadcast Action: A new CDMA SMS has been received containing Service Category
+ * Program Data (updates the list of enabled broadcast channels). The intent will
+ * have the following extra values:</p>
+ *
+ * <ul>
+ * <li><em>operations</em> - An array of CdmaSmsCbProgramData objects containing
+ * the service category operations (add/delete/clear) to perform.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED";
+
+ /**
+ * Broadcast Action: The SIM storage for SMS messages is full. If
+ * space is not freed, messages targeted for the SIM (class 2) may
+ * not be saved.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SIM_FULL_ACTION =
+ "android.provider.Telephony.SIM_FULL";
+
+ /**
+ * Broadcast Action: An incoming SMS has been rejected by the
+ * telephony framework. This intent is sent in lieu of any
+ * of the RECEIVED_ACTION intents. The intent will have the
+ * following extra value:</p>
+ *
+ * <ul>
+ * <li><em>result</em> - An int result code, eg,
+ * <code>{@link #RESULT_SMS_OUT_OF_MEMORY}</code>,
+ * indicating the error returned to the network.</li>
+ * </ul>
+
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_REJECTED_ACTION =
+ "android.provider.Telephony.SMS_REJECTED";
+
+ /**
+ * Read the PDUs out of an {@link #SMS_RECEIVED_ACTION} or a
+ * {@link #DATA_SMS_RECEIVED_ACTION} intent.
+ *
+ * @param intent the intent to read from
+ * @return an array of SmsMessages for the PDUs
+ */
+ public static SmsMessage[] getMessagesFromIntent(
+ Intent intent) {
+ Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
+ String format = intent.getStringExtra("format");
+ byte[][] pduObjs = new byte[messages.length][];
+
+ for (int i = 0; i < messages.length; i++) {
+ pduObjs[i] = (byte[]) messages[i];
+ }
+ byte[][] pdus = new byte[pduObjs.length][];
+ int pduCount = pdus.length;
+ SmsMessage[] msgs = new SmsMessage[pduCount];
+ for (int i = 0; i < pduCount; i++) {
+ pdus[i] = pduObjs[i];
+ msgs[i] = SmsMessage.createFromPdu(pdus[i], format);
+ }
+ return msgs;
+ }
+ }
+ }
+
+ /**
+ * Base columns for tables that contain MMSs.
+ */
+ public interface BaseMmsColumns extends BaseColumns {
+
+ public static final int MESSAGE_BOX_ALL = 0;
+ public static final int MESSAGE_BOX_INBOX = 1;
+ public static final int MESSAGE_BOX_SENT = 2;
+ public static final int MESSAGE_BOX_DRAFTS = 3;
+ public static final int MESSAGE_BOX_OUTBOX = 4;
+
+ /**
+ * The date the message was received.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = "date";
+
+ /**
+ * The date the message was sent.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
+ * The box which the message belong to, for example, MESSAGE_BOX_INBOX.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_BOX = "msg_box";
+
+ /**
+ * Has the message been read.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String READ = "read";
+
+ /**
+ * Indicates whether this message has been seen by the user. The "seen" flag will be
+ * used to figure out whether we need to throw up a statusbar notification or not.
+ */
+ public static final String SEEN = "seen";
+
+ /**
+ * The Message-ID of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_ID = "m_id";
+
+ /**
+ * The subject of the message, if present.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SUBJECT = "sub";
+
+ /**
+ * The character set of the subject, if present.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SUBJECT_CHARSET = "sub_cs";
+
+ /**
+ * The Content-Type of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_TYPE = "ct_t";
+
+ /**
+ * The Content-Location of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_LOCATION = "ct_l";
+
+ /**
+ * The address of the sender.
+ * <P>Type: TEXT</P>
+ */
+ public static final String FROM = "from";
+
+ /**
+ * The address of the recipients.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TO = "to";
+
+ /**
+ * The address of the cc. recipients.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CC = "cc";
+
+ /**
+ * The address of the bcc. recipients.
+ * <P>Type: TEXT</P>
+ */
+ public static final String BCC = "bcc";
+
+ /**
+ * The expiry time of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String EXPIRY = "exp";
+
+ /**
+ * The class of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_CLASS = "m_cls";
+
+ /**
+ * The type of the message defined by MMS spec.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_TYPE = "m_type";
+
+ /**
+ * The version of specification that this message conform.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MMS_VERSION = "v";
+
+ /**
+ * The size of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_SIZE = "m_size";
+
+ /**
+ * The priority of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PRIORITY = "pri";
+
+ /**
+ * The read-report of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String READ_REPORT = "rr";
+
+ /**
+ * Whether the report is allowed.
+ * <P>Type: TEXT</P>
+ */
+ public static final String REPORT_ALLOWED = "rpt_a";
+
+ /**
+ * The response-status of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RESPONSE_STATUS = "resp_st";
+
+ /**
+ * The status of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String STATUS = "st";
+
+ /**
+ * The transaction-id of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TRANSACTION_ID = "tr_id";
+
+ /**
+ * The retrieve-status of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RETRIEVE_STATUS = "retr_st";
+
+ /**
+ * The retrieve-text of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RETRIEVE_TEXT = "retr_txt";
+
+ /**
+ * The character set of the retrieve-text.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RETRIEVE_TEXT_CHARSET = "retr_txt_cs";
+
+ /**
+ * The read-status of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String READ_STATUS = "read_status";
+
+ /**
+ * The content-class of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTENT_CLASS = "ct_cls";
+
+ /**
+ * The delivery-report of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DELIVERY_REPORT = "d_rpt";
+
+ /**
+ * The delivery-time-token of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DELIVERY_TIME_TOKEN = "d_tm_tok";
+
+ /**
+ * The delivery-time of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DELIVERY_TIME = "d_tm";
+
+ /**
+ * The response-text of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RESPONSE_TEXT = "resp_txt";
+
+ /**
+ * The sender-visibility of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SENDER_VISIBILITY = "s_vis";
+
+ /**
+ * The reply-charging of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String REPLY_CHARGING = "r_chg";
+
+ /**
+ * The reply-charging-deadline-token of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String REPLY_CHARGING_DEADLINE_TOKEN = "r_chg_dl_tok";
+
+ /**
+ * The reply-charging-deadline of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String REPLY_CHARGING_DEADLINE = "r_chg_dl";
+
+ /**
+ * The reply-charging-id of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String REPLY_CHARGING_ID = "r_chg_id";
+
+ /**
+ * The reply-charging-size of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String REPLY_CHARGING_SIZE = "r_chg_sz";
+
+ /**
+ * The previously-sent-by of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PREVIOUSLY_SENT_BY = "p_s_by";
+
+ /**
+ * The previously-sent-date of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PREVIOUSLY_SENT_DATE = "p_s_d";
+
+ /**
+ * The store of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String STORE = "store";
+
+ /**
+ * The mm-state of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MM_STATE = "mm_st";
+
+ /**
+ * The mm-flags-token of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MM_FLAGS_TOKEN = "mm_flg_tok";
+
+ /**
+ * The mm-flags of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MM_FLAGS = "mm_flg";
+
+ /**
+ * The store-status of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String STORE_STATUS = "store_st";
+
+ /**
+ * The store-status-text of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String STORE_STATUS_TEXT = "store_st_txt";
+
+ /**
+ * The stored of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String STORED = "stored";
+
+ /**
+ * The totals of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TOTALS = "totals";
+
+ /**
+ * The mbox-totals of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MBOX_TOTALS = "mb_t";
+
+ /**
+ * The mbox-totals-token of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MBOX_TOTALS_TOKEN = "mb_t_tok";
+
+ /**
+ * The quotas of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String QUOTAS = "qt";
+
+ /**
+ * The mbox-quotas of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MBOX_QUOTAS = "mb_qt";
+
+ /**
+ * The mbox-quotas-token of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MBOX_QUOTAS_TOKEN = "mb_qt_tok";
+
+ /**
+ * The message-count of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_COUNT = "m_cnt";
+
+ /**
+ * The start of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String START = "start";
+
+ /**
+ * The distribution-indicator of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String DISTRIBUTION_INDICATOR = "d_ind";
+
+ /**
+ * The element-descriptor of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ELEMENT_DESCRIPTOR = "e_des";
+
+ /**
+ * The limit of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String LIMIT = "limit";
+
+ /**
+ * The recommended-retrieval-mode of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RECOMMENDED_RETRIEVAL_MODE = "r_r_mod";
+
+ /**
+ * The recommended-retrieval-mode-text of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RECOMMENDED_RETRIEVAL_MODE_TEXT = "r_r_mod_txt";
+
+ /**
+ * The status-text of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String STATUS_TEXT = "st_txt";
+
+ /**
+ * The applic-id of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String APPLIC_ID = "apl_id";
+
+ /**
+ * The reply-applic-id of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String REPLY_APPLIC_ID = "r_apl_id";
+
+ /**
+ * The aux-applic-id of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String AUX_APPLIC_ID = "aux_apl_id";
+
+ /**
+ * The drm-content of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String DRM_CONTENT = "drm_c";
+
+ /**
+ * The adaptation-allowed of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ADAPTATION_ALLOWED = "adp_a";
+
+ /**
+ * The replace-id of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String REPLACE_ID = "repl_id";
+
+ /**
+ * The cancel-id of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CANCEL_ID = "cl_id";
+
+ /**
+ * The cancel-status of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CANCEL_STATUS = "cl_st";
+
+ /**
+ * The thread ID of the message
+ * <P>Type: INTEGER</P>
+ */
+ public static final String THREAD_ID = "thread_id";
+
+ /**
+ * Has the message been locked?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String LOCKED = "locked";
+
+ /**
+ * Meta data used externally.
+ * <P>Type: TEXT</P>
+ */
+ public static final String META_DATA = "meta_data";
+ }
+
+ /**
+ * Columns for the "canonical_addresses" table used by MMS and
+ * SMS."
+ */
+ public interface CanonicalAddressesColumns extends BaseColumns {
+ /**
+ * An address used in MMS or SMS. Email addresses are
+ * converted to lower case and are compared by string
+ * equality. Other addresses are compared using
+ * PHONE_NUMBERS_EQUAL.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ADDRESS = "address";
+ }
+
+ /**
+ * Columns for the "threads" table used by MMS and SMS.
+ */
+ public interface ThreadsColumns extends BaseColumns {
+ /**
+ * The date at which the thread was created.
+ *
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = "date";
+
+ /**
+ * A string encoding of the recipient IDs of the recipients of
+ * the message, in numerical order and separated by spaces.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RECIPIENT_IDS = "recipient_ids";
+
+ /**
+ * The message count of the thread.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_COUNT = "message_count";
+ /**
+ * Indicates whether all messages of the thread have been read.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String READ = "read";
+
+ /**
+ * The snippet of the latest message in the thread.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SNIPPET = "snippet";
+ /**
+ * The charset of the snippet.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SNIPPET_CHARSET = "snippet_cs";
+ /**
+ * Type of the thread, either Threads.COMMON_THREAD or
+ * Threads.BROADCAST_THREAD.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TYPE = "type";
+ /**
+ * Indicates whether there is a transmission error in the thread.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR = "error";
+ /**
+ * Indicates whether this thread contains any attachments.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String HAS_ATTACHMENT = "has_attachment";
+ }
+
+ /**
+ * Helper functions for the "threads" table used by MMS and SMS.
+ */
+ public static final class Threads implements ThreadsColumns {
+ private static final String[] ID_PROJECTION = { BaseColumns._ID };
+ private static final String STANDARD_ENCODING = "UTF-8";
+ private static final Uri THREAD_ID_CONTENT_URI = Uri.parse(
+ "content://mms-sms/threadID");
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ MmsSms.CONTENT_URI, "conversations");
+ public static final Uri OBSOLETE_THREADS_URI = Uri.withAppendedPath(
+ CONTENT_URI, "obsolete");
+
+ public static final int COMMON_THREAD = 0;
+ public static final int BROADCAST_THREAD = 1;
+
+ // No one should construct an instance of this class.
+ private Threads() {
+ }
+
+ /**
+ * This is a single-recipient version of
+ * getOrCreateThreadId. It's convenient for use with SMS
+ * messages.
+ */
+ public static long getOrCreateThreadId(Context context, String recipient) {
+ Set<String> recipients = new HashSet<String>();
+
+ recipients.add(recipient);
+ return getOrCreateThreadId(context, recipients);
+ }
+
+ /**
+ * Given the recipients list and subject of an unsaved message,
+ * return its thread ID. If the message starts a new thread,
+ * allocate a new thread ID. Otherwise, use the appropriate
+ * existing thread ID.
+ *
+ * Find the thread ID of the same set of recipients (in
+ * any order, without any additions). If one
+ * is found, return it. Otherwise, return a unique thread ID.
+ */
+ public static long getOrCreateThreadId(
+ Context context, Set<String> recipients) {
+ Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
+
+ for (String recipient : recipients) {
+ if (Mms.isEmailAddress(recipient)) {
+ recipient = Mms.extractAddrSpec(recipient);
+ }
+
+ uriBuilder.appendQueryParameter("recipient", recipient);
+ }
+
+ Uri uri = uriBuilder.build();
+ //if (DEBUG) Log.v(TAG, "getOrCreateThreadId uri: " + uri);
+
+ Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
+ uri, ID_PROJECTION, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ } else {
+ Log.e(TAG, "getOrCreateThreadId returned no rows!");
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ Log.e(TAG, "getOrCreateThreadId failed with uri " + uri.toString());
+ throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
+ }
+ }
+
+ /**
+ * Contains all MMS messages.
+ */
+ public static final class Mms implements BaseMmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://mms");
+
+ public static final Uri REPORT_REQUEST_URI = Uri.withAppendedPath(
+ CONTENT_URI, "report-request");
+
+ public static final Uri REPORT_STATUS_URI = Uri.withAppendedPath(
+ CONTENT_URI, "report-status");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * mailbox = name-addr
+ * name-addr = [display-name] angle-addr
+ * angle-addr = [CFWS] "<" addr-spec ">" [CFWS]
+ */
+ public static final Pattern NAME_ADDR_EMAIL_PATTERN =
+ Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
+
+ /**
+ * quoted-string = [CFWS]
+ * DQUOTE *([FWS] qcontent) [FWS] DQUOTE
+ * [CFWS]
+ */
+ public static final Pattern QUOTED_STRING_PATTERN =
+ Pattern.compile("\\s*\"([^\"]*)\"\\s*");
+
+ public static final Cursor query(
+ ContentResolver cr, String[] projection) {
+ return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+ }
+
+ public static final Cursor query(
+ ContentResolver cr, String[] projection,
+ String where, String orderBy) {
+ return cr.query(CONTENT_URI, projection,
+ where, null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+ }
+
+ public static final String getMessageBoxName(int msgBox) {
+ switch (msgBox) {
+ case MESSAGE_BOX_ALL:
+ return "all";
+ case MESSAGE_BOX_INBOX:
+ return "inbox";
+ case MESSAGE_BOX_SENT:
+ return "sent";
+ case MESSAGE_BOX_DRAFTS:
+ return "drafts";
+ case MESSAGE_BOX_OUTBOX:
+ return "outbox";
+ default:
+ throw new IllegalArgumentException("Invalid message box: " + msgBox);
+ }
+ }
+
+ public static String extractAddrSpec(String address) {
+ Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
+
+ if (match.matches()) {
+ return match.group(2);
+ }
+ return address;
+ }
+
+ /**
+ * Returns true if the address is an email address
+ *
+ * @param address the input address to be tested
+ * @return true if address is an email address
+ */
+ public static boolean isEmailAddress(String address) {
+ if (TextUtils.isEmpty(address)) {
+ return false;
+ }
+
+ String s = extractAddrSpec(address);
+ Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
+ return match.matches();
+ }
+
+ /**
+ * Returns true if the number is a Phone number
+ *
+ * @param number the input number to be tested
+ * @return true if number is a Phone number
+ */
+ public static boolean isPhoneNumber(String number) {
+ if (TextUtils.isEmpty(number)) {
+ return false;
+ }
+
+ Matcher match = Patterns.PHONE.matcher(number);
+ return match.matches();
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app's inbox.
+ */
+ public static final class Inbox implements BaseMmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/inbox");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app's sent box.
+ */
+ public static final class Sent implements BaseMmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/sent");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app's drafts box.
+ */
+ public static final class Draft implements BaseMmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/drafts");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app's outbox.
+ */
+ public static final class Outbox implements BaseMmsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/outbox");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ public static final class Addr implements BaseColumns {
+ /**
+ * The ID of MM which this address entry belongs to.
+ */
+ public static final String MSG_ID = "msg_id";
+
+ /**
+ * The ID of contact entry in Phone Book.
+ */
+ public static final String CONTACT_ID = "contact_id";
+
+ /**
+ * The address text.
+ */
+ public static final String ADDRESS = "address";
+
+ /**
+ * Type of address, must be one of PduHeaders.BCC,
+ * PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO.
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * Character set of this entry.
+ */
+ public static final String CHARSET = "charset";
+ }
+
+ public static final class Part implements BaseColumns {
+ /**
+ * The identifier of the message which this part belongs to.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MSG_ID = "mid";
+
+ /**
+ * The order of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SEQ = "seq";
+
+ /**
+ * The content type of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_TYPE = "ct";
+
+ /**
+ * The name of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String NAME = "name";
+
+ /**
+ * The charset of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CHARSET = "chset";
+
+ /**
+ * The file name of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String FILENAME = "fn";
+
+ /**
+ * The content disposition of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_DISPOSITION = "cd";
+
+ /**
+ * The content ID of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTENT_ID = "cid";
+
+ /**
+ * The content location of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTENT_LOCATION = "cl";
+
+ /**
+ * The start of content-type of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CT_START = "ctt_s";
+
+ /**
+ * The type of content-type of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CT_TYPE = "ctt_t";
+
+ /**
+ * The location(on filesystem) of the binary data of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String _DATA = "_data";
+
+ public static final String TEXT = "text";
+
+ }
+
+ public static final class Rate {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ Mms.CONTENT_URI, "rate");
+ /**
+ * When a message was successfully sent.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SENT_TIME = "sent_time";
+ }
+
+ public static final class Intents {
+ private Intents() {
+ // Non-instantiatable.
+ }
+
+ /**
+ * The extra field to store the contents of the Intent,
+ * which should be an array of Uri.
+ */
+ public static final String EXTRA_CONTENTS = "contents";
+ /**
+ * The extra field to store the type of the contents,
+ * which should be an array of String.
+ */
+ public static final String EXTRA_TYPES = "types";
+ /**
+ * The extra field to store the 'Cc' addresses.
+ */
+ public static final String EXTRA_CC = "cc";
+ /**
+ * The extra field to store the 'Bcc' addresses;
+ */
+ public static final String EXTRA_BCC = "bcc";
+ /**
+ * The extra field to store the 'Subject'.
+ */
+ public static final String EXTRA_SUBJECT = "subject";
+ /**
+ * Indicates that the contents of specified URIs were changed.
+ * The application which is showing or caching these contents
+ * should be updated.
+ */
+ public static final String
+ CONTENT_CHANGED_ACTION = "android.intent.action.CONTENT_CHANGED";
+ /**
+ * An extra field which stores the URI of deleted contents.
+ */
+ public static final String DELETED_CONTENTS = "deleted_contents";
+ }
+ }
+
+ /**
+ * Contains all MMS and SMS messages.
+ */
+ public static final class MmsSms implements BaseColumns {
+ /**
+ * The column to distinguish SMS &amp; MMS messages in query results.
+ */
+ public static final String TYPE_DISCRIMINATOR_COLUMN =
+ "transport_type";
+
+ public static final Uri CONTENT_URI = Uri.parse("content://mms-sms/");
+
+ public static final Uri CONTENT_CONVERSATIONS_URI = Uri.parse(
+ "content://mms-sms/conversations");
+
+ public static final Uri CONTENT_FILTER_BYPHONE_URI = Uri.parse(
+ "content://mms-sms/messages/byphone");
+
+ public static final Uri CONTENT_UNDELIVERED_URI = Uri.parse(
+ "content://mms-sms/undelivered");
+
+ public static final Uri CONTENT_DRAFT_URI = Uri.parse(
+ "content://mms-sms/draft");
+
+ public static final Uri CONTENT_LOCKED_URI = Uri.parse(
+ "content://mms-sms/locked");
+
+ /***
+ * Pass in a query parameter called "pattern" which is the text
+ * to search for.
+ * The sort order is fixed to be thread_id ASC,date DESC.
+ */
+ public static final Uri SEARCH_URI = Uri.parse(
+ "content://mms-sms/search");
+
+ // Constants for message protocol types.
+ public static final int SMS_PROTO = 0;
+ public static final int MMS_PROTO = 1;
+
+ // Constants for error types of pending messages.
+ public static final int NO_ERROR = 0;
+ public static final int ERR_TYPE_GENERIC = 1;
+ public static final int ERR_TYPE_SMS_PROTO_TRANSIENT = 2;
+ public static final int ERR_TYPE_MMS_PROTO_TRANSIENT = 3;
+ public static final int ERR_TYPE_TRANSPORT_FAILURE = 4;
+ public static final int ERR_TYPE_GENERIC_PERMANENT = 10;
+ public static final int ERR_TYPE_SMS_PROTO_PERMANENT = 11;
+ public static final int ERR_TYPE_MMS_PROTO_PERMANENT = 12;
+
+ public static final class PendingMessages implements BaseColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ MmsSms.CONTENT_URI, "pending");
+ /**
+ * The type of transport protocol(MMS or SMS).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PROTO_TYPE = "proto_type";
+ /**
+ * The ID of the message to be sent or downloaded.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MSG_ID = "msg_id";
+ /**
+ * The type of the message to be sent or downloaded.
+ * This field is only valid for MM. For SM, its value is always
+ * set to 0.
+ */
+ public static final String MSG_TYPE = "msg_type";
+ /**
+ * The type of the error code.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_TYPE = "err_type";
+ /**
+ * The error code of sending/retrieving process.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_CODE = "err_code";
+ /**
+ * How many times we tried to send or download the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RETRY_INDEX = "retry_index";
+ /**
+ * The time to do next retry.
+ */
+ public static final String DUE_TIME = "due_time";
+ /**
+ * The time we last tried to send or download the message.
+ */
+ public static final String LAST_TRY = "last_try";
+ }
+
+ public static final class WordsTable {
+ public static final String ID = "_id";
+ public static final String SOURCE_ROW_ID = "source_id";
+ public static final String TABLE_ID = "table_to_use";
+ public static final String INDEXED_TEXT = "index_text";
+ }
+ }
+
+ public static final class Carriers implements BaseColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://telephony/carriers");
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+ public static final String NAME = "name";
+
+ public static final String APN = "apn";
+
+ public static final String PROXY = "proxy";
+
+ public static final String PORT = "port";
+
+ public static final String MMSPROXY = "mmsproxy";
+
+ public static final String MMSPORT = "mmsport";
+
+ public static final String SERVER = "server";
+
+ public static final String USER = "user";
+
+ public static final String PASSWORD = "password";
+
+ public static final String MMSC = "mmsc";
+
+ public static final String MCC = "mcc";
+
+ public static final String MNC = "mnc";
+
+ public static final String NUMERIC = "numeric";
+
+ public static final String AUTH_TYPE = "authtype";
+
+ public static final String TYPE = "type";
+
+ public static final String INACTIVE_TIMER = "inactivetimer";
+
+ // Only if enabled try Data Connection.
+ public static final String ENABLED = "enabled";
+
+ // Rules apply based on class.
+ public static final String CLASS = "class";
+
+ /**
+ * The protocol to be used to connect to this APN.
+ *
+ * One of the PDP_type values in TS 27.007 section 10.1.1.
+ * For example, "IP", "IPV6", "IPV4V6", or "PPP".
+ */
+ public static final String PROTOCOL = "protocol";
+
+ /**
+ * The protocol to be used to connect to this APN when roaming.
+ *
+ * The syntax is the same as protocol.
+ */
+ public static final String ROAMING_PROTOCOL = "roaming_protocol";
+
+ public static final String CURRENT = "current";
+
+ /**
+ * Current status of APN
+ * true : enabled APN, false : disabled APN.
+ */
+ public static final String CARRIER_ENABLED = "carrier_enabled";
+
+ /**
+ * Radio Access Technology info
+ * To check what values can hold, refer to ServiceState.java.
+ * This should be spread to other technologies,
+ * but currently only used for LTE(14) and EHRPD(13).
+ */
+ public static final String BEARER = "bearer";
+ }
+
+ /**
+ * Contains received SMS cell broadcast messages.
+ */
+ public static final class CellBroadcasts implements BaseColumns {
+
+ /** Not instantiable. */
+ private CellBroadcasts() {}
+
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://cellbroadcasts");
+
+ /**
+ * Message geographical scope.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+
+ /**
+ * Message serial number.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SERIAL_NUMBER = "serial_number";
+
+ /**
+ * PLMN of broadcast sender. (SERIAL_NUMBER + PLMN + LAC + CID) uniquely identifies a
+ * broadcast for duplicate detection purposes.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PLMN = "plmn";
+
+ /**
+ * Location Area (GSM) or Service Area (UMTS) of broadcast sender. Unused for CDMA.
+ * Only included if Geographical Scope of message is not PLMN wide (01).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String LAC = "lac";
+
+ /**
+ * Cell ID of message sender (GSM/UMTS). Unused for CDMA. Only included when the
+ * Geographical Scope of message is cell wide (00 or 11).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CID = "cid";
+
+ /**
+ * Message code (OBSOLETE: merged into SERIAL_NUMBER).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String V1_MESSAGE_CODE = "message_code";
+
+ /**
+ * Message identifier (OBSOLETE: renamed to SERVICE_CATEGORY).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String V1_MESSAGE_IDENTIFIER = "message_id";
+
+ /**
+ * Service category (GSM/UMTS message identifier, CDMA service category).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SERVICE_CATEGORY = "service_category";
+
+ /**
+ * Message language code.
+ * <P>Type: TEXT</P>
+ */
+ public static final String LANGUAGE_CODE = "language";
+
+ /**
+ * Message body.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_BODY = "body";
+
+ /**
+ * Message delivery time.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DELIVERY_TIME = "date";
+
+ /**
+ * Has the message been viewed?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String MESSAGE_READ = "read";
+
+ /**
+ * Message format (3GPP or 3GPP2).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_FORMAT = "format";
+
+ /**
+ * Message priority (including emergency).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_PRIORITY = "priority";
+
+ /**
+ * ETWS warning type (ETWS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ETWS_WARNING_TYPE = "etws_warning_type";
+
+ /**
+ * CMAS message class (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
+
+ /**
+ * CMAS category (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_CATEGORY = "cmas_category";
+
+ /**
+ * CMAS response type (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_RESPONSE_TYPE = "cmas_response_type";
+
+ /**
+ * CMAS severity (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_SEVERITY = "cmas_severity";
+
+ /**
+ * CMAS urgency (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_URGENCY = "cmas_urgency";
+
+ /**
+ * CMAS certainty (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_CERTAINTY = "cmas_certainty";
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC";
+
+ /**
+ * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+ */
+ public static final String[] QUERY_COLUMNS = {
+ _ID,
+ GEOGRAPHICAL_SCOPE,
+ PLMN,
+ LAC,
+ CID,
+ SERIAL_NUMBER,
+ SERVICE_CATEGORY,
+ LANGUAGE_CODE,
+ MESSAGE_BODY,
+ DELIVERY_TIME,
+ MESSAGE_READ,
+ MESSAGE_FORMAT,
+ MESSAGE_PRIORITY,
+ ETWS_WARNING_TYPE,
+ CMAS_MESSAGE_CLASS,
+ CMAS_CATEGORY,
+ CMAS_RESPONSE_TYPE,
+ CMAS_SEVERITY,
+ CMAS_URGENCY,
+ CMAS_CERTAINTY
+ };
+ }
+}
diff --git a/src/java/android/telephony/CellBroadcastMessage.java b/src/java/android/telephony/CellBroadcastMessage.java
new file mode 100644
index 0000000..36c238d
--- /dev/null
+++ b/src/java/android/telephony/CellBroadcastMessage.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2011 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.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbEtwsInfo;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.format.DateUtils;
+import android.text.style.StyleSpan;
+
+/**
+ * Application wrapper for {@link SmsCbMessage}. This is Parcelable so that
+ * decoded broadcast message objects can be passed between running Services.
+ * New broadcasts are received by the CellBroadcastReceiver app, which exports
+ * the database of previously received broadcasts at "content://cellbroadcasts/".
+ * The "android.permission.READ_CELL_BROADCASTS" permission is required to read
+ * from the ContentProvider, and writes to the database are not allowed.<p>
+ *
+ * Use {@link #createFromCursor} to create CellBroadcastMessage objects from rows
+ * in the database cursor returned by the ContentProvider.
+ *
+ * {@hide}
+ */
+public class CellBroadcastMessage implements Parcelable {
+
+ /** Identifier for getExtra() when adding this object to an Intent. */
+ public static final String SMS_CB_MESSAGE_EXTRA =
+ "com.android.cellbroadcastreceiver.SMS_CB_MESSAGE";
+
+ /** SmsCbMessage. */
+ private final SmsCbMessage mSmsCbMessage;
+
+ private final long mDeliveryTime;
+ private boolean mIsRead;
+
+ public CellBroadcastMessage(SmsCbMessage message) {
+ mSmsCbMessage = message;
+ mDeliveryTime = System.currentTimeMillis();
+ mIsRead = false;
+ }
+
+ private CellBroadcastMessage(SmsCbMessage message, long deliveryTime, boolean isRead) {
+ mSmsCbMessage = message;
+ mDeliveryTime = deliveryTime;
+ mIsRead = isRead;
+ }
+
+ private CellBroadcastMessage(Parcel in) {
+ mSmsCbMessage = new SmsCbMessage(in);
+ mDeliveryTime = in.readLong();
+ mIsRead = (in.readInt() != 0);
+ }
+
+ /** Parcelable: no special flags. */
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ mSmsCbMessage.writeToParcel(out, flags);
+ out.writeLong(mDeliveryTime);
+ out.writeInt(mIsRead ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<CellBroadcastMessage> CREATOR
+ = new Parcelable.Creator<CellBroadcastMessage>() {
+ public CellBroadcastMessage createFromParcel(Parcel in) {
+ return new CellBroadcastMessage(in);
+ }
+
+ public CellBroadcastMessage[] newArray(int size) {
+ return new CellBroadcastMessage[size];
+ }
+ };
+
+ /**
+ * Create a CellBroadcastMessage from a row in the database.
+ * @param cursor an open SQLite cursor pointing to the row to read
+ * @return the new CellBroadcastMessage
+ * @throws IllegalArgumentException if one of the required columns is missing
+ */
+ public static CellBroadcastMessage createFromCursor(Cursor cursor) {
+ int geoScope = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE));
+ int serialNum = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERIAL_NUMBER));
+ int category = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERVICE_CATEGORY));
+ String language = cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.LANGUAGE_CODE));
+ String body = cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_BODY));
+ int format = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_FORMAT));
+ int priority = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_PRIORITY));
+
+ String plmn;
+ int plmnColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.PLMN);
+ if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) {
+ plmn = cursor.getString(plmnColumn);
+ } else {
+ plmn = null;
+ }
+
+ int lac;
+ int lacColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.LAC);
+ if (lacColumn != -1 && !cursor.isNull(lacColumn)) {
+ lac = cursor.getInt(lacColumn);
+ } else {
+ lac = -1;
+ }
+
+ int cid;
+ int cidColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.CID);
+ if (cidColumn != -1 && !cursor.isNull(cidColumn)) {
+ cid = cursor.getInt(cidColumn);
+ } else {
+ cid = -1;
+ }
+
+ SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
+
+ SmsCbEtwsInfo etwsInfo;
+ int etwsWarningTypeColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.ETWS_WARNING_TYPE);
+ if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
+ int warningType = cursor.getInt(etwsWarningTypeColumn);
+ etwsInfo = new SmsCbEtwsInfo(warningType, false, false, null);
+ } else {
+ etwsInfo = null;
+ }
+
+ SmsCbCmasInfo cmasInfo;
+ int cmasMessageClassColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS);
+ if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) {
+ int messageClass = cursor.getInt(cmasMessageClassColumn);
+
+ int cmasCategory;
+ int cmasCategoryColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_CATEGORY);
+ if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) {
+ cmasCategory = cursor.getInt(cmasCategoryColumn);
+ } else {
+ cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+ }
+
+ int responseType;
+ int cmasResponseTypeColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE);
+ if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) {
+ responseType = cursor.getInt(cmasResponseTypeColumn);
+ } else {
+ responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+ }
+
+ int severity;
+ int cmasSeverityColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_SEVERITY);
+ if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) {
+ severity = cursor.getInt(cmasSeverityColumn);
+ } else {
+ severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+ }
+
+ int urgency;
+ int cmasUrgencyColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_URGENCY);
+ if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) {
+ urgency = cursor.getInt(cmasUrgencyColumn);
+ } else {
+ urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+ }
+
+ int certainty;
+ int cmasCertaintyColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_CERTAINTY);
+ if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) {
+ certainty = cursor.getInt(cmasCertaintyColumn);
+ } else {
+ certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+ }
+
+ cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
+ urgency, certainty);
+ } else {
+ cmasInfo = null;
+ }
+
+ SmsCbMessage msg = new SmsCbMessage(format, geoScope, serialNum, location, category,
+ language, body, priority, etwsInfo, cmasInfo);
+
+ long deliveryTime = cursor.getLong(cursor.getColumnIndexOrThrow(
+ Telephony.CellBroadcasts.DELIVERY_TIME));
+ boolean isRead = (cursor.getInt(cursor.getColumnIndexOrThrow(
+ Telephony.CellBroadcasts.MESSAGE_READ)) != 0);
+
+ return new CellBroadcastMessage(msg, deliveryTime, isRead);
+ }
+
+ /**
+ * Return a ContentValues object for insertion into the database.
+ * @return a new ContentValues object containing this object's data
+ */
+ public ContentValues getContentValues() {
+ ContentValues cv = new ContentValues(16);
+ SmsCbMessage msg = mSmsCbMessage;
+ cv.put(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE, msg.getGeographicalScope());
+ SmsCbLocation location = msg.getLocation();
+ if (location.getPlmn() != null) {
+ cv.put(Telephony.CellBroadcasts.PLMN, location.getPlmn());
+ }
+ if (location.getLac() != -1) {
+ cv.put(Telephony.CellBroadcasts.LAC, location.getLac());
+ }
+ if (location.getCid() != -1) {
+ cv.put(Telephony.CellBroadcasts.CID, location.getCid());
+ }
+ cv.put(Telephony.CellBroadcasts.SERIAL_NUMBER, msg.getSerialNumber());
+ cv.put(Telephony.CellBroadcasts.SERVICE_CATEGORY, msg.getServiceCategory());
+ cv.put(Telephony.CellBroadcasts.LANGUAGE_CODE, msg.getLanguageCode());
+ cv.put(Telephony.CellBroadcasts.MESSAGE_BODY, msg.getMessageBody());
+ cv.put(Telephony.CellBroadcasts.DELIVERY_TIME, mDeliveryTime);
+ cv.put(Telephony.CellBroadcasts.MESSAGE_READ, mIsRead);
+ cv.put(Telephony.CellBroadcasts.MESSAGE_FORMAT, msg.getMessageFormat());
+ cv.put(Telephony.CellBroadcasts.MESSAGE_PRIORITY, msg.getMessagePriority());
+
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ if (etwsInfo != null) {
+ cv.put(Telephony.CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
+ }
+
+ SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo();
+ if (cmasInfo != null) {
+ cv.put(Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
+ cv.put(Telephony.CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory());
+ cv.put(Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
+ cv.put(Telephony.CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity());
+ cv.put(Telephony.CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency());
+ cv.put(Telephony.CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty());
+ }
+
+ return cv;
+ }
+
+ /**
+ * Set or clear the "read message" flag.
+ * @param isRead true if the message has been read; false if not
+ */
+ public void setIsRead(boolean isRead) {
+ mIsRead = isRead;
+ }
+
+ public String getLanguageCode() {
+ return mSmsCbMessage.getLanguageCode();
+ }
+
+ public int getServiceCategory() {
+ return mSmsCbMessage.getServiceCategory();
+ }
+
+ public long getDeliveryTime() {
+ return mDeliveryTime;
+ }
+
+ public String getMessageBody() {
+ return mSmsCbMessage.getMessageBody();
+ }
+
+ public boolean isRead() {
+ return mIsRead;
+ }
+
+ public int getSerialNumber() {
+ return mSmsCbMessage.getSerialNumber();
+ }
+
+ public SmsCbCmasInfo getCmasWarningInfo() {
+ return mSmsCbMessage.getCmasWarningInfo();
+ }
+
+ public SmsCbEtwsInfo getEtwsWarningInfo() {
+ return mSmsCbMessage.getEtwsWarningInfo();
+ }
+
+ /**
+ * Return whether the broadcast is an emergency (PWS) message type.
+ * This includes lower priority test messages and Amber alerts.
+ *
+ * All public alerts show the flashing warning icon in the dialog,
+ * but only emergency alerts play the alert sound and speak the message.
+ *
+ * @return true if the message is PWS type; false otherwise
+ */
+ public boolean isPublicAlertMessage() {
+ return mSmsCbMessage.isEmergencyMessage();
+ }
+
+ /**
+ * Returns whether the broadcast is an emergency (PWS) message type,
+ * including test messages, but excluding lower priority Amber alert broadcasts.
+ *
+ * @return true if the message is PWS type, excluding Amber alerts
+ */
+ public boolean isEmergencyAlertMessage() {
+ if (!mSmsCbMessage.isEmergencyMessage()) {
+ return false;
+ }
+ SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo();
+ if (cmasInfo != null &&
+ cmasInfo.getMessageClass() == SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return whether the broadcast is an ETWS emergency message type.
+ * @return true if the message is ETWS emergency type; false otherwise
+ */
+ public boolean isEtwsMessage() {
+ return mSmsCbMessage.isEtwsMessage();
+ }
+
+ /**
+ * Return whether the broadcast is a CMAS emergency message type.
+ * @return true if the message is CMAS emergency type; false otherwise
+ */
+ public boolean isCmasMessage() {
+ return mSmsCbMessage.isCmasMessage();
+ }
+
+ /**
+ * Return the CMAS message class.
+ * @return the CMAS message class, e.g. {@link SmsCbCmasInfo#CMAS_CLASS_SEVERE_THREAT}, or
+ * {@link SmsCbCmasInfo#CMAS_CLASS_UNKNOWN} if this is not a CMAS alert
+ */
+ public int getCmasMessageClass() {
+ if (mSmsCbMessage.isCmasMessage()) {
+ return mSmsCbMessage.getCmasWarningInfo().getMessageClass();
+ } else {
+ return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+ }
+ }
+
+ /**
+ * Return whether the broadcast is an ETWS popup alert.
+ * This method checks the message ID and the message code.
+ * @return true if the message indicates an ETWS popup alert
+ */
+ public boolean isEtwsPopupAlert() {
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ return etwsInfo != null && etwsInfo.isPopupAlert();
+ }
+
+ /**
+ * Return whether the broadcast is an ETWS emergency user alert.
+ * This method checks the message ID and the message code.
+ * @return true if the message indicates an ETWS emergency user alert
+ */
+ public boolean isEtwsEmergencyUserAlert() {
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ return etwsInfo != null && etwsInfo.isEmergencyUserAlert();
+ }
+
+ /**
+ * Return whether the broadcast is an ETWS test message.
+ * @return true if the message is an ETWS test message; false otherwise
+ */
+ public boolean isEtwsTestMessage() {
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ return etwsInfo != null &&
+ etwsInfo.getWarningType() == SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
+ }
+
+ /**
+ * Return the abbreviated date string for the message delivery time.
+ * @param context the context object
+ * @return a String to use in the broadcast list UI
+ */
+ public String getDateString(Context context) {
+ int flags = DateUtils.FORMAT_NO_NOON_MIDNIGHT | DateUtils.FORMAT_SHOW_TIME |
+ DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE |
+ DateUtils.FORMAT_CAP_AMPM;
+ return DateUtils.formatDateTime(context, mDeliveryTime, flags);
+ }
+
+ /**
+ * Return the date string for the message delivery time, suitable for text-to-speech.
+ * @param context the context object
+ * @return a String for populating the list item AccessibilityEvent for TTS
+ */
+ public String getSpokenDateString(Context context) {
+ int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
+ return DateUtils.formatDateTime(context, mDeliveryTime, flags);
+ }
+}
diff --git a/src/java/android/telephony/SmsCbCmasInfo.java b/src/java/android/telephony/SmsCbCmasInfo.java
new file mode 100644
index 0000000..7a89d94
--- /dev/null
+++ b/src/java/android/telephony/SmsCbCmasInfo.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2012 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.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}.
+ * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and
+ * 3GPP TS 23.041 (for GSM/UMTS).
+ *
+ * {@hide}
+ */
+public class SmsCbCmasInfo implements Parcelable {
+
+ // CMAS message class (in GSM/UMTS message identifier or CDMA service category).
+
+ /** Presidential-level alert (Korean Public Alert System Class 0 message). */
+ public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00;
+
+ /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */
+ public static final int CMAS_CLASS_EXTREME_THREAT = 0x01;
+
+ /** Severe threat to life and property (Korean Public Alert System Class 1 message). */
+ public static final int CMAS_CLASS_SEVERE_THREAT = 0x02;
+
+ /** Child abduction emergency (AMBER Alert). */
+ public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03;
+
+ /** CMAS test message. */
+ public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04;
+
+ /** CMAS exercise. */
+ public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05;
+
+ /** CMAS category for operator defined use. */
+ public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06;
+
+ /** CMAS category for warning types that are reserved for future extension. */
+ public static final int CMAS_CLASS_UNKNOWN = -1;
+
+ // CMAS alert category (in CDMA type 1 elements record).
+
+ /** CMAS alert category: Geophysical including landslide. */
+ public static final int CMAS_CATEGORY_GEO = 0x00;
+
+ /** CMAS alert category: Meteorological including flood. */
+ public static final int CMAS_CATEGORY_MET = 0x01;
+
+ /** CMAS alert category: General emergency and public safety. */
+ public static final int CMAS_CATEGORY_SAFETY = 0x02;
+
+ /** CMAS alert category: Law enforcement, military, homeland/local/private security. */
+ public static final int CMAS_CATEGORY_SECURITY = 0x03;
+
+ /** CMAS alert category: Rescue and recovery. */
+ public static final int CMAS_CATEGORY_RESCUE = 0x04;
+
+ /** CMAS alert category: Fire suppression and rescue. */
+ public static final int CMAS_CATEGORY_FIRE = 0x05;
+
+ /** CMAS alert category: Medical and public health. */
+ public static final int CMAS_CATEGORY_HEALTH = 0x06;
+
+ /** CMAS alert category: Pollution and other environmental. */
+ public static final int CMAS_CATEGORY_ENV = 0x07;
+
+ /** CMAS alert category: Public and private transportation. */
+ public static final int CMAS_CATEGORY_TRANSPORT = 0x08;
+
+ /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */
+ public static final int CMAS_CATEGORY_INFRA = 0x09;
+
+ /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */
+ public static final int CMAS_CATEGORY_CBRNE = 0x0a;
+
+ /** CMAS alert category: Other events. */
+ public static final int CMAS_CATEGORY_OTHER = 0x0b;
+
+ /**
+ * CMAS alert category is unknown. The category is only available for CDMA broadcasts
+ * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+ */
+ public static final int CMAS_CATEGORY_UNKNOWN = -1;
+
+ // CMAS response type (in CDMA type 1 elements record).
+
+ /** CMAS response type: Take shelter in place. */
+ public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00;
+
+ /** CMAS response type: Evacuate (Relocate). */
+ public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01;
+
+ /** CMAS response type: Make preparations. */
+ public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02;
+
+ /** CMAS response type: Execute a pre-planned activity. */
+ public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03;
+
+ /** CMAS response type: Attend to information sources. */
+ public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04;
+
+ /** CMAS response type: Avoid hazard. */
+ public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05;
+
+ /** CMAS response type: Evaluate the information in this message (not for public warnings). */
+ public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06;
+
+ /** CMAS response type: No action recommended. */
+ public static final int CMAS_RESPONSE_TYPE_NONE = 0x07;
+
+ /**
+ * CMAS response type is unknown. The response type is only available for CDMA broadcasts
+ * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+ */
+ public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1;
+
+ // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+ /** CMAS severity type: Extraordinary threat to life or property. */
+ public static final int CMAS_SEVERITY_EXTREME = 0x0;
+
+ /** CMAS severity type: Significant threat to life or property. */
+ public static final int CMAS_SEVERITY_SEVERE = 0x1;
+
+ /**
+ * CMAS alert severity is unknown. The severity is available for CDMA warning alerts
+ * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+ * Presidential-level alert class (Korean Public Alert System Class 0).
+ */
+ public static final int CMAS_SEVERITY_UNKNOWN = -1;
+
+ // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+ /** CMAS urgency type: Responsive action should be taken immediately. */
+ public static final int CMAS_URGENCY_IMMEDIATE = 0x0;
+
+ /** CMAS urgency type: Responsive action should be taken within the next hour. */
+ public static final int CMAS_URGENCY_EXPECTED = 0x1;
+
+ /**
+ * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts
+ * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+ * Presidential-level alert class (Korean Public Alert System Class 0).
+ */
+ public static final int CMAS_URGENCY_UNKNOWN = -1;
+
+ // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+ /** CMAS certainty type: Determined to have occurred or to be ongoing. */
+ public static final int CMAS_CERTAINTY_OBSERVED = 0x0;
+
+ /** CMAS certainty type: Likely (probability > ~50%). */
+ public static final int CMAS_CERTAINTY_LIKELY = 0x1;
+
+ /**
+ * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts
+ * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+ * Presidential-level alert class (Korean Public Alert System Class 0).
+ */
+ public static final int CMAS_CERTAINTY_UNKNOWN = -1;
+
+ /** CMAS message class. */
+ private final int mMessageClass;
+
+ /** CMAS category. */
+ private final int mCategory;
+
+ /** CMAS response type. */
+ private final int mResponseType;
+
+ /** CMAS severity. */
+ private final int mSeverity;
+
+ /** CMAS urgency. */
+ private final int mUrgency;
+
+ /** CMAS certainty. */
+ private final int mCertainty;
+
+ /** Create a new SmsCbCmasInfo object with the specified values. */
+ public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity,
+ int urgency, int certainty) {
+ mMessageClass = messageClass;
+ mCategory = category;
+ mResponseType = responseType;
+ mSeverity = severity;
+ mUrgency = urgency;
+ mCertainty = certainty;
+ }
+
+ /** Create a new SmsCbCmasInfo object from a Parcel. */
+ SmsCbCmasInfo(Parcel in) {
+ mMessageClass = in.readInt();
+ mCategory = in.readInt();
+ mResponseType = in.readInt();
+ mSeverity = in.readInt();
+ mUrgency = in.readInt();
+ mCertainty = in.readInt();
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMessageClass);
+ dest.writeInt(mCategory);
+ dest.writeInt(mResponseType);
+ dest.writeInt(mSeverity);
+ dest.writeInt(mUrgency);
+ dest.writeInt(mCertainty);
+ }
+
+ /**
+ * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}.
+ * @return one of the {@code CMAS_CLASS} values
+ */
+ public int getMessageClass() {
+ return mMessageClass;
+ }
+
+ /**
+ * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}.
+ * @return one of the {@code CMAS_CATEGORY} values
+ */
+ public int getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}.
+ * @return one of the {@code CMAS_RESPONSE_TYPE} values
+ */
+ public int getResponseType() {
+ return mResponseType;
+ }
+
+ /**
+ * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}.
+ * @return one of the {@code CMAS_SEVERITY} values
+ */
+ public int getSeverity() {
+ return mSeverity;
+ }
+
+ /**
+ * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}.
+ * @return one of the {@code CMAS_URGENCY} values
+ */
+ public int getUrgency() {
+ return mUrgency;
+ }
+
+ /**
+ * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}.
+ * @return one of the {@code CMAS_CERTAINTY} values
+ */
+ public int getCertainty() {
+ return mCertainty;
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory
+ + ", responseType=" + mResponseType + ", severity=" + mSeverity
+ + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}';
+ }
+
+ /**
+ * Describe the kinds of special objects contained in the marshalled representation.
+ * @return a bitmask indicating this Parcelable contains no special objects
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Creator for unparcelling objects. */
+ public static final Parcelable.Creator<SmsCbCmasInfo>
+ CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() {
+ public SmsCbCmasInfo createFromParcel(Parcel in) {
+ return new SmsCbCmasInfo(in);
+ }
+
+ public SmsCbCmasInfo[] newArray(int size) {
+ return new SmsCbCmasInfo[size];
+ }
+ };
+}
diff --git a/src/java/android/telephony/SmsCbEtwsInfo.java b/src/java/android/telephony/SmsCbEtwsInfo.java
new file mode 100644
index 0000000..0890d52
--- /dev/null
+++ b/src/java/android/telephony/SmsCbEtwsInfo.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2012 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.Parcel;
+import android.os.Parcelable;
+import android.text.format.Time;
+
+import com.android.internal.telephony.IccUtils;
+
+import java.util.Arrays;
+
+/**
+ * Contains information elements for a GSM or UMTS ETWS warning notification.
+ * Supported values for each element are defined in 3GPP TS 23.041.
+ *
+ * {@hide}
+ */
+public class SmsCbEtwsInfo implements Parcelable {
+
+ /** ETWS warning type for earthquake. */
+ public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
+
+ /** ETWS warning type for tsunami. */
+ public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01;
+
+ /** ETWS warning type for earthquake and tsunami. */
+ public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02;
+
+ /** ETWS warning type for test messages. */
+ public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 0x03;
+
+ /** ETWS warning type for other emergency types. */
+ public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 0x04;
+
+ /** Unknown ETWS warning type. */
+ public static final int ETWS_WARNING_TYPE_UNKNOWN = -1;
+
+ /** One of the ETWS warning type constants defined in this class. */
+ private final int mWarningType;
+
+ /** Whether or not to activate the emergency user alert tone and vibration. */
+ private final boolean mEmergencyUserAlert;
+
+ /** Whether or not to activate a popup alert. */
+ private final boolean mActivatePopup;
+
+ /**
+ * 50-byte security information (ETWS primary notification for GSM only). As of Release 10,
+ * 3GPP TS 23.041 states that the UE shall ignore the ETWS primary notification timestamp
+ * and digital signature if received. Therefore it is treated as a raw byte array and
+ * parceled with the broadcast intent if present, but the timestamp is only computed if an
+ * application asks for the individual components.
+ */
+ private final byte[] mWarningSecurityInformation;
+
+ /** Create a new SmsCbEtwsInfo object with the specified values. */
+ public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup,
+ byte[] warningSecurityInformation) {
+ mWarningType = warningType;
+ mEmergencyUserAlert = emergencyUserAlert;
+ mActivatePopup = activatePopup;
+ mWarningSecurityInformation = warningSecurityInformation;
+ }
+
+ /** Create a new SmsCbEtwsInfo object from a Parcel. */
+ SmsCbEtwsInfo(Parcel in) {
+ mWarningType = in.readInt();
+ mEmergencyUserAlert = (in.readInt() != 0);
+ mActivatePopup = (in.readInt() != 0);
+ mWarningSecurityInformation = in.createByteArray();
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mWarningType);
+ dest.writeInt(mEmergencyUserAlert ? 1 : 0);
+ dest.writeInt(mActivatePopup ? 1 : 0);
+ dest.writeByteArray(mWarningSecurityInformation);
+ }
+
+ /**
+ * Returns the ETWS warning type.
+ * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE}
+ */
+ public int getWarningType() {
+ return mWarningType;
+ }
+
+ /**
+ * Returns the ETWS emergency user alert flag.
+ * @return true to notify terminal to activate emergency user alert; false otherwise
+ */
+ public boolean isEmergencyUserAlert() {
+ return mEmergencyUserAlert;
+ }
+
+ /**
+ * Returns the ETWS activate popup flag.
+ * @return true to notify terminal to activate display popup; false otherwise
+ */
+ public boolean isPopupAlert() {
+ return mActivatePopup;
+ }
+
+ /**
+ * Returns the Warning-Security-Information timestamp (GSM primary notifications only).
+ * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received.
+ * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present
+ */
+ public long getPrimaryNotificationTimestamp() {
+ if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) {
+ return 0;
+ }
+
+ int year = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[0]);
+ int month = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[1]);
+ int day = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[2]);
+ int hour = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[3]);
+ int minute = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[4]);
+ int second = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[5]);
+
+ // For the timezone, the most significant bit of the
+ // least significant nibble is the sign byte
+ // (meaning the max range of this field is 79 quarter-hours,
+ // which is more than enough)
+
+ byte tzByte = mWarningSecurityInformation[6];
+
+ // Mask out sign bit.
+ int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
+
+ timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+
+ Time time = new Time(Time.TIMEZONE_UTC);
+
+ // We only need to support years above 2000.
+ time.year = 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) - (long) (timezoneOffset * 15 * 60 * 1000);
+ }
+
+ /**
+ * Returns the digital signature (GSM primary notifications only). As of Release 10,
+ * 3GPP TS 23.041 states that the UE shall ignore this value if received.
+ * @return a byte array containing a copy of the primary notification digital signature
+ */
+ public byte[] getPrimaryNotificationSignature() {
+ if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) {
+ return null;
+ }
+ return Arrays.copyOfRange(mWarningSecurityInformation, 7, 50);
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert="
+ + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}';
+ }
+
+ /**
+ * Describe the kinds of special objects contained in the marshalled representation.
+ * @return a bitmask indicating this Parcelable contains no special objects
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Creator for unparcelling objects. */
+ public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() {
+ public SmsCbEtwsInfo createFromParcel(Parcel in) {
+ return new SmsCbEtwsInfo(in);
+ }
+
+ public SmsCbEtwsInfo[] newArray(int size) {
+ return new SmsCbEtwsInfo[size];
+ }
+ };
+}
diff --git a/src/java/android/telephony/SmsCbLocation.java b/src/java/android/telephony/SmsCbLocation.java
new file mode 100644
index 0000000..7b5bd0d
--- /dev/null
+++ b/src/java/android/telephony/SmsCbLocation.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2012 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 android.telephony.gsm.GsmCellLocation;
+
+/**
+ * Represents the location and geographical scope of a cell broadcast message.
+ * For GSM/UMTS, the Location Area and Cell ID are set when the broadcast
+ * geographical scope is cell wide or Location Area wide. For CDMA, the
+ * broadcast geographical scope is always PLMN wide.
+ *
+ * @hide
+ */
+public class SmsCbLocation implements Parcelable {
+
+ /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */
+ private final String mPlmn;
+
+ private final int mLac;
+ private final int mCid;
+
+ /**
+ * Construct an empty location object. This is used for some test cases, and for
+ * cell broadcasts saved in older versions of the database without location info.
+ */
+ public SmsCbLocation() {
+ mPlmn = "";
+ mLac = -1;
+ mCid = -1;
+ }
+
+ /**
+ * Construct a location object for the PLMN. This class is immutable, so
+ * the same object can be reused for multiple broadcasts.
+ */
+ public SmsCbLocation(String plmn) {
+ mPlmn = plmn;
+ mLac = -1;
+ mCid = -1;
+ }
+
+ /**
+ * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so
+ * the same object can be reused for multiple broadcasts.
+ */
+ public SmsCbLocation(String plmn, int lac, int cid) {
+ mPlmn = plmn;
+ mLac = lac;
+ mCid = cid;
+ }
+
+ /**
+ * Initialize the object from a Parcel.
+ */
+ public SmsCbLocation(Parcel in) {
+ mPlmn = in.readString();
+ mLac = in.readInt();
+ mCid = in.readInt();
+ }
+
+ /**
+ * Returns the MCC/MNC of the network as a String.
+ * @return the PLMN identifier (MCC+MNC) as a String
+ */
+ public String getPlmn() {
+ return mPlmn;
+ }
+
+ /**
+ * Returns the GSM location area code, or UMTS service area code.
+ * @return location area code, -1 if unknown, 0xffff max legal value
+ */
+ public int getLac() {
+ return mLac;
+ }
+
+ /**
+ * Returns the GSM or UMTS cell ID.
+ * @return gsm cell id, -1 if unknown, 0xffff max legal value
+ */
+ public int getCid() {
+ return mCid;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = mPlmn.hashCode();
+ hash = hash * 31 + mLac;
+ hash = hash * 31 + mCid;
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || !(o instanceof SmsCbLocation)) {
+ return false;
+ }
+ SmsCbLocation other = (SmsCbLocation) o;
+ return mPlmn.equals(other.mPlmn) && mLac == other.mLac && mCid == other.mCid;
+ }
+
+ @Override
+ public String toString() {
+ return '[' + mPlmn + ',' + mLac + ',' + mCid + ']';
+ }
+
+ /**
+ * Test whether this location is within the location area of the specified object.
+ *
+ * @param area the location area to compare with this location
+ * @return true if this location is contained within the specified location area
+ */
+ public boolean isInLocationArea(SmsCbLocation area) {
+ if (mCid != -1 && mCid != area.mCid) {
+ return false;
+ }
+ if (mLac != -1 && mLac != area.mLac) {
+ return false;
+ }
+ return mPlmn.equals(area.mPlmn);
+ }
+
+ /**
+ * Test whether this location is within the location area of the CellLocation.
+ *
+ * @param plmn the PLMN to use for comparison
+ * @param lac the Location Area (GSM) or Service Area (UMTS) to compare with
+ * @param cid the Cell ID to compare with
+ * @return true if this location is contained within the specified PLMN, LAC, and Cell ID
+ */
+ public boolean isInLocationArea(String plmn, int lac, int cid) {
+ if (!mPlmn.equals(plmn)) {
+ return false;
+ }
+
+ if (mLac != -1 && mLac != lac) {
+ return false;
+ }
+
+ if (mCid != -1 && mCid != cid) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPlmn);
+ dest.writeInt(mLac);
+ dest.writeInt(mCid);
+ }
+
+ public static final Parcelable.Creator<SmsCbLocation> CREATOR
+ = new Parcelable.Creator<SmsCbLocation>() {
+ @Override
+ public SmsCbLocation createFromParcel(Parcel in) {
+ return new SmsCbLocation(in);
+ }
+
+ @Override
+ public SmsCbLocation[] newArray(int size) {
+ return new SmsCbLocation[size];
+ }
+ };
+
+ /**
+ * Describe the kinds of special objects contained in the marshalled representation.
+ * @return a bitmask indicating this Parcelable contains no special objects
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/src/java/android/telephony/SmsCbMessage.java b/src/java/android/telephony/SmsCbMessage.java
new file mode 100644
index 0000000..046bf8c
--- /dev/null
+++ b/src/java/android/telephony/SmsCbMessage.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2010 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.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable object containing a received cell broadcast message. There are four different types
+ * of Cell Broadcast messages:
+ *
+ * <ul>
+ * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
+ * <li>cell information messages, broadcast on channel 50, indicating the current cell name for
+ * roaming purposes (required to display on the idle screen in Brazil)</li>
+ * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
+ * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
+ * </ul>
+ *
+ * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
+ * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
+ * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
+ * two completely different concepts in 3GPP and CDMA.
+ *
+ * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
+ * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
+ * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
+ * application should
+ *
+ * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
+ * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
+ * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
+ * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
+ * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
+ * Service Area in UMTS). The relevant values are concatenated into a single String which will be
+ * unique if the messages are not duplicates.
+ *
+ * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
+ * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
+ *
+ * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
+ * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
+ * Only system applications such as the CellBroadcastReceiver may receive notifications for
+ * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
+ * interference with the immediate display of the alert message and playing of the alert sound and
+ * vibration pattern, which could be caused by poorly written or malicious non-system code.
+ *
+ * @hide
+ */
+public class SmsCbMessage implements Parcelable {
+
+ protected static final String LOG_TAG = "SMSCB";
+
+ /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
+
+ /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
+ public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
+
+ /** Location / service area wide geographical scope (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
+
+ /** Cell wide geographical scope (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
+
+ /** GSM or UMTS format cell broadcast. */
+ public static final int MESSAGE_FORMAT_3GPP = 1;
+
+ /** CDMA format cell broadcast. */
+ public static final int MESSAGE_FORMAT_3GPP2 = 2;
+
+ /** Normal message priority. */
+ public static final int MESSAGE_PRIORITY_NORMAL = 0;
+
+ /** Interactive message priority. */
+ public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
+
+ /** Urgent message priority. */
+ public static final int MESSAGE_PRIORITY_URGENT = 2;
+
+ /** Emergency message priority. */
+ public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
+
+ /** Format of this message (for interpretation of service category values). */
+ private final int mMessageFormat;
+
+ /** Geographical scope of broadcast. */
+ private final int mGeographicalScope;
+
+ /**
+ * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
+ * update number for GSM/UMTS). The serial number plus the location code uniquely identify
+ * a cell broadcast for duplicate detection.
+ */
+ private final int mSerialNumber;
+
+ /**
+ * Location identifier for this message. It consists of the current operator MCC/MNC as a
+ * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+ * message is not binary 01, the Location Area is included for comparison. If the GS is
+ * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
+ */
+ private final SmsCbLocation mLocation;
+
+ /**
+ * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
+ * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
+ * or {@link #getCmasWarningInfo()}.
+ */
+ private final int mServiceCategory;
+
+ /** Message language, as a two-character string, e.g. "en". */
+ private final String mLanguage;
+
+ /** Message body, as a String. */
+ private final String mBody;
+
+ /** Message priority (including emergency priority). */
+ private final int mPriority;
+
+ /** ETWS warning notification information (ETWS warnings only). */
+ private final SmsCbEtwsInfo mEtwsWarningInfo;
+
+ /** CMAS warning notification information (CMAS warnings only). */
+ private final SmsCbCmasInfo mCmasWarningInfo;
+
+ /**
+ * Create a new SmsCbMessage with the specified data.
+ */
+ public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
+ SmsCbLocation location, int serviceCategory, String language, String body,
+ int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) {
+ mMessageFormat = messageFormat;
+ mGeographicalScope = geographicalScope;
+ mSerialNumber = serialNumber;
+ mLocation = location;
+ mServiceCategory = serviceCategory;
+ mLanguage = language;
+ mBody = body;
+ mPriority = priority;
+ mEtwsWarningInfo = etwsWarningInfo;
+ mCmasWarningInfo = cmasWarningInfo;
+ }
+
+ /** Create a new SmsCbMessage object from a Parcel. */
+ public SmsCbMessage(Parcel in) {
+ mMessageFormat = in.readInt();
+ mGeographicalScope = in.readInt();
+ mSerialNumber = in.readInt();
+ mLocation = new SmsCbLocation(in);
+ mServiceCategory = in.readInt();
+ mLanguage = in.readString();
+ mBody = in.readString();
+ mPriority = in.readInt();
+ int type = in.readInt();
+ switch (type) {
+ case 'E':
+ // unparcel ETWS warning information
+ mEtwsWarningInfo = new SmsCbEtwsInfo(in);
+ mCmasWarningInfo = null;
+ break;
+
+ case 'C':
+ // unparcel CMAS warning information
+ mEtwsWarningInfo = null;
+ mCmasWarningInfo = new SmsCbCmasInfo(in);
+ break;
+
+ default:
+ mEtwsWarningInfo = null;
+ mCmasWarningInfo = null;
+ }
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMessageFormat);
+ dest.writeInt(mGeographicalScope);
+ dest.writeInt(mSerialNumber);
+ mLocation.writeToParcel(dest, flags);
+ dest.writeInt(mServiceCategory);
+ dest.writeString(mLanguage);
+ dest.writeString(mBody);
+ dest.writeInt(mPriority);
+ if (mEtwsWarningInfo != null) {
+ // parcel ETWS warning information
+ dest.writeInt('E');
+ mEtwsWarningInfo.writeToParcel(dest, flags);
+ } else if (mCmasWarningInfo != null) {
+ // parcel CMAS warning information
+ dest.writeInt('C');
+ mCmasWarningInfo.writeToParcel(dest, flags);
+ } else {
+ // no ETWS or CMAS warning information
+ dest.writeInt('0');
+ }
+ }
+
+ public static final Parcelable.Creator<SmsCbMessage> CREATOR
+ = new Parcelable.Creator<SmsCbMessage>() {
+ @Override
+ public SmsCbMessage createFromParcel(Parcel in) {
+ return new SmsCbMessage(in);
+ }
+
+ @Override
+ public SmsCbMessage[] newArray(int size) {
+ return new SmsCbMessage[size];
+ }
+ };
+
+ /**
+ * Return the geographical scope of this message (GSM/UMTS only).
+ *
+ * @return Geographical scope
+ */
+ public int getGeographicalScope() {
+ return mGeographicalScope;
+ }
+
+ /**
+ * Return the broadcast serial number of broadcast (message identifier for CDMA, or
+ * geographical scope + message code + update number for GSM/UMTS). The serial number plus
+ * the location code uniquely identify a cell broadcast for duplicate detection.
+ *
+ * @return the 16-bit CDMA message identifier or GSM/UMTS serial number
+ */
+ public int getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ /**
+ * Return the location identifier for this message, consisting of the MCC/MNC as a
+ * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+ * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the
+ * cell ID is also included. The {@link SmsCbLocation} object includes a method to test
+ * if the location is included within another location area or within a PLMN and CellLocation.
+ *
+ * @return the geographical location code for duplicate message detection
+ */
+ public SmsCbLocation getLocation() {
+ return mLocation;
+ }
+
+ /**
+ * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation
+ * of the category is radio technology specific. For ETWS and CMAS warnings, the information
+ * provided by the category is available via {@link #getEtwsWarningInfo()} or
+ * {@link #getCmasWarningInfo()} in a radio technology independent format.
+ *
+ * @return the radio technology specific service category
+ */
+ public int getServiceCategory() {
+ return mServiceCategory;
+ }
+
+ /**
+ * Get the ISO-639-1 language code for this message, or null if unspecified
+ *
+ * @return Language code
+ */
+ public String getLanguageCode() {
+ return mLanguage;
+ }
+
+ /**
+ * Get the body of this message, or null if no body available
+ *
+ * @return Body, or null
+ */
+ public String getMessageBody() {
+ return mBody;
+ }
+
+ /**
+ * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
+ * @return an integer representing 3GPP or 3GPP2 message format
+ */
+ public int getMessageFormat() {
+ return mMessageFormat;
+ }
+
+ /**
+ * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL}
+ * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return
+ * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
+ * @return an integer representing the message priority
+ */
+ public int getMessagePriority() {
+ return mPriority;
+ }
+
+ /**
+ * If this is an ETWS warning notification then this method will return an object containing
+ * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
+ * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
+ * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
+ * ETWS primary notification timestamp and digital signature if received.
+ *
+ * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
+ */
+ public SmsCbEtwsInfo getEtwsWarningInfo() {
+ return mEtwsWarningInfo;
+ }
+
+ /**
+ * If this is a CMAS warning notification then this method will return an object containing
+ * the CMAS message class, category, response type, severity, urgency and certainty.
+ * The message class is always present. Severity, urgency and certainty are present for CDMA
+ * warning notifications containing a type 1 elements record and for GSM and UMTS warnings
+ * except for the Presidential-level alert category. Category and response type are only
+ * available for CDMA notifications containing a type 1 elements record.
+ *
+ * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
+ */
+ public SmsCbCmasInfo getCmasWarningInfo() {
+ return mCmasWarningInfo;
+ }
+
+ /**
+ * Return whether this message is an emergency (PWS) message type.
+ * @return true if the message is a public warning notification; false otherwise
+ */
+ public boolean isEmergencyMessage() {
+ return mPriority == MESSAGE_PRIORITY_EMERGENCY;
+ }
+
+ /**
+ * Return whether this message is an ETWS warning alert.
+ * @return true if the message is an ETWS warning notification; false otherwise
+ */
+ public boolean isEtwsMessage() {
+ return mEtwsWarningInfo != null;
+ }
+
+ /**
+ * Return whether this message is a CMAS warning alert.
+ * @return true if the message is a CMAS warning notification; false otherwise
+ */
+ public boolean isCmasMessage() {
+ return mCmasWarningInfo != null;
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
+ + mSerialNumber + ", location=" + mLocation + ", serviceCategory="
+ + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
+ + ", priority=" + mPriority
+ + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
+ + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}';
+ }
+
+ /**
+ * Describe the kinds of special objects contained in the marshalled representation.
+ * @return a bitmask indicating this Parcelable contains no special objects
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/src/java/android/telephony/SmsManager.java b/src/java/android/telephony/SmsManager.java
new file mode 100644
index 0000000..44bdaeb
--- /dev/null
+++ b/src/java/android/telephony/SmsManager.java
@@ -0,0 +1,522 @@
+/*
+ * 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.app.PendingIntent;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.IccConstants;
+import com.android.internal.telephony.SmsRawData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/*
+ * TODO(code review): Curious question... Why are a lot of these
+ * methods not declared as static, since they do not seem to require
+ * any local object state? Presumably this cannot be changed without
+ * interfering with the API...
+ */
+
+/**
+ * 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 {
+ /** Singleton object constructed during class initialization. */
+ private static final SmsManager sInstance = new SmsManager();
+
+ /**
+ * 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 successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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");
+ }
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ iccISms.sendText(destinationAddress, scAddress, text, sentIntent, deliveryIntent);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Divide a message text into several fragments, 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) {
+ return SmsMessage.fragmentText(text);
+ }
+
+ /**
+ * 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:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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").
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ */
+ 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 iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ iccISms.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 successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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");
+ }
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ iccISms.sendData(destinationAddress, scAddress, destinationPort & 0xFFFF,
+ data, 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() {
+ return sInstance;
+ }
+
+ private SmsManager() {
+ //nothing
+ }
+
+ /**
+ * Copy a raw SMS PDU to the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @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_ICC_READ, STATUS_ON_ICC_UNREAD,
+ * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean copyMessageToIcc(byte[] smsc, byte[] pdu, int status) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ success = iccISms.copyMessageToIccEf(status, pdu, smsc);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Delete the specified message from the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param messageIndex is the record index of the message on ICC
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean
+ deleteMessageFromIcc(int messageIndex) {
+ boolean success = false;
+ byte[] pdu = new byte[IccConstants.SMS_RECORD_LENGTH-1];
+ Arrays.fill(pdu, (byte)0xff);
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ success = iccISms.updateMessageOnIccEf(messageIndex, STATUS_ON_ICC_FREE, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Update the specified message on the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param messageIndex record index of message to update
+ * @param newStatus new message status (STATUS_ON_ICC_READ,
+ * STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
+ * STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
+ * @param pdu the raw PDU to store
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ success = iccISms.updateMessageOnIccEf(messageIndex, newStatus, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Retrieves all messages currently stored on ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
+ *
+ * {@hide}
+ */
+ public static ArrayList<SmsMessage> getAllMessagesFromIcc() {
+ List<SmsRawData> records = null;
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ records = iccISms.getAllMessagesFromIccEf();
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return createMessageListFromRawRecords(records);
+ }
+
+ /**
+ * Enable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier. Note that if two different clients enable the same
+ * message identifier, they must both disable it for the device to stop
+ * receiving those messages. All received messages will be broadcast in an
+ * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param messageIdentifier Message identifier as specified in TS 23.041
+ * @return true if successful, false otherwise
+ * @see #disableCellBroadcast(int)
+ *
+ * {@hide}
+ */
+ public boolean enableCellBroadcast(int messageIdentifier) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ success = iccISms.enableCellBroadcast(messageIdentifier);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Disable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier. Note that if two different clients enable the same
+ * message identifier, they must both disable it for the device to stop
+ * receiving those messages.
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param messageIdentifier Message identifier as specified in TS 23.041
+ * @return true if successful, false otherwise
+ *
+ * @see #enableCellBroadcast(int)
+ *
+ * {@hide}
+ */
+ public boolean disableCellBroadcast(int messageIdentifier) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ success = iccISms.disableCellBroadcast(messageIdentifier);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Enable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier range. Note that if two different clients enable the same
+ * message identifier, they must both disable it for the device to stop
+ * receiving those messages. All received messages will be broadcast in an
+ * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param startMessageId first message identifier as specified in TS 23.041
+ * @param endMessageId last message identifier as specified in TS 23.041
+ * @return true if successful, false otherwise
+ * @see #disableCellBroadcastRange(int, int)
+ *
+ * {@hide}
+ */
+ public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ success = iccISms.enableCellBroadcastRange(startMessageId, endMessageId);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Disable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier range. Note that if two different clients enable the same
+ * message identifier, they must both disable it for the device to stop
+ * receiving those messages.
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param startMessageId first message identifier as specified in TS 23.041
+ * @param endMessageId last message identifier as specified in TS 23.041
+ * @return true if successful, false otherwise
+ *
+ * @see #enableCellBroadcastRange(int, int)
+ *
+ * {@hide}
+ */
+ public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ success = iccISms.disableCellBroadcastRange(startMessageId, endMessageId);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
+ * records returned by <code>getAllMessagesFromIcc()</code>
+ *
+ * @param records SMS EF records, returned by
+ * <code>getAllMessagesFromIcc</code>
+ * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
+ */
+ private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+ ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
+ if (records != null) {
+ int count = records.size();
+ for (int i = 0; i < count; i++) {
+ SmsRawData data = records.get(i);
+ // List contains all records, including "free" records (null)
+ if (data != null) {
+ SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+ if (sms != null) {
+ messages.add(sms);
+ }
+ }
+ }
+ }
+ return messages;
+ }
+
+ // see SmsMessage.getStatusOnIcc
+
+ /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_FREE = 0;
+
+ /** Received and read (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_READ = 1;
+
+ /** Received and unread (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_UNREAD = 3;
+
+ /** Stored and sent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_SENT = 5;
+
+ /** Stored and unsent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_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;
+ /** Failed because we reached the sending queue limit. {@hide} */
+ static public final int RESULT_ERROR_LIMIT_EXCEEDED = 5;
+ /** Failed because FDN is enabled. {@hide} */
+ static public final int RESULT_ERROR_FDN_CHECK_FAILURE = 6;
+}
diff --git a/src/java/android/telephony/SmsMessage.java b/src/java/android/telephony/SmsMessage.java
new file mode 100644
index 0000000..b94609e
--- /dev/null
+++ b/src/java/android/telephony/SmsMessage.java
@@ -0,0 +1,688 @@
+/*
+ * 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.os.Parcel;
+import android.util.Log;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+
+import java.lang.Math;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+
+/**
+ * A Short Message Service message.
+ */
+public class SmsMessage {
+ private static final String LOG_TAG = "SMS";
+
+ /**
+ * SMS Class enumeration.
+ * See TS 23.038.
+ *
+ */
+ public enum MessageClass{
+ UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
+ }
+
+ /** User data text encoding code unit size */
+ public static final int ENCODING_UNKNOWN = 0;
+ public static final int ENCODING_7BIT = 1;
+ public static final int ENCODING_8BIT = 2;
+ public static final int ENCODING_16BIT = 3;
+ /**
+ * @hide This value is not defined in global standard. Only in Korea, this is used.
+ */
+ public static final int ENCODING_KSC5601 = 4;
+
+ /** 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.
+ */
+ 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;
+
+ /**
+ * Indicates a 3GPP format SMS message.
+ * @hide pending API council approval
+ */
+ public static final String FORMAT_3GPP = "3gpp";
+
+ /**
+ * Indicates a 3GPP2 format SMS message.
+ * @hide pending API council approval
+ */
+ public static final String FORMAT_3GPP2 = "3gpp2";
+
+ /** Contains actual SmsMessage. Only public for debugging and for framework layer.
+ *
+ * @hide
+ */
+ public SmsMessageBase mWrappedSmsMessage;
+
+ 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);
+ }
+
+ /**
+ * @hide
+ */
+ protected SubmitPdu(SubmitPduBase spb) {
+ this.encodedMessage = spb.encodedMessage;
+ this.encodedScAddress = spb.encodedScAddress;
+ }
+
+ }
+
+ private SmsMessage(SmsMessageBase smb) {
+ mWrappedSmsMessage = smb;
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU.
+ *
+ * <p><b>This method will soon be deprecated</b> and all applications which handle
+ * incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast
+ * intent <b>must</b> now pass the new {@code format} String extra from the intent
+ * into the new method {@code createFromPdu(byte[], String)} which takes an
+ * extra format parameter. This is required in order to correctly decode the PDU on
+ * devices that require support for both 3GPP and 3GPP2 formats at the same time,
+ * such as dual-mode GSM/CDMA and CDMA/LTE phones.
+ */
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ String format = (PHONE_TYPE_CDMA == activePhone) ?
+ SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
+ return createFromPdu(pdu, format);
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU with the specified message format. The
+ * message format is passed in the {@code SMS_RECEIVED_ACTION} as the {@code format}
+ * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
+ * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
+ *
+ * @param pdu the message PDU from the SMS_RECEIVED_ACTION intent
+ * @param format the format extra from the SMS_RECEIVED_ACTION intent
+ * @hide pending API council approval
+ */
+ public static SmsMessage createFromPdu(byte[] pdu, String format) {
+ SmsMessageBase wrappedMessage;
+
+ if (SmsConstants.FORMAT_3GPP2.equals(format)) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
+ } else if (SmsConstants.FORMAT_3GPP.equals(format)) {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
+ } else {
+ Log.e(LOG_TAG, "createFromPdu(): unsupported message format " + format);
+ return null;
+ }
+
+ return new SmsMessage(wrappedMessage);
+ }
+
+ /**
+ * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+ * +CMT unsolicited response (PDU mode, of course)
+ * +CMT: [&lt;alpha>],<length><CR><LF><pdu>
+ *
+ * Only public for debugging and for RIL
+ *
+ * {@hide}
+ */
+ public static SmsMessage newFromCMT(String[] lines) {
+ // received SMS in 3GPP format
+ SmsMessageBase wrappedMessage =
+ com.android.internal.telephony.gsm.SmsMessage.newFromCMT(lines);
+
+ return new SmsMessage(wrappedMessage);
+ }
+
+ /** @hide */
+ public static SmsMessage newFromParcel(Parcel p) {
+ // received SMS in 3GPP2 format
+ SmsMessageBase wrappedMessage =
+ com.android.internal.telephony.cdma.SmsMessage.newFromParcel(p);
+
+ return new SmsMessage(wrappedMessage);
+ }
+
+ /**
+ * 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) {
+ SmsMessageBase wrappedMessage;
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+
+ if (PHONE_TYPE_CDMA == activePhone) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+ index, data);
+ } else {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+ index, data);
+ }
+
+ return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+ }
+
+ /**
+ * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
+ * length in bytes (not hex chars) less the SMSC header
+ *
+ * FIXME: This method is only used by a CTS test case that isn't run on CDMA devices.
+ * We should probably deprecate it and remove the obsolete test case.
+ */
+ public static int getTPLayerLengthForPDU(String pdu) {
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+
+ if (PHONE_TYPE_CDMA == activePhone) {
+ return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu);
+ } else {
+ return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu);
+ }
+ }
+
+ /*
+ * TODO(cleanup): It would make some sense if the result of
+ * preprocessing a message to determine the proper encoding (i.e.
+ * the resulting data structure from calculateLength) could be
+ * passed as an argument to the actual final encoding function.
+ * This would better ensure that the logic behind size calculation
+ * actually matched the encoding.
+ */
+
+ /**
+ * Calculates the number of SMS's required to encode the message body and
+ * the number of characters remaining until the next message.
+ *
+ * @param msgBody the message to encode
+ * @param use7bitOnly if true, characters that are not part of the
+ * radio-specific 7-bit encoding are counted as single
+ * space chars. If false, and if the messageBody contains
+ * non-7-bit encodable characters, length is calculated
+ * using a 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 an indicator of the encoding
+ * code unit size (see the ENCODING_* definitions in SmsConstants)
+ */
+ public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) {
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ?
+ com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly) :
+ com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly);
+ int ret[] = new int[4];
+ ret[0] = ted.msgCount;
+ ret[1] = ted.codeUnitCount;
+ ret[2] = ted.codeUnitsRemaining;
+ ret[3] = ted.codeUnitSize;
+ return ret;
+ }
+
+ /**
+ * Divide a message text into several fragments, none bigger than
+ * the maximum SMS message text size.
+ *
+ * @param text text, must not be null.
+ * @return an <code>ArrayList</code> of strings that, in order,
+ * comprise the original msg text
+ *
+ * @hide
+ */
+ public static ArrayList<String> fragmentText(String text) {
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ?
+ com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false) :
+ com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false);
+
+ // TODO(cleanup): The code here could be rolled into the logic
+ // below cleanly if these MAX_* constants were defined more
+ // flexibly...
+
+ int limit;
+ if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
+ int udhLength;
+ if (ted.languageTable != 0 && ted.languageShiftTable != 0) {
+ udhLength = GsmAlphabet.UDH_SEPTET_COST_TWO_SHIFT_TABLES;
+ } else if (ted.languageTable != 0 || ted.languageShiftTable != 0) {
+ udhLength = GsmAlphabet.UDH_SEPTET_COST_ONE_SHIFT_TABLE;
+ } else {
+ udhLength = 0;
+ }
+
+ if (ted.msgCount > 1) {
+ udhLength += GsmAlphabet.UDH_SEPTET_COST_CONCATENATED_MESSAGE;
+ }
+
+ if (udhLength != 0) {
+ udhLength += GsmAlphabet.UDH_SEPTET_COST_LENGTH;
+ }
+
+ limit = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength;
+ } else {
+ if (ted.msgCount > 1) {
+ limit = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+ } else {
+ limit = SmsConstants.MAX_USER_DATA_BYTES;
+ }
+ }
+
+ int pos = 0; // Index in code units.
+ int textLen = text.length();
+ ArrayList<String> result = new ArrayList<String>(ted.msgCount);
+ while (pos < textLen) {
+ int nextPos = 0; // Counts code units.
+ if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
+ if (activePhone == PHONE_TYPE_CDMA && ted.msgCount == 1) {
+ // For a singleton CDMA message, the encoding must be ASCII...
+ nextPos = pos + Math.min(limit, textLen - pos);
+ } else {
+ // For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode).
+ nextPos = GsmAlphabet.findGsmSeptetLimitIndex(text, pos, limit,
+ ted.languageTable, ted.languageShiftTable);
+ }
+ } else { // Assume unicode.
+ nextPos = pos + Math.min(limit / 2, textLen - pos);
+ }
+ if ((nextPos <= pos) || (nextPos > textLen)) {
+ Log.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " +
+ nextPos + " >= " + textLen + ")");
+ break;
+ }
+ result.add(text.substring(pos, nextPos));
+ pos = nextPos;
+ }
+ return result;
+ }
+
+ /**
+ * 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 radio
+ * specific (GSM / CDMA) alphabet encoding are converted to as a
+ * single space characters. If false, a messageBody containing
+ * non-GSM or non-CDMA alphabet characters are encoded using
+ * 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) {
+ return calculateLength((CharSequence)messageBody, use7bitOnly);
+ }
+
+ /*
+ * TODO(cleanup): It looks like there is now no useful reason why
+ * apps should generate pdus themselves using these routines,
+ * instead of handing the raw data to SMSDispatcher (and thereby
+ * have the phone process do the encoding). Moreover, CDMA now
+ * has shared state (in the form of the msgId system property)
+ * which can only be modified by the phone process, and hence
+ * makes the output of these routines incorrect. Since they now
+ * serve no purpose, they should probably just return null
+ * directly, and be deprecated. Going further in that direction,
+ * the above parsers of serialized pdu data should probably also
+ * be gotten rid of, hiding all but the necessarily visible
+ * structured data from client apps. A possible concern with
+ * doing this is that apps may be using these routines to generate
+ * pdus that are then sent elsewhere, some network server, for
+ * example, and that always returning null would thereby break
+ * otherwise useful apps.
+ */
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message.
+ * This method will not attempt to use any GSM national language 7 bit encodings.
+ *
+ * @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) {
+ SubmitPduBase spb;
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+
+ if (PHONE_TYPE_CDMA == activePhone) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested, null);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
+ * This method will not attempt to use any GSM national language 7 bit encodings.
+ *
+ * @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 data 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) {
+ SubmitPduBase spb;
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+
+ if (PHONE_TYPE_CDMA == activePhone) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Returns the address of the SMS service center that relayed this message
+ * or null if there is none.
+ */
+ public String getServiceCenterAddress() {
+ return mWrappedSmsMessage.getServiceCenterAddress();
+ }
+
+ /**
+ * Returns the originating address (sender) of this SMS message in String
+ * form or null if unavailable
+ */
+ public String getOriginatingAddress() {
+ return mWrappedSmsMessage.getOriginatingAddress();
+ }
+
+ /**
+ * 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() {
+ return mWrappedSmsMessage.getDisplayOriginatingAddress();
+ }
+
+ /**
+ * 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 mWrappedSmsMessage.getMessageBody();
+ }
+
+ /**
+ * Returns the class of this message.
+ */
+ public MessageClass getMessageClass() {
+ switch(mWrappedSmsMessage.getMessageClass()) {
+ case CLASS_0: return MessageClass.CLASS_0;
+ case CLASS_1: return MessageClass.CLASS_1;
+ case CLASS_2: return MessageClass.CLASS_2;
+ case CLASS_3: return MessageClass.CLASS_3;
+ default: return MessageClass.UNKNOWN;
+
+ }
+ }
+
+ /**
+ * 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() {
+ return mWrappedSmsMessage.getDisplayMessageBody();
+ }
+
+ /**
+ * Unofficial convention of a subject line enclosed in parens empty string
+ * if not present
+ */
+ public String getPseudoSubject() {
+ return mWrappedSmsMessage.getPseudoSubject();
+ }
+
+ /**
+ * Returns the service centre timestamp in currentTimeMillis() format
+ */
+ public long getTimestampMillis() {
+ return mWrappedSmsMessage.getTimestampMillis();
+ }
+
+ /**
+ * 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 mWrappedSmsMessage.isEmail();
+ }
+
+ /**
+ * @return if isEmail() is true, body of the email sent through the gateway.
+ * null otherwise
+ */
+ public String getEmailBody() {
+ return mWrappedSmsMessage.getEmailBody();
+ }
+
+ /**
+ * @return if isEmail() is true, email from address of email sent through
+ * the gateway. null otherwise
+ */
+ public String getEmailFrom() {
+ return mWrappedSmsMessage.getEmailFrom();
+ }
+
+ /**
+ * Get protocol identifier.
+ */
+ public int getProtocolIdentifier() {
+ return mWrappedSmsMessage.getProtocolIdentifier();
+ }
+
+ /**
+ * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+ * SMS
+ */
+ public boolean isReplace() {
+ return mWrappedSmsMessage.isReplace();
+ }
+
+ /**
+ * 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 mWrappedSmsMessage.isCphsMwiMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) clear message
+ */
+ public boolean isMWIClearMessage() {
+ return mWrappedSmsMessage.isMWIClearMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) set message
+ */
+ public boolean isMWISetMessage() {
+ return mWrappedSmsMessage.isMWISetMessage();
+ }
+
+ /**
+ * returns true if this message is a "Message Waiting Indication Group:
+ * Discard Message" notification and should not be stored.
+ */
+ public boolean isMwiDontStore() {
+ return mWrappedSmsMessage.isMwiDontStore();
+ }
+
+ /**
+ * returns the user data section minus the user data header if one was
+ * present.
+ */
+ public byte[] getUserData() {
+ return mWrappedSmsMessage.getUserData();
+ }
+
+ /**
+ * Returns the raw PDU for the message.
+ *
+ * @return the raw PDU for the message.
+ */
+ public byte[] getPdu() {
+ return mWrappedSmsMessage.getPdu();
+ }
+
+ /**
+ * 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
+ * @deprecated Use getStatusOnIcc instead.
+ */
+ @Deprecated public int getStatusOnSim() {
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * Returns the status of the message on the ICC (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the ICC. These are:
+ * SmsManager.STATUS_ON_ICC_FREE
+ * SmsManager.STATUS_ON_ICC_READ
+ * SmsManager.STATUS_ON_ICC_UNREAD
+ * SmsManager.STATUS_ON_ICC_SEND
+ * SmsManager.STATUS_ON_ICC_UNSENT
+ */
+ public int getStatusOnIcc() {
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * 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.
+ * @deprecated Use getIndexOnIcc instead.
+ */
+ @Deprecated public int getIndexOnSim() {
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * Returns the record index of the message on the ICC (1-based index).
+ * @return the record index of the message on the ICC, or -1 if this
+ * SmsMessage was not created from a ICC SMS EF record.
+ */
+ public int getIndexOnIcc() {
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * GSM:
+ * For an SMS-STATUS-REPORT message, this returns the status field from
+ * the status report. This field indicates the status of a previously
+ * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
+ * description of values.
+ * CDMA:
+ * For not interfering with status codes from GSM, the value is
+ * shifted to the bits 31-16.
+ * The value is composed of an error class (bits 25-24) and a status code (bits 23-16).
+ * Possible codes are described in C.S0015-B, v2.0, 4.5.21.
+ *
+ * @return 0 indicates the previously sent message was received.
+ * See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21
+ * for a description of other possible values.
+ */
+ public int getStatus() {
+ return mWrappedSmsMessage.getStatus();
+ }
+
+ /**
+ * Return true iff the message is a SMS-STATUS-REPORT message.
+ */
+ public boolean isStatusReportMessage() {
+ return mWrappedSmsMessage.isStatusReportMessage();
+ }
+
+ /**
+ * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+ * this message.
+ */
+ public boolean isReplyPathPresent() {
+ return mWrappedSmsMessage.isReplyPathPresent();
+ }
+}
diff --git a/src/java/android/telephony/gsm/SmsManager.java b/src/java/android/telephony/gsm/SmsManager.java
new file mode 100644
index 0000000..3b75298
--- /dev/null
+++ b/src/java/android/telephony/gsm/SmsManager.java
@@ -0,0 +1,261 @@
+/*
+ * 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 java.util.ArrayList;
+
+
+/**
+ * Manages SMS operations such as sending data, text, and pdu SMS messages.
+ * Get this object by calling the static method SmsManager.getDefault().
+ * @deprecated Replaced by android.telephony.SmsManager that supports both GSM and CDMA.
+ */
+@Deprecated public final class SmsManager {
+ private static SmsManager sInstance;
+ private android.telephony.SmsManager mSmsMgrProxy;
+
+ /** Get the default instance of the SmsManager
+ *
+ * @return the default instance of the SmsManager
+ * @deprecated Use android.telephony.SmsManager.
+ */
+ @Deprecated
+ public static final SmsManager getDefault() {
+ if (sInstance == null) {
+ sInstance = new SmsManager();
+ }
+ return sInstance;
+ }
+
+ @Deprecated
+ private SmsManager() {
+ mSmsMgrProxy = android.telephony.SmsManager.getDefault();
+ }
+
+ /**
+ * 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 successfully 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 applications,
+ * 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
+ * @deprecated Use android.telephony.SmsManager.
+ */
+ @Deprecated
+ public final void sendTextMessage(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ mSmsMgrProxy.sendTextMessage(destinationAddress, scAddress, text,
+ 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
+ * @deprecated Use android.telephony.SmsManager.
+ */
+ @Deprecated
+ public final ArrayList<String> divideMessage(String text) {
+ return mSmsMgrProxy.divideMessage(text);
+ }
+
+ /**
+ * 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").
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ * @deprecated Use android.telephony.SmsManager.
+ */
+ @Deprecated
+ public final void sendMultipartTextMessage(
+ String destinationAddress, String scAddress, ArrayList<String> parts,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+ mSmsMgrProxy.sendMultipartTextMessage(destinationAddress, scAddress, parts,
+ sentIntents, deliveryIntents);
+ }
+
+ /**
+ * 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
+ * @deprecated Use android.telephony.SmsManager.
+ */
+ @Deprecated
+ public final void sendDataMessage(
+ String destinationAddress, String scAddress, short destinationPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ mSmsMgrProxy.sendDataMessage(destinationAddress, scAddress, destinationPort,
+ data, sentIntent, deliveryIntent);
+ }
+
+ /**
+ * 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
+ * @deprecated Use android.telephony.SmsManager.
+ * {@hide}
+ */
+ @Deprecated
+ public final boolean copyMessageToSim(byte[] smsc, byte[] pdu, int status) {
+ return mSmsMgrProxy.copyMessageToIcc(smsc, pdu, status);
+ }
+
+ /**
+ * Delete the specified message from the SIM.
+ *
+ * @param messageIndex is the record index of the message on SIM
+ * @return true for success
+ * @deprecated Use android.telephony.SmsManager.
+ * {@hide}
+ */
+ @Deprecated
+ public final boolean deleteMessageFromSim(int messageIndex) {
+ return mSmsMgrProxy.deleteMessageFromIcc(messageIndex);
+ }
+
+ /**
+ * 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
+ * @deprecated Use android.telephony.SmsManager.
+ * {@hide}
+ */
+ @Deprecated
+ public final boolean updateMessageOnSim(int messageIndex, int newStatus, byte[] pdu) {
+ return mSmsMgrProxy.updateMessageOnIcc(messageIndex, newStatus, pdu);
+ }
+
+ /**
+ * Retrieves all messages currently stored on SIM.
+ * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
+ * @deprecated Use android.telephony.SmsManager.
+ * {@hide}
+ */
+ @Deprecated
+ public final ArrayList<android.telephony.SmsMessage> getAllMessagesFromSim() {
+ return mSmsMgrProxy.getAllMessagesFromIcc();
+ }
+
+ /** Free space (TS 51.011 10.5.3).
+ * @deprecated Use android.telephony.SmsManager. */
+ @Deprecated static public final int STATUS_ON_SIM_FREE = 0;
+
+ /** Received and read (TS 51.011 10.5.3).
+ * @deprecated Use android.telephony.SmsManager. */
+ @Deprecated static public final int STATUS_ON_SIM_READ = 1;
+
+ /** Received and unread (TS 51.011 10.5.3).
+ * @deprecated Use android.telephony.SmsManager. */
+ @Deprecated static public final int STATUS_ON_SIM_UNREAD = 3;
+
+ /** Stored and sent (TS 51.011 10.5.3).
+ * @deprecated Use android.telephony.SmsManager. */
+ @Deprecated static public final int STATUS_ON_SIM_SENT = 5;
+
+ /** Stored and unsent (TS 51.011 10.5.3).
+ * @deprecated Use android.telephony.SmsManager. */
+ @Deprecated static public final int STATUS_ON_SIM_UNSENT = 7;
+
+ /** Generic failure cause
+ * @deprecated Use android.telephony.SmsManager. */
+ @Deprecated static public final int RESULT_ERROR_GENERIC_FAILURE = 1;
+
+ /** Failed because radio was explicitly turned off
+ * @deprecated Use android.telephony.SmsManager. */
+ @Deprecated static public final int RESULT_ERROR_RADIO_OFF = 2;
+
+ /** Failed because no pdu provided
+ * @deprecated Use android.telephony.SmsManager. */
+ @Deprecated static public final int RESULT_ERROR_NULL_PDU = 3;
+
+ /** Failed because service is currently unavailable
+ * @deprecated Use android.telephony.SmsManager. */
+ @Deprecated static public final int RESULT_ERROR_NO_SERVICE = 4;
+
+}
diff --git a/src/java/android/telephony/gsm/SmsMessage.java b/src/java/android/telephony/gsm/SmsMessage.java
new file mode 100644
index 0000000..7a814c3
--- /dev/null
+++ b/src/java/android/telephony/gsm/SmsMessage.java
@@ -0,0 +1,628 @@
+/*
+ * 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.gsm;
+
+import android.os.Parcel;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+
+import java.util.Arrays;
+
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+
+/**
+ * A Short Message Service message.
+ * @deprecated Replaced by android.telephony.SmsMessage that supports both GSM and CDMA.
+ */
+@Deprecated
+public class SmsMessage {
+ private static final boolean LOCAL_DEBUG = true;
+ private static final String LOG_TAG = "SMS";
+
+ /**
+ * SMS Class enumeration.
+ * See TS 23.038.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public enum MessageClass{
+ UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
+ }
+
+ /** Unknown encoding scheme (see TS 23.038)
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated public static final int ENCODING_UNKNOWN = 0;
+
+ /** 7-bit encoding scheme (see TS 23.038)
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated public static final int ENCODING_7BIT = 1;
+
+ /** 8-bit encoding scheme (see TS 23.038)
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated public static final int ENCODING_8BIT = 2;
+
+ /** 16-bit encoding scheme (see TS 23.038)
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated public static final int ENCODING_16BIT = 3;
+
+ /** The maximum number of payload bytes per message
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated 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.
+ *
+ * @deprecated Use android.telephony.SmsMessage.
+ * @hide pending API Council approval to extend the public API
+ */
+ @Deprecated public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134;
+
+ /** The maximum number of payload septets per message
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated 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.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
+
+ /** Contains actual SmsMessage. Only public for debugging and for framework layer.
+ * @deprecated Use android.telephony.SmsMessage.
+ * {@hide}
+ */
+ @Deprecated public SmsMessageBase mWrappedSmsMessage;
+
+ /** @deprecated Use android.telephony.SmsMessage. */
+ @Deprecated
+ public static class SubmitPdu {
+ /** @deprecated Use android.telephony.SmsMessage. */
+ @Deprecated public byte[] encodedScAddress; // Null if not applicable.
+ /** @deprecated Use android.telephony.SmsMessage. */
+ @Deprecated public byte[] encodedMessage;
+
+ //Constructor
+ /** @deprecated Use android.telephony.SmsMessage. */
+ @Deprecated
+ public SubmitPdu() {
+ }
+
+ /** @deprecated Use android.telephony.SmsMessage.
+ * {@hide}
+ */
+ @Deprecated
+ protected SubmitPdu(SubmitPduBase spb) {
+ this.encodedMessage = spb.encodedMessage;
+ this.encodedScAddress = spb.encodedScAddress;
+ }
+
+ /** @deprecated Use android.telephony.SmsMessage. */
+ @Deprecated
+ public String toString() {
+ return "SubmitPdu: encodedScAddress = "
+ + Arrays.toString(encodedScAddress)
+ + ", encodedMessage = "
+ + Arrays.toString(encodedMessage);
+ }
+ }
+
+ // Constructor
+ /** @deprecated Use android.telephony.SmsMessage. */
+ @Deprecated
+ public SmsMessage() {
+ this(getSmsFacility());
+ }
+
+ private SmsMessage(SmsMessageBase smb) {
+ mWrappedSmsMessage = smb;
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ SmsMessageBase wrappedMessage;
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+
+ if (PHONE_TYPE_CDMA == activePhone) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
+ } else {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
+ }
+
+ return new SmsMessage(wrappedMessage);
+ }
+
+ /**
+ * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
+ * length in bytes (not hex chars) less the SMSC header
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public static int getTPLayerLengthForPDU(String pdu) {
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+
+ if (PHONE_TYPE_CDMA == activePhone) {
+ return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu);
+ } else {
+ return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu);
+ }
+ }
+
+ /**
+ * 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.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public static int[] calculateLength(CharSequence messageBody, boolean use7bitOnly) {
+ GsmAlphabet.TextEncodingDetails ted =
+ com.android.internal.telephony.gsm.SmsMessage
+ .calculateLength(messageBody, use7bitOnly);
+ int ret[] = new int[4];
+ ret[0] = ted.msgCount;
+ ret[1] = ted.codeUnitCount;
+ ret[2] = ted.codeUnitsRemaining;
+ ret[3] = ted.codeUnitSize;
+ return ret;
+ }
+
+ /**
+ * 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.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public static int[] calculateLength(String messageBody, boolean use7bitOnly) {
+ return calculateLength((CharSequence)messageBody, use7bitOnly);
+ }
+
+ /**
+ * 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.
+ * @deprecated Use android.telephony.SmsMessage.
+ * @hide
+ */
+ @Deprecated
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header) {
+ SubmitPduBase spb;
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+
+ if (PHONE_TYPE_CDMA == activePhone) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested,
+ SmsHeader.fromByteArray(header));
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested, header);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * 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.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message, boolean statusReportRequested) {
+ SubmitPduBase spb;
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+
+ if (PHONE_TYPE_CDMA == activePhone) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested, null);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param destinationPort the port to deliver the message to at the
+ * destination
+ * @param data the dat for the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, short destinationPort, byte[] data,
+ boolean statusReportRequested) {
+ SubmitPduBase spb;
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+
+ if (PHONE_TYPE_CDMA == activePhone) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Returns the address of the SMS service center that relayed this message
+ * or null if there is none.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public String getServiceCenterAddress() {
+ return mWrappedSmsMessage.getServiceCenterAddress();
+ }
+
+ /**
+ * Returns the originating address (sender) of this SMS message in String
+ * form or null if unavailable
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public String getOriginatingAddress() {
+ return mWrappedSmsMessage.getOriginatingAddress();
+ }
+
+ /**
+ * Returns the originating address, or email from address if this message
+ * was from an email gateway. Returns null if originating address
+ * unavailable.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public String getDisplayOriginatingAddress() {
+ return mWrappedSmsMessage.getDisplayOriginatingAddress();
+ }
+
+ /**
+ * Returns the message body as a String, if it exists and is text based.
+ * @return message body is there is one, otherwise null
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public String getMessageBody() {
+ return mWrappedSmsMessage.getMessageBody();
+ }
+
+ /**
+ * Returns the class of this message.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public MessageClass getMessageClass() {
+ int index = mWrappedSmsMessage.getMessageClass().ordinal();
+
+ return MessageClass.values()[index];
+ }
+
+ /**
+ * Returns the message body, or email message body if this message was from
+ * an email gateway. Returns null if message body unavailable.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public String getDisplayMessageBody() {
+ return mWrappedSmsMessage.getDisplayMessageBody();
+ }
+
+ /**
+ * Unofficial convention of a subject line enclosed in parens empty string
+ * if not present
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public String getPseudoSubject() {
+ return mWrappedSmsMessage.getPseudoSubject();
+ }
+
+ /**
+ * Returns the service centre timestamp in currentTimeMillis() format
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public long getTimestampMillis() {
+ return mWrappedSmsMessage.getTimestampMillis();
+ }
+
+ /**
+ * 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
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public boolean isEmail() {
+ return mWrappedSmsMessage.isEmail();
+ }
+
+ /**
+ * @return if isEmail() is true, body of the email sent through the gateway.
+ * null otherwise
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public String getEmailBody() {
+ return mWrappedSmsMessage.getEmailBody();
+ }
+
+ /**
+ * @return if isEmail() is true, email from address of email sent through
+ * the gateway. null otherwise
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public String getEmailFrom() {
+ return mWrappedSmsMessage.getEmailFrom();
+ }
+
+ /**
+ * Get protocol identifier.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public int getProtocolIdentifier() {
+ return mWrappedSmsMessage.getProtocolIdentifier();
+ }
+
+ /**
+ * See TS 23.040 9.2.3.9 returns true if this is a "replace short message" SMS
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public boolean isReplace() {
+ return mWrappedSmsMessage.isReplace();
+ }
+
+ /**
+ * 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
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public boolean isCphsMwiMessage() {
+ return mWrappedSmsMessage.isCphsMwiMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) clear message
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public boolean isMWIClearMessage() {
+ return mWrappedSmsMessage.isMWIClearMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) set message
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public boolean isMWISetMessage() {
+ return mWrappedSmsMessage.isMWISetMessage();
+ }
+
+ /**
+ * returns true if this message is a "Message Waiting Indication Group:
+ * Discard Message" notification and should not be stored.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public boolean isMwiDontStore() {
+ return mWrappedSmsMessage.isMwiDontStore();
+ }
+
+ /**
+ * returns the user data section minus the user data header if one was present.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public byte[] getUserData() {
+ return mWrappedSmsMessage.getUserData();
+ }
+
+ /* Not part of the SDK interface and only needed by specific classes:
+ protected SmsHeader getUserDataHeader()
+ */
+
+ /**
+ * Returns the raw PDU for the message.
+ *
+ * @return the raw PDU for the message.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public byte[] getPdu() {
+ return mWrappedSmsMessage.getPdu();
+ }
+
+ /**
+ * 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
+ * @deprecated Use android.telephony.SmsMessage and getStatusOnIcc instead.
+ */
+ @Deprecated
+ public int getStatusOnSim() {
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * Returns the status of the message on the ICC (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the ICC. These are:
+ * SmsManager.STATUS_ON_ICC_FREE
+ * SmsManager.STATUS_ON_ICC_READ
+ * SmsManager.STATUS_ON_ICC_UNREAD
+ * SmsManager.STATUS_ON_ICC_SEND
+ * SmsManager.STATUS_ON_ICC_UNSENT
+ * @deprecated Use android.telephony.SmsMessage.
+ * @hide
+ */
+ @Deprecated
+ public int getStatusOnIcc() {
+
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * 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.
+ * @deprecated Use android.telephony.SmsMessage and getIndexOnIcc instead.
+ */
+ @Deprecated
+ public int getIndexOnSim() {
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * Returns the record index of the message on the ICC (1-based index).
+ * @return the record index of the message on the ICC, or -1 if this
+ * SmsMessage was not created from a ICC SMS EF record.
+ * @deprecated Use android.telephony.SmsMessage.
+ * @hide
+ */
+ @Deprecated
+ public int getIndexOnIcc() {
+
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * GSM:
+ * For an SMS-STATUS-REPORT message, this returns the status field from
+ * the status report. This field indicates the status of a previously
+ * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
+ * description of values.
+ * CDMA:
+ * For not interfering with status codes from GSM, the value is
+ * shifted to the bits 31-16.
+ * The value is composed of an error class (bits 25-24) and a status code (bits 23-16).
+ * Possible codes are described in C.S0015-B, v2.0, 4.5.21.
+ *
+ * @return 0 indicates the previously sent message was received.
+ * See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21
+ * for a description of other possible values.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public int getStatus() {
+ return mWrappedSmsMessage.getStatus();
+ }
+
+ /**
+ * Return true iff the message is a SMS-STATUS-REPORT message.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public boolean isStatusReportMessage() {
+ return mWrappedSmsMessage.isStatusReportMessage();
+ }
+
+ /**
+ * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+ * this message.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ @Deprecated
+ public boolean isReplyPathPresent() {
+ return mWrappedSmsMessage.isReplyPathPresent();
+ }
+
+ /** This method returns the reference to a specific
+ * SmsMessage object, which is used for accessing its static methods.
+ * @return Specific SmsMessage.
+ * @deprecated Use android.telephony.SmsMessage.
+ */
+ private static final SmsMessageBase getSmsFacility(){
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ if (PHONE_TYPE_CDMA == activePhone) {
+ return new com.android.internal.telephony.cdma.SmsMessage();
+ } else {
+ return new com.android.internal.telephony.gsm.SmsMessage();
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/ATParseEx.java b/src/java/com/android/internal/telephony/ATParseEx.java
new file mode 100644
index 0000000..c93b875
--- /dev/null
+++ b/src/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/src/java/com/android/internal/telephony/ATResponseParser.java b/src/java/com/android/internal/telephony/ATResponseParser.java
new file mode 100644
index 0000000..fdb0526
--- /dev/null
+++ b/src/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/src/java/com/android/internal/telephony/AdnRecord.java b/src/java/com/android/internal/telephony/AdnRecord.java
new file mode 100644
index 0000000..1bf2d3c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/AdnRecord.java
@@ -0,0 +1,321 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+
+
+/**
+ *
+ * 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 = null;
+ String number = null;
+ String[] emails;
+ 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_DIALING_NUMBER_START = 2;
+ static final int ADN_DIALING_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;
+ String[] emails;
+
+ efid = source.readInt();
+ recordNumber = source.readInt();
+ alphaTag = source.readString();
+ number = source.readString();
+ emails = source.readStringArray();
+
+ return new AdnRecord(efid, recordNumber, alphaTag, number, emails);
+ }
+
+ 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 (String alphaTag, String number, String[] emails) {
+ this(0, 0, alphaTag, number, emails);
+ }
+
+ public AdnRecord (int efid, int recordNumber, String alphaTag, String number, String[] emails) {
+ this.efid = efid;
+ this.recordNumber = recordNumber;
+ this.alphaTag = alphaTag;
+ this.number = number;
+ this.emails = emails;
+ }
+
+ public AdnRecord(int efid, int recordNumber, String alphaTag, String number) {
+ this.efid = efid;
+ this.recordNumber = recordNumber;
+ this.alphaTag = alphaTag;
+ this.number = number;
+ this.emails = null;
+ }
+
+ //***** Instance Methods
+
+ public String getAlphaTag() {
+ return alphaTag;
+ }
+
+ public String getNumber() {
+ return number;
+ }
+
+ public String[] getEmails() {
+ return emails;
+ }
+
+ public void setEmails(String[] emails) {
+ this.emails = emails;
+ }
+
+ public String toString() {
+ return "ADN Record '" + alphaTag + "' '" + number + " " + emails + "'";
+ }
+
+ public boolean isEmpty() {
+ return TextUtils.isEmpty(alphaTag) && TextUtils.isEmpty(number) && emails == null;
+ }
+
+ public boolean hasExtendedRecord() {
+ return extRecord != 0 && extRecord != 0xff;
+ }
+
+ /** Helper function for {@link #isEqual}. */
+ private static boolean stringCompareNullEqualsEmpty(String s1, String s2) {
+ if (s1 == s2) {
+ return true;
+ }
+ if (s1 == null) {
+ s1 = "";
+ }
+ if (s2 == null) {
+ s2 = "";
+ }
+ return (s1.equals(s2));
+ }
+
+ public boolean isEqual(AdnRecord adn) {
+ return ( stringCompareNullEqualsEmpty(alphaTag, adn.alphaTag) &&
+ stringCompareNullEqualsEmpty(number, adn.number) &&
+ Arrays.equals(emails, adn.emails));
+ }
+ //***** 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);
+ dest.writeStringArray(emails);
+ }
+
+ /**
+ * 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 null for wrong format of dialing number or tag
+ */
+ public byte[] buildAdnString(int recordSize) {
+ byte[] bcdNumber;
+ byte[] byteTag;
+ byte[] adnString;
+ int footerOffset = recordSize - FOOTER_SIZE_BYTES;
+
+ // create an empty record
+ adnString = new byte[recordSize];
+ for (int i = 0; i < recordSize; i++) {
+ adnString[i] = (byte) 0xFF;
+ }
+
+ if (TextUtils.isEmpty(number)) {
+ Log.w(LOG_TAG, "[buildAdnString] Empty dialing number");
+ return adnString; // return the empty record (for delete)
+ } else if (number.length()
+ > (ADN_DIALING_NUMBER_END - ADN_DIALING_NUMBER_START + 1) * 2) {
+ Log.w(LOG_TAG,
+ "[buildAdnString] Max length of dialing number is 20");
+ return null;
+ } else if (alphaTag != null && alphaTag.length() > footerOffset) {
+ Log.w(LOG_TAG,
+ "[buildAdnString] Max length of tag is " + footerOffset);
+ return null;
+ } else {
+ 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; // Capability Id
+ adnString[footerOffset + ADN_EXTENSION_ID]
+ = (byte) 0xFF; // Extension Record Id
+
+ if (!TextUtils.isEmpty(alphaTag)) {
+ 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 = IccUtils.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];
+
+ emails = null;
+
+ } catch (RuntimeException ex) {
+ Log.w(LOG_TAG, "Error parsing AdnRecord", ex);
+ number = "";
+ alphaTag = "";
+ emails = null;
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/AdnRecordCache.java b/src/java/com/android/internal/telephony/AdnRecordCache.java
new file mode 100644
index 0000000..db5f4da
--- /dev/null
+++ b/src/java/com/android/internal/telephony/AdnRecordCache.java
@@ -0,0 +1,364 @@
+/*
+ * 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.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.telephony.gsm.UsimPhoneBookManager;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * {@hide}
+ */
+public final class AdnRecordCache extends Handler implements IccConstants {
+ //***** Instance Variables
+
+ private IccFileHandler mFh;
+ private UsimPhoneBookManager mUsimPhoneBookManager;
+
+ // 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
+
+
+
+ public AdnRecordCache(IccFileHandler fh) {
+ mFh = fh;
+ mUsimPhoneBookManager = new UsimPhoneBookManager(mFh, this);
+ }
+
+ //***** Called from SIMRecords
+
+ /**
+ * Called from SIMRecords.onRadioNotAvailable and SIMRecords.handleSimRefresh.
+ */
+ public void reset() {
+ adnLikeFiles.clear();
+ mUsimPhoneBookManager.reset();
+
+ 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
+ */
+ public 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
+ */
+ 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;
+ case EF_PBR: return 0; // The EF PBR doesn't have an extension record
+ 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
+ */
+ public 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(mFh).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
+ */
+ public 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;
+
+ if (efid == EF_PBR) {
+ oldAdnList = mUsimPhoneBookManager.loadEfFilesFromUsim();
+ } else {
+ 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;
+ }
+
+ if (efid == EF_PBR) {
+ AdnRecord foundAdn = oldAdnList.get(index-1);
+ efid = foundAdn.efid;
+ extensionEF = foundAdn.extRecord;
+ index = foundAdn.recordNumber;
+
+ newAdn.efid = efid;
+ newAdn.extRecord = extensionEF;
+ newAdn.recordNumber = index;
+ }
+
+ Message pendingResponse = userWriteResponse.get(efid);
+
+ if (pendingResponse != null) {
+ sendErrorResponse(response, "Have pending update for EF:" + efid);
+ return;
+ }
+
+ userWriteResponse.put(efid, response);
+
+ new AdnRecordLoader(mFh).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
+ */
+ public void
+ requestLoadAllAdnLike (int efid, int extensionEf, Message response) {
+ ArrayList<Message> waiters;
+ ArrayList<AdnRecord> result;
+
+ if (efid == EF_PBR) {
+ result = mUsimPhoneBookManager.loadEfFilesFromUsim();
+ } else {
+ 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);
+
+
+ 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(mFh).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);
+ mUsimPhoneBookManager.invalidateCache();
+ }
+
+ Message response = userWriteResponse.get(efid);
+ userWriteResponse.delete(efid);
+
+ AsyncResult.forMessage(response, null, ar.exception);
+ response.sendToTarget();
+ break;
+ }
+
+ }
+
+
+}
diff --git a/src/java/com/android/internal/telephony/AdnRecordLoader.java b/src/java/com/android/internal/telephony/AdnRecordLoader.java
new file mode 100644
index 0000000..084fae6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/AdnRecordLoader.java
@@ -0,0 +1,284 @@
+/*
+ * 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 android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+
+public class AdnRecordLoader extends Handler {
+ final static String LOG_TAG = "RIL_AdnRecordLoader";
+
+ //***** Instance Variables
+
+ private IccFileHandler mFh;
+ 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
+
+ public AdnRecordLoader(IccFileHandler fh) {
+ // The telephony unit-test cases may create AdnRecords
+ // in secondary threads
+ super(Looper.getMainLooper());
+ mFh = fh;
+ }
+
+ /**
+ * Resulting AdnRecord is placed in response.obj.result
+ * or response.obj.exception is set
+ */
+ public void
+ loadFromEF(int ef, int extensionEF, int recordNumber,
+ Message response) {
+ this.ef = ef;
+ this.extensionEF = extensionEF;
+ this.recordNumber = recordNumber;
+ this.userResponse = response;
+
+ mFh.loadEFLinearFixed(
+ ef, recordNumber,
+ obtainMessage(EVENT_ADN_LOAD_DONE));
+
+ }
+
+
+ /**
+ * Resulting ArrayList&lt;adnRecord> is placed in response.obj.result
+ * or response.obj.exception is set
+ */
+ public void
+ loadAllFromEF(int ef, int extensionEF,
+ Message response) {
+ this.ef = ef;
+ this.extensionEF = extensionEF;
+ this.userResponse = response;
+
+ mFh.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 phone number
+ * @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
+ */
+ public 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;
+
+ mFh.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("wrong ADN format",
+ ar.exception);
+ }
+
+ mFh.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" + IccUtils.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;
+
+ mFh.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 extension EF: 0x"
+ + Integer.toHexString(extensionEF)
+ + ":" + adn.extRecord
+ + "\n" + IccUtils.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++;
+
+ mFh.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;
+ }
+ }
+
+
+}
diff --git a/src/java/com/android/internal/telephony/ApnContext.java b/src/java/com/android/internal/telephony/ApnContext.java
new file mode 100644
index 0000000..4817a7b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ApnContext.java
@@ -0,0 +1,246 @@
+/*
+ * 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.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Maintain the Apn context
+ */
+public class ApnContext {
+
+ public final String LOG_TAG;
+
+ protected static final boolean DBG = true;
+
+ private final String mApnType;
+
+ private DctConstants.State mState;
+
+ private ArrayList<ApnSetting> mWaitingApns = null;
+
+ /** A zero indicates that all waiting APNs had a permanent error */
+ private AtomicInteger mWaitingApnsPermanentFailureCountDown;
+
+ private ApnSetting mApnSetting;
+
+ DataConnection mDataConnection;
+
+ DataConnectionAc mDataConnectionAc;
+
+ String mReason;
+
+ int mRetryCount;
+
+ /**
+ * user/app requested connection on this APN
+ */
+ AtomicBoolean mDataEnabled;
+
+ /**
+ * carrier requirements met
+ */
+ AtomicBoolean mDependencyMet;
+
+ public ApnContext(String apnType, String logTag) {
+ mApnType = apnType;
+ mState = DctConstants.State.IDLE;
+ setReason(Phone.REASON_DATA_ENABLED);
+ setRetryCount(0);
+ mDataEnabled = new AtomicBoolean(false);
+ mDependencyMet = new AtomicBoolean(true);
+ mWaitingApnsPermanentFailureCountDown = new AtomicInteger(0);
+ LOG_TAG = logTag;
+ }
+
+ public String getApnType() {
+ return mApnType;
+ }
+
+ public synchronized DataConnection getDataConnection() {
+ return mDataConnection;
+ }
+
+ public synchronized void setDataConnection(DataConnection dc) {
+ if (DBG) {
+ log("setDataConnection: old dc=" + mDataConnection + " new dc=" + dc + " this=" + this);
+ }
+ mDataConnection = dc;
+ }
+
+
+ public synchronized DataConnectionAc getDataConnectionAc() {
+ return mDataConnectionAc;
+ }
+
+ public synchronized void setDataConnectionAc(DataConnectionAc dcac) {
+ if (DBG) {
+ log("setDataConnectionAc: old dcac=" + mDataConnectionAc + " new dcac=" + dcac);
+ }
+ if (dcac != null) {
+ dcac.addApnContextSync(this);
+ } else {
+ if (mDataConnectionAc != null) {
+ mDataConnectionAc.removeApnContextSync(this);
+ }
+ }
+ mDataConnectionAc = dcac;
+ }
+
+ public synchronized ApnSetting getApnSetting() {
+ return mApnSetting;
+ }
+
+ public synchronized void setApnSetting(ApnSetting apnSetting) {
+ mApnSetting = apnSetting;
+ }
+
+ public synchronized void setWaitingApns(ArrayList<ApnSetting> waitingApns) {
+ mWaitingApns = waitingApns;
+ mWaitingApnsPermanentFailureCountDown.set(mWaitingApns.size());
+ }
+
+ public int getWaitingApnsPermFailCount() {
+ return mWaitingApnsPermanentFailureCountDown.get();
+ }
+
+ public void decWaitingApnsPermFailCount() {
+ mWaitingApnsPermanentFailureCountDown.decrementAndGet();
+ }
+
+ public synchronized ApnSetting getNextWaitingApn() {
+ ArrayList<ApnSetting> list = mWaitingApns;
+ ApnSetting apn = null;
+
+ if (list != null) {
+ if (!list.isEmpty()) {
+ apn = list.get(0);
+ }
+ }
+ return apn;
+ }
+
+ public synchronized void removeWaitingApn(ApnSetting apn) {
+ if (mWaitingApns != null) {
+ mWaitingApns.remove(apn);
+ }
+ }
+
+ public synchronized ArrayList<ApnSetting> getWaitingApns() {
+ return mWaitingApns;
+ }
+
+ public synchronized void setState(DctConstants.State s) {
+ if (DBG) {
+ log("setState: " + s + ", previous state:" + mState);
+ }
+
+ mState = s;
+
+ if (mState == DctConstants.State.FAILED) {
+ if (mWaitingApns != null) {
+ mWaitingApns.clear(); // when teardown the connection and set to IDLE
+ }
+ }
+ }
+
+ public synchronized DctConstants.State getState() {
+ return mState;
+ }
+
+ public boolean isDisconnected() {
+ DctConstants.State currentState = getState();
+ return ((currentState == DctConstants.State.IDLE) ||
+ currentState == DctConstants.State.FAILED);
+ }
+
+ public synchronized void setReason(String reason) {
+ if (DBG) {
+ log("set reason as " + reason + ",current state " + mState);
+ }
+ mReason = reason;
+ }
+
+ public synchronized String getReason() {
+ return mReason;
+ }
+
+ public synchronized void setRetryCount(int retryCount) {
+ if (DBG) {
+ log("setRetryCount: " + retryCount);
+ }
+ mRetryCount = retryCount;
+ DataConnection dc = mDataConnection;
+ if (dc != null) {
+ dc.setRetryCount(retryCount);
+ }
+ }
+
+ public synchronized int getRetryCount() {
+ return mRetryCount;
+ }
+
+ public boolean isReady() {
+ return mDataEnabled.get() && mDependencyMet.get();
+ }
+
+ public void setEnabled(boolean enabled) {
+ if (DBG) {
+ log("set enabled as " + enabled + ", current state is " + mDataEnabled.get());
+ }
+ mDataEnabled.set(enabled);
+ }
+
+ public boolean isEnabled() {
+ return mDataEnabled.get();
+ }
+
+ public void setDependencyMet(boolean met) {
+ if (DBG) {
+ log("set mDependencyMet as " + met + " current state is " + mDependencyMet.get());
+ }
+ mDependencyMet.set(met);
+ }
+
+ public boolean getDependencyMet() {
+ return mDependencyMet.get();
+ }
+
+ @Override
+ public String toString() {
+ // We don't print mDataConnection because its recursive.
+ return "{mApnType=" + mApnType + " mState=" + getState() + " mWaitingApns=" + mWaitingApns +
+ " mWaitingApnsPermanentFailureCountDown=" + mWaitingApnsPermanentFailureCountDown +
+ " mApnSetting=" + mApnSetting + " mDataConnectionAc=" + mDataConnectionAc +
+ " mReason=" + mReason + " mRetryCount=" + mRetryCount +
+ " mDataEnabled=" + mDataEnabled + " mDependencyMet=" + mDependencyMet + "}";
+ }
+
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[ApnContext:" + mApnType + "] " + s);
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("ApnContext: " + this.toString());
+ }
+}
diff --git a/src/java/com/android/internal/telephony/ApnSetting.java b/src/java/com/android/internal/telephony/ApnSetting.java
new file mode 100755
index 0000000..b84c69c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ApnSetting.java
@@ -0,0 +1,200 @@
+/*
+ * 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;
+
+/**
+ * This class represents a apn setting for create PDP link
+ */
+public class ApnSetting {
+
+ static final String V2_FORMAT_REGEX = "^\\[ApnSettingV2\\]\\s*";
+
+ public final String carrier;
+ public final String apn;
+ public final String proxy;
+ public final String port;
+ public final String mmsc;
+ public final String mmsProxy;
+ public final String mmsPort;
+ public final String user;
+ public final String password;
+ public final int authType;
+ public final String[] types;
+ public final int id;
+ public final String numeric;
+ public final String protocol;
+ public final String roamingProtocol;
+ /**
+ * Current status of APN
+ * true : enabled APN, false : disabled APN.
+ */
+ public final boolean carrierEnabled;
+ /**
+ * Radio Access Technology info
+ * To check what values can hold, refer to ServiceState.java.
+ * This should be spread to other technologies,
+ * but currently only used for LTE(14) and EHRPD(13).
+ */
+ public final int bearer;
+
+ public ApnSetting(int id, String numeric, String carrier, String apn,
+ String proxy, String port,
+ String mmsc, String mmsProxy, String mmsPort,
+ String user, String password, int authType, String[] types,
+ String protocol, String roamingProtocol, boolean carrierEnabled, int bearer) {
+ this.id = id;
+ this.numeric = numeric;
+ 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.authType = authType;
+ this.types = types;
+ this.protocol = protocol;
+ this.roamingProtocol = roamingProtocol;
+ this.carrierEnabled = carrierEnabled;
+ this.bearer = bearer;
+ }
+
+ /**
+ * Creates an ApnSetting object from a string.
+ *
+ * @param data the string to read.
+ *
+ * The string must be in one of two formats (newlines added for clarity,
+ * spaces are optional):
+ *
+ * v1 format:
+ * <carrier>, <apn>, <proxy>, <port>, <mmsc>, <mmsproxy>,
+ * <mmsport>, <user>, <password>, <authtype>, <mcc>,<mnc>,
+ * <type>[, <type>...]
+ *
+ * v2 format:
+ * [ApnSettingV2] <carrier>, <apn>, <proxy>, <port>, <mmsc>, <mmsproxy>,
+ * <mmsport>, <user>, <password>, <authtype>, <mcc>, <mnc>,
+ * <type>[| <type>...], <protocol>, <roaming_protocol>, <carrierEnabled>, <bearer>
+ *
+ * Note that the strings generated by toString() do not contain the username
+ * and password and thus cannot be read by this method.
+ *
+ * @see ApnSettingTest
+ */
+ public static ApnSetting fromString(String data) {
+ if (data == null) return null;
+
+ int version;
+ // matches() operates on the whole string, so append .* to the regex.
+ if (data.matches(V2_FORMAT_REGEX + ".*")) {
+ version = 2;
+ data = data.replaceFirst(V2_FORMAT_REGEX, "");
+ } else {
+ version = 1;
+ }
+
+ String[] a = data.split("\\s*,\\s*");
+ if (a.length < 14) {
+ return null;
+ }
+
+ int authType;
+ try {
+ authType = Integer.parseInt(a[12]);
+ } catch (Exception e) {
+ authType = 0;
+ }
+
+ String[] typeArray;
+ String protocol, roamingProtocol;
+ boolean carrierEnabled;
+ int bearer;
+ if (version == 1) {
+ typeArray = new String[a.length - 13];
+ System.arraycopy(a, 13, typeArray, 0, a.length - 13);
+ protocol = RILConstants.SETUP_DATA_PROTOCOL_IP;
+ roamingProtocol = RILConstants.SETUP_DATA_PROTOCOL_IP;
+ carrierEnabled = true;
+ bearer = 0;
+ } else {
+ if (a.length < 18) {
+ return null;
+ }
+ typeArray = a[13].split("\\s*\\|\\s*");
+ protocol = a[14];
+ roamingProtocol = a[15];
+ try {
+ carrierEnabled = Boolean.parseBoolean(a[16]);
+ } catch (Exception e) {
+ carrierEnabled = true;
+ }
+ bearer = Integer.parseInt(a[17]);
+ }
+
+ return new ApnSetting(-1,a[10]+a[11],a[0],a[1],a[2],a[3],a[7],a[8],
+ a[9],a[4],a[5],authType,typeArray,protocol,roamingProtocol,carrierEnabled,bearer);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[ApnSettingV2] ")
+ .append(carrier)
+ .append(", ").append(id)
+ .append(", ").append(numeric)
+ .append(", ").append(apn)
+ .append(", ").append(proxy)
+ .append(", ").append(mmsc)
+ .append(", ").append(mmsProxy)
+ .append(", ").append(mmsPort)
+ .append(", ").append(port)
+ .append(", ").append(authType).append(", ");
+ for (int i = 0; i < types.length; i++) {
+ sb.append(types[i]);
+ if (i < types.length - 1) {
+ sb.append(" | ");
+ }
+ }
+ sb.append(", ").append(protocol);
+ sb.append(", ").append(roamingProtocol);
+ sb.append(", ").append(carrierEnabled);
+ sb.append(", ").append(bearer);
+ return sb.toString();
+ }
+
+ public boolean canHandleType(String type) {
+ for (String t : types) {
+ // DEFAULT handles all, and HIPRI is handled by DEFAULT
+ if (t.equalsIgnoreCase(type) ||
+ t.equalsIgnoreCase(PhoneConstants.APN_TYPE_ALL) ||
+ (t.equalsIgnoreCase(PhoneConstants.APN_TYPE_DEFAULT) &&
+ type.equalsIgnoreCase(PhoneConstants.APN_TYPE_HIPRI))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // TODO - if we have this function we should also have hashCode.
+ // Also should handle changes in type order and perhaps case-insensitivity
+ public boolean equals(Object o) {
+ if (o instanceof ApnSetting == false) return false;
+ return (this.toString().equals(o.toString()));
+ }
+}
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
new file mode 100644
index 0000000..1b54656
--- /dev/null
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -0,0 +1,647 @@
+/*
+ * 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.os.RegistrantList;
+import android.os.Registrant;
+import android.os.Handler;
+import android.os.AsyncResult;
+import android.os.SystemProperties;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * {@hide}
+ */
+public abstract class BaseCommands implements CommandsInterface {
+ static final String LOG_TAG = "RILB";
+
+ //***** 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 mCallStateRegistrants = new RegistrantList();
+ protected RegistrantList mVoiceNetworkStateRegistrants = new RegistrantList();
+ protected RegistrantList mDataNetworkStateRegistrants = new RegistrantList();
+ protected RegistrantList mVoiceRadioTechChangedRegistrants = new RegistrantList();
+ protected RegistrantList mIccStatusChangedRegistrants = new RegistrantList();
+ protected RegistrantList mVoicePrivacyOnRegistrants = new RegistrantList();
+ protected RegistrantList mVoicePrivacyOffRegistrants = new RegistrantList();
+ protected Registrant mUnsolOemHookRawRegistrant;
+ protected RegistrantList mOtaProvisionRegistrants = new RegistrantList();
+ protected RegistrantList mCallWaitingInfoRegistrants = new RegistrantList();
+ protected RegistrantList mDisplayInfoRegistrants = new RegistrantList();
+ protected RegistrantList mSignalInfoRegistrants = new RegistrantList();
+ protected RegistrantList mNumberInfoRegistrants = new RegistrantList();
+ protected RegistrantList mRedirNumInfoRegistrants = new RegistrantList();
+ protected RegistrantList mLineControlInfoRegistrants = new RegistrantList();
+ protected RegistrantList mT53ClirInfoRegistrants = new RegistrantList();
+ protected RegistrantList mT53AudCntrlInfoRegistrants = new RegistrantList();
+ protected RegistrantList mRingbackToneRegistrants = new RegistrantList();
+ protected RegistrantList mResendIncallMuteRegistrants = new RegistrantList();
+ protected RegistrantList mCdmaSubscriptionChangedRegistrants = new RegistrantList();
+ protected RegistrantList mCdmaPrlChangedRegistrants = new RegistrantList();
+ protected RegistrantList mExitEmergencyCallbackModeRegistrants = new RegistrantList();
+ protected RegistrantList mRilConnectedRegistrants = new RegistrantList();
+ protected RegistrantList mIccRefreshRegistrants = new RegistrantList();
+
+ protected Registrant mGsmSmsRegistrant;
+ protected Registrant mCdmaSmsRegistrant;
+ protected Registrant mNITZTimeRegistrant;
+ protected Registrant mSignalStrengthRegistrant;
+ protected Registrant mUSSDRegistrant;
+ protected Registrant mSmsOnSimRegistrant;
+ protected Registrant mSmsStatusRegistrant;
+ protected Registrant mSsnRegistrant;
+ protected Registrant mCatSessionEndRegistrant;
+ protected Registrant mCatProCmdRegistrant;
+ protected Registrant mCatEventRegistrant;
+ protected Registrant mCatCallSetUpRegistrant;
+ protected Registrant mIccSmsFullRegistrant;
+ protected Registrant mEmergencyCallbackModeRegistrant;
+ protected Registrant mRingRegistrant;
+ protected Registrant mRestrictedStateRegistrant;
+ protected Registrant mGsmBroadcastSmsRegistrant;
+
+ // Preferred network type received from PhoneFactory.
+ // This is used when establishing a connection to the
+ // vendor ril so it starts up in the correct mode.
+ protected int mPreferredNetworkType;
+ // CDMA subscription received from PhoneFactory
+ protected int mCdmaSubscription;
+ // Type of Phone, GSM or CDMA. Set by CDMAPhone or GSMPhone.
+ protected int mPhoneType;
+ // RIL Version
+ protected int mRilVersion = -1;
+
+ 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 unregisterForRadioStateChanged(Handler h) {
+ synchronized (mStateMonitor) {
+ mRadioStateChangedRegistrants.remove(h);
+ }
+ }
+
+ 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 unregisterForOn(Handler h) {
+ synchronized (mStateMonitor) {
+ mOnRegistrants.remove(h);
+ }
+ }
+
+
+ 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 unregisterForAvailable(Handler h) {
+ synchronized(mStateMonitor) {
+ mAvailRegistrants.remove(h);
+ }
+ }
+
+ 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 unregisterForNotAvailable(Handler h) {
+ synchronized (mStateMonitor) {
+ mNotAvailRegistrants.remove(h);
+ }
+ }
+
+ 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));
+ }
+ }
+ }
+ public void unregisterForOffOrNotAvailable(Handler h) {
+ synchronized(mStateMonitor) {
+ mOffOrNotAvailRegistrants.remove(h);
+ }
+ }
+
+ public void registerForCallStateChanged(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+
+ mCallStateRegistrants.add(r);
+ }
+
+ public void unregisterForCallStateChanged(Handler h) {
+ mCallStateRegistrants.remove(h);
+ }
+
+ public void registerForVoiceNetworkStateChanged(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+
+ mVoiceNetworkStateRegistrants.add(r);
+ }
+
+ public void unregisterForVoiceNetworkStateChanged(Handler h) {
+ mVoiceNetworkStateRegistrants.remove(h);
+ }
+
+ public void registerForDataNetworkStateChanged(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+
+ mDataNetworkStateRegistrants.add(r);
+ }
+
+ public void unregisterForDataNetworkStateChanged(Handler h) {
+ mDataNetworkStateRegistrants.remove(h);
+ }
+
+ public void registerForVoiceRadioTechChanged(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mVoiceRadioTechChangedRegistrants.add(r);
+ }
+
+ public void unregisterForVoiceRadioTechChanged(Handler h) {
+ mVoiceRadioTechChangedRegistrants.remove(h);
+ }
+
+ public void registerForIccStatusChanged(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mIccStatusChangedRegistrants.add(r);
+ }
+
+ public void unregisterForIccStatusChanged(Handler h) {
+ mIccStatusChangedRegistrants.remove(h);
+ }
+
+ public void setOnNewGsmSms(Handler h, int what, Object obj) {
+ mGsmSmsRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnNewGsmSms(Handler h) {
+ mGsmSmsRegistrant.clear();
+ }
+
+ public void setOnNewCdmaSms(Handler h, int what, Object obj) {
+ mCdmaSmsRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnNewCdmaSms(Handler h) {
+ mCdmaSmsRegistrant.clear();
+ }
+
+ public void setOnNewGsmBroadcastSms(Handler h, int what, Object obj) {
+ mGsmBroadcastSmsRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnNewGsmBroadcastSms(Handler h) {
+ mGsmBroadcastSmsRegistrant.clear();
+ }
+
+ public void setOnSmsOnSim(Handler h, int what, Object obj) {
+ mSmsOnSimRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnSmsOnSim(Handler h) {
+ mSmsOnSimRegistrant.clear();
+ }
+
+ public void setOnSmsStatus(Handler h, int what, Object obj) {
+ mSmsStatusRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnSmsStatus(Handler h) {
+ mSmsStatusRegistrant.clear();
+ }
+
+ public void setOnSignalStrengthUpdate(Handler h, int what, Object obj) {
+ mSignalStrengthRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnSignalStrengthUpdate(Handler h) {
+ mSignalStrengthRegistrant.clear();
+ }
+
+ public void setOnNITZTime(Handler h, int what, Object obj) {
+ mNITZTimeRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnNITZTime(Handler h) {
+ mNITZTimeRegistrant.clear();
+ }
+
+ public void setOnUSSD(Handler h, int what, Object obj) {
+ mUSSDRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnUSSD(Handler h) {
+ mUSSDRegistrant.clear();
+ }
+
+ public void setOnSuppServiceNotification(Handler h, int what, Object obj) {
+ mSsnRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnSuppServiceNotification(Handler h) {
+ mSsnRegistrant.clear();
+ }
+
+ public void setOnCatSessionEnd(Handler h, int what, Object obj) {
+ mCatSessionEndRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnCatSessionEnd(Handler h) {
+ mCatSessionEndRegistrant.clear();
+ }
+
+ public void setOnCatProactiveCmd(Handler h, int what, Object obj) {
+ mCatProCmdRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnCatProactiveCmd(Handler h) {
+ mCatProCmdRegistrant.clear();
+ }
+
+ public void setOnCatEvent(Handler h, int what, Object obj) {
+ mCatEventRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnCatEvent(Handler h) {
+ mCatEventRegistrant.clear();
+ }
+
+ public void setOnCatCallSetUp(Handler h, int what, Object obj) {
+ mCatCallSetUpRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnCatCallSetUp(Handler h) {
+ mCatCallSetUpRegistrant.clear();
+ }
+
+ public void setOnIccSmsFull(Handler h, int what, Object obj) {
+ mIccSmsFullRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnIccSmsFull(Handler h) {
+ mIccSmsFullRegistrant.clear();
+ }
+
+ public void registerForIccRefresh(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mIccRefreshRegistrants.add(r);
+ }
+ public void setOnIccRefresh(Handler h, int what, Object obj) {
+ registerForIccRefresh(h, what, obj);
+ }
+
+ public void setEmergencyCallbackMode(Handler h, int what, Object obj) {
+ mEmergencyCallbackModeRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unregisterForIccRefresh(Handler h) {
+ mIccRefreshRegistrants.remove(h);
+ }
+ public void unsetOnIccRefresh(Handler h) {
+ unregisterForIccRefresh(h);
+ }
+
+ public void setOnCallRing(Handler h, int what, Object obj) {
+ mRingRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnCallRing(Handler h) {
+ mRingRegistrant.clear();
+ }
+
+ public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mVoicePrivacyOnRegistrants.add(r);
+ }
+
+ public void unregisterForInCallVoicePrivacyOn(Handler h){
+ mVoicePrivacyOnRegistrants.remove(h);
+ }
+
+ public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mVoicePrivacyOffRegistrants.add(r);
+ }
+
+ public void unregisterForInCallVoicePrivacyOff(Handler h){
+ mVoicePrivacyOffRegistrants.remove(h);
+ }
+
+ public void setOnRestrictedStateChanged(Handler h, int what, Object obj) {
+ mRestrictedStateRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnRestrictedStateChanged(Handler h) {
+ mRestrictedStateRegistrant.clear();
+ }
+
+ public void registerForDisplayInfo(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mDisplayInfoRegistrants.add(r);
+ }
+
+ public void unregisterForDisplayInfo(Handler h) {
+ mDisplayInfoRegistrants.remove(h);
+ }
+
+ public void registerForCallWaitingInfo(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mCallWaitingInfoRegistrants.add(r);
+ }
+
+ public void unregisterForCallWaitingInfo(Handler h) {
+ mCallWaitingInfoRegistrants.remove(h);
+ }
+
+ public void registerForSignalInfo(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mSignalInfoRegistrants.add(r);
+ }
+
+ public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
+ mUnsolOemHookRawRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unSetOnUnsolOemHookRaw(Handler h) {
+ mUnsolOemHookRawRegistrant.clear();
+ }
+
+ public void unregisterForSignalInfo(Handler h) {
+ mSignalInfoRegistrants.remove(h);
+ }
+
+ public void registerForCdmaOtaProvision(Handler h,int what, Object obj){
+ Registrant r = new Registrant (h, what, obj);
+ mOtaProvisionRegistrants.add(r);
+ }
+
+ public void unregisterForCdmaOtaProvision(Handler h){
+ mOtaProvisionRegistrants.remove(h);
+ }
+
+ public void registerForNumberInfo(Handler h,int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mNumberInfoRegistrants.add(r);
+ }
+
+ public void unregisterForNumberInfo(Handler h){
+ mNumberInfoRegistrants.remove(h);
+ }
+
+ public void registerForRedirectedNumberInfo(Handler h,int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mRedirNumInfoRegistrants.add(r);
+ }
+
+ public void unregisterForRedirectedNumberInfo(Handler h) {
+ mRedirNumInfoRegistrants.remove(h);
+ }
+
+ public void registerForLineControlInfo(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mLineControlInfoRegistrants.add(r);
+ }
+
+ public void unregisterForLineControlInfo(Handler h) {
+ mLineControlInfoRegistrants.remove(h);
+ }
+
+ public void registerFoT53ClirlInfo(Handler h,int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mT53ClirInfoRegistrants.add(r);
+ }
+
+ public void unregisterForT53ClirInfo(Handler h) {
+ mT53ClirInfoRegistrants.remove(h);
+ }
+
+ public void registerForT53AudioControlInfo(Handler h,int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mT53AudCntrlInfoRegistrants.add(r);
+ }
+
+ public void unregisterForT53AudioControlInfo(Handler h) {
+ mT53AudCntrlInfoRegistrants.remove(h);
+ }
+
+ public void registerForRingbackTone(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mRingbackToneRegistrants.add(r);
+ }
+
+ public void unregisterForRingbackTone(Handler h) {
+ mRingbackToneRegistrants.remove(h);
+ }
+
+ public void registerForResendIncallMute(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mResendIncallMuteRegistrants.add(r);
+ }
+
+ public void unregisterForResendIncallMute(Handler h) {
+ mResendIncallMuteRegistrants.remove(h);
+ }
+
+ @Override
+ public void registerForCdmaSubscriptionChanged(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mCdmaSubscriptionChangedRegistrants.add(r);
+ }
+
+ @Override
+ public void unregisterForCdmaSubscriptionChanged(Handler h) {
+ mCdmaSubscriptionChangedRegistrants.remove(h);
+ }
+
+ @Override
+ public void registerForCdmaPrlChanged(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mCdmaPrlChangedRegistrants.add(r);
+ }
+
+ @Override
+ public void unregisterForCdmaPrlChanged(Handler h) {
+ mCdmaPrlChangedRegistrants.remove(h);
+ }
+
+ @Override
+ public void registerForExitEmergencyCallbackMode(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mExitEmergencyCallbackModeRegistrants.add(r);
+ }
+
+ @Override
+ public void unregisterForExitEmergencyCallbackMode(Handler h) {
+ mExitEmergencyCallbackModeRegistrants.remove(h);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void registerForRilConnected(Handler h, int what, Object obj) {
+ Log.d(LOG_TAG, "registerForRilConnected h=" + h + " w=" + what);
+ Registrant r = new Registrant (h, what, obj);
+ mRilConnectedRegistrants.add(r);
+ if (mRilVersion != -1) {
+ Log.d(LOG_TAG, "Notifying: ril connected mRilVersion=" + mRilVersion);
+ r.notifyRegistrant(new AsyncResult(null, new Integer(mRilVersion), null));
+ }
+ }
+
+ @Override
+ public void unregisterForRilConnected(Handler h) {
+ mRilConnectedRegistrants.remove(h);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setCurrentPreferredNetworkType() {
+ }
+
+ //***** 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 3 values : RADIO_OFF, RADIO_UNAVAILABLE, RADIO_ON.
+ *
+ * @param newState new RadioState decoded from RIL_UNSOL_RADIO_STATE_CHANGED
+ */
+ protected void setRadioState(RadioState newState) {
+ RadioState oldState;
+
+ synchronized (mStateMonitor) {
+ if (false) {
+ Log.v(LOG_TAG, "setRadioState old: " + mState
+ + " new " + newState);
+ }
+
+ oldState = mState;
+ mState = newState;
+
+ if (oldState == mState) {
+ // no state transition
+ return;
+ }
+
+ 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.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() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getLteOnCdmaMode() {
+ return TelephonyManager.getLteOnCdmaModeStatic();
+ }
+
+ @Override
+ public void testingEmergencyCall() {}
+}
diff --git a/src/java/com/android/internal/telephony/Call.java b/src/java/com/android/internal/telephony/Call.java
new file mode 100644
index 0000000..4967ab8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/Call.java
@@ -0,0 +1,255 @@
+/*
+ * 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;
+
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+public abstract class Call {
+ /* Enums */
+
+ public enum State {
+ IDLE, ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING, DISCONNECTED, DISCONNECTING;
+
+ public boolean isAlive() {
+ return !(this == IDLE || this == DISCONNECTED || this == DISCONNECTING);
+ }
+
+ public boolean isRinging() {
+ return this == INCOMING || this == WAITING;
+ }
+
+ public boolean isDialing() {
+ return this == DIALING || this == ALERTING;
+ }
+ }
+
+
+ /* Instance Variables */
+
+ public State state = State.IDLE;
+
+
+ // Flag to indicate if the current calling/caller information
+ // is accurate. If false the information is known to be accurate.
+ //
+ // For CDMA, during call waiting/3 way, there is no network response
+ // if call waiting is answered, network timed out, dropped, 3 way
+ // merged, etc.
+ protected boolean isGeneric = false;
+
+ protected final String LOG_TAG = "Call";
+
+ /* 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 Phone getPhone();
+ public abstract boolean isMultiparty();
+ public abstract void hangup() throws CallStateException;
+
+
+ /**
+ * 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;
+ }
+
+ /**
+ * getState
+ * @return state of class call
+ */
+ public State getState() {
+ return state;
+ }
+
+ /**
+ * 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;
+ time = t;
+ }
+ }
+
+ 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() {
+ long time = Long.MAX_VALUE;
+ List 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 boolean
+ isDialingOrAlerting() {
+ return getState().isDialing();
+ }
+
+ public boolean
+ isRinging() {
+ return getState().isRinging();
+ }
+
+ /**
+ * Returns the Connection associated with this Call that was created
+ * last, or null if there are no Connections in this Call
+ */
+ public Connection
+ getLatestConnection() {
+ List l = getConnections();
+ if (l.size() == 0) {
+ return null;
+ }
+
+ long time = 0;
+ Connection latest = null;
+ for (int i = 0, s = l.size() ; i < s ; i++) {
+ Connection c = (Connection) l.get(i);
+ long t = c.getCreateTime();
+
+ if (t > time) {
+ latest = c;
+ time = t;
+ }
+ }
+
+ return latest;
+ }
+
+ /**
+ * To indicate if the connection information is accurate
+ * or not. false means accurate. Only used for CDMA.
+ */
+ public boolean isGeneric() {
+ return isGeneric;
+ }
+
+ /**
+ * Set the generic instance variable
+ */
+ public void setGeneric(boolean generic) {
+ isGeneric = generic;
+ }
+
+ /**
+ * Hangup call if it is alive
+ */
+ public void hangupIfAlive() {
+ if (getState().isAlive()) {
+ try {
+ hangup();
+ } catch (CallStateException ex) {
+ Log.w(LOG_TAG, " hangupIfActive: caught " + ex);
+ }
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/CallForwardInfo.java b/src/java/com/android/internal/telephony/CallForwardInfo.java
new file mode 100644
index 0000000..8b853b0
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CallForwardInfo.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;
+
+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/src/java/com/android/internal/telephony/CallManager.java b/src/java/com/android/internal/telephony/CallManager.java
new file mode 100644
index 0000000..b87ea50
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CallManager.java
@@ -0,0 +1,1855 @@
+/*
+ * Copyright (C) 2010 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 com.android.internal.telephony.sip.SipPhone;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RegistrantList;
+import android.os.Registrant;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+
+/**
+ * @hide
+ *
+ * CallManager class provides an abstract layer for PhoneApp to access
+ * and control calls. It implements Phone interface.
+ *
+ * CallManager provides call and connection control as well as
+ * channel capability.
+ *
+ * There are three categories of APIs CallManager provided
+ *
+ * 1. Call control and operation, such as dial() and hangup()
+ * 2. Channel capabilities, such as CanConference()
+ * 3. Register notification
+ *
+ *
+ */
+public final class CallManager {
+
+ private static final String LOG_TAG ="CallManager";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ private static final int EVENT_DISCONNECT = 100;
+ private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 101;
+ private static final int EVENT_NEW_RINGING_CONNECTION = 102;
+ private static final int EVENT_UNKNOWN_CONNECTION = 103;
+ private static final int EVENT_INCOMING_RING = 104;
+ private static final int EVENT_RINGBACK_TONE = 105;
+ private static final int EVENT_IN_CALL_VOICE_PRIVACY_ON = 106;
+ private static final int EVENT_IN_CALL_VOICE_PRIVACY_OFF = 107;
+ private static final int EVENT_CALL_WAITING = 108;
+ private static final int EVENT_DISPLAY_INFO = 109;
+ private static final int EVENT_SIGNAL_INFO = 110;
+ private static final int EVENT_CDMA_OTA_STATUS_CHANGE = 111;
+ private static final int EVENT_RESEND_INCALL_MUTE = 112;
+ private static final int EVENT_MMI_INITIATE = 113;
+ private static final int EVENT_MMI_COMPLETE = 114;
+ private static final int EVENT_ECM_TIMER_RESET = 115;
+ private static final int EVENT_SUBSCRIPTION_INFO_READY = 116;
+ private static final int EVENT_SUPP_SERVICE_FAILED = 117;
+ private static final int EVENT_SERVICE_STATE_CHANGED = 118;
+ private static final int EVENT_POST_DIAL_CHARACTER = 119;
+
+ // Singleton instance
+ private static final CallManager INSTANCE = new CallManager();
+
+ // list of registered phones, which are PhoneBase objs
+ private final ArrayList<Phone> mPhones;
+
+ // list of supported ringing calls
+ private final ArrayList<Call> mRingingCalls;
+
+ // list of supported background calls
+ private final ArrayList<Call> mBackgroundCalls;
+
+ // list of supported foreground calls
+ private final ArrayList<Call> mForegroundCalls;
+
+ // empty connection list
+ private final ArrayList<Connection> emptyConnections = new ArrayList<Connection>();
+
+ // default phone as the first phone registered, which is PhoneBase obj
+ private Phone mDefaultPhone;
+
+ // state registrants
+ protected final RegistrantList mPreciseCallStateRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mNewRingingConnectionRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mIncomingRingRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mDisconnectRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mMmiRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mUnknownConnectionRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mRingbackToneRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mInCallVoicePrivacyOnRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mInCallVoicePrivacyOffRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mCallWaitingRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mDisplayInfoRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mSignalInfoRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mCdmaOtaStatusChangeRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mResendIncallMuteRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mMmiInitiateRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mMmiCompleteRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mEcmTimerResetRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mSubscriptionInfoReadyRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mSuppServiceFailedRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mServiceStateChangedRegistrants
+ = new RegistrantList();
+
+ protected final RegistrantList mPostDialCharacterRegistrants
+ = new RegistrantList();
+
+ private CallManager() {
+ mPhones = new ArrayList<Phone>();
+ mRingingCalls = new ArrayList<Call>();
+ mBackgroundCalls = new ArrayList<Call>();
+ mForegroundCalls = new ArrayList<Call>();
+ mDefaultPhone = null;
+ }
+
+ /**
+ * get singleton instance of CallManager
+ * @return CallManager
+ */
+ public static CallManager getInstance() {
+ return INSTANCE;
+ }
+
+ /**
+ * Get the corresponding PhoneBase obj
+ *
+ * @param phone a Phone object
+ * @return the corresponding PhoneBase obj in Phone if Phone
+ * is a PhoneProxy obj
+ * or the Phone itself if Phone is not a PhoneProxy obj
+ */
+ private static Phone getPhoneBase(Phone phone) {
+ if (phone instanceof PhoneProxy) {
+ return phone.getForegroundCall().getPhone();
+ }
+ return phone;
+ }
+
+ /**
+ * Check if two phones refer to the same PhoneBase obj
+ *
+ * Note: PhoneBase, not PhoneProxy, is to be used inside of CallManager
+ *
+ * Both PhoneBase and PhoneProxy implement Phone interface, so
+ * they have same phone APIs, such as dial(). The real implementation, for
+ * example in GSM, is in GSMPhone as extend from PhoneBase, so that
+ * foregroundCall.getPhone() returns GSMPhone obj. On the other hand,
+ * PhoneFactory.getDefaultPhone() returns PhoneProxy obj, which has a class
+ * member of GSMPhone.
+ *
+ * So for phone returned by PhoneFacotry, which is used by PhoneApp,
+ * phone.getForegroundCall().getPhone() != phone
+ * but
+ * isSamePhone(phone, phone.getForegroundCall().getPhone()) == true
+ *
+ * @param p1 is the first Phone obj
+ * @param p2 is the second Phone obj
+ * @return true if p1 and p2 refer to the same phone
+ */
+ public static boolean isSamePhone(Phone p1, Phone p2) {
+ return (getPhoneBase(p1) == getPhoneBase(p2));
+ }
+
+ /**
+ * Returns all the registered phone objects.
+ * @return all the registered phone objects.
+ */
+ public List<Phone> getAllPhones() {
+ return Collections.unmodifiableList(mPhones);
+ }
+
+ /**
+ * Get current coarse-grained voice call state.
+ * If the Call Manager has an active call and call waiting occurs,
+ * then the phone state is RINGING not OFFHOOK
+ *
+ */
+ public PhoneConstants.State getState() {
+ PhoneConstants.State s = PhoneConstants.State.IDLE;
+
+ for (Phone phone : mPhones) {
+ if (phone.getState() == PhoneConstants.State.RINGING) {
+ s = PhoneConstants.State.RINGING;
+ } else if (phone.getState() == PhoneConstants.State.OFFHOOK) {
+ if (s == PhoneConstants.State.IDLE) s = PhoneConstants.State.OFFHOOK;
+ }
+ }
+ return s;
+ }
+
+ /**
+ * @return the service state of CallManager, which represents the
+ * highest priority state of all the service states of phones
+ *
+ * The priority is defined as
+ *
+ * STATE_IN_SERIVCE > STATE_OUT_OF_SERIVCE > STATE_EMERGENCY > STATE_POWER_OFF
+ *
+ */
+
+ public int getServiceState() {
+ int resultState = ServiceState.STATE_OUT_OF_SERVICE;
+
+ for (Phone phone : mPhones) {
+ int serviceState = phone.getServiceState().getState();
+ if (serviceState == ServiceState.STATE_IN_SERVICE) {
+ // IN_SERVICE has the highest priority
+ resultState = serviceState;
+ break;
+ } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
+ // OUT_OF_SERVICE replaces EMERGENCY_ONLY and POWER_OFF
+ // Note: EMERGENCY_ONLY is not in use at this moment
+ if ( resultState == ServiceState.STATE_EMERGENCY_ONLY ||
+ resultState == ServiceState.STATE_POWER_OFF) {
+ resultState = serviceState;
+ }
+ } else if (serviceState == ServiceState.STATE_EMERGENCY_ONLY) {
+ if (resultState == ServiceState.STATE_POWER_OFF) {
+ resultState = serviceState;
+ }
+ }
+ }
+ return resultState;
+ }
+
+ /**
+ * Register phone to CallManager
+ * @param phone to be registered
+ * @return true if register successfully
+ */
+ public boolean registerPhone(Phone phone) {
+ Phone basePhone = getPhoneBase(phone);
+
+ if (basePhone != null && !mPhones.contains(basePhone)) {
+
+ if (DBG) {
+ Log.d(LOG_TAG, "registerPhone(" +
+ phone.getPhoneName() + " " + phone + ")");
+ }
+
+ if (mPhones.isEmpty()) {
+ mDefaultPhone = basePhone;
+ }
+ mPhones.add(basePhone);
+ mRingingCalls.add(basePhone.getRingingCall());
+ mBackgroundCalls.add(basePhone.getBackgroundCall());
+ mForegroundCalls.add(basePhone.getForegroundCall());
+ registerForPhoneStates(basePhone);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * unregister phone from CallManager
+ * @param phone to be unregistered
+ */
+ public void unregisterPhone(Phone phone) {
+ Phone basePhone = getPhoneBase(phone);
+
+ if (basePhone != null && mPhones.contains(basePhone)) {
+
+ if (DBG) {
+ Log.d(LOG_TAG, "unregisterPhone(" +
+ phone.getPhoneName() + " " + phone + ")");
+ }
+
+ mPhones.remove(basePhone);
+ mRingingCalls.remove(basePhone.getRingingCall());
+ mBackgroundCalls.remove(basePhone.getBackgroundCall());
+ mForegroundCalls.remove(basePhone.getForegroundCall());
+ unregisterForPhoneStates(basePhone);
+ if (basePhone == mDefaultPhone) {
+ if (mPhones.isEmpty()) {
+ mDefaultPhone = null;
+ } else {
+ mDefaultPhone = mPhones.get(0);
+ }
+ }
+ }
+ }
+
+ /**
+ * return the default phone or null if no phone available
+ */
+ public Phone getDefaultPhone() {
+ return mDefaultPhone;
+ }
+
+ /**
+ * @return the phone associated with the foreground call
+ */
+ public Phone getFgPhone() {
+ return getActiveFgCall().getPhone();
+ }
+
+ /**
+ * @return the phone associated with the background call
+ */
+ public Phone getBgPhone() {
+ return getFirstActiveBgCall().getPhone();
+ }
+
+ /**
+ * @return the phone associated with the ringing call
+ */
+ public Phone getRingingPhone() {
+ return getFirstActiveRingingCall().getPhone();
+ }
+
+ public void setAudioMode() {
+ Context context = getContext();
+ if (context == null) return;
+ AudioManager audioManager = (AudioManager)
+ context.getSystemService(Context.AUDIO_SERVICE);
+
+ // change the audio mode and request/abandon audio focus according to phone state,
+ // but only on audio mode transitions
+ switch (getState()) {
+ case RINGING:
+ if (audioManager.getMode() != AudioManager.MODE_RINGTONE) {
+ // only request audio focus if the ringtone is going to be heard
+ if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
+ if (VDBG) Log.d(LOG_TAG, "requestAudioFocus on STREAM_RING");
+ audioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ }
+ audioManager.setMode(AudioManager.MODE_RINGTONE);
+ }
+ break;
+ case OFFHOOK:
+ Phone offhookPhone = getFgPhone();
+ if (getActiveFgCallState() == Call.State.IDLE) {
+ // There is no active Fg calls, the OFFHOOK state
+ // is set by the Bg call. So set the phone to bgPhone.
+ offhookPhone = getBgPhone();
+ }
+
+ int newAudioMode = AudioManager.MODE_IN_CALL;
+ if (offhookPhone instanceof SipPhone) {
+ // enable IN_COMMUNICATION audio mode instead for sipPhone
+ newAudioMode = AudioManager.MODE_IN_COMMUNICATION;
+ }
+ if (audioManager.getMode() != newAudioMode) {
+ // request audio focus before setting the new mode
+ if (VDBG) Log.d(LOG_TAG, "requestAudioFocus on STREAM_VOICE_CALL");
+ audioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ audioManager.setMode(newAudioMode);
+ }
+ break;
+ case IDLE:
+ if (audioManager.getMode() != AudioManager.MODE_NORMAL) {
+ audioManager.setMode(AudioManager.MODE_NORMAL);
+ if (VDBG) Log.d(LOG_TAG, "abandonAudioFocus");
+ // abandon audio focus after the mode has been set back to normal
+ audioManager.abandonAudioFocusForCall();
+ }
+ break;
+ }
+ }
+
+ private Context getContext() {
+ Phone defaultPhone = getDefaultPhone();
+ return ((defaultPhone == null) ? null : defaultPhone.getContext());
+ }
+
+ private void registerForPhoneStates(Phone phone) {
+ // for common events supported by all phones
+ phone.registerForPreciseCallStateChanged(mHandler, EVENT_PRECISE_CALL_STATE_CHANGED, null);
+ phone.registerForDisconnect(mHandler, EVENT_DISCONNECT, null);
+ phone.registerForNewRingingConnection(mHandler, EVENT_NEW_RINGING_CONNECTION, null);
+ phone.registerForUnknownConnection(mHandler, EVENT_UNKNOWN_CONNECTION, null);
+ phone.registerForIncomingRing(mHandler, EVENT_INCOMING_RING, null);
+ phone.registerForRingbackTone(mHandler, EVENT_RINGBACK_TONE, null);
+ phone.registerForInCallVoicePrivacyOn(mHandler, EVENT_IN_CALL_VOICE_PRIVACY_ON, null);
+ phone.registerForInCallVoicePrivacyOff(mHandler, EVENT_IN_CALL_VOICE_PRIVACY_OFF, null);
+ phone.registerForDisplayInfo(mHandler, EVENT_DISPLAY_INFO, null);
+ phone.registerForSignalInfo(mHandler, EVENT_SIGNAL_INFO, null);
+ phone.registerForResendIncallMute(mHandler, EVENT_RESEND_INCALL_MUTE, null);
+ phone.registerForMmiInitiate(mHandler, EVENT_MMI_INITIATE, null);
+ phone.registerForMmiComplete(mHandler, EVENT_MMI_COMPLETE, null);
+ phone.registerForSuppServiceFailed(mHandler, EVENT_SUPP_SERVICE_FAILED, null);
+ phone.registerForServiceStateChanged(mHandler, EVENT_SERVICE_STATE_CHANGED, null);
+
+ // for events supported only by GSM and CDMA phone
+ if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
+ phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+ phone.setOnPostDialCharacter(mHandler, EVENT_POST_DIAL_CHARACTER, null);
+ }
+
+ // for events supported only by CDMA phone
+ if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA ){
+ phone.registerForCdmaOtaStatusChange(mHandler, EVENT_CDMA_OTA_STATUS_CHANGE, null);
+ phone.registerForSubscriptionInfoReady(mHandler, EVENT_SUBSCRIPTION_INFO_READY, null);
+ phone.registerForCallWaiting(mHandler, EVENT_CALL_WAITING, null);
+ phone.registerForEcmTimerReset(mHandler, EVENT_ECM_TIMER_RESET, null);
+ }
+ }
+
+ private void unregisterForPhoneStates(Phone phone) {
+ // for common events supported by all phones
+ phone.unregisterForPreciseCallStateChanged(mHandler);
+ phone.unregisterForDisconnect(mHandler);
+ phone.unregisterForNewRingingConnection(mHandler);
+ phone.unregisterForUnknownConnection(mHandler);
+ phone.unregisterForIncomingRing(mHandler);
+ phone.unregisterForRingbackTone(mHandler);
+ phone.unregisterForInCallVoicePrivacyOn(mHandler);
+ phone.unregisterForInCallVoicePrivacyOff(mHandler);
+ phone.unregisterForDisplayInfo(mHandler);
+ phone.unregisterForSignalInfo(mHandler);
+ phone.unregisterForResendIncallMute(mHandler);
+ phone.unregisterForMmiInitiate(mHandler);
+ phone.unregisterForMmiComplete(mHandler);
+ phone.unregisterForSuppServiceFailed(mHandler);
+ phone.unregisterForServiceStateChanged(mHandler);
+
+ // for events supported only by GSM and CDMA phone
+ if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
+ phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+ phone.setOnPostDialCharacter(null, EVENT_POST_DIAL_CHARACTER, null);
+ }
+
+ // for events supported only by CDMA phone
+ if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA ){
+ phone.unregisterForCdmaOtaStatusChange(mHandler);
+ phone.unregisterForSubscriptionInfoReady(mHandler);
+ phone.unregisterForCallWaiting(mHandler);
+ phone.unregisterForEcmTimerReset(mHandler);
+ }
+ }
+
+ /**
+ * Answers a ringing or waiting call.
+ *
+ * Active call, if any, go on hold.
+ * If active call can't be held, i.e., a background call of the same channel exists,
+ * the active call will be hang up.
+ *
+ * Answering occurs asynchronously, and final notification occurs via
+ * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @exception CallStateException when call is not ringing or waiting
+ */
+ public void acceptCall(Call ringingCall) throws CallStateException {
+ Phone ringingPhone = ringingCall.getPhone();
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "acceptCall(" +ringingCall + " from " + ringingCall.getPhone() + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if ( hasActiveFgCall() ) {
+ Phone activePhone = getActiveFgCall().getPhone();
+ boolean hasBgCall = ! (activePhone.getBackgroundCall().isIdle());
+ boolean sameChannel = (activePhone == ringingPhone);
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "hasBgCall: "+ hasBgCall + "sameChannel:" + sameChannel);
+ }
+
+ if (sameChannel && hasBgCall) {
+ getActiveFgCall().hangup();
+ } else if (!sameChannel && !hasBgCall) {
+ activePhone.switchHoldingAndActive();
+ } else if (!sameChannel && hasBgCall) {
+ getActiveFgCall().hangup();
+ }
+ }
+
+ ringingPhone.acceptCall();
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End acceptCall(" +ringingCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+ }
+
+ /**
+ * Reject (ignore) a ringing call. In GSM, this means UDUB
+ * (User Determined User Busy). Reject occurs asynchronously,
+ * and final notification occurs via
+ * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @exception CallStateException when no call is ringing or waiting
+ */
+ public void rejectCall(Call ringingCall) throws CallStateException {
+ if (VDBG) {
+ Log.d(LOG_TAG, "rejectCall(" +ringingCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ Phone ringingPhone = ringingCall.getPhone();
+
+ ringingPhone.rejectCall();
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End rejectCall(" +ringingCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+ }
+
+ /**
+ * Places active call on hold, and makes held call active.
+ * Switch occurs asynchronously and may fail.
+ *
+ * There are 4 scenarios
+ * 1. only active call but no held call, aka, hold
+ * 2. no active call but only held call, aka, unhold
+ * 3. both active and held calls from same phone, aka, swap
+ * 4. active and held calls from different phones, aka, phone swap
+ *
+ * Final notification occurs via
+ * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @exception CallStateException if active call is ringing, waiting, or
+ * dialing/alerting, or heldCall can't be active.
+ * In these cases, this operation may not be performed.
+ */
+ public void switchHoldingAndActive(Call heldCall) throws CallStateException {
+ Phone activePhone = null;
+ Phone heldPhone = null;
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "switchHoldingAndActive(" +heldCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (hasActiveFgCall()) {
+ activePhone = getActiveFgCall().getPhone();
+ }
+
+ if (heldCall != null) {
+ heldPhone = heldCall.getPhone();
+ }
+
+ if (activePhone != null) {
+ activePhone.switchHoldingAndActive();
+ }
+
+ if (heldPhone != null && heldPhone != activePhone) {
+ heldPhone.switchHoldingAndActive();
+ }
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End switchHoldingAndActive(" +heldCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+ }
+
+ /**
+ * Hangup foreground call and resume the specific background call
+ *
+ * Note: this is noop if there is no foreground call or the heldCall is null
+ *
+ * @param heldCall to become foreground
+ * @throws CallStateException
+ */
+ public void hangupForegroundResumeBackground(Call heldCall) throws CallStateException {
+ Phone foregroundPhone = null;
+ Phone backgroundPhone = null;
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "hangupForegroundResumeBackground(" +heldCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (hasActiveFgCall()) {
+ foregroundPhone = getFgPhone();
+ if (heldCall != null) {
+ backgroundPhone = heldCall.getPhone();
+ if (foregroundPhone == backgroundPhone) {
+ getActiveFgCall().hangup();
+ } else {
+ // the call to be hangup and resumed belongs to different phones
+ getActiveFgCall().hangup();
+ switchHoldingAndActive(heldCall);
+ }
+ }
+ }
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End hangupForegroundResumeBackground(" +heldCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public boolean canConference(Call heldCall) {
+ Phone activePhone = null;
+ Phone heldPhone = null;
+
+ if (hasActiveFgCall()) {
+ activePhone = getActiveFgCall().getPhone();
+ }
+
+ if (heldCall != null) {
+ heldPhone = heldCall.getPhone();
+ }
+
+ return heldPhone.getClass().equals(activePhone.getClass());
+ }
+
+ /**
+ * Conferences holding and active. Conference occurs asynchronously
+ * and may fail. Final notification occurs via
+ * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @exception CallStateException if canConference() would return false.
+ * In these cases, this operation may not be performed.
+ */
+ public void conference(Call heldCall) throws CallStateException {
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "conference(" +heldCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+
+ Phone fgPhone = getFgPhone();
+ if (fgPhone instanceof SipPhone) {
+ ((SipPhone) fgPhone).conference(heldCall);
+ } else if (canConference(heldCall)) {
+ fgPhone.conference();
+ } else {
+ throw(new CallStateException("Can't conference foreground and selected background call"));
+ }
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End conference(" +heldCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ }
+
+ /**
+ * 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.
+ */
+ public Connection dial(Phone phone, String dialString) throws CallStateException {
+ Phone basePhone = getPhoneBase(phone);
+ Connection result;
+
+ if (VDBG) {
+ Log.d(LOG_TAG, " dial(" + basePhone + ", "+ dialString + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (!canDial(phone)) {
+ throw new CallStateException("cannot dial in current state");
+ }
+
+ if ( hasActiveFgCall() ) {
+ Phone activePhone = getActiveFgCall().getPhone();
+ boolean hasBgCall = !(activePhone.getBackgroundCall().isIdle());
+
+ if (DBG) {
+ Log.d(LOG_TAG, "hasBgCall: "+ hasBgCall + " sameChannel:" + (activePhone == basePhone));
+ }
+
+ if (activePhone != basePhone) {
+ if (hasBgCall) {
+ Log.d(LOG_TAG, "Hangup");
+ getActiveFgCall().hangup();
+ } else {
+ Log.d(LOG_TAG, "Switch");
+ activePhone.switchHoldingAndActive();
+ }
+ }
+ }
+
+ result = basePhone.dial(dialString);
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End dial(" + basePhone + ", "+ dialString + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ return result;
+ }
+
+ /**
+ * 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.
+ */
+ public Connection dial(Phone phone, String dialString, UUSInfo uusInfo) throws CallStateException {
+ return phone.dial(dialString, uusInfo);
+ }
+
+ /**
+ * clear disconnect connection for each phone
+ */
+ public void clearDisconnected() {
+ for(Phone phone : mPhones) {
+ phone.clearDisconnected();
+ }
+ }
+
+ /**
+ * Phone can make a call only if ALL of the following are true:
+ * - Phone is not powered off
+ * - There's no incoming or waiting call
+ * - There's available call slot in either foreground or background
+ * - The foreground call is ACTIVE or IDLE or DISCONNECTED.
+ * (We mainly need to make sure it *isn't* DIALING or ALERTING.)
+ * @param phone
+ * @return true if the phone can make a new call
+ */
+ private boolean canDial(Phone phone) {
+ int serviceState = phone.getServiceState().getState();
+ boolean hasRingingCall = hasActiveRingingCall();
+ boolean hasActiveCall = hasActiveFgCall();
+ boolean hasHoldingCall = hasActiveBgCall();
+ boolean allLinesTaken = hasActiveCall && hasHoldingCall;
+ Call.State fgCallState = getActiveFgCallState();
+
+ boolean result = (serviceState != ServiceState.STATE_POWER_OFF
+ && !hasRingingCall
+ && !allLinesTaken
+ && ((fgCallState == Call.State.ACTIVE)
+ || (fgCallState == Call.State.IDLE)
+ || (fgCallState == Call.State.DISCONNECTED)));
+
+ if (result == false) {
+ Log.d(LOG_TAG, "canDial serviceState=" + serviceState
+ + " hasRingingCall=" + hasRingingCall
+ + " hasActiveCall=" + hasActiveCall
+ + " hasHoldingCall=" + hasHoldingCall
+ + " allLinesTaken=" + allLinesTaken
+ + " fgCallState=" + fgCallState);
+ }
+ return result;
+ }
+
+ /**
+ * 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.
+ */
+ public boolean canTransfer(Call heldCall) {
+ Phone activePhone = null;
+ Phone heldPhone = null;
+
+ if (hasActiveFgCall()) {
+ activePhone = getActiveFgCall().getPhone();
+ }
+
+ if (heldCall != null) {
+ heldPhone = heldCall.getPhone();
+ }
+
+ return (heldPhone == activePhone && activePhone.canTransfer());
+ }
+
+ /**
+ * Connects the held call and active call
+ * Disconnects the subscriber from both calls
+ *
+ * Explicit Call Transfer occurs asynchronously
+ * and may fail. Final notification occurs via
+ * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @exception CallStateException if canTransfer() would return false.
+ * In these cases, this operation may not be performed.
+ */
+ public void explicitCallTransfer(Call heldCall) throws CallStateException {
+ if (VDBG) {
+ Log.d(LOG_TAG, " explicitCallTransfer(" + heldCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (canTransfer(heldCall)) {
+ heldCall.getPhone().explicitCallTransfer();
+ }
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End explicitCallTransfer(" + heldCall + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ }
+
+ /**
+ * Returns a list of MMI codes that are pending for a phone. (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.
+ * @return null if phone doesn't have or support mmi code
+ */
+ public List<? extends MmiCode> getPendingMmiCodes(Phone phone) {
+ Log.e(LOG_TAG, "getPendingMmiCodes not implemented");
+ return null;
+ }
+
+ /**
+ * 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.
+ * @return false if phone doesn't support ussd service
+ */
+ public boolean sendUssdResponse(Phone phone, String ussdMessge) {
+ Log.e(LOG_TAG, "sendUssdResponse not implemented");
+ return false;
+ }
+
+ /**
+ * 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.
+ */
+
+ public void setMute(boolean muted) {
+ if (VDBG) {
+ Log.d(LOG_TAG, " setMute(" + muted + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (hasActiveFgCall()) {
+ getActiveFgCall().getPhone().setMute(muted);
+ }
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End setMute(" + muted + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+ }
+
+ /**
+ * Gets current mute status. Use
+ * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}
+ * as a change notifcation, although presently phone state changed is not
+ * fired when setMute() is called.
+ *
+ * @return true is muting, false is unmuting
+ */
+ public boolean getMute() {
+ if (hasActiveFgCall()) {
+ return getActiveFgCall().getPhone().getMute();
+ } else if (hasActiveBgCall()) {
+ return getFirstActiveBgCall().getPhone().getMute();
+ }
+ return false;
+ }
+
+ /**
+ * Enables or disables echo suppression.
+ */
+ public void setEchoSuppressionEnabled(boolean enabled) {
+ if (VDBG) {
+ Log.d(LOG_TAG, " setEchoSuppression(" + enabled + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (hasActiveFgCall()) {
+ getActiveFgCall().getPhone().setEchoSuppressionEnabled(enabled);
+ }
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End setEchoSuppression(" + enabled + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+ }
+
+ /**
+ * Play a DTMF tone on the active call.
+ *
+ * @param c should be one of 0-9, '*' or '#'. Other values will be
+ * silently ignored.
+ * @return false if no active call or the active call doesn't support
+ * dtmf tone
+ */
+ public boolean sendDtmf(char c) {
+ boolean result = false;
+
+ if (VDBG) {
+ Log.d(LOG_TAG, " sendDtmf(" + c + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (hasActiveFgCall()) {
+ getActiveFgCall().getPhone().sendDtmf(c);
+ result = true;
+ }
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End sendDtmf(" + c + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+ return result;
+ }
+
+ /**
+ * Start to paly a DTMF tone on the active call.
+ * or there is a playing DTMF tone.
+ * @param c should be one of 0-9, '*' or '#'. Other values will be
+ * silently ignored.
+ *
+ * @return false if no active call or the active call doesn't support
+ * dtmf tone
+ */
+ public boolean startDtmf(char c) {
+ boolean result = false;
+
+ if (VDBG) {
+ Log.d(LOG_TAG, " startDtmf(" + c + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (hasActiveFgCall()) {
+ getActiveFgCall().getPhone().startDtmf(c);
+ result = true;
+ }
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End startDtmf(" + c + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ return result;
+ }
+
+ /**
+ * Stop the playing DTMF tone. Ignored if there is no playing DTMF
+ * tone or no active call.
+ */
+ public void stopDtmf() {
+ if (VDBG) {
+ Log.d(LOG_TAG, " stopDtmf()" );
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (hasActiveFgCall()) getFgPhone().stopDtmf();
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End stopDtmf()");
+ Log.d(LOG_TAG, this.toString());
+ }
+ }
+
+ /**
+ * send burst DTMF tone, it can send the string as single character or multiple character
+ * ignore if there is no active call or not valid digits string.
+ * Valid digit means only includes characters ISO-LATIN characters 0-9, *, #
+ * The difference between sendDtmf and sendBurstDtmf is sendDtmf only sends one character,
+ * this api can send single character and multiple character, also, this api has response
+ * back to caller.
+ *
+ * @param dtmfString is string representing the dialing digit(s) in the active call
+ * @param on the DTMF ON length in milliseconds, or 0 for default
+ * @param off the DTMF OFF length in milliseconds, or 0 for default
+ * @param onComplete is the callback message when the action is processed by BP
+ *
+ */
+ public boolean sendBurstDtmf(String dtmfString, int on, int off, Message onComplete) {
+ if (hasActiveFgCall()) {
+ getActiveFgCall().getPhone().sendBurstDtmf(dtmfString, on, off, onComplete);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 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>
+ */
+ public void registerForDisconnect(Handler h, int what, Object obj) {
+ mDisconnectRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for voice disconnection notification.
+ * Extraneous calls are tolerated silently
+ */
+ public void unregisterForDisconnect(Handler h){
+ mDisconnectRegistrants.remove(h);
+ }
+
+ /**
+ * Register for getting notifications for change in the Call State {@link Call.State}
+ * This is called PreciseCallState because the call state is more precise than the
+ * {@link Phone.State} which can be obtained using the {@link PhoneStateListener}
+ *
+ * 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.
+ */
+ public void registerForPreciseCallStateChanged(Handler h, int what, Object obj){
+ mPreciseCallStateRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for voice call state change notifications.
+ * Extraneous calls are tolerated silently.
+ */
+ public void unregisterForPreciseCallStateChanged(Handler h){
+ mPreciseCallStateRegistrants.remove(h);
+ }
+
+ /**
+ * 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.
+ */
+ public void registerForUnknownConnection(Handler h, int what, Object obj){
+ mUnknownConnectionRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for unknown connection notifications.
+ */
+ public void unregisterForUnknownConnection(Handler h){
+ mUnknownConnectionRegistrants.remove(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()
+ */
+ public void registerForNewRingingConnection(Handler h, int what, Object obj){
+ mNewRingingConnectionRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for new ringing connection notification.
+ * Extraneous calls are tolerated silently
+ */
+
+ public void unregisterForNewRingingConnection(Handler h){
+ mNewRingingConnectionRegistrants.remove(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>
+ */
+ public void registerForIncomingRing(Handler h, int what, Object obj){
+ mIncomingRingRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for ring notification.
+ * Extraneous calls are tolerated silently
+ */
+
+ public void unregisterForIncomingRing(Handler h){
+ mIncomingRingRegistrants.remove(h);
+ }
+
+ /**
+ * Notifies when out-band ringback tone is needed.<p>
+ *
+ * Messages received from this:
+ * Message.obj will be an AsyncResult
+ * AsyncResult.userObj = obj
+ * AsyncResult.result = boolean, true to start play ringback tone
+ * and false to stop. <p>
+ */
+ public void registerForRingbackTone(Handler h, int what, Object obj){
+ mRingbackToneRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for ringback tone notification.
+ */
+
+ public void unregisterForRingbackTone(Handler h){
+ mRingbackToneRegistrants.remove(h);
+ }
+
+ /**
+ * Registers the handler to reset the uplink mute state to get
+ * uplink audio.
+ */
+ public void registerForResendIncallMute(Handler h, int what, Object obj){
+ mResendIncallMuteRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for resend incall mute notifications.
+ */
+ public void unregisterForResendIncallMute(Handler h){
+ mResendIncallMuteRegistrants.remove(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.
+ */
+ public void registerForMmiInitiate(Handler h, int what, Object obj){
+ mMmiInitiateRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for new MMI initiate notification.
+ * Extraneous calls are tolerated silently
+ */
+ public void unregisterForMmiInitiate(Handler h){
+ mMmiInitiateRegistrants.remove(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
+ */
+ public void registerForMmiComplete(Handler h, int what, Object obj){
+ mMmiCompleteRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for MMI complete notification.
+ * Extraneous calls are tolerated silently
+ */
+ public void unregisterForMmiComplete(Handler h){
+ mMmiCompleteRegistrants.remove(h);
+ }
+
+ /**
+ * Registration point for Ecm timer reset
+ * @param h handler to notify
+ * @param what user-defined message code
+ * @param obj placed in Message.obj
+ */
+ public void registerForEcmTimerReset(Handler h, int what, Object obj){
+ mEcmTimerResetRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregister for notification for Ecm timer reset
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForEcmTimerReset(Handler h){
+ mEcmTimerResetRegistrants.remove(h);
+ }
+
+ /**
+ * Register for ServiceState changed.
+ * Message.obj will contain an AsyncResult.
+ * AsyncResult.result will be a ServiceState instance
+ */
+ public void registerForServiceStateChanged(Handler h, int what, Object obj){
+ mServiceStateChangedRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for ServiceStateChange notification.
+ * Extraneous calls are tolerated silently
+ */
+ public void unregisterForServiceStateChanged(Handler h){
+ mServiceStateChangedRegistrants.remove(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.
+ */
+ public void registerForSuppServiceFailed(Handler h, int what, Object obj){
+ mSuppServiceFailedRegistrants.addUnique(h, what, 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.
+ */
+ public void unregisterForSuppServiceFailed(Handler h){
+ mSuppServiceFailedRegistrants.remove(h);
+ }
+
+ /**
+ * Register for notifications when a sInCall VoicePrivacy is enabled
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){
+ mInCallVoicePrivacyOnRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregister for notifications when a sInCall VoicePrivacy is enabled
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForInCallVoicePrivacyOn(Handler h){
+ mInCallVoicePrivacyOnRegistrants.remove(h);
+ }
+
+ /**
+ * Register for notifications when a sInCall VoicePrivacy is disabled
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){
+ mInCallVoicePrivacyOffRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregister for notifications when a sInCall VoicePrivacy is disabled
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForInCallVoicePrivacyOff(Handler h){
+ mInCallVoicePrivacyOffRegistrants.remove(h);
+ }
+
+ /**
+ * Register for notifications when CDMA call waiting comes
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ public void registerForCallWaiting(Handler h, int what, Object obj){
+ mCallWaitingRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregister for notifications when CDMA Call waiting comes
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForCallWaiting(Handler h){
+ mCallWaitingRegistrants.remove(h);
+ }
+
+
+ /**
+ * Register for signal information 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.
+ */
+
+ public void registerForSignalInfo(Handler h, int what, Object obj){
+ mSignalInfoRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for signal information notifications.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForSignalInfo(Handler h){
+ mSignalInfoRegistrants.remove(h);
+ }
+
+ /**
+ * Register for display information 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.
+ */
+ public void registerForDisplayInfo(Handler h, int what, Object obj){
+ mDisplayInfoRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregisters for display information notifications.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForDisplayInfo(Handler h) {
+ mDisplayInfoRegistrants.remove(h);
+ }
+
+ /**
+ * Register for notifications when CDMA OTA Provision status change
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj){
+ mCdmaOtaStatusChangeRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregister for notifications when CDMA OTA Provision status change
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForCdmaOtaStatusChange(Handler h){
+ mCdmaOtaStatusChangeRegistrants.remove(h);
+ }
+
+ /**
+ * Registration point for subscription info ready
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ public void registerForSubscriptionInfoReady(Handler h, int what, Object obj){
+ mSubscriptionInfoReadyRegistrants.addUnique(h, what, obj);
+ }
+
+ /**
+ * Unregister for notifications for subscription info
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForSubscriptionInfoReady(Handler h){
+ mSubscriptionInfoReadyRegistrants.remove(h);
+ }
+
+ /**
+ * 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>
+ *
+ */
+ public void registerForPostDialCharacter(Handler h, int what, Object obj){
+ mPostDialCharacterRegistrants.addUnique(h, what, obj);
+ }
+
+ public void unregisterForPostDialCharacter(Handler h){
+ mPostDialCharacterRegistrants.remove(h);
+ }
+
+ /* APIs to access foregroudCalls, backgroudCalls, and ringingCalls
+ * 1. APIs to access list of calls
+ * 2. APIs to check if any active call, which has connection other than
+ * disconnected ones, pleaser refer to Call.isIdle()
+ * 3. APIs to return first active call
+ * 4. APIs to return the connections of first active call
+ * 5. APIs to return other property of first active call
+ */
+
+ /**
+ * @return list of all ringing calls
+ */
+ public List<Call> getRingingCalls() {
+ return Collections.unmodifiableList(mRingingCalls);
+ }
+
+ /**
+ * @return list of all foreground calls
+ */
+ public List<Call> getForegroundCalls() {
+ return Collections.unmodifiableList(mForegroundCalls);
+ }
+
+ /**
+ * @return list of all background calls
+ */
+ public List<Call> getBackgroundCalls() {
+ return Collections.unmodifiableList(mBackgroundCalls);
+ }
+
+ /**
+ * Return true if there is at least one active foreground call
+ */
+ public boolean hasActiveFgCall() {
+ return (getFirstActiveCall(mForegroundCalls) != null);
+ }
+
+ /**
+ * Return true if there is at least one active background call
+ */
+ public boolean hasActiveBgCall() {
+ // TODO since hasActiveBgCall may get called often
+ // better to cache it to improve performance
+ return (getFirstActiveCall(mBackgroundCalls) != null);
+ }
+
+ /**
+ * Return true if there is at least one active ringing call
+ *
+ */
+ public boolean hasActiveRingingCall() {
+ return (getFirstActiveCall(mRingingCalls) != null);
+ }
+
+ /**
+ * return the active foreground call from foreground calls
+ *
+ * Active call means the call is NOT in Call.State.IDLE
+ *
+ * 1. If there is active foreground call, return it
+ * 2. If there is no active foreground call, return the
+ * foreground call associated with default phone, which state is IDLE.
+ * 3. If there is no phone registered at all, return null.
+ *
+ */
+ public Call getActiveFgCall() {
+ Call call = getFirstNonIdleCall(mForegroundCalls);
+ if (call == null) {
+ call = (mDefaultPhone == null)
+ ? null
+ : mDefaultPhone.getForegroundCall();
+ }
+ return call;
+ }
+
+ // Returns the first call that is not in IDLE state. If both active calls
+ // and disconnecting/disconnected calls exist, return the first active call.
+ private Call getFirstNonIdleCall(List<Call> calls) {
+ Call result = null;
+ for (Call call : calls) {
+ if (!call.isIdle()) {
+ return call;
+ } else if (call.getState() != Call.State.IDLE) {
+ if (result == null) result = call;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * return one active background call from background calls
+ *
+ * Active call means the call is NOT idle defined by Call.isIdle()
+ *
+ * 1. If there is only one active background call, return it
+ * 2. If there is more than one active background call, return the first one
+ * 3. If there is no active background call, return the background call
+ * associated with default phone, which state is IDLE.
+ * 4. If there is no background call at all, return null.
+ *
+ * Complete background calls list can be get by getBackgroundCalls()
+ */
+ public Call getFirstActiveBgCall() {
+ Call call = getFirstNonIdleCall(mBackgroundCalls);
+ if (call == null) {
+ call = (mDefaultPhone == null)
+ ? null
+ : mDefaultPhone.getBackgroundCall();
+ }
+ return call;
+ }
+
+ /**
+ * return one active ringing call from ringing calls
+ *
+ * Active call means the call is NOT idle defined by Call.isIdle()
+ *
+ * 1. If there is only one active ringing call, return it
+ * 2. If there is more than one active ringing call, return the first one
+ * 3. If there is no active ringing call, return the ringing call
+ * associated with default phone, which state is IDLE.
+ * 4. If there is no ringing call at all, return null.
+ *
+ * Complete ringing calls list can be get by getRingingCalls()
+ */
+ public Call getFirstActiveRingingCall() {
+ Call call = getFirstNonIdleCall(mRingingCalls);
+ if (call == null) {
+ call = (mDefaultPhone == null)
+ ? null
+ : mDefaultPhone.getRingingCall();
+ }
+ return call;
+ }
+
+ /**
+ * @return the state of active foreground call
+ * return IDLE if there is no active foreground call
+ */
+ public Call.State getActiveFgCallState() {
+ Call fgCall = getActiveFgCall();
+
+ if (fgCall != null) {
+ return fgCall.getState();
+ }
+
+ return Call.State.IDLE;
+ }
+
+ /**
+ * @return the connections of active foreground call
+ * return empty list if there is no active foreground call
+ */
+ public List<Connection> getFgCallConnections() {
+ Call fgCall = getActiveFgCall();
+ if ( fgCall != null) {
+ return fgCall.getConnections();
+ }
+ return emptyConnections;
+ }
+
+ /**
+ * @return the connections of active background call
+ * return empty list if there is no active background call
+ */
+ public List<Connection> getBgCallConnections() {
+ Call bgCall = getFirstActiveBgCall();
+ if ( bgCall != null) {
+ return bgCall.getConnections();
+ }
+ return emptyConnections;
+ }
+
+ /**
+ * @return the latest connection of active foreground call
+ * return null if there is no active foreground call
+ */
+ public Connection getFgCallLatestConnection() {
+ Call fgCall = getActiveFgCall();
+ if ( fgCall != null) {
+ return fgCall.getLatestConnection();
+ }
+ return null;
+ }
+
+ /**
+ * @return true if there is at least one Foreground call in disconnected state
+ */
+ public boolean hasDisconnectedFgCall() {
+ return (getFirstCallOfState(mForegroundCalls, Call.State.DISCONNECTED) != null);
+ }
+
+ /**
+ * @return true if there is at least one background call in disconnected state
+ */
+ public boolean hasDisconnectedBgCall() {
+ return (getFirstCallOfState(mBackgroundCalls, Call.State.DISCONNECTED) != null);
+ }
+
+ /**
+ * @return the first active call from a call list
+ */
+ private Call getFirstActiveCall(ArrayList<Call> calls) {
+ for (Call call : calls) {
+ if (!call.isIdle()) {
+ return call;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the first call in a the Call.state from a call list
+ */
+ private Call getFirstCallOfState(ArrayList<Call> calls, Call.State state) {
+ for (Call call : calls) {
+ if (call.getState() == state) {
+ return call;
+ }
+ }
+ return null;
+ }
+
+
+ private boolean hasMoreThanOneRingingCall() {
+ int count = 0;
+ for (Call call : mRingingCalls) {
+ if (call.getState().isRinging()) {
+ if (++count > 1) return true;
+ }
+ }
+ return false;
+ }
+
+ private Handler mHandler = new Handler() {
+
+ @Override
+ public void handleMessage(Message msg) {
+
+ switch (msg.what) {
+ case EVENT_DISCONNECT:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_DISCONNECT)");
+ mDisconnectRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_PRECISE_CALL_STATE_CHANGED:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)");
+ mPreciseCallStateRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_NEW_RINGING_CONNECTION:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_NEW_RINGING_CONNECTION)");
+ if (getActiveFgCallState().isDialing() || hasMoreThanOneRingingCall()) {
+ Connection c = (Connection) ((AsyncResult) msg.obj).result;
+ try {
+ Log.d(LOG_TAG, "silently drop incoming call: " + c.getCall());
+ c.getCall().hangup();
+ } catch (CallStateException e) {
+ Log.w(LOG_TAG, "new ringing connection", e);
+ }
+ } else {
+ mNewRingingConnectionRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ }
+ break;
+ case EVENT_UNKNOWN_CONNECTION:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_UNKNOWN_CONNECTION)");
+ mUnknownConnectionRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_INCOMING_RING:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_INCOMING_RING)");
+ // The event may come from RIL who's not aware of an ongoing fg call
+ if (!hasActiveFgCall()) {
+ mIncomingRingRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ }
+ break;
+ case EVENT_RINGBACK_TONE:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_RINGBACK_TONE)");
+ mRingbackToneRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_IN_CALL_VOICE_PRIVACY_ON:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_IN_CALL_VOICE_PRIVACY_ON)");
+ mInCallVoicePrivacyOnRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_IN_CALL_VOICE_PRIVACY_OFF:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_IN_CALL_VOICE_PRIVACY_OFF)");
+ mInCallVoicePrivacyOffRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_CALL_WAITING:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_CALL_WAITING)");
+ mCallWaitingRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_DISPLAY_INFO:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_DISPLAY_INFO)");
+ mDisplayInfoRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_SIGNAL_INFO:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_SIGNAL_INFO)");
+ mSignalInfoRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_CDMA_OTA_STATUS_CHANGE:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_CDMA_OTA_STATUS_CHANGE)");
+ mCdmaOtaStatusChangeRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_RESEND_INCALL_MUTE:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_RESEND_INCALL_MUTE)");
+ mResendIncallMuteRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_MMI_INITIATE:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_MMI_INITIATE)");
+ mMmiInitiateRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_MMI_COMPLETE:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_MMI_COMPLETE)");
+ mMmiCompleteRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_ECM_TIMER_RESET:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_ECM_TIMER_RESET)");
+ mEcmTimerResetRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_SUBSCRIPTION_INFO_READY:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_SUBSCRIPTION_INFO_READY)");
+ mSubscriptionInfoReadyRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_SUPP_SERVICE_FAILED:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_SUPP_SERVICE_FAILED)");
+ mSuppServiceFailedRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_SERVICE_STATE_CHANGED:
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_SERVICE_STATE_CHANGED)");
+ mServiceStateChangedRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+ break;
+ case EVENT_POST_DIAL_CHARACTER:
+ // we need send the character that is being processed in msg.arg1
+ // so can't use notifyRegistrants()
+ if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_POST_DIAL_CHARACTER)");
+ for(int i=0; i < mPostDialCharacterRegistrants.size(); i++) {
+ Message notifyMsg;
+ notifyMsg = ((Registrant)mPostDialCharacterRegistrants.get(i)).messageForRegistrant();
+ notifyMsg.obj = msg.obj;
+ notifyMsg.arg1 = msg.arg1;
+ notifyMsg.sendToTarget();
+ }
+ break;
+ }
+ }
+ };
+
+ @Override
+ public String toString() {
+ Call call;
+ StringBuilder b = new StringBuilder();
+
+ b.append("CallManager {");
+ b.append("\nstate = " + getState());
+ call = getActiveFgCall();
+ b.append("\n- Foreground: " + getActiveFgCallState());
+ b.append(" from " + call.getPhone());
+ b.append("\n Conn: ").append(getFgCallConnections());
+ call = getFirstActiveBgCall();
+ b.append("\n- Background: " + call.getState());
+ b.append(" from " + call.getPhone());
+ b.append("\n Conn: ").append(getBgCallConnections());
+ call = getFirstActiveRingingCall();
+ b.append("\n- Ringing: " +call.getState());
+ b.append(" from " + call.getPhone());
+
+ for (Phone phone : getAllPhones()) {
+ if (phone != null) {
+ b.append("\nPhone: " + phone + ", name = " + phone.getPhoneName()
+ + ", state = " + phone.getState());
+ call = phone.getForegroundCall();
+ b.append("\n- Foreground: ").append(call);
+ call = phone.getBackgroundCall();
+ b.append(" Background: ").append(call);
+ call = phone.getRingingCall();
+ b.append(" Ringing: ").append(call);
+ }
+ }
+ b.append("\n}");
+ return b.toString();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/CallStateException.java b/src/java/com/android/internal/telephony/CallStateException.java
new file mode 100644
index 0000000..6087124
--- /dev/null
+++ b/src/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/src/java/com/android/internal/telephony/CallTracker.java b/src/java/com/android/internal/telephony/CallTracker.java
new file mode 100644
index 0000000..62caf01
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CallTracker.java
@@ -0,0 +1,182 @@
+/*
+ * 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.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandException;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+
+/**
+ * {@hide}
+ */
+public abstract class CallTracker extends Handler {
+
+ private static final boolean DBG_POLL = false;
+
+ //***** Constants
+
+ static final int POLL_DELAY_MSEC = 250;
+
+ protected int pendingOperations;
+ protected boolean needsPoll;
+ protected Message lastRelevantPoll;
+
+ public CommandsInterface cm;
+
+
+ //***** Events
+
+ protected static final int EVENT_POLL_CALLS_RESULT = 1;
+ protected static final int EVENT_CALL_STATE_CHANGE = 2;
+ protected static final int EVENT_REPOLL_AFTER_DELAY = 3;
+ protected static final int EVENT_OPERATION_COMPLETE = 4;
+ protected static final int EVENT_GET_LAST_CALL_FAIL_CAUSE = 5;
+
+ protected static final int EVENT_SWITCH_RESULT = 8;
+ protected static final int EVENT_RADIO_AVAILABLE = 9;
+ protected static final int EVENT_RADIO_NOT_AVAILABLE = 10;
+ protected static final int EVENT_CONFERENCE_RESULT = 11;
+ protected static final int EVENT_SEPARATE_RESULT = 12;
+ protected static final int EVENT_ECT_RESULT = 13;
+ protected static final int EVENT_EXIT_ECM_RESPONSE_CDMA = 14;
+ protected static final int EVENT_CALL_WAITING_INFO_CDMA = 15;
+ protected static final int EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA = 16;
+
+ protected void pollCallsWhenSafe() {
+ needsPoll = true;
+
+ if (checkNoOperationsPending()) {
+ lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
+ cm.getCurrentCalls(lastRelevantPoll);
+ }
+ }
+
+ protected void
+ pollCallsAfterDelay() {
+ Message msg = obtainMessage();
+
+ msg.what = EVENT_REPOLL_AFTER_DELAY;
+ sendMessageDelayed(msg, POLL_DELAY_MSEC);
+ }
+
+ protected boolean
+ isCommandExceptionRadioNotAvailable(Throwable e) {
+ return e != null && e instanceof CommandException
+ && ((CommandException)e).getCommandError()
+ == CommandException.Error.RADIO_NOT_AVAILABLE;
+ }
+
+ protected abstract void handlePollCalls(AsyncResult ar);
+
+ protected void handleRadioAvailable() {
+ pollCallsWhenSafe();
+ }
+
+ /**
+ * 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 invocation
+ * of getCurrentCalls() will be postponed until this
+ * operation is also complete.
+ */
+ protected Message
+ obtainNoPollCompleteMessage(int what) {
+ pendingOperations++;
+ lastRelevantPoll = null;
+ return obtainMessage(what);
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * Routine called from dial to check if the number is a test Emergency number
+ * and if so remap the number. This allows a short emergency number to be remapped
+ * to a regular number for testing how the frameworks handles emergency numbers
+ * without actually calling an emergency number.
+ *
+ * This is not a full test and is not a substitute for testing real emergency
+ * numbers but can be useful.
+ *
+ * To use this feature set a system property ril.test.emergencynumber to a pair of
+ * numbers separated by a colon. If the first number matches the number parameter
+ * this routine returns the second number. Example:
+ *
+ * ril.test.emergencynumber=112:1-123-123-45678
+ *
+ * To test Dial 112 take call then hang up on MO device to enter ECM
+ * see RIL#processSolicited RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND
+ *
+ * @param number to test if it should be remapped
+ * @return the same number or the remapped number.
+ */
+ protected String checkForTestEmergencyNumber(String dialString) {
+ String testEn = SystemProperties.get("ril.test.emergencynumber");
+ if (DBG_POLL) {
+ log("checkForTestEmergencyNumber: dialString=" + dialString +
+ " testEn=" + testEn);
+ }
+ if (!TextUtils.isEmpty(testEn)) {
+ String values[] = testEn.split(":");
+ log("checkForTestEmergencyNumber: values.length=" + values.length);
+ if (values.length == 2) {
+ if (values[0].equals(
+ android.telephony.PhoneNumberUtils.stripSeparators(dialString))) {
+ cm.testingEmergencyCall();
+ log("checkForTestEmergencyNumber: remap " +
+ dialString + " to " + values[1]);
+ dialString = values[1];
+ }
+ }
+ }
+ return dialString;
+ }
+
+ //***** Overridden from Handler
+ public abstract void handleMessage (Message msg);
+ public abstract void registerForVoiceCallStarted(Handler h, int what, Object obj);
+ public abstract void unregisterForVoiceCallStarted(Handler h);
+ public abstract void registerForVoiceCallEnded(Handler h, int what, Object obj);
+ public abstract void unregisterForVoiceCallEnded(Handler h);
+
+ protected abstract void log(String msg);
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("CallTracker:");
+ pw.println(" pendingOperations=" + pendingOperations);
+ pw.println(" needsPoll=" + needsPoll);
+ pw.println(" lastRelevantPoll=" + lastRelevantPoll);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/CommandException.java b/src/java/com/android/internal/telephony/CommandException.java
new file mode 100644
index 0000000..94c544e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CommandException.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.android.internal.telephony.RILConstants;
+
+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,
+ SIM_ABSENT,
+ SUBSCRIPTION_NOT_AVAILABLE,
+ MODE_NOT_SUPPORTED,
+ FDN_CHECK_FAILURE,
+ ILLEGAL_SIM_OR_ME,
+ }
+
+ 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);
+ case RILConstants.SIM_ABSENT:
+ return new CommandException(Error.SIM_ABSENT);
+ case RILConstants.SUBSCRIPTION_NOT_AVAILABLE:
+ return new CommandException(Error.SUBSCRIPTION_NOT_AVAILABLE);
+ case RILConstants.MODE_NOT_SUPPORTED:
+ return new CommandException(Error.MODE_NOT_SUPPORTED);
+ case RILConstants.FDN_CHECK_FAILURE:
+ return new CommandException(Error.FDN_CHECK_FAILURE);
+ case RILConstants.ILLEGAL_SIM_OR_ME:
+ return new CommandException(Error.ILLEGAL_SIM_OR_ME);
+ default:
+ Log.e("GSM", "Unrecognized RIL errno " + ril_errno);
+ return new CommandException(Error.INVALID_RESPONSE);
+ }
+ }
+
+ public Error getCommandError() {
+ return e;
+ }
+
+
+
+}
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
new file mode 100644
index 0000000..f7757b3
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -0,0 +1,1579 @@
+/*
+ * 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 com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+
+import android.os.Message;
+import android.os.Handler;
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+public interface CommandsInterface {
+ enum RadioState {
+ RADIO_OFF, /* Radio explicitly powered off (eg CFUN=0) */
+ RADIO_UNAVAILABLE, /* Radio unavailable (eg, resetting or not booted) */
+ RADIO_ON; /* Radio is on */
+
+ public boolean isOn() /* and available...*/ {
+ return this == RADIO_ON;
+ }
+
+ public boolean isAvailable() {
+ return this != RADIO_UNAVAILABLE;
+ }
+ }
+
+ //***** 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); //synonym 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;
+
+ // GSM SMS fail cause for acknowledgeLastIncomingSMS. From TS 23.040, 9.2.3.22.
+ static final int GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED = 0xD3;
+ static final int GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY = 0xD4;
+ static final int GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR = 0xD5;
+ static final int GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR = 0xFF;
+
+ // CDMA SMS fail cause for acknowledgeLastIncomingCdmaSms. From TS N.S0005, 6.5.2.125.
+ static final int CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID = 4;
+ static final int CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE = 35;
+ static final int CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM = 39;
+ static final int CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM = 96;
+
+ //***** Methods
+ RadioState getRadioState();
+
+ void getVoiceRadioTechnology(Message result);
+
+ /**
+ * 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);
+ void unregisterForRadioStateChanged(Handler h);
+
+ void registerForVoiceRadioTechChanged(Handler h, int what, Object obj);
+ void unregisterForVoiceRadioTechChanged(Handler h);
+
+ /**
+ * 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);
+ void unregisterForOn(Handler h);
+
+ /**
+ * 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 unregisterForOffOrNotAvailable(Handler h);
+
+ /**
+ * Fires on any change in ICC status
+ */
+ void registerForIccStatusChanged(Handler h, int what, Object obj);
+ void unregisterForIccStatusChanged(Handler h);
+
+ void registerForCallStateChanged(Handler h, int what, Object obj);
+ void unregisterForCallStateChanged(Handler h);
+ void registerForVoiceNetworkStateChanged(Handler h, int what, Object obj);
+ void unregisterForVoiceNetworkStateChanged(Handler h);
+ void registerForDataNetworkStateChanged(Handler h, int what, Object obj);
+ void unregisterForDataNetworkStateChanged(Handler h);
+
+ /** InCall voice privacy notifications */
+ void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj);
+ void unregisterForInCallVoicePrivacyOn(Handler h);
+ void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj);
+ void unregisterForInCallVoicePrivacyOff(Handler h);
+
+ /**
+ * unlike the register* methods, there's only one new 3GPP format 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 setOnNewGsmSms(Handler h, int what, Object obj);
+ void unSetOnNewGsmSms(Handler h);
+
+ /**
+ * unlike the register* methods, there's only one new 3GPP2 format 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 setOnNewCdmaSms(Handler h, int what, Object obj);
+ void unSetOnNewCdmaSms(Handler h);
+
+ /**
+ * Set the handler for SMS Cell Broadcast messages.
+ *
+ * AsyncResult.result is a byte array containing the SMS-CB PDU
+ */
+ void setOnNewGsmBroadcastSms(Handler h, int what, Object obj);
+ void unSetOnNewGsmBroadcastSms(Handler h);
+
+ /**
+ * 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);
+ void unSetOnSmsOnSim(Handler h);
+
+ /**
+ * 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);
+ void unSetOnSmsStatus(Handler h);
+
+ /**
+ * 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)[1] is a Long containing the milliseconds since boot as
+ * returned by elapsedRealtime() 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);
+ void unSetOnNITZTime(Handler h);
+
+ /**
+ * 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);
+ void unSetOnUSSD(Handler h);
+
+ /**
+ * 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);
+ void unSetOnSignalStrengthUpdate(Handler h);
+
+ /**
+ * Sets the handler for SIM/RUIM 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 setOnIccSmsFull(Handler h, int what, Object obj);
+ void unSetOnIccSmsFull(Handler h);
+
+ /**
+ * Sets the handler for SIM Refresh notifications.
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForIccRefresh(Handler h, int what, Object obj);
+ void unregisterForIccRefresh(Handler h);
+
+ void setOnIccRefresh(Handler h, int what, Object obj);
+ void unsetOnIccRefresh(Handler h);
+
+ /**
+ * 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);
+ void unSetOnCallRing(Handler h);
+
+ /**
+ * Sets the handler for RESTRICTED_STATE changed notification,
+ * eg, for Domain Specific Access Control
+ * unlike the register* methods, there's only one signal strength handler
+ *
+ * AsyncResult.result is an int[1]
+ * response.obj.result[0] is a bitmask of RIL_RESTRICTED_STATE_* values
+ */
+
+ void setOnRestrictedStateChanged(Handler h, int what, Object obj);
+ void unSetOnRestrictedStateChanged(Handler h);
+
+ /**
+ * 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);
+ void unSetOnSuppServiceNotification(Handler h);
+
+ /**
+ * Sets the handler for Session End Notifications for CAT.
+ * 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 setOnCatSessionEnd(Handler h, int what, Object obj);
+ void unSetOnCatSessionEnd(Handler h);
+
+ /**
+ * Sets the handler for Proactive Commands for CAT.
+ * 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 setOnCatProactiveCmd(Handler h, int what, Object obj);
+ void unSetOnCatProactiveCmd(Handler h);
+
+ /**
+ * Sets the handler for Event Notifications for CAT.
+ * 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 setOnCatEvent(Handler h, int what, Object obj);
+ void unSetOnCatEvent(Handler h);
+
+ /**
+ * Sets the handler for Call Set Up Notifications for CAT.
+ * 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 setOnCatCallSetUp(Handler h, int what, Object obj);
+ void unSetOnCatCallSetUp(Handler h);
+
+ /**
+ * 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);
+ //void unSetSuppServiceNotifications(Handler h);
+
+ /**
+ * Sets the handler for Event Notifications for CDMA Display Info.
+ * 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 registerForDisplayInfo(Handler h, int what, Object obj);
+ void unregisterForDisplayInfo(Handler h);
+
+ /**
+ * Sets the handler for Event Notifications for CallWaiting Info.
+ * 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 registerForCallWaitingInfo(Handler h, int what, Object obj);
+ void unregisterForCallWaitingInfo(Handler h);
+
+ /**
+ * Sets the handler for Event Notifications for Signal Info.
+ * 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 registerForSignalInfo(Handler h, int what, Object obj);
+ void unregisterForSignalInfo(Handler h);
+
+ /**
+ * Registers the handler for CDMA number information record
+ * 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 registerForNumberInfo(Handler h, int what, Object obj);
+ void unregisterForNumberInfo(Handler h);
+
+ /**
+ * Registers the handler for CDMA redirected number Information record
+ * 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 registerForRedirectedNumberInfo(Handler h, int what, Object obj);
+ void unregisterForRedirectedNumberInfo(Handler h);
+
+ /**
+ * Registers the handler for CDMA line control information record
+ * 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 registerForLineControlInfo(Handler h, int what, Object obj);
+ void unregisterForLineControlInfo(Handler h);
+
+ /**
+ * Registers the handler for CDMA T53 CLIR information record
+ * 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 registerFoT53ClirlInfo(Handler h, int what, Object obj);
+ void unregisterForT53ClirInfo(Handler h);
+
+ /**
+ * Registers the handler for CDMA T53 audio control information record
+ * 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 registerForT53AudioControlInfo(Handler h, int what, Object obj);
+ void unregisterForT53AudioControlInfo(Handler h);
+
+ /**
+ * Fires on if Modem enters Emergency Callback mode
+ */
+ void setEmergencyCallbackMode(Handler h, int what, Object obj);
+
+ /**
+ * Fires on any CDMA OTA provision status change
+ */
+ void registerForCdmaOtaProvision(Handler h,int what, Object obj);
+ void unregisterForCdmaOtaProvision(Handler h);
+
+ /**
+ * Registers the handler when out-band ringback tone is needed.<p>
+ *
+ * Messages received from this:
+ * Message.obj will be an AsyncResult
+ * AsyncResult.userObj = obj
+ * AsyncResult.result = boolean. <p>
+ */
+ void registerForRingbackTone(Handler h, int what, Object obj);
+ void unregisterForRingbackTone(Handler h);
+
+ /**
+ * Registers the handler when mute/unmute need to be resent to get
+ * uplink audio during a call.<p>
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ *
+ */
+ void registerForResendIncallMute(Handler h, int what, Object obj);
+ void unregisterForResendIncallMute(Handler h);
+
+ /**
+ * Registers the handler for when Cdma subscription changed events
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ *
+ */
+ void registerForCdmaSubscriptionChanged(Handler h, int what, Object obj);
+ void unregisterForCdmaSubscriptionChanged(Handler h);
+
+ /**
+ * Registers the handler for when Cdma prl changed events
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ *
+ */
+ void registerForCdmaPrlChanged(Handler h, int what, Object obj);
+ void unregisterForCdmaPrlChanged(Handler h);
+
+ /**
+ * Registers the handler for when Cdma prl changed events
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ *
+ */
+ void registerForExitEmergencyCallbackMode(Handler h, int what, Object obj);
+ void unregisterForExitEmergencyCallbackMode(Handler h);
+
+ /**
+ * Registers the handler for RIL_UNSOL_RIL_CONNECT events.
+ *
+ * When ril connects or disconnects a message is sent to the registrant
+ * which contains an AsyncResult, ar, in msg.obj. The ar.result is an
+ * Integer which is the version of the ril or -1 if the ril disconnected.
+ *
+ * @param h Handler for notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForRilConnected(Handler h, int what, Object obj);
+ void unregisterForRilConnected(Handler h);
+
+ /**
+ * Supply the ICC PIN to the ICC 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 supplyIccPin(String pin, Message result);
+
+ /**
+ * Supply the PIN for the app with this AID on the ICC card
+ *
+ * AID (Application ID), See ETSI 102.221 8.1 and 101.220 4
+ *
+ * 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 supplyIccPinForApp(String pin, String aid, Message result);
+
+ /**
+ * Supply the ICC PUK and newPin to the ICC 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 supplyIccPuk(String puk, String newPin, Message result);
+
+ /**
+ * Supply the PUK, new pin for the app with this AID on the ICC card
+ *
+ * AID (Application ID), See ETSI 102.221 8.1 and 101.220 4
+ *
+ * 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 supplyIccPukForApp(String puk, String newPin, String aid, Message result);
+
+ /**
+ * Supply the ICC PIN2 to the ICC card
+ * Only called following operation where ICC_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 supplyIccPin2(String pin2, Message result);
+
+ /**
+ * Supply the PIN2 for the app with this AID on the ICC card
+ * Only called following operation where ICC_PIN2 was
+ * returned as a a failure from a previous operation
+ *
+ * AID (Application ID), See ETSI 102.221 8.1 and 101.220 4
+ *
+ * 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 supplyIccPin2ForApp(String pin2, String aid, 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 supplyIccPuk2(String puk2, String newPin2, Message result);
+
+ /**
+ * Supply the PUK2, newPin2 for the app with this AID on the ICC card
+ * Only called following operation where SIM_PUK2 was
+ * returned as a a failure from a previous operation
+ *
+ * AID (Application ID), See ETSI 102.221 8.1 and 101.220 4
+ *
+ * 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 supplyIccPuk2ForApp(String puk2, String newPin2, String aid, Message result);
+
+ void changeIccPin(String oldPin, String newPin, Message result);
+ void changeIccPinForApp(String oldPin, String newPin, String aidPtr, Message result);
+ void changeIccPin2(String oldPin2, String newPin2, Message result);
+ void changeIccPin2ForApp(String oldPin2, String newPin2, String aidPtr, 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 DataCallState
+ * @deprecated Do not use.
+ */
+ @Deprecated
+ 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 contains a List of DataCallState
+ */
+ void getDataCallList(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 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, UUSInfo uusInfo, 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 IMSI on success
+ */
+ void getIMSIForApp(String aid, 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);
+
+ /**
+ * Set preferred Voice Privacy (VP).
+ *
+ * @param enable true is enhanced and false is normal VP
+ * @param result is a callback message
+ */
+ void setPreferredVoicePrivacy(boolean enable, Message result);
+
+ /**
+ * Get currently set preferred Voice Privacy (VP) mode.
+ *
+ * @param result is a callback message
+ */
+ void getPreferredVoicePrivacy(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
+ * @deprecated Do not use.
+ */
+ @Deprecated
+ void getLastPdpFailCause (Message result);
+
+ /**
+ * The preferred new alternative to getLastPdpFailCause
+ * that is also CDMA-compatible.
+ */
+ void getLastDataCallFailCause (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 getVoiceRegistrationState (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 getDataRegistrationState (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);
+
+ /**
+ * ar.exception carries exception on failure
+ * ar.userObject contains the orignal value of result.obj
+ * ar.result is null on success and failure
+ */
+ void sendBurstDtmf(String dtmfString, int on, int off, 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);
+
+ /**
+ * @param pdu is CDMA-SMS in internal pseudo-PDU format
+ * @param response sent when operation completes
+ */
+ void sendCdmaSms(byte[] 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);
+
+ /**
+ * Deletes the specified SMS record from RUIM memory (EF_SMS in DF_CDMA).
+ *
+ * @param index index of the SMS record to delete
+ * @param response sent when operation completes
+ */
+ void deleteSmsOnRuim(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_ICC_READ
+ * SmsManger.STATUS_ON_ICC_UNREAD
+ * SmsManger.STATUS_ON_ICC_SENT
+ * SmsManger.STATUS_ON_ICC_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 writeSmsToRuim(int status, String pdu, Message response);
+
+ void setRadioPower(boolean on, Message response);
+
+ void acknowledgeLastIncomingGsmSms(boolean success, int cause, Message response);
+
+ void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message response);
+
+ /**
+ * Acknowledge successful or failed receipt of last incoming SMS,
+ * including acknowledgement TPDU to send as the RP-User-Data element
+ * of the RP-ACK or RP-ERROR PDU.
+ *
+ * @param success true to send RP-ACK, false to send RP-ERROR
+ * @param ackPdu the acknowledgement TPDU in hexadecimal format
+ * @param response sent when operation completes.
+ */
+ void acknowledgeIncomingGsmSmsWithPdu(boolean success, String ackPdu, Message response);
+
+ /**
+ * parameters equivalent to 27.007 AT+CRSM command
+ * response.obj will be an AsyncResult
+ * response.obj.result will be an IccIoResult on success
+ */
+ void iccIO (int command, int fileid, String path, int p1, int p2, int p3,
+ String data, String pin2, Message response);
+
+ /**
+ * parameters equivalent to 27.007 AT+CRSM command
+ * response.obj will be an AsyncResult
+ * response.obj.userObj will be a IccIoResult on success
+ */
+ void iccIOForApp (int command, int fileid, String path, int p1, int p2, int p3,
+ String data, String pin2, String aid, 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 service 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);
+
+ /**
+ * (AsyncResult)response.obj).result will be an Integer representing
+ * the sum of enabled service classes (sum of SERVICE_CLASS_*) for the
+ * application with appId.
+ *
+ * @param facility one of CB_FACILTY_*
+ * @param password password or "" if not required
+ * @param serviceClass is a sum of SERVICE_CLASS_*
+ * @param appId is application Id or null if none
+ * @param response is callback message
+ */
+
+ void queryFacilityLockForApp(String facility, String password, int serviceClass, String appId,
+ 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);
+
+ /**
+ * Set the facility lock for the app with this AID on the ICC card.
+ *
+ * @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 appId is application Id or null if none
+ * @param response is callback message
+ */
+ void setFacilityLockForApp(String facility, boolean lockState, String password,
+ int serviceClass, String appId, 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);
+
+ /**
+ * Set the current preferred network type. This will be the last
+ * networkType that was passed to setPreferredNetworkType.
+ */
+ void setCurrentPreferredNetworkType();
+
+ /**
+ * 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 information (lac and/or cid) has changed.
+ *
+ * @param enable true to enable, false to disable
+ * @param response callback message
+ */
+ void setLocationUpdates(boolean enable, Message response);
+
+ /**
+ * Gets the default SMSC address.
+ *
+ * @param result Callback message contains the SMSC address.
+ */
+ void getSmscAddress(Message result);
+
+ /**
+ * Sets the default SMSC address.
+ *
+ * @param address new SMSC address
+ * @param result Callback message is empty on completion
+ */
+ void setSmscAddress(String address, Message result);
+
+ /**
+ * Indicates whether there is storage available for new SMS messages.
+ * @param available true if storage is available
+ * @param result callback message
+ */
+ void reportSmsMemoryStatus(boolean available, Message result);
+
+ /**
+ * Indicates to the vendor ril that StkService is running
+ * and is ready to receive RIL_UNSOL_STK_XXXX commands.
+ *
+ * @param result callback message
+ */
+ void reportStkServiceIsRunning(Message result);
+
+ 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);
+
+ /**
+ * Send ENVELOPE to the SIM, such as an SMS-PP data download envelope
+ * for a SIM data download message. This method has one difference
+ * from {@link #sendEnvelope}: The SW1 and SW2 status bytes from the UICC response
+ * are returned along with the response data.
+ *
+ * response.obj will be an AsyncResult
+ * response.obj.result will be an IccIoResult on success
+ *
+ * @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 sendEnvelopeWithStatus(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);
+
+ /**
+ * Activate or deactivate cell broadcast SMS for GSM.
+ *
+ * @param activate
+ * true = activate, false = deactivate
+ * @param result Callback message is empty on completion
+ */
+ public void setGsmBroadcastActivation(boolean activate, Message result);
+
+ /**
+ * Configure cell broadcast SMS for GSM.
+ *
+ * @param response Callback message is empty on completion
+ */
+ public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response);
+
+ /**
+ * Query the current configuration of cell broadcast SMS of GSM.
+ *
+ * @param response
+ * Callback message contains the configuration from the modem
+ * on completion
+ */
+ public void getGsmBroadcastConfig(Message response);
+
+ //***** new Methods for CDMA support
+
+ /**
+ * Request the device ESN / MEID / IMEI / IMEISV.
+ * "response" is const char **
+ * [0] is IMEI if GSM subscription is available
+ * [1] is IMEISV if GSM subscription is available
+ * [2] is ESN if CDMA subscription is available
+ * [3] is MEID if CDMA subscription is available
+ */
+ public void getDeviceIdentity(Message response);
+
+ /**
+ * Request the device MDN / H_SID / H_NID / MIN.
+ * "response" is const char **
+ * [0] is MDN if CDMA subscription is available
+ * [1] is a comma separated list of H_SID (Home SID) in decimal format
+ * if CDMA subscription is available
+ * [2] is a comma separated list of H_NID (Home NID) in decimal format
+ * if CDMA subscription is available
+ * [3] is MIN (10 digits, MIN2+MIN1) if CDMA subscription is available
+ */
+ public void getCDMASubscription(Message response);
+
+ /**
+ * Send Flash Code.
+ * "response" is is NULL
+ * [0] is a FLASH string
+ */
+ public void sendCDMAFeatureCode(String FeatureCode, Message response);
+
+ /** Set the Phone type created */
+ void setPhoneType(int phoneType);
+
+ /**
+ * Query the CDMA roaming preference setting
+ *
+ * @param response is callback message to report one of CDMA_RM_*
+ */
+ void queryCdmaRoamingPreference(Message response);
+
+ /**
+ * Requests to set the CDMA roaming preference
+ * @param cdmaRoamingType one of CDMA_RM_*
+ * @param response is callback message
+ */
+ void setCdmaRoamingPreference(int cdmaRoamingType, Message response);
+
+ /**
+ * Requests to set the CDMA subscription mode
+ * @param cdmaSubscriptionType one of CDMA_SUBSCRIPTION_*
+ * @param response is callback message
+ */
+ void setCdmaSubscriptionSource(int cdmaSubscriptionType, Message response);
+
+ /**
+ * Requests to get the CDMA subscription srouce
+ * @param response is callback message
+ */
+ void getCdmaSubscriptionSource(Message response);
+
+ /**
+ * Set the TTY mode
+ *
+ * @param ttyMode one of the following:
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_OFF}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_FULL}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_HCO}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
+ * @param response is callback message
+ */
+ void setTTYMode(int ttyMode, Message response);
+
+ /**
+ * Query the TTY mode
+ * (AsyncResult)response.obj).result is an int[] with element [0] set to
+ * tty mode:
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_OFF}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_FULL}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_HCO}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
+ * @param response is callback message
+ */
+ void queryTTYMode(Message response);
+
+ /**
+ * Setup a packet data connection On successful completion, the result
+ * message will return a {@link DataCallState} object containing the connection
+ * information.
+ *
+ * @param radioTechnology
+ * indicates whether to setup connection on radio technology CDMA
+ * (0) or GSM/UMTS (1)
+ * @param profile
+ * Profile Number or NULL to indicate default profile
+ * @param apn
+ * the APN to connect to if radio technology is GSM/UMTS.
+ * Otherwise null for CDMA.
+ * @param user
+ * the username for APN, or NULL
+ * @param password
+ * the password for APN, or NULL
+ * @param authType
+ * the PAP / CHAP auth type. Values is one of SETUP_DATA_AUTH_*
+ * @param protocol
+ * one of the PDP_type values in TS 27.007 section 10.1.1.
+ * For example, "IP", "IPV6", "IPV4V6", or "PPP".
+ * @param result
+ * Callback message
+ */
+ public void setupDataCall(String radioTechnology, String profile,
+ String apn, String user, String password, String authType,
+ String protocol, Message result);
+
+ /**
+ * Deactivate packet data connection
+ *
+ * @param cid
+ * The connection ID
+ * @param reason
+ * Data disconnect reason.
+ * @param result
+ * Callback message is empty on completion
+ */
+ public void deactivateDataCall(int cid, int reason, Message result);
+
+ /**
+ * Activate or deactivate cell broadcast SMS for CDMA.
+ *
+ * @param activate
+ * true = activate, false = deactivate
+ * @param result
+ * Callback message is empty on completion
+ */
+ public void setCdmaBroadcastActivation(boolean activate, Message result);
+
+ /**
+ * Configure cdma cell broadcast SMS.
+ *
+ * @param result
+ * Callback message is empty on completion
+ */
+ // TODO: Change the configValuesArray to a RIL_BroadcastSMSConfig
+ public void setCdmaBroadcastConfig(int[] configValuesArray, Message result);
+
+ /**
+ * Query the current configuration of cdma cell broadcast SMS.
+ *
+ * @param result
+ * Callback message contains the configuration from the modem on completion
+ */
+ public void getCdmaBroadcastConfig(Message result);
+
+ /**
+ * Requests the radio's system selection module to exit emergency callback mode.
+ * This function should only be called from CDMAPHone.java.
+ *
+ * @param response callback message
+ */
+ public void exitEmergencyCallbackMode(Message response);
+
+ /**
+ * Request the status of the ICC and UICC cards.
+ *
+ * @param result
+ * Callback message containing {@link IccCardStatus} structure for the card.
+ */
+ public void getIccCardStatus(Message result);
+
+ /**
+ * Return if the current radio is LTE on CDMA. This
+ * is a tri-state return value as for a period of time
+ * the mode may be unknown.
+ *
+ * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE}
+ * or {@link Phone#LTE_ON_CDMA_TRUE}
+ */
+ public int getLteOnCdmaMode();
+
+ /**
+ * Request the ISIM application on the UICC to perform the AKA
+ * challenge/response algorithm for IMS authentication. The nonce string
+ * and challenge response are Base64 encoded Strings.
+ *
+ * @param nonce the nonce string to pass with the ISIM authentication request
+ * @param response a callback message with the String response in the obj field
+ */
+ public void requestIsimAuthentication(String nonce, Message response);
+
+ /**
+ * Notifiy that we are testing an emergency call
+ */
+ public void testingEmergencyCall();
+}
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
new file mode 100644
index 0000000..554d974
--- /dev/null
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -0,0 +1,309 @@
+/*
+ * 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.util.Log;
+
+/**
+ * {@hide}
+ */
+public abstract class Connection {
+
+ //Caller Name Display
+ protected String cnapName;
+ protected int cnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
+
+ private static String LOG_TAG = "TelephonyConnection";
+
+ 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 */
+ NUMBER_UNREACHABLE, /* cannot reach the peer */
+ SERVER_UNREACHABLE, /* cannot reach the server */
+ INVALID_CREDENTIALS, /* invalid credentials */
+ OUT_OF_NETWORK, /* calling from out of network is not allowed */
+ SERVER_ERROR, /* server error */
+ TIMED_OUT, /* client timed out */
+ 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 */
+ ICC_ERROR, /* No ICC, ICC locked, or other ICC error */
+ CALL_BARRED, /* call was blocked by call barring */
+ FDN_BLOCKED, /* call was blocked by fixed dial number */
+ CS_RESTRICTED, /* call was blocked by restricted all voice access */
+ CS_RESTRICTED_NORMAL, /* call was blocked by restricted normal voice access */
+ CS_RESTRICTED_EMERGENCY, /* call was blocked by restricted emergency voice access */
+ UNOBTAINABLE_NUMBER, /* Unassigned number (3GPP TS 24.008 table 10.5.123) */
+ CDMA_LOCKED_UNTIL_POWER_CYCLE, /* MS is locked until next power cycle */
+ CDMA_DROP,
+ CDMA_INTERCEPT, /* INTERCEPT order received, MS state idle entered */
+ CDMA_REORDER, /* MS has been redirected, call is cancelled */
+ CDMA_SO_REJECT, /* service option rejection */
+ CDMA_RETRY_ORDER, /* requested service is rejected, retry delay is set */
+ CDMA_ACCESS_FAILURE,
+ CDMA_PREEMPTED,
+ CDMA_NOT_EMERGENCY, /* not an emergency call */
+ CDMA_ACCESS_BLOCKED, /* Access Blocked by CDMA network */
+ ERROR_UNSPECIFIED
+ }
+
+ Object userData;
+
+ /* Instance Methods */
+
+ /**
+ * Gets address (e.g. phone number) associated with connection.
+ * TODO: distinguish reasons for unavailability
+ *
+ * @return address or null if unavailable
+ */
+
+ public abstract String getAddress();
+
+ /**
+ * Gets CNAP name associated with connection.
+ * @return cnap name or null if unavailable
+ */
+ public String getCnapName() {
+ return cnapName;
+ }
+
+ /**
+ * Get original dial string.
+ * @return original dial string or null if unavailable
+ */
+ public String getOrigDialString(){
+ return null;
+ }
+
+ /**
+ * Gets CNAP presentation associated with connection.
+ * @return cnap name or null if unavailable
+ */
+
+ public int getCnapNamePresentation() {
+ return cnapNamePresentation;
+ };
+
+ /**
+ * @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 (approximately).
+ * 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() */
+ PAUSE /* The post dial string playback is pausing for a
+ call to processNextPostDialChar*/
+ }
+
+ public void clearUserData(){
+ userData = null;
+ }
+
+ 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();
+
+ /**
+ * Returns the caller id presentation type for incoming and waiting calls
+ * @return one of PRESENTATION_*
+ */
+ public abstract int getNumberPresentation();
+
+ /**
+ * Returns the User to User Signaling (UUS) information associated with
+ * incoming and waiting calls
+ * @return UUSInfo containing the UUS userdata.
+ */
+ public abstract UUSInfo getUUSInfo();
+
+ /**
+ * Build a human representation of a connection instance, suitable for debugging.
+ * Don't log personal stuff unless in debug mode.
+ * @return a string representing the internal state of this connection.
+ */
+ public String toString() {
+ StringBuilder str = new StringBuilder(128);
+
+ if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
+ str.append("addr: " + getAddress())
+ .append(" pres.: " + getNumberPresentation())
+ .append(" dial: " + getOrigDialString())
+ .append(" postdial: " + getRemainingPostDialString())
+ .append(" cnap name: " + getCnapName())
+ .append("(" + getCnapNamePresentation() + ")");
+ }
+ str.append(" incoming: " + isIncoming())
+ .append(" state: " + getState())
+ .append(" post dial state: " + getPostDialState());
+ return str.toString();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/DataCallState.java b/src/java/com/android/internal/telephony/DataCallState.java
new file mode 100644
index 0000000..efbf608
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DataCallState.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2009 Qualcomm Innovation Center, Inc. All Rights Reserved.
+ * Copyright (C) 2009 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.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.telephony.DataConnection.FailCause;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * This is RIL_Data_Call_Response_v5 from ril.h
+ * TODO: Rename to DataCallResponse.
+ */
+public class DataCallState {
+ private final boolean DBG = true;
+ private final String LOG_TAG = "GSM";
+
+ public int version = 0;
+ public int status = 0;
+ public int cid = 0;
+ public int active = 0;
+ public String type = "";
+ public String ifname = "";
+ public String [] addresses = new String[0];
+ public String [] dnses = new String[0];
+ public String[] gateways = new String[0];
+ public int suggestedRetryTime = -1;
+
+ /**
+ * Class returned by onSetupConnectionCompleted.
+ */
+ public enum SetupResult {
+ SUCCESS,
+ ERR_BadCommand,
+ ERR_UnacceptableParameter,
+ ERR_GetLastErrorFromRil,
+ ERR_Stale,
+ ERR_RilError;
+
+ public FailCause mFailCause;
+
+ SetupResult() {
+ mFailCause = FailCause.fromInt(0);
+ }
+
+ @Override
+ public String toString() {
+ return name() + " SetupResult.mFailCause=" + mFailCause;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("DataCallState: {")
+ .append("version=").append(version)
+ .append(" status=").append(status)
+ .append(" retry=").append(suggestedRetryTime)
+ .append(" cid=").append(cid)
+ .append(" active=").append(active)
+ .append(" type=").append(type)
+ .append("' ifname='").append(ifname);
+ sb.append("' addresses=[");
+ for (String addr : addresses) {
+ sb.append(addr);
+ sb.append(",");
+ }
+ if (addresses.length > 0) sb.deleteCharAt(sb.length()-1);
+ sb.append("] dnses=[");
+ for (String addr : dnses) {
+ sb.append(addr);
+ sb.append(",");
+ }
+ if (dnses.length > 0) sb.deleteCharAt(sb.length()-1);
+ sb.append("] gateways=[");
+ for (String addr : gateways) {
+ sb.append(addr);
+ sb.append(",");
+ }
+ if (gateways.length > 0) sb.deleteCharAt(sb.length()-1);
+ sb.append("]}");
+ return sb.toString();
+ }
+
+ public SetupResult setLinkProperties(LinkProperties linkProperties,
+ boolean okToUseSystemPropertyDns) {
+ SetupResult result;
+
+ // Start with clean network properties and if we have
+ // a failure we'll clear again at the bottom of this code.
+ if (linkProperties == null)
+ linkProperties = new LinkProperties();
+ else
+ linkProperties.clear();
+
+ if (status == FailCause.NONE.getErrorCode()) {
+ String propertyPrefix = "net." + ifname + ".";
+
+ try {
+ // set interface name
+ linkProperties.setInterfaceName(ifname);
+
+ // set link addresses
+ if (addresses != null && addresses.length > 0) {
+ for (String addr : addresses) {
+ addr = addr.trim();
+ if (addr.isEmpty()) continue;
+ LinkAddress la;
+ int addrPrefixLen;
+
+ String [] ap = addr.split("/");
+ if (ap.length == 2) {
+ addr = ap[0];
+ addrPrefixLen = Integer.parseInt(ap[1]);
+ } else {
+ addrPrefixLen = 0;
+ }
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(addr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric ip addr=" + addr);
+ }
+ if (! ia.isAnyLocalAddress()) {
+ if (addrPrefixLen == 0) {
+ // Assume point to point
+ addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128;
+ }
+ if (DBG) Log.d(LOG_TAG, "addr/pl=" + addr + "/" + addrPrefixLen);
+ la = new LinkAddress(ia, addrPrefixLen);
+ linkProperties.addLinkAddress(la);
+ }
+ }
+ } else {
+ throw new UnknownHostException("no address for ifname=" + ifname);
+ }
+
+ // set dns servers
+ if (dnses != null && dnses.length > 0) {
+ for (String addr : dnses) {
+ addr = addr.trim();
+ if (addr.isEmpty()) continue;
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(addr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric dns addr=" + addr);
+ }
+ if (! ia.isAnyLocalAddress()) {
+ linkProperties.addDns(ia);
+ }
+ }
+ } else if (okToUseSystemPropertyDns){
+ String dnsServers[] = new String[2];
+ dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
+ dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
+ for (String dnsAddr : dnsServers) {
+ dnsAddr = dnsAddr.trim();
+ if (dnsAddr.isEmpty()) continue;
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(dnsAddr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
+ }
+ if (! ia.isAnyLocalAddress()) {
+ linkProperties.addDns(ia);
+ }
+ }
+ } else {
+ throw new UnknownHostException("Empty dns response and no system default dns");
+ }
+
+ // set gateways
+ if ((gateways == null) || (gateways.length == 0)) {
+ String sysGateways = SystemProperties.get(propertyPrefix + "gw");
+ if (sysGateways != null) {
+ gateways = sysGateways.split(" ");
+ } else {
+ gateways = new String[0];
+ }
+ }
+ for (String addr : gateways) {
+ addr = addr.trim();
+ if (addr.isEmpty()) continue;
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(addr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric gateway addr=" + addr);
+ }
+ if (! ia.isAnyLocalAddress()) {
+ linkProperties.addRoute(new RouteInfo(ia));
+ }
+ }
+
+ result = SetupResult.SUCCESS;
+ } catch (UnknownHostException e) {
+ Log.d(LOG_TAG, "setLinkProperties: UnknownHostException " + e);
+ e.printStackTrace();
+ result = SetupResult.ERR_UnacceptableParameter;
+ }
+ } else {
+ if (version < 4) {
+ result = SetupResult.ERR_GetLastErrorFromRil;
+ } else {
+ result = SetupResult.ERR_RilError;
+ }
+ }
+
+ // An error occurred so clear properties
+ if (result != SetupResult.SUCCESS) {
+ if(DBG) {
+ Log.d(LOG_TAG, "setLinkProperties: error clearing LinkProperties " +
+ "status=" + status + " result=" + result);
+ }
+ linkProperties.clear();
+ }
+
+ return result;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/DataConnection.java b/src/java/com/android/internal/telephony/DataConnection.java
new file mode 100644
index 0000000..9751040
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DataConnection.java
@@ -0,0 +1,1286 @@
+/*
+ * 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 com.android.internal.telephony.DataCallState.SetupResult;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import android.app.PendingIntent;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.net.ProxyProperties;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * {@hide}
+ *
+ * DataConnection StateMachine.
+ *
+ * This is an abstract base class for representing a single data connection.
+ * Instances of this class such as <code>CdmaDataConnection</code> and
+ * <code>GsmDataConnection</code>, * represent a connection via the cellular network.
+ * There may be multiple data connections and all of them are managed by the
+ * <code>DataConnectionTracker</code>.
+ *
+ * Instances are asynchronous state machines and have two primary entry points
+ * <code>connect()</code> and <code>disconnect</code>. The message a parameter will be returned
+ * hen the operation completes. The <code>msg.obj</code> will contain an AsyncResult
+ * object and <code>AsyncResult.userObj</code> is the original <code>msg.obj</code>. if successful
+ * with the <code>AsyncResult.result == null</code> and <code>AsyncResult.exception == null</code>.
+ * If an error <code>AsyncResult.result = FailCause</code> and
+ * <code>AsyncResult.exception = new Exception()</code>.
+ *
+ * The other public methods are provided for debugging.
+ */
+public abstract class DataConnection extends StateMachine {
+ protected static final boolean DBG = true;
+ protected static final boolean VDBG = false;
+
+ protected static AtomicInteger mCount = new AtomicInteger(0);
+ protected AsyncChannel mAc;
+
+ protected List<ApnContext> mApnList = null;
+ PendingIntent mReconnectIntent = null;
+
+ private DataConnectionTracker mDataConnectionTracker = null;
+
+ /**
+ * Used internally for saving connecting parameters.
+ */
+ protected static class ConnectionParams {
+ public ConnectionParams(ApnSetting apn, Message onCompletedMsg) {
+ this.apn = apn;
+ this.onCompletedMsg = onCompletedMsg;
+ }
+
+ public int tag;
+ public ApnSetting apn;
+ public Message onCompletedMsg;
+ }
+
+ /**
+ * Used internally for saving disconnecting parameters.
+ */
+ protected static class DisconnectParams {
+ public DisconnectParams(String reason, Message onCompletedMsg) {
+ this.reason = reason;
+ this.onCompletedMsg = onCompletedMsg;
+ }
+ public int tag;
+ public String reason;
+ public Message onCompletedMsg;
+ }
+
+ /**
+ * Returned as the reason for a connection failure as defined
+ * by RIL_DataCallFailCause in ril.h and some local errors.
+ */
+ public enum FailCause {
+ NONE(0),
+
+ // This series of errors as specified by the standards
+ // specified in ril.h
+ OPERATOR_BARRED(0x08),
+ INSUFFICIENT_RESOURCES(0x1A),
+ MISSING_UNKNOWN_APN(0x1B),
+ UNKNOWN_PDP_ADDRESS_TYPE(0x1C),
+ USER_AUTHENTICATION(0x1D),
+ ACTIVATION_REJECT_GGSN(0x1E),
+ ACTIVATION_REJECT_UNSPECIFIED(0x1F),
+ SERVICE_OPTION_NOT_SUPPORTED(0x20),
+ SERVICE_OPTION_NOT_SUBSCRIBED(0x21),
+ SERVICE_OPTION_OUT_OF_ORDER(0x22),
+ NSAPI_IN_USE(0x23),
+ ONLY_IPV4_ALLOWED(0x32),
+ ONLY_IPV6_ALLOWED(0x33),
+ ONLY_SINGLE_BEARER_ALLOWED(0x34),
+ PROTOCOL_ERRORS(0x6F),
+
+ // Local errors generated by Vendor RIL
+ // specified in ril.h
+ REGISTRATION_FAIL(-1),
+ GPRS_REGISTRATION_FAIL(-2),
+ SIGNAL_LOST(-3),
+ PREF_RADIO_TECH_CHANGED(-4),
+ RADIO_POWER_OFF(-5),
+ TETHERED_CALL_ACTIVE(-6),
+ ERROR_UNSPECIFIED(0xFFFF),
+
+ // Errors generated by the Framework
+ // specified here
+ UNKNOWN(0x10000),
+ RADIO_NOT_AVAILABLE(0x10001),
+ UNACCEPTABLE_NETWORK_PARAMETER(0x10002),
+ CONNECTION_TO_DATACONNECTIONAC_BROKEN(0x10003);
+
+ private final int mErrorCode;
+ private static final HashMap<Integer, FailCause> sErrorCodeToFailCauseMap;
+ static {
+ sErrorCodeToFailCauseMap = new HashMap<Integer, FailCause>();
+ for (FailCause fc : values()) {
+ sErrorCodeToFailCauseMap.put(fc.getErrorCode(), fc);
+ }
+ }
+
+ FailCause(int errorCode) {
+ mErrorCode = errorCode;
+ }
+
+ int getErrorCode() {
+ return mErrorCode;
+ }
+
+ public boolean isPermanentFail() {
+ return (this == OPERATOR_BARRED) || (this == MISSING_UNKNOWN_APN) ||
+ (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) ||
+ (this == SERVICE_OPTION_NOT_SUPPORTED) ||
+ (this == SERVICE_OPTION_NOT_SUBSCRIBED) || (this == NSAPI_IN_USE) ||
+ (this == PROTOCOL_ERRORS);
+ }
+
+ public boolean isEventLoggable() {
+ return (this == OPERATOR_BARRED) || (this == INSUFFICIENT_RESOURCES) ||
+ (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) ||
+ (this == ACTIVATION_REJECT_GGSN) || (this == ACTIVATION_REJECT_UNSPECIFIED) ||
+ (this == SERVICE_OPTION_NOT_SUBSCRIBED) ||
+ (this == SERVICE_OPTION_NOT_SUPPORTED) ||
+ (this == SERVICE_OPTION_OUT_OF_ORDER) || (this == NSAPI_IN_USE) ||
+ (this == PROTOCOL_ERRORS) ||
+ (this == UNACCEPTABLE_NETWORK_PARAMETER);
+ }
+
+ public static FailCause fromInt(int errorCode) {
+ FailCause fc = sErrorCodeToFailCauseMap.get(errorCode);
+ if (fc == null) {
+ fc = UNKNOWN;
+ }
+ return fc;
+ }
+ }
+
+ public static class CallSetupException extends Exception {
+ private int mRetryOverride = -1;
+
+ CallSetupException (int retryOverride) {
+ mRetryOverride = retryOverride;
+ }
+
+ public int getRetryOverride() {
+ return mRetryOverride;
+ }
+ }
+
+ // ***** Event codes for driving the state machine
+ protected static final int BASE = Protocol.BASE_DATA_CONNECTION;
+ protected static final int EVENT_CONNECT = BASE + 0;
+ protected static final int EVENT_SETUP_DATA_CONNECTION_DONE = BASE + 1;
+ protected static final int EVENT_GET_LAST_FAIL_DONE = BASE + 2;
+ protected static final int EVENT_DEACTIVATE_DONE = BASE + 3;
+ protected static final int EVENT_DISCONNECT = BASE + 4;
+ protected static final int EVENT_RIL_CONNECTED = BASE + 5;
+ protected static final int EVENT_DISCONNECT_ALL = BASE + 6;
+
+ private static final int CMD_TO_STRING_COUNT = EVENT_DISCONNECT_ALL - BASE + 1;
+ private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
+ static {
+ sCmdToString[EVENT_CONNECT - BASE] = "EVENT_CONNECT";
+ sCmdToString[EVENT_SETUP_DATA_CONNECTION_DONE - BASE] =
+ "EVENT_SETUP_DATA_CONNECTION_DONE";
+ sCmdToString[EVENT_GET_LAST_FAIL_DONE - BASE] = "EVENT_GET_LAST_FAIL_DONE";
+ sCmdToString[EVENT_DEACTIVATE_DONE - BASE] = "EVENT_DEACTIVATE_DONE";
+ sCmdToString[EVENT_DISCONNECT - BASE] = "EVENT_DISCONNECT";
+ sCmdToString[EVENT_RIL_CONNECTED - BASE] = "EVENT_RIL_CONNECTED";
+ sCmdToString[EVENT_DISCONNECT_ALL - BASE] = "EVENT_DISCONNECT_ALL";
+ }
+ protected static String cmdToString(int cmd) {
+ cmd -= BASE;
+ if ((cmd >= 0) && (cmd < sCmdToString.length)) {
+ return sCmdToString[cmd];
+ } else {
+ return null;
+ }
+ }
+
+ //***** Tag IDs for EventLog
+ protected static final int EVENT_LOG_BAD_DNS_ADDRESS = 50100;
+
+ //***** Member Variables
+ protected ApnSetting mApn;
+ protected int mTag;
+ protected PhoneBase phone;
+ protected int mRilVersion = -1;
+ protected int cid;
+ protected LinkProperties mLinkProperties = new LinkProperties();
+ protected LinkCapabilities mCapabilities = new LinkCapabilities();
+ protected long createTime;
+ protected long lastFailTime;
+ protected FailCause lastFailCause;
+ protected int mRetryOverride = -1;
+ protected static final String NULL_IP = "0.0.0.0";
+ protected int mRefCount;
+ Object userData;
+
+ //***** Abstract methods
+ @Override
+ public abstract String toString();
+
+ protected abstract void onConnect(ConnectionParams cp);
+
+ protected abstract boolean isDnsOk(String[] domainNameServers);
+
+ protected abstract void log(String s);
+
+ //***** Constructor
+ protected DataConnection(PhoneBase phone, String name, int id, RetryManager rm,
+ DataConnectionTracker dct) {
+ super(name);
+ setLogRecSize(100);
+ if (DBG) log("DataConnection constructor E");
+ this.phone = phone;
+ this.mDataConnectionTracker = dct;
+ mId = id;
+ mRetryMgr = rm;
+ this.cid = -1;
+
+ setDbg(false);
+ addState(mDefaultState);
+ addState(mInactiveState, mDefaultState);
+ addState(mActivatingState, mDefaultState);
+ addState(mActiveState, mDefaultState);
+ addState(mDisconnectingState, mDefaultState);
+ addState(mDisconnectingErrorCreatingConnection, mDefaultState);
+ setInitialState(mInactiveState);
+
+ mApnList = new ArrayList<ApnContext>();
+ if (DBG) log("DataConnection constructor X");
+ }
+
+ /**
+ * Shut down this instance and its state machine.
+ */
+ private void shutDown() {
+ if (DBG) log("shutDown");
+
+ if (mAc != null) {
+ mAc.disconnected();
+ mAc = null;
+ }
+ mApnList = null;
+ mReconnectIntent = null;
+ mDataConnectionTracker = null;
+ mApn = null;
+ phone = null;
+ mLinkProperties = null;
+ mCapabilities = null;
+ lastFailCause = null;
+ userData = null;
+ }
+
+ /**
+ * TearDown the data connection.
+ *
+ * @param o will be returned in AsyncResult.userObj
+ * and is either a DisconnectParams or ConnectionParams.
+ */
+ private void tearDownData(Object o) {
+ int discReason = RILConstants.DEACTIVATE_REASON_NONE;
+ if ((o != null) && (o instanceof DisconnectParams)) {
+ DisconnectParams dp = (DisconnectParams)o;
+ Message m = dp.onCompletedMsg;
+ if (TextUtils.equals(dp.reason, Phone.REASON_RADIO_TURNED_OFF)) {
+ discReason = RILConstants.DEACTIVATE_REASON_RADIO_OFF;
+ } else if (TextUtils.equals(dp.reason, Phone.REASON_PDP_RESET)) {
+ discReason = RILConstants.DEACTIVATE_REASON_PDP_RESET;
+ }
+ }
+ if (phone.mCM.getRadioState().isOn()) {
+ if (DBG) log("tearDownData radio is on, call deactivateDataCall");
+ phone.mCM.deactivateDataCall(cid, discReason, obtainMessage(EVENT_DEACTIVATE_DONE, o));
+ } else {
+ if (DBG) log("tearDownData radio is off sendMessage EVENT_DEACTIVATE_DONE immediately");
+ AsyncResult ar = new AsyncResult(o, null, null);
+ sendMessage(obtainMessage(EVENT_DEACTIVATE_DONE, ar));
+ }
+ }
+
+ /**
+ * Send the connectionCompletedMsg.
+ *
+ * @param cp is the ConnectionParams
+ * @param cause
+ */
+ private void notifyConnectCompleted(ConnectionParams cp, FailCause cause) {
+ Message connectionCompletedMsg = cp.onCompletedMsg;
+ if (connectionCompletedMsg == null) {
+ return;
+ }
+
+ long timeStamp = System.currentTimeMillis();
+ connectionCompletedMsg.arg1 = cid;
+
+ if (cause == FailCause.NONE) {
+ createTime = timeStamp;
+ AsyncResult.forMessage(connectionCompletedMsg);
+ } else {
+ lastFailCause = cause;
+ lastFailTime = timeStamp;
+ AsyncResult.forMessage(connectionCompletedMsg, cause,
+ new CallSetupException(mRetryOverride));
+ }
+ if (DBG) log("notifyConnectionCompleted at " + timeStamp + " cause=" + cause);
+
+ connectionCompletedMsg.sendToTarget();
+ }
+
+ /**
+ * Send ar.userObj if its a message, which is should be back to originator.
+ *
+ * @param dp is the DisconnectParams.
+ */
+ private void notifyDisconnectCompleted(DisconnectParams dp, boolean sendAll) {
+ if (VDBG) log("NotifyDisconnectCompleted");
+
+ ApnContext alreadySent = null;
+ String reason = null;
+
+ if (dp.onCompletedMsg != null) {
+ // Get ApnContext, but only valid on GSM devices this is a string on CDMA devices.
+ Message msg = dp.onCompletedMsg;
+ if (msg.obj instanceof ApnContext) {
+ alreadySent = (ApnContext)msg.obj;
+ }
+ reason = dp.reason;
+ if (VDBG) {
+ log(String.format("msg=%s msg.obj=%s", msg.toString(),
+ ((msg.obj instanceof String) ? (String) msg.obj : "<no-reason>")));
+ }
+ AsyncResult.forMessage(msg);
+ msg.sendToTarget();
+ }
+ if (sendAll) {
+ for (ApnContext a : mApnList) {
+ if (a == alreadySent) continue;
+ if (reason != null) a.setReason(reason);
+ Message msg = mDataConnectionTracker.obtainMessage(
+ DctConstants.EVENT_DISCONNECT_DONE, a);
+ AsyncResult.forMessage(msg);
+ msg.sendToTarget();
+ }
+ }
+
+ if (DBG) log("NotifyDisconnectCompleted DisconnectParams=" + dp);
+ }
+
+ protected int getRilRadioTechnology(int defaultRilRadioTechnology) {
+ int rilRadioTechnology;
+ if (mRilVersion < 6) {
+ rilRadioTechnology = defaultRilRadioTechnology;
+ } else {
+ rilRadioTechnology = phone.getServiceState().getRilRadioTechnology() + 2;
+ }
+ return rilRadioTechnology;
+ }
+
+ /*
+ * **************************************************************************
+ * Begin Members and methods owned by DataConnectionTracker but stored
+ * in a DataConnection because there is one per connection.
+ * **************************************************************************
+ */
+
+ /*
+ * The id is owned by DataConnectionTracker.
+ */
+ private int mId;
+
+ /**
+ * Get the DataConnection ID
+ */
+ public int getDataConnectionId() {
+ return mId;
+ }
+
+ /*
+ * The retry manager is currently owned by the DataConnectionTracker but is stored
+ * in the DataConnection because there is one per connection. These methods
+ * should only be used by the DataConnectionTracker although someday the retrying
+ * maybe managed by the DataConnection itself and these methods could disappear.
+ */
+ private RetryManager mRetryMgr;
+
+ /**
+ * @return retry manager retryCount
+ */
+ public int getRetryCount() {
+ return mRetryMgr.getRetryCount();
+ }
+
+ /**
+ * set retry manager retryCount
+ */
+ public void setRetryCount(int retryCount) {
+ if (DBG) log("setRetryCount: " + retryCount);
+ mRetryMgr.setRetryCount(retryCount);
+ }
+
+ /**
+ * @return retry manager retryTimer
+ */
+ public int getRetryTimer() {
+ return mRetryMgr.getRetryTimer();
+ }
+
+ /**
+ * increaseRetryCount of retry manager
+ */
+ public void increaseRetryCount() {
+ mRetryMgr.increaseRetryCount();
+ }
+
+ /**
+ * @return retry manager isRetryNeeded
+ */
+ public boolean isRetryNeeded() {
+ return mRetryMgr.isRetryNeeded();
+ }
+
+ /**
+ * resetRetryCount of retry manager
+ */
+ public void resetRetryCount() {
+ mRetryMgr.resetRetryCount();
+ }
+
+ /**
+ * set retryForeverUsingLasttimeout of retry manager
+ */
+ public void retryForeverUsingLastTimeout() {
+ mRetryMgr.retryForeverUsingLastTimeout();
+ }
+
+ /**
+ * @return retry manager isRetryForever
+ */
+ public boolean isRetryForever() {
+ return mRetryMgr.isRetryForever();
+ }
+
+ /**
+ * @return whether the retry config is set successfully or not
+ */
+ public boolean configureRetry(int maxRetryCount, int retryTime, int randomizationTime) {
+ return mRetryMgr.configure(maxRetryCount, retryTime, randomizationTime);
+ }
+
+ /**
+ * @return whether the retry config is set successfully or not
+ */
+ public boolean configureRetry(String configStr) {
+ return mRetryMgr.configure(configStr);
+ }
+
+ /*
+ * **************************************************************************
+ * End members owned by DataConnectionTracker
+ * **************************************************************************
+ */
+
+ /**
+ * Clear all settings called when entering mInactiveState.
+ */
+ protected void clearSettings() {
+ if (DBG) log("clearSettings");
+
+ createTime = -1;
+ lastFailTime = -1;
+ lastFailCause = FailCause.NONE;
+ mRetryOverride = -1;
+ mRefCount = 0;
+ cid = -1;
+
+ mLinkProperties = new LinkProperties();
+ mApn = null;
+ }
+
+ /**
+ * Process setup completion.
+ *
+ * @param ar is the result
+ * @return SetupResult.
+ */
+ private DataCallState.SetupResult onSetupConnectionCompleted(AsyncResult ar) {
+ DataCallState response = (DataCallState) ar.result;
+ ConnectionParams cp = (ConnectionParams) ar.userObj;
+ DataCallState.SetupResult result;
+
+ if (ar.exception != null) {
+ if (DBG) {
+ log("onSetupConnectionCompleted failed, ar.exception=" + ar.exception +
+ " response=" + response);
+ }
+
+ if (ar.exception instanceof CommandException
+ && ((CommandException) (ar.exception)).getCommandError()
+ == CommandException.Error.RADIO_NOT_AVAILABLE) {
+ result = DataCallState.SetupResult.ERR_BadCommand;
+ result.mFailCause = FailCause.RADIO_NOT_AVAILABLE;
+ } else if ((response == null) || (response.version < 4)) {
+ result = DataCallState.SetupResult.ERR_GetLastErrorFromRil;
+ } else {
+ result = DataCallState.SetupResult.ERR_RilError;
+ result.mFailCause = FailCause.fromInt(response.status);
+ }
+ } else if (cp.tag != mTag) {
+ if (DBG) {
+ log("BUG: onSetupConnectionCompleted is stale cp.tag=" + cp.tag + ", mtag=" + mTag);
+ }
+ result = DataCallState.SetupResult.ERR_Stale;
+ } else if (response.status != 0) {
+ result = DataCallState.SetupResult.ERR_RilError;
+ result.mFailCause = FailCause.fromInt(response.status);
+ } else {
+ if (DBG) log("onSetupConnectionCompleted received DataCallState: " + response);
+ cid = response.cid;
+ result = updateLinkProperty(response).setupResult;
+ }
+
+ return result;
+ }
+
+ private int getSuggestedRetryTime(AsyncResult ar) {
+ int retry = -1;
+ if (ar.exception == null) {
+ DataCallState response = (DataCallState) ar.result;
+ retry = response.suggestedRetryTime;
+ }
+ return retry;
+ }
+
+ private DataCallState.SetupResult setLinkProperties(DataCallState response,
+ LinkProperties lp) {
+ // Check if system property dns usable
+ boolean okToUseSystemPropertyDns = false;
+ String propertyPrefix = "net." + response.ifname + ".";
+ String dnsServers[] = new String[2];
+ dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
+ dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
+ okToUseSystemPropertyDns = isDnsOk(dnsServers);
+
+ // set link properties based on data call response
+ return response.setLinkProperties(lp, okToUseSystemPropertyDns);
+ }
+
+ public static class UpdateLinkPropertyResult {
+ public DataCallState.SetupResult setupResult = DataCallState.SetupResult.SUCCESS;
+ public LinkProperties oldLp;
+ public LinkProperties newLp;
+ public UpdateLinkPropertyResult(LinkProperties curLp) {
+ oldLp = curLp;
+ newLp = curLp;
+ }
+ }
+
+ private UpdateLinkPropertyResult updateLinkProperty(DataCallState newState) {
+ UpdateLinkPropertyResult result = new UpdateLinkPropertyResult(mLinkProperties);
+
+ if (newState == null) return result;
+
+ DataCallState.SetupResult setupResult;
+ result.newLp = new LinkProperties();
+
+ // set link properties based on data call response
+ result.setupResult = setLinkProperties(newState, result.newLp);
+ if (result.setupResult != DataCallState.SetupResult.SUCCESS) {
+ if (DBG) log("updateLinkProperty failed : " + result.setupResult);
+ return result;
+ }
+ // copy HTTP proxy as it is not part DataCallState.
+ result.newLp.setHttpProxy(mLinkProperties.getHttpProxy());
+
+ if (DBG && (! result.oldLp.equals(result.newLp))) {
+ log("updateLinkProperty old LP=" + result.oldLp);
+ log("updateLinkProperty new LP=" + result.newLp);
+ }
+ mLinkProperties = result.newLp;
+
+ return result;
+ }
+
+ /**
+ * The parent state for all other states.
+ */
+ private class DcDefaultState extends State {
+ @Override
+ public void enter() {
+ phone.mCM.registerForRilConnected(getHandler(), EVENT_RIL_CONNECTED, null);
+ }
+ @Override
+ public void exit() {
+ phone.mCM.unregisterForRilConnected(getHandler());
+ shutDown();
+ }
+ @Override
+ public boolean processMessage(Message msg) {
+ boolean retVal = HANDLED;
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+ if (mAc != null) {
+ if (VDBG) log("Disconnecting to previous connection mAc=" + mAc);
+ mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
+ } else {
+ mAc = new AsyncChannel();
+ mAc.connected(null, getHandler(), msg.replyTo);
+ if (VDBG) log("DcDefaultState: FULL_CONNECTION reply connected");
+ mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL, mId, "hi");
+ }
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ if (VDBG) log("CMD_CHANNEL_DISCONNECTED");
+ quit();
+ break;
+ }
+ case DataConnectionAc.REQ_IS_INACTIVE: {
+ boolean val = getCurrentState() == mInactiveState;
+ if (VDBG) log("REQ_IS_INACTIVE isInactive=" + val);
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_IS_INACTIVE, val ? 1 : 0);
+ break;
+ }
+ case DataConnectionAc.REQ_GET_CID: {
+ if (VDBG) log("REQ_GET_CID cid=" + cid);
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_CID, cid);
+ break;
+ }
+ case DataConnectionAc.REQ_GET_APNSETTING: {
+ if (VDBG) log("REQ_GET_APNSETTING apnSetting=" + mApn);
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_APNSETTING, mApn);
+ break;
+ }
+ case DataConnectionAc.REQ_GET_LINK_PROPERTIES: {
+ LinkProperties lp = new LinkProperties(mLinkProperties);
+ if (VDBG) log("REQ_GET_LINK_PROPERTIES linkProperties" + lp);
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_LINK_PROPERTIES, lp);
+ break;
+ }
+ case DataConnectionAc.REQ_SET_LINK_PROPERTIES_HTTP_PROXY: {
+ ProxyProperties proxy = (ProxyProperties) msg.obj;
+ if (VDBG) log("REQ_SET_LINK_PROPERTIES_HTTP_PROXY proxy=" + proxy);
+ mLinkProperties.setHttpProxy(proxy);
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_SET_LINK_PROPERTIES_HTTP_PROXY);
+ break;
+ }
+ case DataConnectionAc.REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE: {
+ DataCallState newState = (DataCallState) msg.obj;
+ UpdateLinkPropertyResult result =
+ updateLinkProperty(newState);
+ if (VDBG) {
+ log("REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE result="
+ + result + " newState=" + newState);
+ }
+ mAc.replyToMessage(msg,
+ DataConnectionAc.RSP_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE,
+ result);
+ break;
+ }
+ case DataConnectionAc.REQ_GET_LINK_CAPABILITIES: {
+ LinkCapabilities lc = new LinkCapabilities(mCapabilities);
+ if (VDBG) log("REQ_GET_LINK_CAPABILITIES linkCapabilities" + lc);
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_LINK_CAPABILITIES, lc);
+ break;
+ }
+ case DataConnectionAc.REQ_RESET:
+ if (VDBG) log("DcDefaultState: msg.what=REQ_RESET");
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_RESET);
+ transitionTo(mInactiveState);
+ break;
+ case DataConnectionAc.REQ_GET_REFCOUNT: {
+ if (VDBG) log("REQ_GET_REFCOUNT refCount=" + mRefCount);
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_REFCOUNT, mRefCount);
+ break;
+ }
+ case DataConnectionAc.REQ_ADD_APNCONTEXT: {
+ ApnContext apnContext = (ApnContext) msg.obj;
+ if (VDBG) log("REQ_ADD_APNCONTEXT apn=" + apnContext.getApnType());
+ if (!mApnList.contains(apnContext)) {
+ mApnList.add(apnContext);
+ }
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_ADD_APNCONTEXT);
+ break;
+ }
+ case DataConnectionAc.REQ_REMOVE_APNCONTEXT: {
+ ApnContext apnContext = (ApnContext) msg.obj;
+ if (VDBG) log("REQ_REMOVE_APNCONTEXT apn=" + apnContext.getApnType());
+ mApnList.remove(apnContext);
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_REMOVE_APNCONTEXT);
+ break;
+ }
+ case DataConnectionAc.REQ_GET_APNCONTEXT_LIST: {
+ if (VDBG) log("REQ_GET_APNCONTEXT_LIST num in list=" + mApnList.size());
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_APNCONTEXT_LIST,
+ new ArrayList<ApnContext>(mApnList));
+ break;
+ }
+ case DataConnectionAc.REQ_SET_RECONNECT_INTENT: {
+ PendingIntent intent = (PendingIntent) msg.obj;
+ if (VDBG) log("REQ_SET_RECONNECT_INTENT");
+ mReconnectIntent = intent;
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_SET_RECONNECT_INTENT);
+ break;
+ }
+ case DataConnectionAc.REQ_GET_RECONNECT_INTENT: {
+ if (VDBG) log("REQ_GET_RECONNECT_INTENT");
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_RECONNECT_INTENT,
+ mReconnectIntent);
+ break;
+ }
+ case EVENT_CONNECT:
+ if (DBG) log("DcDefaultState: msg.what=EVENT_CONNECT, fail not expected");
+ ConnectionParams cp = (ConnectionParams) msg.obj;
+ notifyConnectCompleted(cp, FailCause.UNKNOWN);
+ break;
+
+ case EVENT_DISCONNECT:
+ if (DBG) {
+ log("DcDefaultState deferring msg.what=EVENT_DISCONNECT" + mRefCount);
+ }
+ deferMessage(msg);
+ break;
+
+ case EVENT_DISCONNECT_ALL:
+ if (DBG) {
+ log("DcDefaultState deferring msg.what=EVENT_DISCONNECT_ALL" + mRefCount);
+ }
+ deferMessage(msg);
+ break;
+
+ case EVENT_RIL_CONNECTED:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ mRilVersion = (Integer)ar.result;
+ if (DBG) {
+ log("DcDefaultState: msg.what=EVENT_RIL_CONNECTED mRilVersion=" +
+ mRilVersion);
+ }
+ } else {
+ log("Unexpected exception on EVENT_RIL_CONNECTED");
+ mRilVersion = -1;
+ }
+ break;
+
+ default:
+ if (DBG) {
+ log("DcDefaultState: shouldn't happen but ignore msg.what=0x" +
+ Integer.toHexString(msg.what));
+ }
+ break;
+ }
+
+ return retVal;
+ }
+ }
+ private DcDefaultState mDefaultState = new DcDefaultState();
+
+ /**
+ * The state machine is inactive and expects a EVENT_CONNECT.
+ */
+ private class DcInactiveState extends State {
+ private ConnectionParams mConnectionParams = null;
+ private FailCause mFailCause = null;
+ private DisconnectParams mDisconnectParams = null;
+
+ public void setEnterNotificationParams(ConnectionParams cp, FailCause cause,
+ int retryOverride) {
+ if (VDBG) log("DcInactiveState: setEnterNoticationParams cp,cause");
+ mConnectionParams = cp;
+ mFailCause = cause;
+ mRetryOverride = retryOverride;
+ }
+
+ public void setEnterNotificationParams(DisconnectParams dp) {
+ if (VDBG) log("DcInactiveState: setEnterNoticationParams dp");
+ mDisconnectParams = dp;
+ }
+
+ @Override
+ public void enter() {
+ mTag += 1;
+
+ /**
+ * Now that we've transitioned to Inactive state we
+ * can send notifications. Previously we sent the
+ * notifications in the processMessage handler but
+ * that caused a race condition because the synchronous
+ * call to isInactive.
+ */
+ if ((mConnectionParams != null) && (mFailCause != null)) {
+ if (VDBG) log("DcInactiveState: enter notifyConnectCompleted");
+ notifyConnectCompleted(mConnectionParams, mFailCause);
+ }
+ if (mDisconnectParams != null) {
+ if (VDBG) log("DcInactiveState: enter notifyDisconnectCompleted");
+ notifyDisconnectCompleted(mDisconnectParams, true);
+ }
+ clearSettings();
+ }
+
+ @Override
+ public void exit() {
+ // clear notifications
+ mConnectionParams = null;
+ mFailCause = null;
+ mDisconnectParams = null;
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ boolean retVal;
+
+ switch (msg.what) {
+ case DataConnectionAc.REQ_RESET:
+ if (DBG) {
+ log("DcInactiveState: msg.what=RSP_RESET, ignore we're already reset");
+ }
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_RESET);
+ retVal = HANDLED;
+ break;
+
+ case EVENT_CONNECT:
+ ConnectionParams cp = (ConnectionParams) msg.obj;
+ cp.tag = mTag;
+ if (DBG) {
+ log("DcInactiveState msg.what=EVENT_CONNECT." + "RefCount = "
+ + mRefCount);
+ }
+ mRefCount = 1;
+ onConnect(cp);
+ transitionTo(mActivatingState);
+ retVal = HANDLED;
+ break;
+
+ case EVENT_DISCONNECT:
+ if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT");
+ notifyDisconnectCompleted((DisconnectParams)msg.obj, false);
+ retVal = HANDLED;
+ break;
+
+ case EVENT_DISCONNECT_ALL:
+ if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT_ALL");
+ notifyDisconnectCompleted((DisconnectParams)msg.obj, false);
+ retVal = HANDLED;
+ break;
+
+ default:
+ if (VDBG) {
+ log("DcInactiveState nothandled msg.what=0x" +
+ Integer.toHexString(msg.what));
+ }
+ retVal = NOT_HANDLED;
+ break;
+ }
+ return retVal;
+ }
+ }
+ private DcInactiveState mInactiveState = new DcInactiveState();
+
+ /**
+ * The state machine is activating a connection.
+ */
+ private class DcActivatingState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ boolean retVal;
+ AsyncResult ar;
+ ConnectionParams cp;
+
+ switch (msg.what) {
+ case EVENT_CONNECT:
+ if (DBG) log("DcActivatingState deferring msg.what=EVENT_CONNECT refCount = "
+ + mRefCount);
+ deferMessage(msg);
+ retVal = HANDLED;
+ break;
+
+ case EVENT_SETUP_DATA_CONNECTION_DONE:
+ if (DBG) log("DcActivatingState msg.what=EVENT_SETUP_DATA_CONNECTION_DONE");
+
+ ar = (AsyncResult) msg.obj;
+ cp = (ConnectionParams) ar.userObj;
+
+ DataCallState.SetupResult result = onSetupConnectionCompleted(ar);
+ if (DBG) log("DcActivatingState onSetupConnectionCompleted result=" + result);
+ switch (result) {
+ case SUCCESS:
+ // All is well
+ mActiveState.setEnterNotificationParams(cp, FailCause.NONE);
+ transitionTo(mActiveState);
+ break;
+ case ERR_BadCommand:
+ // Vendor ril rejected the command and didn't connect.
+ // Transition to inactive but send notifications after
+ // we've entered the mInactive state.
+ mInactiveState.setEnterNotificationParams(cp, result.mFailCause, -1);
+ transitionTo(mInactiveState);
+ break;
+ case ERR_UnacceptableParameter:
+ // The addresses given from the RIL are bad
+ tearDownData(cp);
+ transitionTo(mDisconnectingErrorCreatingConnection);
+ break;
+ case ERR_GetLastErrorFromRil:
+ // Request failed and this is an old RIL
+ phone.mCM.getLastDataCallFailCause(
+ obtainMessage(EVENT_GET_LAST_FAIL_DONE, cp));
+ break;
+ case ERR_RilError:
+ // Request failed and mFailCause has the reason
+ mInactiveState.setEnterNotificationParams(cp, result.mFailCause,
+ getSuggestedRetryTime(ar));
+ transitionTo(mInactiveState);
+ break;
+ case ERR_Stale:
+ // Request is stale, ignore.
+ break;
+ default:
+ throw new RuntimeException("Unknown SetupResult, should not happen");
+ }
+ retVal = HANDLED;
+ break;
+
+ case EVENT_GET_LAST_FAIL_DONE:
+ ar = (AsyncResult) msg.obj;
+ cp = (ConnectionParams) ar.userObj;
+ FailCause cause = FailCause.UNKNOWN;
+
+ if (cp.tag == mTag) {
+ if (DBG) log("DcActivatingState msg.what=EVENT_GET_LAST_FAIL_DONE");
+ if (ar.exception == null) {
+ int rilFailCause = ((int[]) (ar.result))[0];
+ cause = FailCause.fromInt(rilFailCause);
+ }
+ // Transition to inactive but send notifications after
+ // we've entered the mInactive state.
+ mInactiveState.setEnterNotificationParams(cp, cause, -1);
+ transitionTo(mInactiveState);
+ } else {
+ if (DBG) {
+ log("DcActivatingState EVENT_GET_LAST_FAIL_DONE is stale cp.tag="
+ + cp.tag + ", mTag=" + mTag);
+ }
+ }
+
+ retVal = HANDLED;
+ break;
+
+ default:
+ if (VDBG) {
+ log("DcActivatingState not handled msg.what=0x" +
+ Integer.toHexString(msg.what));
+ }
+ retVal = NOT_HANDLED;
+ break;
+ }
+ return retVal;
+ }
+ }
+ private DcActivatingState mActivatingState = new DcActivatingState();
+
+ /**
+ * The state machine is connected, expecting an EVENT_DISCONNECT.
+ */
+ private class DcActiveState extends State {
+ private ConnectionParams mConnectionParams = null;
+ private FailCause mFailCause = null;
+
+ public void setEnterNotificationParams(ConnectionParams cp, FailCause cause) {
+ if (VDBG) log("DcInactiveState: setEnterNoticationParams cp,cause");
+ mConnectionParams = cp;
+ mFailCause = cause;
+ }
+
+ @Override public void enter() {
+ /**
+ * Now that we've transitioned to Active state we
+ * can send notifications. Previously we sent the
+ * notifications in the processMessage handler but
+ * that caused a race condition because the synchronous
+ * call to isActive.
+ */
+ if ((mConnectionParams != null) && (mFailCause != null)) {
+ if (VDBG) log("DcActiveState: enter notifyConnectCompleted");
+ notifyConnectCompleted(mConnectionParams, mFailCause);
+ }
+ }
+
+ @Override
+ public void exit() {
+ // clear notifications
+ mConnectionParams = null;
+ mFailCause = null;
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ boolean retVal;
+
+ switch (msg.what) {
+ case EVENT_CONNECT:
+ mRefCount++;
+ if (DBG) log("DcActiveState msg.what=EVENT_CONNECT RefCount=" + mRefCount);
+ if (msg.obj != null) {
+ notifyConnectCompleted((ConnectionParams) msg.obj, FailCause.NONE);
+ }
+ retVal = HANDLED;
+ break;
+ case EVENT_DISCONNECT:
+ mRefCount--;
+ if (DBG) log("DcActiveState msg.what=EVENT_DISCONNECT RefCount=" + mRefCount);
+ if (mRefCount == 0)
+ {
+ DisconnectParams dp = (DisconnectParams) msg.obj;
+ dp.tag = mTag;
+ tearDownData(dp);
+ transitionTo(mDisconnectingState);
+ } else {
+ if (msg.obj != null) {
+ notifyDisconnectCompleted((DisconnectParams) msg.obj, false);
+ }
+ }
+ retVal = HANDLED;
+ break;
+
+ case EVENT_DISCONNECT_ALL:
+ if (DBG) {
+ log("DcActiveState msg.what=EVENT_DISCONNECT_ALL RefCount=" + mRefCount);
+ }
+ mRefCount = 0;
+ DisconnectParams dp = (DisconnectParams) msg.obj;
+ dp.tag = mTag;
+ tearDownData(dp);
+ transitionTo(mDisconnectingState);
+ retVal = HANDLED;
+ break;
+
+ default:
+ if (VDBG) {
+ log("DcActiveState not handled msg.what=0x" +
+ Integer.toHexString(msg.what));
+ }
+ retVal = NOT_HANDLED;
+ break;
+ }
+ return retVal;
+ }
+ }
+ private DcActiveState mActiveState = new DcActiveState();
+
+ /**
+ * The state machine is disconnecting.
+ */
+ private class DcDisconnectingState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ boolean retVal;
+
+ switch (msg.what) {
+ case EVENT_CONNECT:
+ if (DBG) log("DcDisconnectingState msg.what=EVENT_CONNECT. Defer. RefCount = "
+ + mRefCount);
+ deferMessage(msg);
+ retVal = HANDLED;
+ break;
+
+ case EVENT_DEACTIVATE_DONE:
+ if (DBG) log("DcDisconnectingState msg.what=EVENT_DEACTIVATE_DONE");
+ AsyncResult ar = (AsyncResult) msg.obj;
+ DisconnectParams dp = (DisconnectParams) ar.userObj;
+ if (dp.tag == mTag) {
+ // Transition to inactive but send notifications after
+ // we've entered the mInactive state.
+ mInactiveState.setEnterNotificationParams((DisconnectParams) ar.userObj);
+ transitionTo(mInactiveState);
+ } else {
+ if (DBG) log("DcDisconnectState EVENT_DEACTIVATE_DONE stale dp.tag="
+ + dp.tag + " mTag=" + mTag);
+ }
+ retVal = HANDLED;
+ break;
+
+ default:
+ if (VDBG) {
+ log("DcDisconnectingState not handled msg.what=0x" +
+ Integer.toHexString(msg.what));
+ }
+ retVal = NOT_HANDLED;
+ break;
+ }
+ return retVal;
+ }
+ }
+ private DcDisconnectingState mDisconnectingState = new DcDisconnectingState();
+
+ /**
+ * The state machine is disconnecting after an creating a connection.
+ */
+ private class DcDisconnectionErrorCreatingConnection extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ boolean retVal;
+
+ switch (msg.what) {
+ case EVENT_DEACTIVATE_DONE:
+ AsyncResult ar = (AsyncResult) msg.obj;
+ ConnectionParams cp = (ConnectionParams) ar.userObj;
+ if (cp.tag == mTag) {
+ if (DBG) {
+ log("DcDisconnectionErrorCreatingConnection" +
+ " msg.what=EVENT_DEACTIVATE_DONE");
+ }
+
+ // Transition to inactive but send notifications after
+ // we've entered the mInactive state.
+ mInactiveState.setEnterNotificationParams(cp,
+ FailCause.UNACCEPTABLE_NETWORK_PARAMETER, -1);
+ transitionTo(mInactiveState);
+ } else {
+ if (DBG) {
+ log("DcDisconnectionErrorCreatingConnection EVENT_DEACTIVATE_DONE" +
+ " stale dp.tag=" + cp.tag + ", mTag=" + mTag);
+ }
+ }
+ retVal = HANDLED;
+ break;
+
+ default:
+ if (VDBG) {
+ log("DcDisconnectionErrorCreatingConnection not handled msg.what=0x"
+ + Integer.toHexString(msg.what));
+ }
+ retVal = NOT_HANDLED;
+ break;
+ }
+ return retVal;
+ }
+ }
+ private DcDisconnectionErrorCreatingConnection mDisconnectingErrorCreatingConnection =
+ new DcDisconnectionErrorCreatingConnection();
+
+ // ******* public interface
+
+ /**
+ * Bring up a connection to the apn and return an AsyncResult in onCompletedMsg.
+ * Used for cellular networks that use Acesss Point Names (APN) such
+ * as GSM networks.
+ *
+ * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
+ * With AsyncResult.userObj set to the original msg.obj,
+ * AsyncResult.result = FailCause and AsyncResult.exception = Exception().
+ * @param apn is the Access Point Name to bring up a connection to
+ */
+ public void bringUp(Message onCompletedMsg, ApnSetting apn) {
+ sendMessage(obtainMessage(EVENT_CONNECT, new ConnectionParams(apn, onCompletedMsg)));
+ }
+
+ /**
+ * Tear down the connection through the apn on the network.
+ *
+ * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
+ * With AsyncResult.userObj set to the original msg.obj.
+ */
+ public void tearDown(String reason, Message onCompletedMsg) {
+ sendMessage(obtainMessage(EVENT_DISCONNECT, new DisconnectParams(reason, onCompletedMsg)));
+ }
+
+ /**
+ * Tear down the connection through the apn on the network. Ignores refcount and
+ * and always tears down.
+ *
+ * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
+ * With AsyncResult.userObj set to the original msg.obj.
+ */
+ public void tearDownAll(String reason, Message onCompletedMsg) {
+ sendMessage(obtainMessage(EVENT_DISCONNECT_ALL,
+ new DisconnectParams(reason, onCompletedMsg)));
+ }
+
+ /**
+ * @return the string for msg.what as our info.
+ */
+ @Override
+ protected String getWhatToString(int what) {
+ String info = null;
+ info = cmdToString(what);
+ if (info == null) {
+ info = DataConnectionAc.cmdToString(what);
+ }
+ return info;
+ }
+
+ /**
+ * Dump the current state.
+ *
+ * @param fd
+ * @param pw
+ * @param args
+ */
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print("DataConnection ");
+ super.dump(fd, pw, args);
+ pw.println(" mApnList=" + mApnList);
+ pw.flush();
+ pw.println(" mDataConnectionTracker=" + mDataConnectionTracker);
+ pw.println(" mApn=" + mApn);
+ pw.println(" mTag=" + mTag);
+ pw.flush();
+ pw.println(" phone=" + phone);
+ pw.println(" mRilVersion=" + mRilVersion);
+ pw.println(" cid=" + cid);
+ pw.flush();
+ pw.println(" mLinkProperties=" + mLinkProperties);
+ pw.flush();
+ pw.println(" mCapabilities=" + mCapabilities);
+ pw.println(" createTime=" + TimeUtils.logTimeOfDay(createTime));
+ pw.println(" lastFailTime=" + TimeUtils.logTimeOfDay(lastFailTime));
+ pw.println(" lastFailCause=" + lastFailCause);
+ pw.flush();
+ pw.println(" mRetryOverride=" + mRetryOverride);
+ pw.println(" mRefCount=" + mRefCount);
+ pw.println(" userData=" + userData);
+ if (mRetryMgr != null) pw.println(" " + mRetryMgr);
+ pw.flush();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/DataConnectionAc.java b/src/java/com/android/internal/telephony/DataConnectionAc.java
new file mode 100644
index 0000000..a24414f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DataConnectionAc.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) 2011 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 com.android.internal.telephony.DataConnection.UpdateLinkPropertyResult;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+import android.app.PendingIntent;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.net.ProxyProperties;
+import android.os.Message;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * AsyncChannel to a DataConnection
+ */
+public class DataConnectionAc extends AsyncChannel {
+ private static final boolean DBG = false;
+ private String mLogTag;
+
+ public DataConnection dataConnection;
+
+ public static final int BASE = Protocol.BASE_DATA_CONNECTION_AC;
+
+ public static final int REQ_IS_INACTIVE = BASE + 0;
+ public static final int RSP_IS_INACTIVE = BASE + 1;
+
+ public static final int REQ_GET_CID = BASE + 2;
+ public static final int RSP_GET_CID = BASE + 3;
+
+ public static final int REQ_GET_APNSETTING = BASE + 4;
+ public static final int RSP_GET_APNSETTING = BASE + 5;
+
+ public static final int REQ_GET_LINK_PROPERTIES = BASE + 6;
+ public static final int RSP_GET_LINK_PROPERTIES = BASE + 7;
+
+ public static final int REQ_SET_LINK_PROPERTIES_HTTP_PROXY = BASE + 8;
+ public static final int RSP_SET_LINK_PROPERTIES_HTTP_PROXY = BASE + 9;
+
+ public static final int REQ_GET_LINK_CAPABILITIES = BASE + 10;
+ public static final int RSP_GET_LINK_CAPABILITIES = BASE + 11;
+
+ public static final int REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE = BASE + 12;
+ public static final int RSP_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE = BASE + 13;
+
+ public static final int REQ_RESET = BASE + 14;
+ public static final int RSP_RESET = BASE + 15;
+
+ public static final int REQ_GET_REFCOUNT = BASE + 16;
+ public static final int RSP_GET_REFCOUNT = BASE + 17;
+
+ public static final int REQ_ADD_APNCONTEXT = BASE + 18;
+ public static final int RSP_ADD_APNCONTEXT = BASE + 19;
+
+ public static final int REQ_REMOVE_APNCONTEXT = BASE + 20;
+ public static final int RSP_REMOVE_APNCONTEXT = BASE + 21;
+
+ public static final int REQ_GET_APNCONTEXT_LIST = BASE + 22;
+ public static final int RSP_GET_APNCONTEXT_LIST = BASE + 23;
+
+ public static final int REQ_SET_RECONNECT_INTENT = BASE + 24;
+ public static final int RSP_SET_RECONNECT_INTENT = BASE + 25;
+
+ public static final int REQ_GET_RECONNECT_INTENT = BASE + 26;
+ public static final int RSP_GET_RECONNECT_INTENT = BASE + 27;
+
+ private static final int CMD_TO_STRING_COUNT = RSP_GET_RECONNECT_INTENT - BASE + 1;
+ private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
+ static {
+ sCmdToString[REQ_IS_INACTIVE - BASE] = "REQ_IS_INACTIVE";
+ sCmdToString[RSP_IS_INACTIVE - BASE] = "RSP_IS_INACTIVE";
+ sCmdToString[REQ_GET_CID - BASE] = "REQ_GET_CID";
+ sCmdToString[RSP_GET_CID - BASE] = "RSP_GET_CID";
+ sCmdToString[REQ_GET_APNSETTING - BASE] = "REQ_GET_APNSETTING";
+ sCmdToString[RSP_GET_APNSETTING - BASE] = "RSP_GET_APNSETTING";
+ sCmdToString[REQ_GET_LINK_PROPERTIES - BASE] = "REQ_GET_LINK_PROPERTIES";
+ sCmdToString[RSP_GET_LINK_PROPERTIES - BASE] = "RSP_GET_LINK_PROPERTIES";
+ sCmdToString[REQ_SET_LINK_PROPERTIES_HTTP_PROXY - BASE] =
+ "REQ_SET_LINK_PROPERTIES_HTTP_PROXY";
+ sCmdToString[RSP_SET_LINK_PROPERTIES_HTTP_PROXY - BASE] =
+ "RSP_SET_LINK_PROPERTIES_HTTP_PROXY";
+ sCmdToString[REQ_GET_LINK_CAPABILITIES - BASE] = "REQ_GET_LINK_CAPABILITIES";
+ sCmdToString[RSP_GET_LINK_CAPABILITIES - BASE] = "RSP_GET_LINK_CAPABILITIES";
+ sCmdToString[REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE - BASE] =
+ "REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE";
+ sCmdToString[RSP_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE - BASE] =
+ "RSP_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE";
+ sCmdToString[REQ_RESET - BASE] = "REQ_RESET";
+ sCmdToString[RSP_RESET - BASE] = "RSP_RESET";
+ sCmdToString[REQ_GET_REFCOUNT - BASE] = "REQ_GET_REFCOUNT";
+ sCmdToString[RSP_GET_REFCOUNT - BASE] = "RSP_GET_REFCOUNT";
+ sCmdToString[REQ_ADD_APNCONTEXT - BASE] = "REQ_ADD_APNCONTEXT";
+ sCmdToString[RSP_ADD_APNCONTEXT - BASE] = "RSP_ADD_APNCONTEXT";
+ sCmdToString[REQ_REMOVE_APNCONTEXT - BASE] = "REQ_REMOVE_APNCONTEXT";
+ sCmdToString[RSP_REMOVE_APNCONTEXT - BASE] = "RSP_REMOVE_APNCONTEXT";
+ sCmdToString[REQ_GET_APNCONTEXT_LIST - BASE] = "REQ_GET_APNCONTEXT_LIST";
+ sCmdToString[RSP_GET_APNCONTEXT_LIST - BASE] = "RSP_GET_APNCONTEXT_LIST";
+ sCmdToString[REQ_SET_RECONNECT_INTENT - BASE] = "REQ_SET_RECONNECT_INTENT";
+ sCmdToString[RSP_SET_RECONNECT_INTENT - BASE] = "RSP_SET_RECONNECT_INTENT";
+ sCmdToString[REQ_GET_RECONNECT_INTENT - BASE] = "REQ_GET_RECONNECT_INTENT";
+ sCmdToString[RSP_GET_RECONNECT_INTENT - BASE] = "RSP_GET_RECONNECT_INTENT";
+ }
+ protected static String cmdToString(int cmd) {
+ cmd -= BASE;
+ if ((cmd >= 0) && (cmd < sCmdToString.length)) {
+ return sCmdToString[cmd];
+ } else {
+ return AsyncChannel.cmdToString(cmd + BASE);
+ }
+ }
+
+ /**
+ * enum used to notify action taken or necessary to be
+ * taken after the link property is changed.
+ */
+ public enum LinkPropertyChangeAction {
+ NONE, CHANGED, RESET;
+
+ public static LinkPropertyChangeAction fromInt(int value) {
+ if (value == NONE.ordinal()) {
+ return NONE;
+ } else if (value == CHANGED.ordinal()) {
+ return CHANGED;
+ } else if (value == RESET.ordinal()) {
+ return RESET;
+ } else {
+ throw new RuntimeException("LinkPropertyChangeAction.fromInt: bad value=" + value);
+ }
+ }
+ }
+
+ public DataConnectionAc(DataConnection dc, String logTag) {
+ dataConnection = dc;
+ mLogTag = logTag;
+ }
+
+ /**
+ * Request if the state machine is in the inactive state.
+ * Response {@link #rspIsInactive}
+ */
+ public void reqIsInactive() {
+ sendMessage(REQ_IS_INACTIVE);
+ if (DBG) log("reqIsInactive");
+ }
+
+ /**
+ * Evaluate RSP_IS_INACTIVE.
+ *
+ * @return true if the state machine is in the inactive state.
+ */
+ public boolean rspIsInactive(Message response) {
+ boolean retVal = response.arg1 == 1;
+ if (DBG) log("rspIsInactive=" + retVal);
+ return retVal;
+ }
+
+ /**
+ * @return true if the state machine is in the inactive state.
+ */
+ public boolean isInactiveSync() {
+ Message response = sendMessageSynchronously(REQ_IS_INACTIVE);
+ if ((response != null) && (response.what == RSP_IS_INACTIVE)) {
+ return rspIsInactive(response);
+ } else {
+ log("rspIsInactive error response=" + response);
+ return false;
+ }
+ }
+
+ /**
+ * Request the Connection ID.
+ * Response {@link #rspCid}
+ */
+ public void reqCid() {
+ sendMessage(REQ_GET_CID);
+ if (DBG) log("reqCid");
+ }
+
+ /**
+ * Evaluate a RSP_GET_CID message and return the cid.
+ *
+ * @param response Message
+ * @return connection id or -1 if an error
+ */
+ public int rspCid(Message response) {
+ int retVal = response.arg1;
+ if (DBG) log("rspCid=" + retVal);
+ return retVal;
+ }
+
+ /**
+ * @return connection id or -1 if an error
+ */
+ public int getCidSync() {
+ Message response = sendMessageSynchronously(REQ_GET_CID);
+ if ((response != null) && (response.what == RSP_GET_CID)) {
+ return rspCid(response);
+ } else {
+ log("rspCid error response=" + response);
+ return -1;
+ }
+ }
+
+ /**
+ * Request the Reference Count.
+ * Response {@link #rspRefCount}
+ */
+ public void reqRefCount() {
+ sendMessage(REQ_GET_REFCOUNT);
+ if (DBG) log("reqRefCount");
+ }
+
+ /**
+ * Evaluate a RSP_GET_REFCOUNT message and return the refCount.
+ *
+ * @param response Message
+ * @return ref count or -1 if an error
+ */
+ public int rspRefCount(Message response) {
+ int retVal = response.arg1;
+ if (DBG) log("rspRefCount=" + retVal);
+ return retVal;
+ }
+
+ /**
+ * @return connection id or -1 if an error
+ */
+ public int getRefCountSync() {
+ Message response = sendMessageSynchronously(REQ_GET_REFCOUNT);
+ if ((response != null) && (response.what == RSP_GET_REFCOUNT)) {
+ return rspRefCount(response);
+ } else {
+ log("rspRefCount error response=" + response);
+ return -1;
+ }
+ }
+
+ /**
+ * Request the connections ApnSetting.
+ * Response {@link #rspApnSetting}
+ */
+ public void reqApnSetting() {
+ sendMessage(REQ_GET_APNSETTING);
+ if (DBG) log("reqApnSetting");
+ }
+
+ /**
+ * Evaluate a RSP_APN_SETTING message and return the ApnSetting.
+ *
+ * @param response Message
+ * @return ApnSetting, maybe null
+ */
+ public ApnSetting rspApnSetting(Message response) {
+ ApnSetting retVal = (ApnSetting) response.obj;
+ if (DBG) log("rspApnSetting=" + retVal);
+ return retVal;
+ }
+
+ /**
+ * Get the connections ApnSetting.
+ *
+ * @return ApnSetting or null if an error
+ */
+ public ApnSetting getApnSettingSync() {
+ Message response = sendMessageSynchronously(REQ_GET_APNSETTING);
+ if ((response != null) && (response.what == RSP_GET_APNSETTING)) {
+ return rspApnSetting(response);
+ } else {
+ log("getApnSetting error response=" + response);
+ return null;
+ }
+ }
+
+ /**
+ * Request the connections LinkProperties.
+ * Response {@link #rspLinkProperties}
+ */
+ public void reqLinkProperties() {
+ sendMessage(REQ_GET_LINK_PROPERTIES);
+ if (DBG) log("reqLinkProperties");
+ }
+
+ /**
+ * Evaluate RSP_GET_LINK_PROPERTIES
+ *
+ * @param response
+ * @return LinkProperties, maybe null.
+ */
+ public LinkProperties rspLinkProperties(Message response) {
+ LinkProperties retVal = (LinkProperties) response.obj;
+ if (DBG) log("rspLinkProperties=" + retVal);
+ return retVal;
+ }
+
+ /**
+ * Get the connections LinkProperties.
+ *
+ * @return LinkProperties or null if an error
+ */
+ public LinkProperties getLinkPropertiesSync() {
+ Message response = sendMessageSynchronously(REQ_GET_LINK_PROPERTIES);
+ if ((response != null) && (response.what == RSP_GET_LINK_PROPERTIES)) {
+ return rspLinkProperties(response);
+ } else {
+ log("getLinkProperties error response=" + response);
+ return null;
+ }
+ }
+
+ /**
+ * Request setting the connections LinkProperties.HttpProxy.
+ * Response RSP_SET_LINK_PROPERTIES when complete.
+ */
+ public void reqSetLinkPropertiesHttpProxy(ProxyProperties proxy) {
+ sendMessage(REQ_SET_LINK_PROPERTIES_HTTP_PROXY, proxy);
+ if (DBG) log("reqSetLinkPropertiesHttpProxy proxy=" + proxy);
+ }
+
+ /**
+ * Set the connections LinkProperties.HttpProxy
+ */
+ public void setLinkPropertiesHttpProxySync(ProxyProperties proxy) {
+ Message response =
+ sendMessageSynchronously(REQ_SET_LINK_PROPERTIES_HTTP_PROXY, proxy);
+ if ((response != null) && (response.what == RSP_SET_LINK_PROPERTIES_HTTP_PROXY)) {
+ if (DBG) log("setLinkPropertiesHttpPoxy ok");
+ } else {
+ log("setLinkPropertiesHttpPoxy error response=" + response);
+ }
+ }
+
+ /**
+ * Request update LinkProperties from DataCallState
+ * Response {@link #rspUpdateLinkPropertiesDataCallState}
+ */
+ public void reqUpdateLinkPropertiesDataCallState(DataCallState newState) {
+ sendMessage(REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE, newState);
+ if (DBG) log("reqUpdateLinkPropertiesDataCallState");
+ }
+
+ public UpdateLinkPropertyResult rspUpdateLinkPropertiesDataCallState(Message response) {
+ UpdateLinkPropertyResult retVal = (UpdateLinkPropertyResult)response.obj;
+ if (DBG) log("rspUpdateLinkPropertiesState: retVal=" + retVal);
+ return retVal;
+ }
+
+ /**
+ * Update link properties in the data connection
+ *
+ * @return the removed and added addresses.
+ */
+ public UpdateLinkPropertyResult updateLinkPropertiesDataCallStateSync(DataCallState newState) {
+ Message response =
+ sendMessageSynchronously(REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE, newState);
+ if ((response != null) &&
+ (response.what == RSP_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE)) {
+ return rspUpdateLinkPropertiesDataCallState(response);
+ } else {
+ log("getLinkProperties error response=" + response);
+ return new UpdateLinkPropertyResult(new LinkProperties());
+ }
+ }
+
+ /**
+ * Request the connections LinkCapabilities.
+ * Response {@link #rspLinkCapabilities}
+ */
+ public void reqLinkCapabilities() {
+ sendMessage(REQ_GET_LINK_CAPABILITIES);
+ if (DBG) log("reqLinkCapabilities");
+ }
+
+ /**
+ * Evaluate RSP_GET_LINK_CAPABILITIES
+ *
+ * @param response
+ * @return LinkCapabilites, maybe null.
+ */
+ public LinkCapabilities rspLinkCapabilities(Message response) {
+ LinkCapabilities retVal = (LinkCapabilities) response.obj;
+ if (DBG) log("rspLinkCapabilities=" + retVal);
+ return retVal;
+ }
+
+ /**
+ * Get the connections LinkCapabilities.
+ *
+ * @return LinkCapabilities or null if an error
+ */
+ public LinkCapabilities getLinkCapabilitiesSync() {
+ Message response = sendMessageSynchronously(REQ_GET_LINK_CAPABILITIES);
+ if ((response != null) && (response.what == RSP_GET_LINK_CAPABILITIES)) {
+ return rspLinkCapabilities(response);
+ } else {
+ log("getLinkCapabilities error response=" + response);
+ return null;
+ }
+ }
+
+ /**
+ * Request the connections LinkCapabilities.
+ * Response RSP_RESET when complete
+ */
+ public void reqReset() {
+ sendMessage(REQ_RESET);
+ if (DBG) log("reqReset");
+ }
+
+ /**
+ * Reset the connection and wait for it to complete.
+ */
+ public void resetSync() {
+ Message response = sendMessageSynchronously(REQ_RESET);
+ if ((response != null) && (response.what == RSP_RESET)) {
+ if (DBG) log("restSync ok");
+ } else {
+ log("restSync error response=" + response);
+ }
+ }
+
+ /**
+ * Request to add ApnContext association.
+ * Response RSP_ADD_APNCONTEXT when complete.
+ */
+ public void reqAddApnContext(ApnContext apnContext) {
+ Message response = sendMessageSynchronously(REQ_ADD_APNCONTEXT, apnContext);
+ if (DBG) log("reqAddApnContext");
+ }
+
+ /**
+ * Add ApnContext association synchronoulsy.
+ *
+ * @param ApnContext to associate
+ */
+ public void addApnContextSync(ApnContext apnContext) {
+ Message response = sendMessageSynchronously(REQ_ADD_APNCONTEXT, apnContext);
+ if ((response != null) && (response.what == RSP_ADD_APNCONTEXT)) {
+ if (DBG) log("addApnContext ok");
+ } else {
+ log("addApnContext error response=" + response);
+ }
+ }
+
+ /**
+ * Request to remove ApnContext association.
+ * Response RSP_REMOVE_APNCONTEXT when complete.
+ */
+ public void reqRemomveApnContext(ApnContext apnContext) {
+ Message response = sendMessageSynchronously(REQ_REMOVE_APNCONTEXT, apnContext);
+ if (DBG) log("reqRemomveApnContext");
+ }
+
+ /**
+ * Remove ApnContext associateion.
+ *
+ * @param ApnContext to dissociate
+ */
+ public void removeApnContextSync(ApnContext apnContext) {
+ Message response = sendMessageSynchronously(REQ_REMOVE_APNCONTEXT, apnContext);
+ if ((response != null) && (response.what == RSP_REMOVE_APNCONTEXT)) {
+ if (DBG) log("removeApnContext ok");
+ } else {
+ log("removeApnContext error response=" + response);
+ }
+ }
+
+ /**
+ * Request to retrive ApnContext List associated with DC.
+ * Response RSP_GET_APNCONTEXT_LIST when complete.
+ */
+ public void reqGetApnList(ApnContext apnContext) {
+ Message response = sendMessageSynchronously(REQ_GET_APNCONTEXT_LIST);
+ if (DBG) log("reqGetApnList");
+ }
+
+ /**
+ * Retrieve Collection of ApnContext from the response message.
+ *
+ * @param Message sent from DC in response to REQ_GET_APNCONTEXT_LIST.
+ * @return Collection of ApnContext
+ */
+ public Collection<ApnContext> rspApnList(Message response) {
+ Collection<ApnContext> retVal = (Collection<ApnContext>)response.obj;
+ if (retVal == null) retVal = new ArrayList<ApnContext>();
+ return retVal;
+ }
+
+ /**
+ * Retrieve collection of ApnContext currently associated with
+ * the DataConnectionA synchronously.
+ *
+ * @return Collection of ApnContext
+ */
+ public Collection<ApnContext> getApnListSync() {
+ Message response = sendMessageSynchronously(REQ_GET_APNCONTEXT_LIST);
+ if ((response != null) && (response.what == RSP_GET_APNCONTEXT_LIST)) {
+ if (DBG) log("getApnList ok");
+ return rspApnList(response);
+ } else {
+ log("getApnList error response=" + response);
+ // return dummy list with no entry
+ return new ArrayList<ApnContext>();
+ }
+ }
+
+ /**
+ * Request to set Pending ReconnectIntent to DC.
+ * Response RSP_SET_RECONNECT_INTENT when complete.
+ */
+ public void reqSetReconnectIntent(PendingIntent intent) {
+ Message response = sendMessageSynchronously(REQ_SET_RECONNECT_INTENT, intent);
+ if (DBG) log("reqSetReconnectIntent");
+ }
+
+ /**
+ * Set pending reconnect intent to DC synchronously.
+ *
+ * @param PendingIntent to set.
+ */
+ public void setReconnectIntentSync(PendingIntent intent) {
+ Message response = sendMessageSynchronously(REQ_SET_RECONNECT_INTENT, intent);
+ if ((response != null) && (response.what == RSP_SET_RECONNECT_INTENT)) {
+ if (DBG) log("setReconnectIntent ok");
+ } else {
+ log("setReconnectIntent error response=" + response);
+ }
+ }
+
+ /**
+ * Request to get Pending ReconnectIntent to DC.
+ * Response RSP_GET_RECONNECT_INTENT when complete.
+ */
+ public void reqGetReconnectIntent() {
+ Message response = sendMessageSynchronously(REQ_GET_RECONNECT_INTENT);
+ if (DBG) log("reqGetReconnectIntent");
+ }
+
+ /**
+ * Retrieve reconnect intent from response message from DC.
+ *
+ * @param Message which contains the reconnect intent.
+ * @return PendingIntent from the response.
+ */
+ public PendingIntent rspReconnectIntent(Message response) {
+ PendingIntent retVal = (PendingIntent) response.obj;
+ return retVal;
+ }
+
+ /**
+ * Retrieve reconnect intent currently set in DC synchronously.
+ *
+ * @return PendingIntent reconnect intent current ly set in DC
+ */
+ public PendingIntent getReconnectIntentSync() {
+ Message response = sendMessageSynchronously(REQ_GET_RECONNECT_INTENT);
+ if ((response != null) && (response.what == RSP_GET_RECONNECT_INTENT)) {
+ if (DBG) log("getReconnectIntent ok");
+ return rspReconnectIntent(response);
+ } else {
+ log("getReconnectIntent error response=" + response);
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return dataConnection.getName();
+ }
+
+ private void log(String s) {
+ android.util.Log.d(mLogTag, "DataConnectionAc " + s);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/DataConnectionTracker.java b/src/java/com/android/internal/telephony/DataConnectionTracker.java
new file mode 100644
index 0000000..89a02d3
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -0,0 +1,1203 @@
+/*
+ * 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.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.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.TrafficStats;
+import android.net.wifi.WifiManager;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.telephony.DataConnection.FailCause;
+import com.android.internal.telephony.DctConstants;
+import com.android.internal.util.AsyncChannel;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * {@hide}
+ */
+public abstract class DataConnectionTracker extends Handler {
+ protected static final boolean DBG = true;
+ protected static final boolean VDBG = false;
+
+
+ /** Delay between APN attempts.
+ Note the property override mechanism is there just for testing purpose only. */
+ protected static final int APN_DELAY_MILLIS =
+ SystemProperties.getInt("persist.radio.apn_delay", 5000);
+
+ protected Object mDataEnabledLock = new Object();
+
+ // responds to the setInternalDataEnabled call - used internally to turn off data
+ // for example during emergency calls
+ protected boolean mInternalDataEnabled = true;
+
+ // responds to public (user) API to enable/disable data use
+ // independent of mInternalDataEnabled and requests for APN access
+ // persisted
+ protected boolean mUserDataEnabled = true;
+
+ // TODO: move away from static state once 5587429 is fixed.
+ protected static boolean sPolicyDataEnabled = true;
+
+ private boolean[] dataEnabled = new boolean[DctConstants.APN_NUM_TYPES];
+
+ private int enabledCount = 0;
+
+ /* Currently requested APN type (TODO: This should probably be a parameter not a member) */
+ protected String mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+
+ /** Retry configuration: A doubling of retry times from 5secs to 30minutes */
+ protected static final String DEFAULT_DATA_RETRY_CONFIG = "default_randomization=2000,"
+ + "5000,10000,20000,40000,80000:5000,160000:5000,"
+ + "320000:5000,640000:5000,1280000:5000,1800000:5000";
+
+ /** Retry configuration for secondary networks: 4 tries in 20 sec */
+ protected static final String SECONDARY_DATA_RETRY_CONFIG =
+ "max_retries=3, 5000, 5000, 5000";
+
+ /** Slow poll when attempting connection recovery. */
+ protected static final int POLL_NETSTAT_SLOW_MILLIS = 5000;
+ /** Default max failure count before attempting to network re-registration. */
+ protected static final int DEFAULT_MAX_PDP_RESET_FAIL = 3;
+
+ /**
+ * After detecting a potential connection problem, this is the max number
+ * of subsequent polls before attempting recovery.
+ */
+ protected static final int NO_RECV_POLL_LIMIT = 24;
+ // 1 sec. default polling interval when screen is on.
+ protected static final int POLL_NETSTAT_MILLIS = 1000;
+ // 10 min. default polling interval when screen is off.
+ protected static final int POLL_NETSTAT_SCREEN_OFF_MILLIS = 1000*60*10;
+ // 2 min for round trip time
+ protected static final int POLL_LONGEST_RTT = 120 * 1000;
+ // Default sent packets without ack which triggers initial recovery steps
+ protected static final int NUMBER_SENT_PACKETS_OF_HANG = 10;
+ // how long to wait before switching back to default APN
+ protected static final int RESTORE_DEFAULT_APN_DELAY = 1 * 60 * 1000;
+ // system property that can override the above value
+ protected static final String APN_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore";
+ // represents an invalid IP address
+ protected static final String NULL_IP = "0.0.0.0";
+
+ // Default for the data stall alarm while non-aggressive stall detection
+ protected static final int DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60 * 6;
+ // Default for the data stall alarm for aggressive stall detection
+ protected static final int DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60;
+ // If attempt is less than this value we're doing first level recovery
+ protected static final int DATA_STALL_NO_RECV_POLL_LIMIT = 1;
+ // Tag for tracking stale alarms
+ protected static final String DATA_STALL_ALARM_TAG_EXTRA = "data.stall.alram.tag";
+
+ // TODO: See if we can remove INTENT_RECONNECT_ALARM
+ // having to have different values for GSM and
+ // CDMA. If so we can then remove the need for
+ // getActionIntentReconnectAlarm.
+ protected static final String INTENT_RECONNECT_ALARM_EXTRA_REASON =
+ "reconnect_alarm_extra_reason";
+
+ // Used for debugging. Send the INTENT with an optional counter value with the number
+ // of times the setup is to fail before succeeding. If the counter isn't passed the
+ // setup will fail once. Example fail two times with FailCause.SIGNAL_LOST(-3)
+ // adb shell am broadcast \
+ // -a com.android.internal.telephony.dataconnectiontracker.intent_set_fail_data_setup_counter \
+ // --ei fail_data_setup_counter 3 --ei fail_data_setup_fail_cause -3
+ protected static final String INTENT_SET_FAIL_DATA_SETUP_COUNTER =
+ "com.android.internal.telephony.dataconnectiontracker.intent_set_fail_data_setup_counter";
+ protected static final String FAIL_DATA_SETUP_COUNTER = "fail_data_setup_counter";
+ protected int mFailDataSetupCounter = 0;
+ protected static final String FAIL_DATA_SETUP_FAIL_CAUSE = "fail_data_setup_fail_cause";
+ protected FailCause mFailDataSetupFailCause = FailCause.ERROR_UNSPECIFIED;
+
+ protected static final String DEFALUT_DATA_ON_BOOT_PROP = "net.def_data_on_boot";
+
+ // member variables
+ protected PhoneBase mPhone;
+ protected DctConstants.Activity mActivity = DctConstants.Activity.NONE;
+ protected DctConstants.State mState = DctConstants.State.IDLE;
+ protected Handler mDataConnectionTracker = null;
+
+
+ protected long mTxPkts;
+ protected long mRxPkts;
+ protected int mNetStatPollPeriod;
+ protected boolean mNetStatPollEnabled = false;
+
+ protected TxRxSum mDataStallTxRxSum = new TxRxSum(0, 0);
+ // Used to track stale data stall alarms.
+ protected int mDataStallAlarmTag = (int) SystemClock.elapsedRealtime();
+ // The current data stall alarm intent
+ protected PendingIntent mDataStallAlarmIntent = null;
+ // Number of packets sent since the last received packet
+ protected long mSentSinceLastRecv;
+ // Controls when a simple recovery attempt it to be tried
+ protected int mNoRecvPollCount = 0;
+
+ // wifi connection status will be updated by sticky intent
+ protected boolean mIsWifiConnected = false;
+
+ /** Intent sent when the reconnect alarm fires. */
+ protected PendingIntent mReconnectIntent = null;
+
+ /** CID of active data connection */
+ protected int mCidActive;
+
+ // When false we will not auto attach and manually attaching is required.
+ protected boolean mAutoAttachOnCreation = false;
+
+ // State of screen
+ // (TODO: Reconsider tying directly to screen, maybe this is
+ // really a lower power mode")
+ protected boolean mIsScreenOn = true;
+
+ /** Allows the generation of unique Id's for DataConnection objects */
+ protected AtomicInteger mUniqueIdGenerator = new AtomicInteger(0);
+
+ /** The data connections. */
+ protected HashMap<Integer, DataConnection> mDataConnections =
+ new HashMap<Integer, DataConnection>();
+
+ /** The data connection async channels */
+ protected HashMap<Integer, DataConnectionAc> mDataConnectionAsyncChannels =
+ new HashMap<Integer, DataConnectionAc>();
+
+ /** Convert an ApnType string to Id (TODO: Use "enumeration" instead of String for ApnType) */
+ protected HashMap<String, Integer> mApnToDataConnectionId =
+ new HashMap<String, Integer>();
+
+ /** Phone.APN_TYPE_* ===> ApnContext */
+ protected ConcurrentHashMap<String, ApnContext> mApnContexts =
+ new ConcurrentHashMap<String, ApnContext>();
+
+ /* Currently active APN */
+ protected ApnSetting mActiveApn;
+
+ /** allApns holds all apns */
+ protected ArrayList<ApnSetting> mAllApns = null;
+
+ /** preferred apn */
+ protected ApnSetting mPreferredApn = null;
+
+ /** Is packet service restricted by network */
+ protected boolean mIsPsRestricted = false;
+
+ /* Once disposed dont handle any messages */
+ protected boolean mIsDisposed = false;
+
+ protected BroadcastReceiver mIntentReceiver = new BroadcastReceiver ()
+ {
+ @Override
+ public void onReceive(Context context, Intent intent)
+ {
+ String action = intent.getAction();
+ if (DBG) log("onReceive: action=" + action);
+ if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ mIsScreenOn = true;
+ stopNetStatPoll();
+ startNetStatPoll();
+ restartDataStallAlarm();
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ mIsScreenOn = false;
+ stopNetStatPoll();
+ startNetStatPoll();
+ restartDataStallAlarm();
+ } else if (action.startsWith(getActionIntentReconnectAlarm())) {
+ log("Reconnect alarm. Previous state was " + mState);
+ onActionIntentReconnectAlarm(intent);
+ } else if (action.equals(getActionIntentDataStallAlarm())) {
+ onActionIntentDataStallAlarm(intent);
+ } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ final android.net.NetworkInfo networkInfo = (NetworkInfo)
+ intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ mIsWifiConnected = (networkInfo != null && networkInfo.isConnected());
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ final boolean enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
+
+ if (!enabled) {
+ // when WiFi got disabled, the NETWORK_STATE_CHANGED_ACTION
+ // quit and won't report disconnected until next enabling.
+ mIsWifiConnected = false;
+ }
+ } else if (action.equals(INTENT_SET_FAIL_DATA_SETUP_COUNTER)) {
+ mFailDataSetupCounter = intent.getIntExtra(FAIL_DATA_SETUP_COUNTER, 1);
+ mFailDataSetupFailCause = FailCause.fromInt(
+ intent.getIntExtra(FAIL_DATA_SETUP_FAIL_CAUSE,
+ FailCause.ERROR_UNSPECIFIED.getErrorCode()));
+ if (DBG) log("set mFailDataSetupCounter=" + mFailDataSetupCounter +
+ " mFailDataSetupFailCause=" + mFailDataSetupFailCause);
+ }
+ }
+ };
+
+ private final DataRoamingSettingObserver mDataRoamingSettingObserver;
+
+ private class DataRoamingSettingObserver extends ContentObserver {
+ public DataRoamingSettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void register(Context context) {
+ final ContentResolver resolver = context.getContentResolver();
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.DATA_ROAMING), false, this);
+ }
+
+ public void unregister(Context context) {
+ final ContentResolver resolver = context.getContentResolver();
+ resolver.unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ // already running on mPhone handler thread
+ handleDataOnRoamingChange();
+ }
+ }
+
+ /**
+ * Maintian the sum of transmit and receive packets.
+ *
+ * The packet counts are initizlied and reset to -1 and
+ * remain -1 until they can be updated.
+ */
+ public class TxRxSum {
+ public long txPkts;
+ public long rxPkts;
+
+ public TxRxSum() {
+ reset();
+ }
+
+ public TxRxSum(long txPkts, long rxPkts) {
+ this.txPkts = txPkts;
+ this.rxPkts = rxPkts;
+ }
+
+ public TxRxSum(TxRxSum sum) {
+ txPkts = sum.txPkts;
+ rxPkts = sum.rxPkts;
+ }
+
+ public void reset() {
+ txPkts = -1;
+ rxPkts = -1;
+ }
+
+ public String toString() {
+ return "{txSum=" + txPkts + " rxSum=" + rxPkts + "}";
+ }
+
+ public void updateTxRxSum() {
+ boolean txUpdated = false, rxUpdated = false;
+ long txSum = 0, rxSum = 0;
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.getState() == DctConstants.State.CONNECTED) {
+ DataConnectionAc dcac = apnContext.getDataConnectionAc();
+ if (dcac == null) continue;
+
+ LinkProperties linkProp = dcac.getLinkPropertiesSync();
+ if (linkProp == null) continue;
+
+ String iface = linkProp.getInterfaceName();
+
+ if (iface != null) {
+ long stats = TrafficStats.getTxPackets(iface);
+ if (stats > 0) {
+ txUpdated = true;
+ txSum += stats;
+ }
+ stats = TrafficStats.getRxPackets(iface);
+ if (stats > 0) {
+ rxUpdated = true;
+ rxSum += stats;
+ }
+ }
+ }
+ }
+ if (txUpdated) this.txPkts = txSum;
+ if (rxUpdated) this.rxPkts = rxSum;
+ }
+ }
+
+ protected boolean isDataSetupCompleteOk(AsyncResult ar) {
+ if (ar.exception != null) {
+ if (DBG) log("isDataSetupCompleteOk return false, ar.result=" + ar.result);
+ return false;
+ }
+ if (mFailDataSetupCounter <= 0) {
+ if (DBG) log("isDataSetupCompleteOk return true");
+ return true;
+ }
+ ar.result = mFailDataSetupFailCause;
+ if (DBG) {
+ log("isDataSetupCompleteOk return false" +
+ " mFailDataSetupCounter=" + mFailDataSetupCounter +
+ " mFailDataSetupFailCause=" + mFailDataSetupFailCause);
+ }
+ mFailDataSetupCounter -= 1;
+ return false;
+ }
+
+ protected void onActionIntentReconnectAlarm(Intent intent) {
+ String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON);
+ if (mState == DctConstants.State.FAILED) {
+ Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_CONNECTION);
+ msg.arg1 = 0; // tearDown is false
+ msg.arg2 = 0;
+ msg.obj = reason;
+ sendMessage(msg);
+ }
+ sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA));
+ }
+
+ protected void onActionIntentDataStallAlarm(Intent intent) {
+ if (VDBG) log("onActionIntentDataStallAlarm: action=" + intent.getAction());
+ Message msg = obtainMessage(DctConstants.EVENT_DATA_STALL_ALARM,
+ intent.getAction());
+ msg.arg1 = intent.getIntExtra(DATA_STALL_ALARM_TAG_EXTRA, 0);
+ sendMessage(msg);
+ }
+
+ /**
+ * Default constructor
+ */
+ protected DataConnectionTracker(PhoneBase phone) {
+ super();
+ if (DBG) log("DCT.constructor");
+ mPhone = phone;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(getActionIntentReconnectAlarm());
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(INTENT_SET_FAIL_DATA_SETUP_COUNTER);
+
+ mUserDataEnabled = Settings.Secure.getInt(
+ mPhone.getContext().getContentResolver(), Settings.Secure.MOBILE_DATA, 1) == 1;
+
+ // TODO: Why is this registering the phone as the receiver of the intent
+ // and not its own handler?
+ mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
+
+ // This preference tells us 1) initial condition for "dataEnabled",
+ // and 2) whether the RIL will setup the baseband to auto-PS attach.
+
+ dataEnabled[DctConstants.APN_DEFAULT_ID] =
+ SystemProperties.getBoolean(DEFALUT_DATA_ON_BOOT_PROP,true);
+ if (dataEnabled[DctConstants.APN_DEFAULT_ID]) {
+ enabledCount++;
+ }
+
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
+ mAutoAttachOnCreation = sp.getBoolean(PhoneBase.DATA_DISABLED_ON_BOOT_KEY, false);
+
+ // watch for changes to Settings.Secure.DATA_ROAMING
+ mDataRoamingSettingObserver = new DataRoamingSettingObserver(mPhone);
+ mDataRoamingSettingObserver.register(mPhone.getContext());
+ }
+
+ public void dispose() {
+ if (DBG) log("DCT.dispose");
+ for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+ dcac.disconnect();
+ }
+ mDataConnectionAsyncChannels.clear();
+ mIsDisposed = true;
+ mPhone.getContext().unregisterReceiver(this.mIntentReceiver);
+ mDataRoamingSettingObserver.unregister(mPhone.getContext());
+ }
+
+ protected void broadcastMessenger() {
+ Intent intent = new Intent(DctConstants.ACTION_DATA_CONNECTION_TRACKER_MESSENGER);
+ intent.putExtra(DctConstants.EXTRA_MESSENGER, new Messenger(this));
+ mPhone.getContext().sendBroadcast(intent);
+ }
+
+ public DctConstants.Activity getActivity() {
+ return mActivity;
+ }
+
+ public boolean isApnTypeActive(String type) {
+ // TODO: support simultaneous with List instead
+ if (PhoneConstants.APN_TYPE_DUN.equals(type)) {
+ ApnSetting dunApn = fetchDunApn();
+ if (dunApn != null) {
+ return ((mActiveApn != null) && (dunApn.toString().equals(mActiveApn.toString())));
+ }
+ }
+ return mActiveApn != null && mActiveApn.canHandleType(type);
+ }
+
+ protected ApnSetting fetchDunApn() {
+ if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)) {
+ log("fetchDunApn: net.tethering.noprovisioning=true ret: null");
+ return null;
+ }
+ Context c = mPhone.getContext();
+ String apnData = Settings.Secure.getString(c.getContentResolver(),
+ Settings.Secure.TETHER_DUN_APN);
+ ApnSetting dunSetting = ApnSetting.fromString(apnData);
+ if (dunSetting != null) {
+ if (VDBG) log("fetchDunApn: secure TETHER_DUN_APN dunSetting=" + dunSetting);
+ return dunSetting;
+ }
+
+ apnData = c.getResources().getString(R.string.config_tether_apndata);
+ dunSetting = ApnSetting.fromString(apnData);
+ if (VDBG) log("fetchDunApn: config_tether_apndata dunSetting=" + dunSetting);
+ return dunSetting;
+ }
+
+ public String[] getActiveApnTypes() {
+ String[] result;
+ if (mActiveApn != null) {
+ result = mActiveApn.types;
+ } else {
+ result = new String[1];
+ result[0] = PhoneConstants.APN_TYPE_DEFAULT;
+ }
+ return result;
+ }
+
+ /** TODO: See if we can remove */
+ public String getActiveApnString(String apnType) {
+ String result = null;
+ if (mActiveApn != null) {
+ result = mActiveApn.apn;
+ }
+ return result;
+ }
+
+ /**
+ * Modify {@link Settings.Secure#DATA_ROAMING} value.
+ */
+ public void setDataOnRoamingEnabled(boolean enabled) {
+ if (getDataOnRoamingEnabled() != enabled) {
+ final ContentResolver resolver = mPhone.getContext().getContentResolver();
+ Settings.Secure.putInt(resolver, Settings.Secure.DATA_ROAMING, enabled ? 1 : 0);
+ // will trigger handleDataOnRoamingChange() through observer
+ }
+ }
+
+ /**
+ * Return current {@link Settings.Secure#DATA_ROAMING} value.
+ */
+ public boolean getDataOnRoamingEnabled() {
+ try {
+ final ContentResolver resolver = mPhone.getContext().getContentResolver();
+ return Settings.Secure.getInt(resolver, Settings.Secure.DATA_ROAMING) != 0;
+ } catch (SettingNotFoundException snfe) {
+ return false;
+ }
+ }
+
+ private void handleDataOnRoamingChange() {
+ if (mPhone.getServiceState().getRoaming()) {
+ if (getDataOnRoamingEnabled()) {
+ resetAllRetryCounts();
+ }
+ sendMessage(obtainMessage(DctConstants.EVENT_ROAMING_ON));
+ }
+ }
+
+ // abstract methods
+ protected abstract String getActionIntentReconnectAlarm();
+ protected abstract String getActionIntentDataStallAlarm();
+ protected abstract void startNetStatPoll();
+ protected abstract void stopNetStatPoll();
+ protected abstract void restartDataStallAlarm();
+ protected abstract void restartRadio();
+ protected abstract void log(String s);
+ protected abstract void loge(String s);
+ protected abstract boolean isDataAllowed();
+ protected abstract boolean isApnTypeAvailable(String type);
+ public abstract DctConstants.State getState(String apnType);
+ protected abstract void setState(DctConstants.State s);
+ protected abstract void gotoIdleAndNotifyDataConnection(String reason);
+
+ protected abstract boolean onTrySetupData(String reason);
+ protected abstract void onRoamingOff();
+ protected abstract void onRoamingOn();
+ protected abstract void onRadioAvailable();
+ protected abstract void onRadioOffOrNotAvailable();
+ protected abstract void onDataSetupComplete(AsyncResult ar);
+ protected abstract void onDisconnectDone(int connId, AsyncResult ar);
+ protected abstract void onVoiceCallStarted();
+ protected abstract void onVoiceCallEnded();
+ protected abstract void onCleanUpConnection(boolean tearDown, int apnId, String reason);
+ protected abstract void onCleanUpAllConnections(String cause);
+ protected abstract boolean isDataPossible(String apnType);
+
+ protected void onDataStallAlarm(int tag) {
+ loge("onDataStallAlarm: not impleted tag=" + tag);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ log("DISCONNECTED_CONNECTED: msg=" + msg);
+ DataConnectionAc dcac = (DataConnectionAc) msg.obj;
+ mDataConnectionAsyncChannels.remove(dcac.dataConnection.getDataConnectionId());
+ dcac.disconnected();
+ break;
+ }
+ case DctConstants.EVENT_ENABLE_NEW_APN:
+ onEnableApn(msg.arg1, msg.arg2);
+ break;
+
+ case DctConstants.EVENT_TRY_SETUP_DATA:
+ String reason = null;
+ if (msg.obj instanceof String) {
+ reason = (String) msg.obj;
+ }
+ onTrySetupData(reason);
+ break;
+
+ case DctConstants.EVENT_DATA_STALL_ALARM:
+ onDataStallAlarm(msg.arg1);
+ break;
+
+ case DctConstants.EVENT_ROAMING_OFF:
+ if (getDataOnRoamingEnabled() == false) {
+ resetAllRetryCounts();
+ }
+ onRoamingOff();
+ break;
+
+ case DctConstants.EVENT_ROAMING_ON:
+ onRoamingOn();
+ break;
+
+ case DctConstants.EVENT_RADIO_AVAILABLE:
+ onRadioAvailable();
+ break;
+
+ case DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
+ onRadioOffOrNotAvailable();
+ break;
+
+ case DctConstants.EVENT_DATA_SETUP_COMPLETE:
+ mCidActive = msg.arg1;
+ onDataSetupComplete((AsyncResult) msg.obj);
+ break;
+
+ case DctConstants.EVENT_DISCONNECT_DONE:
+ log("DataConnectoinTracker.handleMessage: EVENT_DISCONNECT_DONE msg=" + msg);
+ onDisconnectDone(msg.arg1, (AsyncResult) msg.obj);
+ break;
+
+ case DctConstants.EVENT_VOICE_CALL_STARTED:
+ onVoiceCallStarted();
+ break;
+
+ case DctConstants.EVENT_VOICE_CALL_ENDED:
+ onVoiceCallEnded();
+ break;
+
+ case DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS: {
+ onCleanUpAllConnections((String) msg.obj);
+ break;
+ }
+ case DctConstants.EVENT_CLEAN_UP_CONNECTION: {
+ boolean tearDown = (msg.arg1 == 0) ? false : true;
+ onCleanUpConnection(tearDown, msg.arg2, (String) msg.obj);
+ break;
+ }
+ case DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE: {
+ boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
+ onSetInternalDataEnabled(enabled);
+ break;
+ }
+ case DctConstants.EVENT_RESET_DONE: {
+ if (DBG) log("EVENT_RESET_DONE");
+ onResetDone((AsyncResult) msg.obj);
+ break;
+ }
+ case DctConstants.CMD_SET_USER_DATA_ENABLE: {
+ final boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
+ if (DBG) log("CMD_SET_USER_DATA_ENABLE enabled=" + enabled);
+ onSetUserDataEnabled(enabled);
+ break;
+ }
+ case DctConstants.CMD_SET_DEPENDENCY_MET: {
+ boolean met = (msg.arg1 == DctConstants.ENABLED) ? true : false;
+ if (DBG) log("CMD_SET_DEPENDENCY_MET met=" + met);
+ Bundle bundle = msg.getData();
+ if (bundle != null) {
+ String apnType = (String)bundle.get(DctConstants.APN_TYPE_KEY);
+ if (apnType != null) {
+ onSetDependencyMet(apnType, met);
+ }
+ }
+ break;
+ }
+ case DctConstants.CMD_SET_POLICY_DATA_ENABLE: {
+ final boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
+ onSetPolicyDataEnabled(enabled);
+ break;
+ }
+ default:
+ Log.e("DATA", "Unidentified event msg=" + msg);
+ break;
+ }
+ }
+
+ /**
+ * Report on whether data connectivity is enabled
+ *
+ * @return {@code false} if data connectivity has been explicitly disabled,
+ * {@code true} otherwise.
+ */
+ public boolean getAnyDataEnabled() {
+ final boolean result;
+ synchronized (mDataEnabledLock) {
+ result = (mInternalDataEnabled && mUserDataEnabled && sPolicyDataEnabled
+ && (enabledCount != 0));
+ }
+ if (!result && DBG) log("getAnyDataEnabled " + result);
+ return result;
+ }
+
+ protected boolean isEmergency() {
+ final boolean result;
+ synchronized (mDataEnabledLock) {
+ result = mPhone.isInEcm() || mPhone.isInEmergencyCall();
+ }
+ log("isEmergency: result=" + result);
+ return result;
+ }
+
+ protected int apnTypeToId(String type) {
+ if (TextUtils.equals(type, PhoneConstants.APN_TYPE_DEFAULT)) {
+ return DctConstants.APN_DEFAULT_ID;
+ } else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_MMS)) {
+ return DctConstants.APN_MMS_ID;
+ } else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_SUPL)) {
+ return DctConstants.APN_SUPL_ID;
+ } else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_DUN)) {
+ return DctConstants.APN_DUN_ID;
+ } else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_HIPRI)) {
+ return DctConstants.APN_HIPRI_ID;
+ } else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_IMS)) {
+ return DctConstants.APN_IMS_ID;
+ } else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_FOTA)) {
+ return DctConstants.APN_FOTA_ID;
+ } else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_CBS)) {
+ return DctConstants.APN_CBS_ID;
+ } else {
+ return DctConstants.APN_INVALID_ID;
+ }
+ }
+
+ protected String apnIdToType(int id) {
+ switch (id) {
+ case DctConstants.APN_DEFAULT_ID:
+ return PhoneConstants.APN_TYPE_DEFAULT;
+ case DctConstants.APN_MMS_ID:
+ return PhoneConstants.APN_TYPE_MMS;
+ case DctConstants.APN_SUPL_ID:
+ return PhoneConstants.APN_TYPE_SUPL;
+ case DctConstants.APN_DUN_ID:
+ return PhoneConstants.APN_TYPE_DUN;
+ case DctConstants.APN_HIPRI_ID:
+ return PhoneConstants.APN_TYPE_HIPRI;
+ case DctConstants.APN_IMS_ID:
+ return PhoneConstants.APN_TYPE_IMS;
+ case DctConstants.APN_FOTA_ID:
+ return PhoneConstants.APN_TYPE_FOTA;
+ case DctConstants.APN_CBS_ID:
+ return PhoneConstants.APN_TYPE_CBS;
+ default:
+ log("Unknown id (" + id + ") in apnIdToType");
+ return PhoneConstants.APN_TYPE_DEFAULT;
+ }
+ }
+
+ protected LinkProperties getLinkProperties(String apnType) {
+ int id = apnTypeToId(apnType);
+
+ if (isApnIdEnabled(id)) {
+ // TODO - remove this cdma-only hack and support multiple DCs.
+ DataConnectionAc dcac = mDataConnectionAsyncChannels.get(0);
+ return dcac.getLinkPropertiesSync();
+ } else {
+ return new LinkProperties();
+ }
+ }
+
+ protected LinkCapabilities getLinkCapabilities(String apnType) {
+ int id = apnTypeToId(apnType);
+ if (isApnIdEnabled(id)) {
+ // TODO - remove this cdma-only hack and support multiple DCs.
+ DataConnectionAc dcac = mDataConnectionAsyncChannels.get(0);
+ return dcac.getLinkCapabilitiesSync();
+ } else {
+ return new LinkCapabilities();
+ }
+ }
+
+ // tell all active apns of the current condition
+ protected void notifyDataConnection(String reason) {
+ for (int id = 0; id < DctConstants.APN_NUM_TYPES; id++) {
+ if (dataEnabled[id]) {
+ mPhone.notifyDataConnection(reason, apnIdToType(id));
+ }
+ }
+ notifyOffApnsOfAvailability(reason);
+ }
+
+ // a new APN has gone active and needs to send events to catch up with the
+ // current condition
+ private void notifyApnIdUpToCurrent(String reason, int apnId) {
+ switch (mState) {
+ case IDLE:
+ case INITING:
+ break;
+ case CONNECTING:
+ case SCANNING:
+ mPhone.notifyDataConnection(reason, apnIdToType(apnId),
+ PhoneConstants.DataState.CONNECTING);
+ break;
+ case CONNECTED:
+ case DISCONNECTING:
+ mPhone.notifyDataConnection(reason, apnIdToType(apnId),
+ PhoneConstants.DataState.CONNECTING);
+ mPhone.notifyDataConnection(reason, apnIdToType(apnId),
+ PhoneConstants.DataState.CONNECTED);
+ break;
+ }
+ }
+
+ // since we normally don't send info to a disconnected APN, we need to do this specially
+ private void notifyApnIdDisconnected(String reason, int apnId) {
+ mPhone.notifyDataConnection(reason, apnIdToType(apnId),
+ PhoneConstants.DataState.DISCONNECTED);
+ }
+
+ // disabled apn's still need avail/unavail notificiations - send them out
+ protected void notifyOffApnsOfAvailability(String reason) {
+ if (DBG) log("notifyOffApnsOfAvailability - reason= " + reason);
+ for (int id = 0; id < DctConstants.APN_NUM_TYPES; id++) {
+ if (!isApnIdEnabled(id)) {
+ notifyApnIdDisconnected(reason, id);
+ }
+ }
+ }
+
+ public boolean isApnTypeEnabled(String apnType) {
+ if (apnType == null) {
+ return false;
+ } else {
+ return isApnIdEnabled(apnTypeToId(apnType));
+ }
+ }
+
+ protected synchronized boolean isApnIdEnabled(int id) {
+ if (id != DctConstants.APN_INVALID_ID) {
+ return dataEnabled[id];
+ }
+ return false;
+ }
+
+ /**
+ * Ensure that we are connected to an APN of the specified type.
+ *
+ * @param type the APN type (currently the only valid values are
+ * {@link Phone#APN_TYPE_MMS} and {@link Phone#APN_TYPE_SUPL})
+ * @return Success is indicated by {@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.
+ */
+ public synchronized int enableApnType(String type) {
+ int id = apnTypeToId(type);
+ if (id == DctConstants.APN_INVALID_ID) {
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ if (DBG) {
+ log("enableApnType(" + type + "), isApnTypeActive = " + isApnTypeActive(type)
+ + ", isApnIdEnabled =" + isApnIdEnabled(id) + " and state = " + mState);
+ }
+
+ if (!isApnTypeAvailable(type)) {
+ if (DBG) log("type not available");
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ }
+
+ if (isApnIdEnabled(id)) {
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ } else {
+ setEnabled(id, true);
+ }
+ return PhoneConstants.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 values are currently
+ * {@link Phone#APN_TYPE_MMS} and {@link Phone#APN_TYPE_SUPL}.
+ * @return Success is indicated by {@code PhoneConstants.APN_ALREADY_ACTIVE} or
+ * {@code PhoneConstants.APN_REQUEST_STARTED}. In the latter case, a
+ * broadcast will be sent by the ConnectivityManager when a
+ * connection to the APN has been disconnected. A {@code
+ * PhoneConstants.APN_REQUEST_FAILED} is returned if the type parameter is
+ * invalid or if the apn wasn't enabled.
+ */
+ public synchronized int disableApnType(String type) {
+ if (DBG) log("disableApnType(" + type + ")");
+ int id = apnTypeToId(type);
+ if (id == DctConstants.APN_INVALID_ID) {
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+ if (isApnIdEnabled(id)) {
+ setEnabled(id, false);
+ if (isApnTypeActive(PhoneConstants.APN_TYPE_DEFAULT)) {
+ if (dataEnabled[DctConstants.APN_DEFAULT_ID]) {
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ } else {
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+ } else {
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+ } else {
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+ }
+
+ protected void setEnabled(int id, boolean enable) {
+ if (DBG) {
+ log("setEnabled(" + id + ", " + enable + ") with old state = " + dataEnabled[id]
+ + " and enabledCount = " + enabledCount);
+ }
+ Message msg = obtainMessage(DctConstants.EVENT_ENABLE_NEW_APN);
+ msg.arg1 = id;
+ msg.arg2 = (enable ? DctConstants.ENABLED : DctConstants.DISABLED);
+ sendMessage(msg);
+ }
+
+ protected void onEnableApn(int apnId, int enabled) {
+ if (DBG) {
+ log("EVENT_APN_ENABLE_REQUEST apnId=" + apnId + ", apnType=" + apnIdToType(apnId) +
+ ", enabled=" + enabled + ", dataEnabled = " + dataEnabled[apnId] +
+ ", enabledCount = " + enabledCount + ", isApnTypeActive = " +
+ isApnTypeActive(apnIdToType(apnId)));
+ }
+ if (enabled == DctConstants.ENABLED) {
+ synchronized (this) {
+ if (!dataEnabled[apnId]) {
+ dataEnabled[apnId] = true;
+ enabledCount++;
+ }
+ }
+ String type = apnIdToType(apnId);
+ if (!isApnTypeActive(type)) {
+ mRequestedApnType = type;
+ onEnableNewApn();
+ } else {
+ notifyApnIdUpToCurrent(Phone.REASON_APN_SWITCHED, apnId);
+ }
+ } else {
+ // disable
+ boolean didDisable = false;
+ synchronized (this) {
+ if (dataEnabled[apnId]) {
+ dataEnabled[apnId] = false;
+ enabledCount--;
+ didDisable = true;
+ }
+ }
+ if (didDisable) {
+ if ((enabledCount == 0) || (apnId == DctConstants.APN_DUN_ID)) {
+ mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+ onCleanUpConnection(true, apnId, Phone.REASON_DATA_DISABLED);
+ }
+
+ // send the disconnect msg manually, since the normal route wont send
+ // it (it's not enabled)
+ notifyApnIdDisconnected(Phone.REASON_DATA_DISABLED, apnId);
+ if (dataEnabled[DctConstants.APN_DEFAULT_ID] == true
+ && !isApnTypeActive(PhoneConstants.APN_TYPE_DEFAULT)) {
+ // TODO - this is an ugly way to restore the default conn - should be done
+ // by a real contention manager and policy that disconnects the lower pri
+ // stuff as enable requests come in and pops them back on as we disable back
+ // down to the lower pri stuff
+ mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+ onEnableNewApn();
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when we switch APNs.
+ *
+ * mRequestedApnType is set prior to call
+ * To be overridden.
+ */
+ protected void onEnableNewApn() {
+ }
+
+ /**
+ * Called when EVENT_RESET_DONE is received so goto
+ * IDLE state and send notifications to those interested.
+ *
+ * TODO - currently unused. Needs to be hooked into DataConnection cleanup
+ * TODO - needs to pass some notion of which connection is reset..
+ */
+ protected void onResetDone(AsyncResult ar) {
+ if (DBG) log("EVENT_RESET_DONE");
+ String reason = null;
+ if (ar.userObj instanceof String) {
+ reason = (String) ar.userObj;
+ }
+ gotoIdleAndNotifyDataConnection(reason);
+ }
+
+ /**
+ * 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.
+ *
+ * @param enable indicates whether to enable ({@code true}) or disable (
+ * {@code false}) data
+ * @return {@code true} if the operation succeeded
+ */
+ public boolean setInternalDataEnabled(boolean enable) {
+ if (DBG)
+ log("setInternalDataEnabled(" + enable + ")");
+
+ Message msg = obtainMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE);
+ msg.arg1 = (enable ? DctConstants.ENABLED : DctConstants.DISABLED);
+ sendMessage(msg);
+ return true;
+ }
+
+ protected void onSetInternalDataEnabled(boolean enabled) {
+ synchronized (mDataEnabledLock) {
+ mInternalDataEnabled = enabled;
+ if (enabled) {
+ log("onSetInternalDataEnabled: changed to enabled, try to setup data call");
+ resetAllRetryCounts();
+ onTrySetupData(Phone.REASON_DATA_ENABLED);
+ } else {
+ log("onSetInternalDataEnabled: changed to disabled, cleanUpAllConnections");
+ cleanUpAllConnections(null);
+ }
+ }
+ }
+
+ public void cleanUpAllConnections(String cause) {
+ Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS);
+ msg.obj = cause;
+ sendMessage(msg);
+ }
+
+ public abstract boolean isDisconnected();
+
+ protected void onSetUserDataEnabled(boolean enabled) {
+ synchronized (mDataEnabledLock) {
+ final boolean prevEnabled = getAnyDataEnabled();
+ if (mUserDataEnabled != enabled) {
+ mUserDataEnabled = enabled;
+ Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
+ Settings.Secure.MOBILE_DATA, enabled ? 1 : 0);
+ if (getDataOnRoamingEnabled() == false &&
+ mPhone.getServiceState().getRoaming() == true) {
+ if (enabled) {
+ notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON);
+ } else {
+ notifyOffApnsOfAvailability(Phone.REASON_DATA_DISABLED);
+ }
+ }
+ if (prevEnabled != getAnyDataEnabled()) {
+ if (!prevEnabled) {
+ resetAllRetryCounts();
+ onTrySetupData(Phone.REASON_DATA_ENABLED);
+ } else {
+ onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
+ }
+ }
+ }
+ }
+ }
+
+ protected void onSetDependencyMet(String apnType, boolean met) {
+ }
+
+ protected void onSetPolicyDataEnabled(boolean enabled) {
+ synchronized (mDataEnabledLock) {
+ final boolean prevEnabled = getAnyDataEnabled();
+ if (sPolicyDataEnabled != enabled) {
+ sPolicyDataEnabled = enabled;
+ if (prevEnabled != getAnyDataEnabled()) {
+ if (!prevEnabled) {
+ resetAllRetryCounts();
+ onTrySetupData(Phone.REASON_DATA_ENABLED);
+ } else {
+ onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
+ }
+ }
+ }
+ }
+ }
+
+ protected String getReryConfig(boolean forDefault) {
+ int nt = mPhone.getServiceState().getNetworkType();
+
+ if ((nt == TelephonyManager.NETWORK_TYPE_CDMA) ||
+ (nt == TelephonyManager.NETWORK_TYPE_1xRTT) ||
+ (nt == TelephonyManager.NETWORK_TYPE_EVDO_0) ||
+ (nt == TelephonyManager.NETWORK_TYPE_EVDO_A) ||
+ (nt == TelephonyManager.NETWORK_TYPE_EVDO_B) ||
+ (nt == TelephonyManager.NETWORK_TYPE_EHRPD)) {
+ // CDMA variant
+ return SystemProperties.get("ro.cdma.data_retry_config");
+ } else {
+ // Use GSM varient for all others.
+ if (forDefault) {
+ return SystemProperties.get("ro.gsm.data_retry_config");
+ } else {
+ return SystemProperties.get("ro.gsm.2nd_data_retry_config");
+ }
+ }
+ }
+
+ protected void resetAllRetryCounts() {
+ for (ApnContext ac : mApnContexts.values()) {
+ ac.setRetryCount(0);
+ }
+ for (DataConnection dc : mDataConnections.values()) {
+ dc.resetRetryCount();
+ }
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("DataConnectionTracker:");
+ pw.println(" mInternalDataEnabled=" + mInternalDataEnabled);
+ pw.println(" mUserDataEnabled=" + mUserDataEnabled);
+ pw.println(" sPolicyDataEnabed=" + sPolicyDataEnabled);
+ pw.println(" dataEnabled:");
+ for(int i=0; i < dataEnabled.length; i++) {
+ pw.printf(" dataEnabled[%d]=%b\n", i, dataEnabled[i]);
+ }
+ pw.flush();
+ pw.println(" enabledCount=" + enabledCount);
+ pw.println(" mRequestedApnType=" + mRequestedApnType);
+ pw.println(" mPhone=" + mPhone.getPhoneName());
+ pw.println(" mActivity=" + mActivity);
+ pw.println(" mState=" + mState);
+ pw.println(" mTxPkts=" + mTxPkts);
+ pw.println(" mRxPkts=" + mRxPkts);
+ pw.println(" mNetStatPollPeriod=" + mNetStatPollPeriod);
+ pw.println(" mNetStatPollEnabled=" + mNetStatPollEnabled);
+ pw.println(" mDataStallTxRxSum=" + mDataStallTxRxSum);
+ pw.println(" mDataStallAlarmTag=" + mDataStallAlarmTag);
+ pw.println(" mSentSinceLastRecv=" + mSentSinceLastRecv);
+ pw.println(" mNoRecvPollCount=" + mNoRecvPollCount);
+ pw.println(" mIsWifiConnected=" + mIsWifiConnected);
+ pw.println(" mReconnectIntent=" + mReconnectIntent);
+ pw.println(" mCidActive=" + mCidActive);
+ pw.println(" mAutoAttachOnCreation=" + mAutoAttachOnCreation);
+ pw.println(" mIsScreenOn=" + mIsScreenOn);
+ pw.println(" mUniqueIdGenerator=" + mUniqueIdGenerator);
+ pw.flush();
+ pw.println(" ***************************************");
+ Set<Entry<Integer, DataConnection> > mDcSet = mDataConnections.entrySet();
+ pw.println(" mDataConnections: count=" + mDcSet.size());
+ for (Entry<Integer, DataConnection> entry : mDcSet) {
+ pw.printf(" *** mDataConnection[%d] \n", entry.getKey());
+ entry.getValue().dump(fd, pw, args);
+ }
+ pw.println(" ***************************************");
+ pw.flush();
+ Set<Entry<String, Integer>> mApnToDcIdSet = mApnToDataConnectionId.entrySet();
+ pw.println(" mApnToDataConnectonId size=" + mApnToDcIdSet.size());
+ for (Entry<String, Integer> entry : mApnToDcIdSet) {
+ pw.printf(" mApnToDataConnectonId[%s]=%d\n", entry.getKey(), entry.getValue());
+ }
+ pw.println(" ***************************************");
+ pw.flush();
+ if (mApnContexts != null) {
+ Set<Entry<String, ApnContext>> mApnContextsSet = mApnContexts.entrySet();
+ pw.println(" mApnContexts size=" + mApnContextsSet.size());
+ for (Entry<String, ApnContext> entry : mApnContextsSet) {
+ entry.getValue().dump(fd, pw, args);
+ }
+ pw.println(" ***************************************");
+ } else {
+ pw.println(" mApnContexts=null");
+ }
+ pw.flush();
+ pw.println(" mActiveApn=" + mActiveApn);
+ if (mAllApns != null) {
+ pw.println(" mAllApns size=" + mAllApns.size());
+ for (int i=0; i < mAllApns.size(); i++) {
+ pw.printf(" mAllApns[%d]: %s\n", i, mAllApns.get(i));
+ }
+ pw.flush();
+ } else {
+ pw.println(" mAllApns=null");
+ }
+ pw.println(" mPreferredApn=" + mPreferredApn);
+ pw.println(" mIsPsRestricted=" + mIsPsRestricted);
+ pw.println(" mIsDisposed=" + mIsDisposed);
+ pw.println(" mIntentReceiver=" + mIntentReceiver);
+ pw.println(" mDataRoamingSettingObserver=" + mDataRoamingSettingObserver);
+ pw.flush();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/DebugService.java b/src/java/com/android/internal/telephony/DebugService.java
new file mode 100644
index 0000000..29fea6e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DebugService.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2012 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.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A debug service that will dump telephony's state
+ *
+ * Currently this "Service" has a proxy in the phone app
+ * com.android.phone.TelephonyDebugService which actually
+ * invokes the dump method.
+ */
+public class DebugService {
+ private static String TAG = "DebugService";
+
+ /** Constructor */
+ public DebugService() {
+ log("DebugService:");
+ }
+
+ /**
+ * Dump the state of various objects, add calls to other objects as desired.
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ log("dump: +");
+ PhoneProxy phoneProxy = null;
+ PhoneBase phoneBase = null;
+
+ try {
+ phoneProxy = (PhoneProxy) PhoneFactory.getDefaultPhone();
+ } catch (Exception e) {
+ pw.println("Telephony DebugService: Could not getDefaultPhone e=" + e);
+ return;
+ }
+ try {
+ phoneBase = (PhoneBase)phoneProxy.getActivePhone();
+ } catch (Exception e) {
+ pw.println("Telephony DebugService: Could not PhoneBase e=" + e);
+ return;
+ }
+
+ /**
+ * Surround each of the sub dump's with try/catch so even
+ * if one fails we'll be able to dump the next ones.
+ */
+ pw.println();
+ pw.println("++++++++++++++++++++++++++++++++");
+ pw.flush();
+ try {
+ phoneBase.dump(fd, pw, args);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ pw.flush();
+ pw.println("++++++++++++++++++++++++++++++++");
+ try {
+ phoneBase.mDataConnectionTracker.dump(fd, pw, args);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ pw.flush();
+ pw.println("++++++++++++++++++++++++++++++++");
+ try {
+ phoneBase.getServiceStateTracker().dump(fd, pw, args);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ pw.flush();
+ pw.println("++++++++++++++++++++++++++++++++");
+ try {
+ phoneBase.getCallTracker().dump(fd, pw, args);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ pw.flush();
+ pw.println("++++++++++++++++++++++++++++++++");
+ try {
+ ((RIL)phoneBase.mCM).dump(fd, pw, args);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ pw.flush();
+ pw.println("++++++++++++++++++++++++++++++++");
+ log("dump: -");
+ }
+
+ private static void log(String s) {
+ Log.d(TAG, "DebugService " + s);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
new file mode 100644
index 0000000..4d16443
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -0,0 +1,281 @@
+/*
+ * 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.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.CellInfo;
+import android.telephony.ServiceState;
+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) {
+ ServiceState ss = sender.getServiceState();
+ if (ss == null) {
+ ss = new ServiceState();
+ ss.setStateOutOfService();
+ }
+ try {
+ mRegistry.notifyServiceState(ss);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifySignalStrength(Phone sender) {
+ try {
+ mRegistry.notifySignalStrength(sender.getSignalStrength());
+ } 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, String apnType,
+ PhoneConstants.DataState state) {
+ doNotifyDataConnection(sender, reason, apnType, state);
+ }
+
+ private void doNotifyDataConnection(Phone sender, String reason, String apnType,
+ PhoneConstants.DataState state) {
+ // TODO
+ // use apnType as the key to which connection we're talking about.
+ // pass apnType back up to fetch particular for this one.
+ TelephonyManager telephony = TelephonyManager.getDefault();
+ LinkProperties linkProperties = null;
+ LinkCapabilities linkCapabilities = null;
+ boolean roaming = false;
+
+ if (state == PhoneConstants.DataState.CONNECTED) {
+ linkProperties = sender.getLinkProperties(apnType);
+ linkCapabilities = sender.getLinkCapabilities(apnType);
+ }
+ ServiceState ss = sender.getServiceState();
+ if (ss != null) roaming = ss.getRoaming();
+
+ try {
+ mRegistry.notifyDataConnection(
+ convertDataState(state),
+ sender.isDataConnectivityPossible(apnType), reason,
+ sender.getActiveApnHost(apnType),
+ apnType,
+ linkProperties,
+ linkCapabilities,
+ ((telephony!=null) ? telephony.getNetworkType() :
+ TelephonyManager.NETWORK_TYPE_UNKNOWN),
+ roaming);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyDataConnectionFailed(Phone sender, String reason, String apnType) {
+ try {
+ mRegistry.notifyDataConnectionFailed(reason, apnType);
+ } 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
+ }
+ }
+
+ public void notifyCellInfo(Phone sender, CellInfo cellInfo) {
+ try {
+ mRegistry.notifyCellInfo(cellInfo);
+ } catch (RemoteException ex) {
+
+ }
+ }
+
+ public void notifyOtaspChanged(Phone sender, int otaspMode) {
+ try {
+ mRegistry.notifyOtaspChanged(otaspMode);
+ } 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(PhoneConstants.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 PhoneConstants.State convertCallState(int state) {
+ switch (state) {
+ case TelephonyManager.CALL_STATE_RINGING:
+ return PhoneConstants.State.RINGING;
+ case TelephonyManager.CALL_STATE_OFFHOOK:
+ return PhoneConstants.State.OFFHOOK;
+ default:
+ return PhoneConstants.State.IDLE;
+ }
+ }
+
+ /**
+ * Convert the {@link DataState} enum into the TelephonyManager.DATA_* constants
+ * for the public API.
+ */
+ public static int convertDataState(PhoneConstants.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 PhoneConstants.DataState convertDataState(int state) {
+ switch (state) {
+ case TelephonyManager.DATA_CONNECTING:
+ return PhoneConstants.DataState.CONNECTING;
+ case TelephonyManager.DATA_CONNECTED:
+ return PhoneConstants.DataState.CONNECTED;
+ case TelephonyManager.DATA_SUSPENDED:
+ return PhoneConstants.DataState.SUSPENDED;
+ default:
+ return PhoneConstants.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;
+ case DORMANT:
+ return TelephonyManager.DATA_ACTIVITY_DORMANT;
+ 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;
+ case TelephonyManager.DATA_ACTIVITY_DORMANT:
+ return Phone.DataActivityState.DORMANT;
+ default:
+ return Phone.DataActivityState.NONE;
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/DriverCall.java b/src/java/com/android/internal/telephony/DriverCall.java
new file mode 100644
index 0000000..b1e63ae
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DriverCall.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 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 = "RILB";
+
+ 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 boolean isVoicePrivacy;
+ public int als;
+ public int numberPresentation;
+ public String name;
+ public int namePresentation;
+ public UUSInfo uusInfo;
+
+ /** 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();
+
+ // use ALLOWED as default presentation while parsing CLCC
+ ret.numberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
+
+ if (p.hasMore()) {
+ // Some lame implementations return strings
+ // like "NOT AVAILABLE" in the CLCC line
+ ret.number = PhoneNumberUtils.extractNetworkPortionAlt(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 + ","
+ + state + ","
+ + "toa=" + TOA + ","
+ + (isMpty ? "conf" : "norm") + ","
+ + (isMT ? "mt" : "mo") + ","
+ + als + ","
+ + (isVoice ? "voc" : "nonvoc") + ","
+ + (isVoicePrivacy ? "evp" : "noevp") + ","
+ /*+ "number=" + number */ + ",cli=" + numberPresentation + ","
+ /*+ "name="+ name */ + "," + namePresentation;
+ }
+
+ 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);
+ }
+ }
+
+ public static int
+ presentationFromCLIP(int cli) throws ATParseEx
+ {
+ switch(cli) {
+ case 0: return PhoneConstants.PRESENTATION_ALLOWED;
+ case 1: return PhoneConstants.PRESENTATION_RESTRICTED;
+ case 2: return PhoneConstants.PRESENTATION_UNKNOWN;
+ case 3: return PhoneConstants.PRESENTATION_PAYPHONE;
+ default:
+ throw new ATParseEx("illegal presentation " + cli);
+ }
+ }
+
+ //***** 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/src/java/com/android/internal/telephony/EventLogTags.logtags b/src/java/com/android/internal/telephony/EventLogTags.logtags
new file mode 100644
index 0000000..427e5da
--- /dev/null
+++ b/src/java/com/android/internal/telephony/EventLogTags.logtags
@@ -0,0 +1,73 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.internal.telephony;
+
+# PDP Context has a bad DNS address
+50100 pdp_bad_dns_address (dns_address|3)
+
+# For data connection on PDP context, reached the data-out-without-data-in
+# packet count that triggers a countdown to radio restart
+50101 pdp_radio_reset_countdown_triggered (out_packet_count|1|1)
+
+# Radio restart - timed out with no incoming packets.
+50102 pdp_radio_reset (out_packet_count|1|1)
+
+# PDP context reset - timed out with no incoming packets.
+50103 pdp_context_reset (out_packet_count|1|1)
+
+# Reregister to data network - timed out with no incoming packets.
+50104 pdp_reregister_network (out_packet_count|1|1)
+
+# PDP Setup failures
+50105 pdp_setup_fail (cause|1|5), (cid|1|5), (network_type|1|5)
+
+# Call drops
+50106 call_drop (cause|1|5), (cid|1|5), (network_type|1|5)
+
+# Data network registration failed after successful voice registration
+50107 data_network_registration_fail (op_numeric|1|5), (cid|1|5)
+
+# Suspicious status of data connection while radio poweroff
+50108 data_network_status_on_radio_off (dc_state|3), (enable|1|5)
+
+# PDP drop caused by network
+50109 pdp_network_drop (cid|1|5), (network_type|1|5)
+
+# CDMA data network setup failure
+50110 cdma_data_setup_failed (cause|1|5), (cid|1|5), (network_type|1|5)
+
+# CDMA data network drop
+50111 cdma_data_drop (cid|1|5), (network_type|1|5)
+
+# GSM radio access technology switched
+50112 gsm_rat_switched (cid|1|5), (network_from|1|5), (network_to|1|5)
+
+# GSM data connection state transition
+50113 gsm_data_state_change (oldState|3), (newState|3)
+
+# GSM service state transition
+50114 gsm_service_state_change (oldState|1|5), (oldGprsState|1|5), (newState|1|5), (newGprsState|1|5)
+
+# CDMA data connection state transition
+50115 cdma_data_state_change (oldState|3), (newState|3)
+
+# CDMA service state transition
+50116 cdma_service_state_change (oldState|1|5), (oldDataState|1|5), (newState|1|5), (newDataState|1|5)
+
+# Bad IP address
+50117 bad_ip_address (ip_address|3)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_GET_DATA_CALL_LIST
+50118 data_stall_recovery_get_data_call_list (out_packet_count|1|1)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_CLEANUP
+50119 data_stall_recovery_cleanup (out_packet_count|1|1)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_REREGISTER
+50120 data_stall_recovery_reregister (out_packet_count|1|1)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_RADIO_RESTART
+50121 data_stall_recovery_radio_restart (out_packet_count|1|1)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_RADIO_RESTART_WITH_PROP
+50122 data_stall_recovery_radio_restart_with_prop (out_packet_count|1|1)
diff --git a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
new file mode 100644
index 0000000..f700dfe
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IIccPhoneBook.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;
+
+import com.android.internal.telephony.AdnRecord;
+
+
+
+/** Interface for applications to access the ICC phone book.
+ *
+ * <p>The following code snippet demonstrates a static method to
+ * retrieve the IIccPhoneBook interface from Android:</p>
+ * <pre>private static IIccPhoneBook getSimPhoneBookInterface()
+ throws DeadObjectException {
+ IServiceManager sm = ServiceManagerNative.getDefault();
+ IIccPhoneBook spb;
+ spb = IIccPhoneBook.Stub.asInterface(sm.getService("iccphonebook"));
+ return spb;
+}
+ * </pre>
+ */
+
+interface IIccPhoneBook {
+
+ /**
+ * 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/src/java/com/android/internal/telephony/ISms.aidl b/src/java/com/android/internal/telephony/ISms.aidl
new file mode 100644
index 0000000..735f986
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ISms.aidl
@@ -0,0 +1,201 @@
+/*
+** 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;
+
+import android.app.PendingIntent;
+import com.android.internal.telephony.SmsRawData;
+
+/** Interface for applications to access the ICC phone book.
+ *
+ * <p>The following code snippet demonstrates a static method to
+ * retrieve the ISms interface from Android:</p>
+ * <pre>private static ISms getSmsInterface()
+ throws DeadObjectException {
+ IServiceManager sm = ServiceManagerNative.getDefault();
+ ISms ss;
+ ss = ISms.Stub.asInterface(sm.getService("isms"));
+ return ss;
+}
+ * </pre>
+ */
+
+interface ISms {
+ /**
+ * Retrieves all messages currently stored on ICC.
+ *
+ * @return list of SmsRawData of all sms on ICC
+ */
+ List<SmsRawData> getAllMessagesFromIccEf();
+
+ /**
+ * Update the specified message on the ICC.
+ *
+ * @param messageIndex record index of message to update
+ * @param newStatus new message status (STATUS_ON_ICC_READ,
+ * STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
+ * STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
+ * @param pdu the raw PDU to store
+ * @return success or not
+ *
+ */
+ boolean updateMessageOnIccEf(int messageIndex, int newStatus,
+ in byte[] pdu);
+
+ /**
+ * Copy a raw SMS PDU to the ICC.
+ *
+ * @param pdu the raw PDU to store
+ * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
+ * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+ * @return success or not
+ *
+ */
+ boolean copyMessageToIccEf(int status, in byte[] pdu, in byte[] smsc);
+
+ /**
+ * Send a data SMS.
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * default SMSC
+ * @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:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * 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").
+ */
+ void sendData(in String destAddr, in String scAddr, in int destPort,
+ in byte[] data, in PendingIntent sentIntent, in PendingIntent deliveryIntent);
+
+ /**
+ * Send an SMS.
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * 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:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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").
+ */
+ void sendText(in String destAddr, in String scAddr, in String text,
+ 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);
+
+ /**
+ * Enable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier. Note that if two different clients enable the same
+ * message identifier, they must both disable it for the device to stop
+ * receiving those messages.
+ *
+ * @param messageIdentifier Message identifier as specified in TS 23.041
+ * @return true if successful, false otherwise
+ *
+ * @see #disableCellBroadcast(int)
+ */
+ boolean enableCellBroadcast(int messageIdentifier);
+
+ /**
+ * Disable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier. Note that if two different clients enable the same
+ * message identifier, they must both disable it for the device to stop
+ * receiving those messages.
+ *
+ * @param messageIdentifier Message identifier as specified in TS 23.041
+ * @return true if successful, false otherwise
+ *
+ * @see #enableCellBroadcast(int)
+ */
+ boolean disableCellBroadcast(int messageIdentifier);
+
+ /**
+ * Enable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier range. Note that if two different clients enable
+ * a message identifier range, they must both disable it for the device
+ * to stop receiving those messages.
+ *
+ * @param startMessageId first message identifier as specified in TS 23.041
+ * @param endMessageId last message identifier as specified in TS 23.041
+ * @return true if successful, false otherwise
+ *
+ * @see #disableCellBroadcastRange(int, int)
+ */
+ boolean enableCellBroadcastRange(int startMessageId, int endMessageId);
+
+ /**
+ * Disable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier range. Note that if two different clients enable
+ * a message identifier range, they must both disable it for the device
+ * to stop receiving those messages.
+ *
+ * @param startMessageId first message identifier as specified in TS 23.041
+ * @param endMessageId last message identifier as specified in TS 23.041
+ * @return true if successful, false otherwise
+ *
+ * @see #enableCellBroadcastRange(int, int)
+ */
+ boolean disableCellBroadcastRange(int startMessageId, int endMessageId);
+
+}
diff --git a/src/java/com/android/internal/telephony/IccCard.java b/src/java/com/android/internal/telephony/IccCard.java
new file mode 100644
index 0000000..740292c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccCard.java
@@ -0,0 +1,958 @@
+/*
+ * 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 static android.Manifest.permission.READ_PHONE_STATE;
+import android.app.ActivityManagerNative;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.CommandsInterface.RadioState;
+import com.android.internal.telephony.gsm.SIMFileHandler;
+import com.android.internal.telephony.gsm.SIMRecords;
+import com.android.internal.telephony.cat.CatService;
+import com.android.internal.telephony.cdma.CDMALTEPhone;
+import com.android.internal.telephony.cdma.CdmaLteUiccFileHandler;
+import com.android.internal.telephony.cdma.CdmaLteUiccRecords;
+import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
+import com.android.internal.telephony.cdma.RuimFileHandler;
+import com.android.internal.telephony.cdma.RuimRecords;
+
+import com.android.internal.R;
+
+/**
+ * {@hide}
+ */
+public class IccCard {
+ protected String mLogTag;
+ protected boolean mDbg;
+
+ protected IccCardStatus mIccCardStatus = null;
+ protected IccCardConstants.State mState = null;
+ private final Object mStateMonitor = new Object();
+
+ protected boolean is3gpp = true;
+ protected boolean isSubscriptionFromIccCard = true;
+ protected CdmaSubscriptionSourceManager mCdmaSSM = null;
+ protected PhoneBase mPhone;
+ private IccRecords mIccRecords;
+ private IccFileHandler mIccFileHandler;
+ private CatService mCatService;
+
+ private RegistrantList mAbsentRegistrants = new RegistrantList();
+ private RegistrantList mPinLockedRegistrants = new RegistrantList();
+ private RegistrantList mNetworkLockedRegistrants = new RegistrantList();
+ protected RegistrantList mReadyRegistrants = new RegistrantList();
+ protected RegistrantList mRuimReadyRegistrants = new RegistrantList();
+
+ private boolean mDesiredPinLocked;
+ private boolean mDesiredFdnEnabled;
+ private boolean mIccPinLocked = true; // Default to locked
+ private boolean mIccFdnEnabled = false; // Default to disabled.
+ // Will be updated when SIM_READY.
+
+ /* Parameter is3gpp's values to be passed to constructor */
+ public final static boolean CARD_IS_3GPP = true;
+ public final static boolean CARD_IS_NOT_3GPP = false;
+
+ protected static final int EVENT_ICC_LOCKED = 1;
+ private static final int EVENT_GET_ICC_STATUS_DONE = 2;
+ protected static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 3;
+ private static final int EVENT_PINPUK_DONE = 4;
+ private static final int EVENT_REPOLL_STATUS_DONE = 5;
+ protected static final int EVENT_ICC_READY = 6;
+ private static final int EVENT_QUERY_FACILITY_LOCK_DONE = 7;
+ private static final int EVENT_CHANGE_FACILITY_LOCK_DONE = 8;
+ private static final int EVENT_CHANGE_ICC_PASSWORD_DONE = 9;
+ private static final int EVENT_QUERY_FACILITY_FDN_DONE = 10;
+ private static final int EVENT_CHANGE_FACILITY_FDN_DONE = 11;
+ private static final int EVENT_ICC_STATUS_CHANGED = 12;
+ private static final int EVENT_CARD_REMOVED = 13;
+ private static final int EVENT_CARD_ADDED = 14;
+ protected static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 15;
+ protected static final int EVENT_RADIO_ON = 16;
+
+ public IccCardConstants.State getState() {
+ if (mState == null) {
+ switch(mPhone.mCM.getRadioState()) {
+ /* This switch block must not return anything in
+ * IccCardConstants.State.isLocked() or IccCardConstants.State.ABSENT.
+ * If it does, handleSimStatus() may break
+ */
+ case RADIO_OFF:
+ case RADIO_UNAVAILABLE:
+ return IccCardConstants.State.UNKNOWN;
+ default:
+ if (!is3gpp && !isSubscriptionFromIccCard) {
+ // CDMA can get subscription from NV. In that case,
+ // subscription is ready as soon as Radio is ON.
+ return IccCardConstants.State.READY;
+ }
+ }
+ } else {
+ return mState;
+ }
+
+ return IccCardConstants.State.UNKNOWN;
+ }
+
+ public IccCard(PhoneBase phone, String logTag, Boolean is3gpp, Boolean dbg) {
+ mLogTag = logTag;
+ mDbg = dbg;
+ if (mDbg) log("[IccCard] Creating card type " + (is3gpp ? "3gpp" : "3gpp2"));
+ mPhone = phone;
+ this.is3gpp = is3gpp;
+ mCdmaSSM = CdmaSubscriptionSourceManager.getInstance(mPhone.getContext(),
+ mPhone.mCM, mHandler, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
+ if (phone.mCM.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE
+ && phone instanceof CDMALTEPhone) {
+ mIccFileHandler = new CdmaLteUiccFileHandler(this, "", mPhone.mCM);
+ mIccRecords = new CdmaLteUiccRecords(this, mPhone.mContext, mPhone.mCM);
+ } else {
+ // Correct aid will be set later (when GET_SIM_STATUS returns)
+ mIccFileHandler = is3gpp ? new SIMFileHandler(this, "", mPhone.mCM) :
+ new RuimFileHandler(this, "", mPhone.mCM);
+ mIccRecords = is3gpp ? new SIMRecords(this, mPhone.mContext, mPhone.mCM) :
+ new RuimRecords(this, mPhone.mContext, mPhone.mCM);
+ }
+ mCatService = CatService.getInstance(mPhone.mCM, mIccRecords,
+ mPhone.mContext, mIccFileHandler, this);
+ mPhone.mCM.registerForOffOrNotAvailable(mHandler, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+ mPhone.mCM.registerForOn(mHandler, EVENT_RADIO_ON, null);
+ mPhone.mCM.registerForIccStatusChanged(mHandler, EVENT_ICC_STATUS_CHANGED, null);
+ }
+
+ public void dispose() {
+ if (mDbg) log("[IccCard] Disposing card type " + (is3gpp ? "3gpp" : "3gpp2"));
+ mPhone.mCM.unregisterForIccStatusChanged(mHandler);
+ mPhone.mCM.unregisterForOffOrNotAvailable(mHandler);
+ mPhone.mCM.unregisterForOn(mHandler);
+ mCatService.dispose();
+ mCdmaSSM.dispose(mHandler);
+ mIccRecords.dispose();
+ mIccFileHandler.dispose();
+ }
+
+ protected void finalize() {
+ if (mDbg) log("[IccCard] Finalized card type " + (is3gpp ? "3gpp" : "3gpp2"));
+ }
+
+ public IccRecords getIccRecords() {
+ return mIccRecords;
+ }
+
+ public IccFileHandler getIccFileHandler() {
+ return mIccFileHandler;
+ }
+
+ /**
+ * Notifies handler of any transition into IccCardConstants.State.ABSENT
+ */
+ public void registerForAbsent(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+
+ mAbsentRegistrants.add(r);
+
+ if (getState() == IccCardConstants.State.ABSENT) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForAbsent(Handler h) {
+ mAbsentRegistrants.remove(h);
+ }
+
+ /**
+ * Notifies handler of any transition into IccCardConstants.State.NETWORK_LOCKED
+ */
+ public void registerForNetworkLocked(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+
+ mNetworkLockedRegistrants.add(r);
+
+ if (getState() == IccCardConstants.State.NETWORK_LOCKED) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForNetworkLocked(Handler h) {
+ mNetworkLockedRegistrants.remove(h);
+ }
+
+ /**
+ * Notifies handler of any transition into IccCardConstants.State.isPinLocked()
+ */
+ public void registerForLocked(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+
+ mPinLockedRegistrants.add(r);
+
+ if (getState().isPinLocked()) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForLocked(Handler h) {
+ mPinLockedRegistrants.remove(h);
+ }
+
+ public void registerForReady(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+
+ synchronized (mStateMonitor) {
+ mReadyRegistrants.add(r);
+
+ if (getState() == IccCardConstants.State.READY) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ }
+
+ public void unregisterForReady(Handler h) {
+ synchronized (mStateMonitor) {
+ mReadyRegistrants.remove(h);
+ }
+ }
+
+ public IccCardConstants.State getRuimState() {
+ if(mIccCardStatus != null) {
+ return getAppState(mIccCardStatus.getCdmaSubscriptionAppIndex());
+ } else {
+ return IccCardConstants.State.UNKNOWN;
+ }
+ }
+
+ public void registerForRuimReady(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+
+ synchronized (mStateMonitor) {
+ mRuimReadyRegistrants.add(r);
+
+ if (getState() == IccCardConstants.State.READY &&
+ getRuimState() == IccCardConstants.State.READY ) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ }
+
+ public void unregisterForRuimReady(Handler h) {
+ synchronized (mStateMonitor) {
+ mRuimReadyRegistrants.remove(h);
+ }
+ }
+
+ /**
+ * Supply the ICC PIN to the ICC
+ *
+ * When the operation is complete, onComplete will be sent to its
+ * 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
+ *
+ *
+ */
+
+ public void supplyPin (String pin, Message onComplete) {
+ mPhone.mCM.supplyIccPin(pin, mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+
+ public void supplyPuk (String puk, String newPin, Message onComplete) {
+ mPhone.mCM.supplyIccPuk(puk, newPin,
+ mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+
+ public void supplyPin2 (String pin2, Message onComplete) {
+ mPhone.mCM.supplyIccPin2(pin2,
+ mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+
+ public void supplyPuk2 (String puk2, String newPin2, Message onComplete) {
+ mPhone.mCM.supplyIccPuk2(puk2, newPin2,
+ mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+
+ public void supplyNetworkDepersonalization (String pin, Message onComplete) {
+ mPhone.mCM.supplyNetworkDepersonalization(pin,
+ mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
+ }
+
+ /**
+ * Check whether ICC pin lock is enabled
+ * This is a sync call which returns the cached pin enabled state
+ *
+ * @return true for ICC locked enabled
+ * false for ICC locked disabled
+ */
+ public boolean getIccLockEnabled() {
+ return mIccPinLocked;
+ }
+
+ /**
+ * Check whether ICC fdn (fixed dialing number) is enabled
+ * This is a sync call which returns the cached pin enabled state
+ *
+ * @return true for ICC fdn enabled
+ * false for ICC fdn disabled
+ */
+ public boolean getIccFdnEnabled() {
+ return mIccFdnEnabled;
+ }
+
+ /**
+ * Set the ICC 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 ICC 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
+ */
+ public void setIccLockEnabled (boolean enabled,
+ String password, Message onComplete) {
+ int serviceClassX;
+ serviceClassX = CommandsInterface.SERVICE_CLASS_VOICE +
+ CommandsInterface.SERVICE_CLASS_DATA +
+ CommandsInterface.SERVICE_CLASS_FAX;
+
+ mDesiredPinLocked = enabled;
+
+ mPhone.mCM.setFacilityLock(CommandsInterface.CB_FACILITY_BA_SIM,
+ enabled, password, serviceClassX,
+ mHandler.obtainMessage(EVENT_CHANGE_FACILITY_LOCK_DONE, onComplete));
+ }
+
+ /**
+ * Set the ICC 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 ICC 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
+ */
+ public void setIccFdnEnabled (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;
+
+ mPhone.mCM.setFacilityLock(CommandsInterface.CB_FACILITY_BA_FD,
+ enabled, password, serviceClassX,
+ mHandler.obtainMessage(EVENT_CHANGE_FACILITY_FDN_DONE, onComplete));
+ }
+
+ /**
+ * Change the ICC password used in ICC 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
+ */
+ public void changeIccLockPassword(String oldPassword, String newPassword,
+ Message onComplete) {
+ mPhone.mCM.changeIccPin(oldPassword, newPassword,
+ mHandler.obtainMessage(EVENT_CHANGE_ICC_PASSWORD_DONE, onComplete));
+
+ }
+
+ /**
+ * Change the ICC password used in ICC 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
+ */
+ public void changeIccFdnPassword(String oldPassword, String newPassword,
+ Message onComplete) {
+ mPhone.mCM.changeIccPin2(oldPassword, newPassword,
+ mHandler.obtainMessage(EVENT_CHANGE_ICC_PASSWORD_DONE, onComplete));
+
+ }
+
+
+ /**
+ * Returns service provider name stored in ICC 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 ICC card
+ * null if no service provider name associated or the record is not
+ * yet available
+ *
+ */
+ public String getServiceProviderName () {
+ return mPhone.mIccRecords.getServiceProviderName();
+ }
+
+ protected void updateStateProperty() {
+ mPhone.setSystemProperty(TelephonyProperties.PROPERTY_SIM_STATE, getState().toString());
+ }
+
+ private void getIccCardStatusDone(AsyncResult ar) {
+ if (ar.exception != null) {
+ Log.e(mLogTag,"Error getting ICC status. "
+ + "RIL_REQUEST_GET_ICC_STATUS should "
+ + "never return an error", ar.exception);
+ return;
+ }
+ handleIccCardStatus((IccCardStatus) ar.result);
+ }
+
+ private void handleIccCardStatus(IccCardStatus newCardStatus) {
+ boolean transitionedIntoPinLocked;
+ boolean transitionedIntoAbsent;
+ boolean transitionedIntoNetworkLocked;
+ boolean transitionedIntoPermBlocked;
+ boolean isIccCardRemoved;
+ boolean isIccCardAdded;
+
+ IccCardConstants.State oldState, newState;
+ IccCardConstants.State oldRuimState = getRuimState();
+
+ oldState = mState;
+ mIccCardStatus = newCardStatus;
+ newState = getIccCardState();
+
+ synchronized (mStateMonitor) {
+ mState = newState;
+ updateStateProperty();
+ if (oldState != IccCardConstants.State.READY &&
+ newState == IccCardConstants.State.READY) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_ICC_READY));
+ mReadyRegistrants.notifyRegistrants();
+ } else if (newState.isPinLocked()) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_ICC_LOCKED));
+ }
+ if (oldRuimState != IccCardConstants.State.READY &&
+ getRuimState() == IccCardConstants.State.READY) {
+ mRuimReadyRegistrants.notifyRegistrants();
+ }
+ }
+
+ transitionedIntoPinLocked = (
+ (oldState != IccCardConstants.State.PIN_REQUIRED &&
+ newState == IccCardConstants.State.PIN_REQUIRED)
+ || (oldState != IccCardConstants.State.PUK_REQUIRED &&
+ newState == IccCardConstants.State.PUK_REQUIRED));
+ transitionedIntoAbsent = (oldState != IccCardConstants.State.ABSENT &&
+ newState == IccCardConstants.State.ABSENT);
+ transitionedIntoNetworkLocked = (oldState != IccCardConstants.State.NETWORK_LOCKED
+ && newState == IccCardConstants.State.NETWORK_LOCKED);
+ transitionedIntoPermBlocked = (oldState != IccCardConstants.State.PERM_DISABLED
+ && newState == IccCardConstants.State.PERM_DISABLED);
+ isIccCardRemoved = (oldState != null && oldState.iccCardExist() &&
+ newState == IccCardConstants.State.ABSENT);
+ isIccCardAdded = (oldState == IccCardConstants.State.ABSENT &&
+ newState != null && newState.iccCardExist());
+
+ if (transitionedIntoPinLocked) {
+ if (mDbg) log("Notify SIM pin or puk locked.");
+ mPinLockedRegistrants.notifyRegistrants();
+ broadcastIccStateChangedIntent(IccCardConstants.INTENT_VALUE_ICC_LOCKED,
+ (newState == IccCardConstants.State.PIN_REQUIRED) ?
+ IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN :
+ IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK);
+ } else if (transitionedIntoAbsent) {
+ if (mDbg) log("Notify SIM missing.");
+ mAbsentRegistrants.notifyRegistrants();
+ broadcastIccStateChangedIntent(IccCardConstants.INTENT_VALUE_ICC_ABSENT, null);
+ } else if (transitionedIntoNetworkLocked) {
+ if (mDbg) log("Notify SIM network locked.");
+ mNetworkLockedRegistrants.notifyRegistrants();
+ broadcastIccStateChangedIntent(IccCardConstants.INTENT_VALUE_ICC_LOCKED,
+ IccCardConstants.INTENT_VALUE_LOCKED_NETWORK);
+ } else if (transitionedIntoPermBlocked) {
+ if (mDbg) log("Notify SIM permanently disabled.");
+ broadcastIccStateChangedIntent(IccCardConstants.INTENT_VALUE_ICC_ABSENT,
+ IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED);
+ }
+
+ if (isIccCardRemoved) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARD_REMOVED, null));
+ } else if (isIccCardAdded) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARD_ADDED, null));
+ }
+
+ // Call onReady Record(s) on the IccCard becomes ready (not NV)
+ if (oldState != IccCardConstants.State.READY && newState == IccCardConstants.State.READY &&
+ (is3gpp || isSubscriptionFromIccCard)) {
+ if (!(mIccFileHandler instanceof CdmaLteUiccFileHandler)) {
+ // CdmaLteUicc File Handler deals with both USIM and CSIM.
+ // Do not lock onto one AID for now.
+ mIccFileHandler.setAid(getAid());
+ }
+ mIccRecords.onReady();
+ }
+ }
+
+ private void onIccSwap(boolean isAdded) {
+ // TODO: Here we assume the device can't handle SIM hot-swap
+ // and has to reboot. We may want to add a property,
+ // e.g. REBOOT_ON_SIM_SWAP, to indicate if modem support
+ // hot-swap.
+ DialogInterface.OnClickListener listener = null;
+
+
+ // TODO: SimRecords is not reset while SIM ABSENT (only reset while
+ // Radio_off_or_not_available). Have to reset in both both
+ // added or removed situation.
+ listener = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ if (mDbg) log("Reboot due to SIM swap");
+ PowerManager pm = (PowerManager) mPhone.getContext()
+ .getSystemService(Context.POWER_SERVICE);
+ pm.reboot("SIM is added.");
+ }
+ }
+
+ };
+
+ Resources r = Resources.getSystem();
+
+ String title = (isAdded) ? r.getString(R.string.sim_added_title) :
+ r.getString(R.string.sim_removed_title);
+ String message = (isAdded) ? r.getString(R.string.sim_added_message) :
+ r.getString(R.string.sim_removed_message);
+ String buttonTxt = r.getString(R.string.sim_restart_button);
+
+ AlertDialog dialog = new AlertDialog.Builder(mPhone.getContext())
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(buttonTxt, listener)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ }
+
+ /**
+ * Interperate EVENT_QUERY_FACILITY_LOCK_DONE
+ * @param ar is asyncResult of Query_Facility_Locked
+ */
+ private void onQueryFdnEnabled(AsyncResult ar) {
+ if(ar.exception != null) {
+ if(mDbg) log("Error in querying facility lock:" + ar.exception);
+ return;
+ }
+
+ int[] ints = (int[])ar.result;
+ if(ints.length != 0) {
+ mIccFdnEnabled = (0!=ints[0]);
+ if(mDbg) log("Query facility lock : " + mIccFdnEnabled);
+ } else {
+ Log.e(mLogTag, "[IccCard] Bogus facility lock response");
+ }
+ }
+
+ /**
+ * Interperate EVENT_QUERY_FACILITY_LOCK_DONE
+ * @param ar is asyncResult of Query_Facility_Locked
+ */
+ private void onQueryFacilityLock(AsyncResult ar) {
+ if(ar.exception != null) {
+ if (mDbg) log("Error in querying facility lock:" + ar.exception);
+ return;
+ }
+
+ int[] ints = (int[])ar.result;
+ if(ints.length != 0) {
+ mIccPinLocked = (0!=ints[0]);
+ if(mDbg) log("Query facility lock : " + mIccPinLocked);
+ } else {
+ Log.e(mLogTag, "[IccCard] Bogus facility lock response");
+ }
+ }
+
+ public void broadcastIccStateChangedIntent(String value, String reason) {
+ Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(PhoneConstants.PHONE_NAME_KEY, mPhone.getPhoneName());
+ intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value);
+ intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
+ if(mDbg) log("Broadcasting intent ACTION_SIM_STATE_CHANGED " + value
+ + " reason " + reason);
+ ActivityManagerNative.broadcastStickyIntent(intent, READ_PHONE_STATE);
+ }
+
+ protected Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg){
+ AsyncResult ar;
+ int serviceClassX;
+
+ serviceClassX = CommandsInterface.SERVICE_CLASS_VOICE +
+ CommandsInterface.SERVICE_CLASS_DATA +
+ CommandsInterface.SERVICE_CLASS_FAX;
+
+ if (!mPhone.mIsTheCurrentActivePhone) {
+ Log.e(mLogTag, "Received message " + msg + "[" + msg.what
+ + "] while being destroyed. Ignoring.");
+ return;
+ }
+
+ switch (msg.what) {
+ case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
+ mState = null;
+ updateStateProperty();
+ broadcastIccStateChangedIntent(IccCardConstants.INTENT_VALUE_ICC_NOT_READY,
+ null);
+ break;
+ case EVENT_RADIO_ON:
+ if (!is3gpp) {
+ handleCdmaSubscriptionSource();
+ }
+ mPhone.mCM.getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE));
+ break;
+ case EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
+ handleCdmaSubscriptionSource();
+ break;
+ case EVENT_ICC_READY:
+ if(isSubscriptionFromIccCard) {
+ mPhone.mCM.queryFacilityLock (
+ CommandsInterface.CB_FACILITY_BA_SIM, "", serviceClassX,
+ obtainMessage(EVENT_QUERY_FACILITY_LOCK_DONE));
+ mPhone.mCM.queryFacilityLock (
+ CommandsInterface.CB_FACILITY_BA_FD, "", serviceClassX,
+ obtainMessage(EVENT_QUERY_FACILITY_FDN_DONE));
+ }
+ break;
+ case EVENT_ICC_LOCKED:
+ mPhone.mCM.queryFacilityLock (
+ CommandsInterface.CB_FACILITY_BA_SIM, "", serviceClassX,
+ obtainMessage(EVENT_QUERY_FACILITY_LOCK_DONE));
+ break;
+ case EVENT_GET_ICC_STATUS_DONE:
+ ar = (AsyncResult)msg.obj;
+
+ getIccCardStatusDone(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;
+ mPhone.mCM.getIccCardStatus(
+ 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;
+ getIccCardStatusDone(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) {
+ mIccPinLocked = mDesiredPinLocked;
+ if (mDbg) log( "EVENT_CHANGE_FACILITY_LOCK_DONE: " +
+ "mIccPinLocked= " + mIccPinLocked);
+ } else {
+ Log.e(mLogTag, "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) {
+ mIccFdnEnabled = mDesiredFdnEnabled;
+ if (mDbg) log("EVENT_CHANGE_FACILITY_FDN_DONE: " +
+ "mIccFdnEnabled=" + mIccFdnEnabled);
+ } else {
+ Log.e(mLogTag, "Error change facility fdn with exception "
+ + ar.exception);
+ }
+ AsyncResult.forMessage(((Message)ar.userObj)).exception
+ = ar.exception;
+ ((Message)ar.userObj).sendToTarget();
+ break;
+ case EVENT_CHANGE_ICC_PASSWORD_DONE:
+ ar = (AsyncResult)msg.obj;
+ if(ar.exception != null) {
+ Log.e(mLogTag, "Error in change sim password with exception"
+ + ar.exception);
+ }
+ AsyncResult.forMessage(((Message)ar.userObj)).exception
+ = ar.exception;
+ ((Message)ar.userObj).sendToTarget();
+ break;
+ case EVENT_ICC_STATUS_CHANGED:
+ Log.d(mLogTag, "Received Event EVENT_ICC_STATUS_CHANGED");
+ mPhone.mCM.getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE));
+ break;
+ case EVENT_CARD_REMOVED:
+ onIccSwap(false);
+ break;
+ case EVENT_CARD_ADDED:
+ onIccSwap(true);
+ break;
+ default:
+ Log.e(mLogTag, "[IccCard] Unknown Event " + msg.what);
+ }
+ }
+ };
+
+ private void handleCdmaSubscriptionSource() {
+ if(mCdmaSSM != null) {
+ int newSubscriptionSource = mCdmaSSM.getCdmaSubscriptionSource();
+
+ Log.d(mLogTag, "Received Cdma subscription source: " + newSubscriptionSource);
+
+ boolean isNewSubFromRuim =
+ (newSubscriptionSource == CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM);
+
+ if (isNewSubFromRuim != isSubscriptionFromIccCard) {
+ isSubscriptionFromIccCard = isNewSubFromRuim;
+ // Parse the Stored IccCardStatus Message to set mState correctly.
+ handleIccCardStatus(mIccCardStatus);
+ }
+ }
+ }
+
+ public IccCardConstants.State getIccCardState() {
+ if(!is3gpp && !isSubscriptionFromIccCard) {
+ // CDMA can get subscription from NV. In that case,
+ // subscription is ready as soon as Radio is ON.
+ return IccCardConstants.State.READY;
+ }
+
+ if (mIccCardStatus == null) {
+ Log.e(mLogTag, "[IccCard] IccCardStatus is null");
+ return IccCardConstants.State.ABSENT;
+ }
+
+ // this is common for all radio technologies
+ if (!mIccCardStatus.getCardState().isCardPresent()) {
+ return IccCardConstants.State.ABSENT;
+ }
+
+ RadioState currentRadioState = mPhone.mCM.getRadioState();
+ // check radio technology
+ if( currentRadioState == RadioState.RADIO_OFF ||
+ currentRadioState == RadioState.RADIO_UNAVAILABLE) {
+ return IccCardConstants.State.NOT_READY;
+ }
+
+ if( currentRadioState == RadioState.RADIO_ON ) {
+ IccCardConstants.State csimState =
+ getAppState(mIccCardStatus.getCdmaSubscriptionAppIndex());
+ IccCardConstants.State usimState =
+ getAppState(mIccCardStatus.getGsmUmtsSubscriptionAppIndex());
+
+ if(mDbg) log("USIM=" + usimState + " CSIM=" + csimState);
+
+ if (mPhone.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE) {
+ // UICC card contains both USIM and CSIM
+ // Return consolidated status
+ return getConsolidatedState(csimState, usimState, csimState);
+ }
+
+ // check for CDMA radio technology
+ if (!is3gpp) {
+ return csimState;
+ }
+ return usimState;
+ }
+
+ return IccCardConstants.State.ABSENT;
+ }
+
+ private IccCardConstants.State getAppState(int appIndex) {
+ IccCardApplication app;
+ if (appIndex >= 0 && appIndex < IccCardStatus.CARD_MAX_APPS) {
+ app = mIccCardStatus.getApplication(appIndex);
+ } else {
+ Log.e(mLogTag, "[IccCard] Invalid Subscription Application index:" + appIndex);
+ return IccCardConstants.State.ABSENT;
+ }
+
+ if (app == null) {
+ Log.e(mLogTag, "[IccCard] Subscription Application in not present");
+ return IccCardConstants.State.ABSENT;
+ }
+
+ // check if PIN required
+ if (app.pin1.isPermBlocked()) {
+ return IccCardConstants.State.PERM_DISABLED;
+ }
+ if (app.app_state.isPinRequired()) {
+ return IccCardConstants.State.PIN_REQUIRED;
+ }
+ if (app.app_state.isPukRequired()) {
+ return IccCardConstants.State.PUK_REQUIRED;
+ }
+ if (app.app_state.isSubscriptionPersoEnabled()) {
+ return IccCardConstants.State.NETWORK_LOCKED;
+ }
+ if (app.app_state.isAppReady()) {
+ return IccCardConstants.State.READY;
+ }
+ if (app.app_state.isAppNotReady()) {
+ return IccCardConstants.State.NOT_READY;
+ }
+ return IccCardConstants.State.NOT_READY;
+ }
+
+ private IccCardConstants.State getConsolidatedState(IccCardConstants.State left,
+ IccCardConstants.State right, IccCardConstants.State preferredState) {
+ // Check if either is absent.
+ if (right == IccCardConstants.State.ABSENT) return left;
+ if (left == IccCardConstants.State.ABSENT) return right;
+
+ // Only if both are ready, return ready
+ if ((left == IccCardConstants.State.READY) && (right == IccCardConstants.State.READY)) {
+ return IccCardConstants.State.READY;
+ }
+
+ // Case one is ready, but the other is not.
+ if (((right == IccCardConstants.State.NOT_READY) &&
+ (left == IccCardConstants.State.READY)) ||
+ ((left == IccCardConstants.State.NOT_READY) &&
+ (right == IccCardConstants.State.READY))) {
+ return IccCardConstants.State.NOT_READY;
+ }
+
+ // At this point, the other state is assumed to be one of locked state
+ if (right == IccCardConstants.State.NOT_READY) return left;
+ if (left == IccCardConstants.State.NOT_READY) return right;
+
+ // At this point, FW currently just assumes the status will be
+ // consistent across the applications...
+ return preferredState;
+ }
+
+ public boolean isApplicationOnIcc(IccCardApplication.AppType type) {
+ if (mIccCardStatus == null) return false;
+
+ for (int i = 0 ; i < mIccCardStatus.getNumApplications(); i++) {
+ IccCardApplication app = mIccCardStatus.getApplication(i);
+ if (app != null && app.app_type == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return true if a ICC card is present
+ */
+ public boolean hasIccCard() {
+ if (mIccCardStatus == null) {
+ return false;
+ } else {
+ // Returns ICC card status for both GSM and CDMA mode
+ return mIccCardStatus.getCardState().isCardPresent();
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(mLogTag, "[IccCard] " + msg);
+ }
+
+ protected int getCurrentApplicationIndex() {
+ if (is3gpp) {
+ return mIccCardStatus.getGsmUmtsSubscriptionAppIndex();
+ } else {
+ return mIccCardStatus.getCdmaSubscriptionAppIndex();
+ }
+ }
+
+ public String getAid() {
+ String aid = "";
+ if (mIccCardStatus == null) {
+ return aid;
+ }
+
+ int appIndex = getCurrentApplicationIndex();
+
+ if (appIndex >= 0 && appIndex < IccCardStatus.CARD_MAX_APPS) {
+ IccCardApplication app = mIccCardStatus.getApplication(appIndex);
+ if (app != null) {
+ aid = app.aid;
+ } else {
+ Log.e(mLogTag, "[IccCard] getAid: no current application index=" + appIndex);
+ }
+ } else {
+ Log.e(mLogTag, "[IccCard] getAid: Invalid Subscription Application index=" + appIndex);
+ }
+
+ return aid;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccCardApplication.java b/src/java/com/android/internal/telephony/IccCardApplication.java
new file mode 100644
index 0000000..abb740e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccCardApplication.java
@@ -0,0 +1,226 @@
+/*
+ * 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 com.android.internal.telephony.IccCardStatus.PinState;
+
+
+/**
+ * See also RIL_AppStatus in include/telephony/ril.h
+ *
+ * {@hide}
+ */
+public class IccCardApplication {
+ public enum AppType{
+ APPTYPE_UNKNOWN,
+ APPTYPE_SIM,
+ APPTYPE_USIM,
+ APPTYPE_RUIM,
+ APPTYPE_CSIM,
+ APPTYPE_ISIM
+ };
+
+ public enum AppState{
+ APPSTATE_UNKNOWN,
+ APPSTATE_DETECTED,
+ APPSTATE_PIN,
+ APPSTATE_PUK,
+ APPSTATE_SUBSCRIPTION_PERSO,
+ APPSTATE_READY;
+
+ boolean isPinRequired() {
+ return this == APPSTATE_PIN;
+ }
+
+ boolean isPukRequired() {
+ return this == APPSTATE_PUK;
+ }
+
+ boolean isSubscriptionPersoEnabled() {
+ return this == APPSTATE_SUBSCRIPTION_PERSO;
+ }
+
+ boolean isAppReady() {
+ return this == APPSTATE_READY;
+ }
+
+ boolean isAppNotReady() {
+ return this == APPSTATE_UNKNOWN ||
+ this == APPSTATE_DETECTED;
+ }
+ };
+
+ public enum PersoSubState{
+ PERSOSUBSTATE_UNKNOWN,
+ PERSOSUBSTATE_IN_PROGRESS,
+ PERSOSUBSTATE_READY,
+ PERSOSUBSTATE_SIM_NETWORK,
+ PERSOSUBSTATE_SIM_NETWORK_SUBSET,
+ PERSOSUBSTATE_SIM_CORPORATE,
+ PERSOSUBSTATE_SIM_SERVICE_PROVIDER,
+ PERSOSUBSTATE_SIM_SIM,
+ PERSOSUBSTATE_SIM_NETWORK_PUK,
+ PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK,
+ PERSOSUBSTATE_SIM_CORPORATE_PUK,
+ PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK,
+ PERSOSUBSTATE_SIM_SIM_PUK,
+ PERSOSUBSTATE_RUIM_NETWORK1,
+ PERSOSUBSTATE_RUIM_NETWORK2,
+ PERSOSUBSTATE_RUIM_HRPD,
+ PERSOSUBSTATE_RUIM_CORPORATE,
+ PERSOSUBSTATE_RUIM_SERVICE_PROVIDER,
+ PERSOSUBSTATE_RUIM_RUIM,
+ PERSOSUBSTATE_RUIM_NETWORK1_PUK,
+ PERSOSUBSTATE_RUIM_NETWORK2_PUK,
+ PERSOSUBSTATE_RUIM_HRPD_PUK,
+ PERSOSUBSTATE_RUIM_CORPORATE_PUK,
+ PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK,
+ PERSOSUBSTATE_RUIM_RUIM_PUK;
+
+ boolean isPersoSubStateUnknown() {
+ return this == PERSOSUBSTATE_UNKNOWN;
+ }
+ };
+
+ public AppType app_type;
+ public AppState app_state;
+ // applicable only if app_state == RIL_APPSTATE_SUBSCRIPTION_PERSO
+ public PersoSubState perso_substate;
+ // null terminated string, e.g., from 0xA0, 0x00 -> 0x41, 0x30, 0x30, 0x30 */
+ public String aid;
+ // null terminated string
+ public String app_label;
+ // applicable to USIM and CSIM
+ public int pin1_replaced;
+ public PinState pin1;
+ public PinState pin2;
+
+ AppType AppTypeFromRILInt(int type) {
+ AppType newType;
+ /* RIL_AppType ril.h */
+ switch(type) {
+ case 0: newType = AppType.APPTYPE_UNKNOWN; break;
+ case 1: newType = AppType.APPTYPE_SIM; break;
+ case 2: newType = AppType.APPTYPE_USIM; break;
+ case 3: newType = AppType.APPTYPE_RUIM; break;
+ case 4: newType = AppType.APPTYPE_CSIM; break;
+ case 5: newType = AppType.APPTYPE_ISIM; break;
+ default:
+ throw new RuntimeException(
+ "Unrecognized RIL_AppType: " +type);
+ }
+ return newType;
+ }
+
+ AppState AppStateFromRILInt(int state) {
+ AppState newState;
+ /* RIL_AppState ril.h */
+ switch(state) {
+ case 0: newState = AppState.APPSTATE_UNKNOWN; break;
+ case 1: newState = AppState.APPSTATE_DETECTED; break;
+ case 2: newState = AppState.APPSTATE_PIN; break;
+ case 3: newState = AppState.APPSTATE_PUK; break;
+ case 4: newState = AppState.APPSTATE_SUBSCRIPTION_PERSO; break;
+ case 5: newState = AppState.APPSTATE_READY; break;
+ default:
+ throw new RuntimeException(
+ "Unrecognized RIL_AppState: " +state);
+ }
+ return newState;
+ }
+
+ PersoSubState PersoSubstateFromRILInt(int substate) {
+ PersoSubState newSubState;
+ /* RIL_PeroSubstate ril.h */
+ switch(substate) {
+ case 0: newSubState = PersoSubState.PERSOSUBSTATE_UNKNOWN; break;
+ case 1: newSubState = PersoSubState.PERSOSUBSTATE_IN_PROGRESS; break;
+ case 2: newSubState = PersoSubState.PERSOSUBSTATE_READY; break;
+ case 3: newSubState = PersoSubState.PERSOSUBSTATE_SIM_NETWORK; break;
+ case 4: newSubState = PersoSubState.PERSOSUBSTATE_SIM_NETWORK_SUBSET; break;
+ case 5: newSubState = PersoSubState.PERSOSUBSTATE_SIM_CORPORATE; break;
+ case 6: newSubState = PersoSubState.PERSOSUBSTATE_SIM_SERVICE_PROVIDER; break;
+ case 7: newSubState = PersoSubState.PERSOSUBSTATE_SIM_SIM; break;
+ case 8: newSubState = PersoSubState.PERSOSUBSTATE_SIM_NETWORK_PUK; break;
+ case 9: newSubState = PersoSubState.PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK; break;
+ case 10: newSubState = PersoSubState.PERSOSUBSTATE_SIM_CORPORATE_PUK; break;
+ case 11: newSubState = PersoSubState.PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK; break;
+ case 12: newSubState = PersoSubState.PERSOSUBSTATE_SIM_SIM_PUK; break;
+ case 13: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_NETWORK1; break;
+ case 14: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_NETWORK2; break;
+ case 15: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_HRPD; break;
+ case 16: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_CORPORATE; break;
+ case 17: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_SERVICE_PROVIDER; break;
+ case 18: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_RUIM; break;
+ case 19: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_NETWORK1_PUK; break;
+ case 20: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_NETWORK2_PUK; break;
+ case 21: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_HRPD_PUK ; break;
+ case 22: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_CORPORATE_PUK; break;
+ case 23: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK; break;
+ case 24: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_RUIM_PUK; break;
+ default:
+ throw new RuntimeException(
+ "Unrecognized RIL_PersoSubstate: " +substate);
+ }
+ return newSubState;
+ }
+
+ PinState PinStateFromRILInt(int state) {
+ PinState newPinState;
+ switch(state) {
+ case 0:
+ newPinState = PinState.PINSTATE_UNKNOWN;
+ break;
+ case 1:
+ newPinState = PinState.PINSTATE_ENABLED_NOT_VERIFIED;
+ break;
+ case 2:
+ newPinState = PinState.PINSTATE_ENABLED_VERIFIED;
+ break;
+ case 3:
+ newPinState = PinState.PINSTATE_DISABLED;
+ break;
+ case 4:
+ newPinState = PinState.PINSTATE_ENABLED_BLOCKED;
+ break;
+ case 5:
+ newPinState = PinState.PINSTATE_ENABLED_PERM_BLOCKED;
+ break;
+ default:
+ throw new RuntimeException("Unrecognized RIL_PinState: " + state);
+ }
+ return newPinState;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("{").append(app_type).append(",").append(app_state);
+ if (app_state == AppState.APPSTATE_SUBSCRIPTION_PERSO) {
+ sb.append(",").append(perso_substate);
+ }
+ if (app_type == AppType.APPTYPE_CSIM ||
+ app_type == AppType.APPTYPE_USIM ||
+ app_type == AppType.APPTYPE_ISIM) {
+ sb.append(",pin1=").append(pin1);
+ sb.append(",pin2=").append(pin2);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccCardStatus.java b/src/java/com/android/internal/telephony/IccCardStatus.java
new file mode 100644
index 0000000..a3bdd76
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccCardStatus.java
@@ -0,0 +1,188 @@
+/*
+ * 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;
+
+/**
+ * See also RIL_CardStatus in include/telephony/ril.h
+ *
+ * {@hide}
+ */
+public class IccCardStatus {
+ public static final int CARD_MAX_APPS = 8;
+
+ public enum CardState {
+ CARDSTATE_ABSENT,
+ CARDSTATE_PRESENT,
+ CARDSTATE_ERROR;
+
+ boolean isCardPresent() {
+ return this == CARDSTATE_PRESENT;
+ }
+ }
+
+ public enum PinState {
+ PINSTATE_UNKNOWN,
+ PINSTATE_ENABLED_NOT_VERIFIED,
+ PINSTATE_ENABLED_VERIFIED,
+ PINSTATE_DISABLED,
+ PINSTATE_ENABLED_BLOCKED,
+ PINSTATE_ENABLED_PERM_BLOCKED;
+
+ boolean isPermBlocked() {
+ return this == PINSTATE_ENABLED_PERM_BLOCKED;
+ }
+
+ boolean isPinRequired() {
+ return this == PINSTATE_ENABLED_NOT_VERIFIED;
+ }
+
+ boolean isPukRequired() {
+ return this == PINSTATE_ENABLED_BLOCKED;
+ }
+ }
+
+ private CardState mCardState;
+ private PinState mUniversalPinState;
+ private int mGsmUmtsSubscriptionAppIndex;
+ private int mCdmaSubscriptionAppIndex;
+ private int mImsSubscriptionAppIndex;
+ private int mNumApplications;
+
+ private ArrayList<IccCardApplication> mApplications =
+ new ArrayList<IccCardApplication>(CARD_MAX_APPS);
+
+ public CardState getCardState() {
+ return mCardState;
+ }
+
+ public void setCardState(int state) {
+ switch(state) {
+ case 0:
+ mCardState = CardState.CARDSTATE_ABSENT;
+ break;
+ case 1:
+ mCardState = CardState.CARDSTATE_PRESENT;
+ break;
+ case 2:
+ mCardState = CardState.CARDSTATE_ERROR;
+ break;
+ default:
+ throw new RuntimeException("Unrecognized RIL_CardState: " + state);
+ }
+ }
+
+ public PinState getUniversalPinState() {
+ return mUniversalPinState;
+ }
+
+ public void setUniversalPinState(int state) {
+ switch(state) {
+ case 0:
+ mUniversalPinState = PinState.PINSTATE_UNKNOWN;
+ break;
+ case 1:
+ mUniversalPinState = PinState.PINSTATE_ENABLED_NOT_VERIFIED;
+ break;
+ case 2:
+ mUniversalPinState = PinState.PINSTATE_ENABLED_VERIFIED;
+ break;
+ case 3:
+ mUniversalPinState = PinState.PINSTATE_DISABLED;
+ break;
+ case 4:
+ mUniversalPinState = PinState.PINSTATE_ENABLED_BLOCKED;
+ break;
+ case 5:
+ mUniversalPinState = PinState.PINSTATE_ENABLED_PERM_BLOCKED;
+ break;
+ default:
+ throw new RuntimeException("Unrecognized RIL_PinState: " + state);
+ }
+ }
+
+ public int getGsmUmtsSubscriptionAppIndex() {
+ return mGsmUmtsSubscriptionAppIndex;
+ }
+
+ public void setGsmUmtsSubscriptionAppIndex(int gsmUmtsSubscriptionAppIndex) {
+ mGsmUmtsSubscriptionAppIndex = gsmUmtsSubscriptionAppIndex;
+ }
+
+ public int getCdmaSubscriptionAppIndex() {
+ return mCdmaSubscriptionAppIndex;
+ }
+
+ public void setCdmaSubscriptionAppIndex(int cdmaSubscriptionAppIndex) {
+ mCdmaSubscriptionAppIndex = cdmaSubscriptionAppIndex;
+ }
+
+ public int getImsSubscriptionAppIndex() {
+ return mImsSubscriptionAppIndex;
+ }
+
+ public void setImsSubscriptionAppIndex(int imsSubscriptionAppIndex) {
+ mImsSubscriptionAppIndex = imsSubscriptionAppIndex;
+ }
+
+ public int getNumApplications() {
+ return mNumApplications;
+ }
+
+ public void setNumApplications(int numApplications) {
+ mNumApplications = numApplications;
+ }
+
+ public void addApplication(IccCardApplication application) {
+ mApplications.add(application);
+ }
+
+ public IccCardApplication getApplication(int index) {
+ return mApplications.get(index);
+ }
+
+ @Override
+ public String toString() {
+ IccCardApplication app;
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("IccCardState {").append(mCardState).append(",")
+ .append(mUniversalPinState)
+ .append(",num_apps=").append(mNumApplications)
+ .append(",gsm_id=").append(mGsmUmtsSubscriptionAppIndex);
+ if (mGsmUmtsSubscriptionAppIndex >=0
+ && mGsmUmtsSubscriptionAppIndex <CARD_MAX_APPS) {
+ app = getApplication(mGsmUmtsSubscriptionAppIndex);
+ sb.append(app == null ? "null" : app);
+ }
+
+ sb.append(",cmda_id=").append(mCdmaSubscriptionAppIndex);
+ if (mCdmaSubscriptionAppIndex >=0
+ && mCdmaSubscriptionAppIndex <CARD_MAX_APPS) {
+ app = getApplication(mCdmaSubscriptionAppIndex);
+ sb.append(app == null ? "null" : app);
+ }
+
+ sb.append(",ism_id=").append(mImsSubscriptionAppIndex);
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/IccConstants.java b/src/java/com/android/internal/telephony/IccConstants.java
new file mode 100644
index 0000000..1ba6dfe
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccConstants.java
@@ -0,0 +1,90 @@
+/*
+ * 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 IccConstants {
+ // GSM SIM file ids from TS 51.011
+ static final int EF_ADN = 0x6F3A;
+ static final int EF_FDN = 0x6F3B;
+ static final int EF_SDN = 0x6F49;
+ static final int EF_EXT1 = 0x6F4A;
+ static final int EF_EXT2 = 0x6F4B;
+ static final int EF_EXT3 = 0x6F4C;
+ static final int EF_EXT6 = 0x6fc8; // Ext record for EF[MBDN]
+ static final int EF_MWIS = 0x6FCA;
+ static final int EF_MBDN = 0x6fc7;
+ static final int EF_PNN = 0x6fc5;
+ static final int EF_SPN = 0x6F46;
+ static final int EF_SMS = 0x6F3C;
+ static final int EF_ICCID = 0x2fe2;
+ static final int EF_AD = 0x6FAD;
+ static final int EF_MBI = 0x6fc9;
+ static final int EF_MSISDN = 0x6f40;
+ static final int EF_SPDI = 0x6fcd;
+ static final int EF_SST = 0x6f38;
+ static final int EF_CFIS = 0x6FCB;
+ static final int EF_IMG = 0x4f20;
+
+ // USIM SIM file ids from TS 31.102
+ public static final int EF_PBR = 0x4F30;
+
+ // GSM SIM file ids from CPHS (phase 2, version 4.2) CPHS4_2.WW6
+ static final int EF_MAILBOX_CPHS = 0x6F17;
+ static final int EF_VOICE_MAIL_INDICATOR_CPHS = 0x6F11;
+ static final int EF_CFF_CPHS = 0x6F13;
+ static final int EF_SPN_CPHS = 0x6f14;
+ static final int EF_SPN_SHORT_CPHS = 0x6f18;
+ static final int EF_INFO_CPHS = 0x6f16;
+ static final int EF_CSP_CPHS = 0x6f15;
+
+ // CDMA RUIM file ids from 3GPP2 C.S0023-0
+ static final int EF_CST = 0x6f32;
+ static final int EF_RUIM_SPN =0x6F41;
+
+ // ETSI TS.102.221
+ static final int EF_PL = 0x2F05;
+ // 3GPP2 C.S0065
+ static final int EF_CSIM_LI = 0x6F3A;
+ static final int EF_CSIM_SPN =0x6F41;
+ static final int EF_CSIM_MDN = 0x6F44;
+ static final int EF_CSIM_IMSIM = 0x6F22;
+ static final int EF_CSIM_CDMAHOME = 0x6F28;
+ static final int EF_CSIM_EPRL = 0x6F5A;
+
+ //ISIM access
+ static final int EF_IMPU = 0x6f04;
+ static final int EF_IMPI = 0x6f02;
+ static final int EF_DOMAIN = 0x6f03;
+ static final int EF_IST = 0x6f07;
+ static final int EF_PCSCF = 0x6f09;
+
+ // SMS record length from TS 51.011 10.5.3
+ static public final int SMS_RECORD_LENGTH = 176;
+
+ static final String MF_SIM = "3F00";
+ static final String DF_TELECOM = "7F10";
+ static final String DF_PHONEBOOK = "5F3A";
+ static final String DF_GRAPHICS = "5F50";
+ static final String DF_GSM = "7F20";
+ static final String DF_CDMA = "7F25";
+
+ //ISIM access
+ static final String DF_ADFISIM = "7FFF";
+}
diff --git a/src/java/com/android/internal/telephony/IccException.java b/src/java/com/android/internal/telephony/IccException.java
new file mode 100644
index 0000000..1659a4e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 IccException extends Exception {
+ public IccException() {
+
+ }
+
+ public IccException(String s) {
+ super(s);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccFileHandler.java b/src/java/com/android/internal/telephony/IccFileHandler.java
new file mode 100644
index 0000000..70d8f7a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccFileHandler.java
@@ -0,0 +1,551 @@
+/*
+ * 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;
+
+import android.os.*;
+import android.util.Log;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public abstract class IccFileHandler extends Handler implements IccConstants {
+
+ //from TS 11.11 9.1 or elsewhere
+ static protected final int COMMAND_READ_BINARY = 0xb0;
+ static protected final int COMMAND_UPDATE_BINARY = 0xd6;
+ static protected final int COMMAND_READ_RECORD = 0xb2;
+ static protected final int COMMAND_UPDATE_RECORD = 0xdc;
+ static protected final int COMMAND_SEEK = 0xa2;
+ static protected final int COMMAND_GET_RESPONSE = 0xc0;
+
+ // from TS 11.11 9.2.5
+ static protected final int READ_RECORD_MODE_ABSOLUTE = 4;
+
+ //***** types of files TS 11.11 9.3
+ static protected final int EF_TYPE_TRANSPARENT = 0;
+ static protected final int EF_TYPE_LINEAR_FIXED = 1;
+ static protected final int EF_TYPE_CYCLIC = 3;
+
+ //***** types of files TS 11.11 9.3
+ static protected final int TYPE_RFU = 0;
+ static protected final int TYPE_MF = 1;
+ static protected final int TYPE_DF = 2;
+ static protected final int TYPE_EF = 4;
+
+ // size of GET_RESPONSE for EF's
+ static protected final int GET_RESPONSE_EF_SIZE_BYTES = 15;
+ static protected final int GET_RESPONSE_EF_IMG_SIZE_BYTES = 10;
+
+ // Byte order received in response to COMMAND_GET_RESPONSE
+ // Refer TS 51.011 Section 9.2.1
+ static protected final int RESPONSE_DATA_RFU_1 = 0;
+ static protected final int RESPONSE_DATA_RFU_2 = 1;
+
+ static protected final int RESPONSE_DATA_FILE_SIZE_1 = 2;
+ static protected final int RESPONSE_DATA_FILE_SIZE_2 = 3;
+
+ static protected final int RESPONSE_DATA_FILE_ID_1 = 4;
+ static protected final int RESPONSE_DATA_FILE_ID_2 = 5;
+ static protected final int RESPONSE_DATA_FILE_TYPE = 6;
+ static protected final int RESPONSE_DATA_RFU_3 = 7;
+ static protected final int RESPONSE_DATA_ACCESS_CONDITION_1 = 8;
+ static protected final int RESPONSE_DATA_ACCESS_CONDITION_2 = 9;
+ static protected final int RESPONSE_DATA_ACCESS_CONDITION_3 = 10;
+ static protected final int RESPONSE_DATA_FILE_STATUS = 11;
+ static protected final int RESPONSE_DATA_LENGTH = 12;
+ static protected final int RESPONSE_DATA_STRUCTURE = 13;
+ static protected final int RESPONSE_DATA_RECORD_LENGTH = 14;
+
+
+ //***** Events
+
+ /** Finished retrieving size of transparent EF; start loading. */
+ static protected final int EVENT_GET_BINARY_SIZE_DONE = 4;
+ /** Finished loading contents of transparent EF; post result. */
+ static protected final int EVENT_READ_BINARY_DONE = 5;
+ /** Finished retrieving size of records for linear-fixed EF; now load. */
+ static protected final int EVENT_GET_RECORD_SIZE_DONE = 6;
+ /** Finished loading single record from a linear-fixed EF; post result. */
+ static protected final int EVENT_READ_RECORD_DONE = 7;
+ /** Finished retrieving record size; post result. */
+ static protected final int EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE = 8;
+ /** Finished retrieving image instance record; post result. */
+ static protected final int EVENT_READ_IMG_DONE = 9;
+ /** Finished retrieving icon data; post result. */
+ static protected final int EVENT_READ_ICON_DONE = 10;
+
+ // member variables
+ protected final CommandsInterface mCi;
+ protected final IccCard mParentCard;
+ protected String mAid;
+
+ 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;
+ }
+ }
+
+ /**
+ * Default constructor
+ */
+ protected IccFileHandler(IccCard card, String aid, CommandsInterface ci) {
+ mParentCard = card;
+ mAid = aid;
+ mCi = ci;
+ }
+
+ public void dispose() {
+ }
+
+ //***** 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[]
+ *
+ */
+ public void loadEFLinearFixed(int fileid, int recordNum, Message onLoaded) {
+ Message response
+ = obtainMessage(EVENT_GET_RECORD_SIZE_DONE,
+ new LoadLinearFixedContext(fileid, recordNum, onLoaded));
+
+ mCi.iccIOForApp(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid),
+ 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, mAid, 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(IccConstants.EF_IMG, recordNum,
+ onLoaded));
+
+ // TODO(): Verify when path changes are done.
+ mCi.iccIOForApp(COMMAND_GET_RESPONSE, IccConstants.EF_IMG, "img",
+ recordNum, READ_RECORD_MODE_ABSOLUTE,
+ GET_RESPONSE_EF_IMG_SIZE_BYTES, null, null, mAid, 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]
+ */
+ public void getEFLinearRecordSize(int fileid, Message onLoaded) {
+ Message response
+ = obtainMessage(EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE,
+ new LoadLinearFixedContext(fileid, onLoaded));
+ mCi.iccIOForApp(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid),
+ 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, mAid, response);
+ }
+
+ /**
+ * Load all records from a SIM Linear Fixed EF
+ *
+ * @param fileid EF id
+ * @param onLoaded
+ *
+ * ((AsyncResult)(onLoaded.obj)).result is an ArrayList<byte[]>
+ *
+ */
+ public void loadEFLinearFixedAll(int fileid, Message onLoaded) {
+ Message response = obtainMessage(EVENT_GET_RECORD_SIZE_DONE,
+ new LoadLinearFixedContext(fileid,onLoaded));
+
+ mCi.iccIOForApp(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid),
+ 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, mAid, response);
+ }
+
+ /**
+ * Load a SIM Transparent EF
+ *
+ * @param fileid EF id
+ * @param onLoaded
+ *
+ * ((AsyncResult)(onLoaded.obj)).result is the byte[]
+ *
+ */
+
+ public void loadEFTransparent(int fileid, Message onLoaded) {
+ Message response = obtainMessage(EVENT_GET_BINARY_SIZE_DONE,
+ fileid, 0, onLoaded);
+
+ mCi.iccIOForApp(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid),
+ 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, mAid, 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);
+
+ mCi.iccIOForApp(COMMAND_READ_BINARY, fileid, "img", highOffset, lowOffset,
+ length, null, null, mAid, 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 IccIoResult on success
+ */
+ public void updateEFLinearFixed(int fileid, int recordNum, byte[] data,
+ String pin2, Message onComplete) {
+ mCi.iccIOForApp(COMMAND_UPDATE_RECORD, fileid, getEFPath(fileid),
+ recordNum, READ_RECORD_MODE_ABSOLUTE, data.length,
+ IccUtils.bytesToHexString(data), pin2, mAid, onComplete);
+ }
+
+ /**
+ * Update a transparent EF
+ * @param fileid EF id
+ * @param data must be exactly as long as the EF
+ */
+ public void updateEFTransparent(int fileid, byte[] data, Message onComplete) {
+ mCi.iccIOForApp(COMMAND_UPDATE_BINARY, fileid, getEFPath(fileid),
+ 0, 0, data.length,
+ IccUtils.bytesToHexString(data), null, mAid, onComplete);
+ }
+
+
+ //***** Abstract Methods
+
+
+ //***** Private Methods
+
+ private void sendResult(Message response, Object result, Throwable ex) {
+ if (response == null) {
+ return;
+ }
+
+ AsyncResult.forMessage(response, result, ex);
+
+ response.sendToTarget();
+ }
+
+ //***** Overridden from Handler
+
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+ IccIoResult result;
+ Message response = null;
+ String str;
+ LoadLinearFixedContext lc;
+
+ IccException iccException;
+ 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 = (IccIoResult) ar.result;
+ response = lc.onLoaded;
+
+ iccException = result.getException();
+ if (iccException != null) {
+ sendResult(response, result.payload, ar.exception);
+ }
+ break;
+ case EVENT_READ_ICON_DONE:
+ ar = (AsyncResult) msg.obj;
+ response = (Message) ar.userObj;
+ result = (IccIoResult) ar.result;
+
+ iccException = result.getException();
+ if (iccException != 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 = (IccIoResult) ar.result;
+ response = lc.onLoaded;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ iccException = result.getException();
+ if (iccException != null) {
+ sendResult(response, null, iccException);
+ break;
+ }
+
+ data = result.payload;
+
+ if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE] ||
+ EF_TYPE_LINEAR_FIXED != data[RESPONSE_DATA_STRUCTURE]) {
+ throw new IccFileTypeMismatch();
+ }
+
+ 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 = (IccIoResult) ar.result;
+ response = lc.onLoaded;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ iccException = result.getException();
+
+ if (iccException != null) {
+ sendResult(response, null, iccException);
+ break;
+ }
+
+ data = result.payload;
+ fileid = lc.efid;
+ recordNum = lc.recordNum;
+
+ if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE]) {
+ throw new IccFileTypeMismatch();
+ }
+
+ if (EF_TYPE_LINEAR_FIXED != data[RESPONSE_DATA_STRUCTURE]) {
+ throw new IccFileTypeMismatch();
+ }
+
+ 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);
+ }
+
+ mCi.iccIOForApp(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid),
+ lc.recordNum,
+ READ_RECORD_MODE_ABSOLUTE,
+ lc.recordSize, null, null, mAid,
+ obtainMessage(EVENT_READ_RECORD_DONE, lc));
+ break;
+ case EVENT_GET_BINARY_SIZE_DONE:
+ ar = (AsyncResult)msg.obj;
+ response = (Message) ar.userObj;
+ result = (IccIoResult) ar.result;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ iccException = result.getException();
+
+ if (iccException != null) {
+ sendResult(response, null, iccException);
+ break;
+ }
+
+ data = result.payload;
+
+ fileid = msg.arg1;
+
+ if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE]) {
+ throw new IccFileTypeMismatch();
+ }
+
+ if (EF_TYPE_TRANSPARENT != data[RESPONSE_DATA_STRUCTURE]) {
+ throw new IccFileTypeMismatch();
+ }
+
+ size = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8)
+ + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff);
+
+ mCi.iccIOForApp(COMMAND_READ_BINARY, fileid, getEFPath(fileid),
+ 0, 0, size, null, null, mAid,
+ obtainMessage(EVENT_READ_BINARY_DONE,
+ fileid, 0, response));
+ break;
+
+ case EVENT_READ_RECORD_DONE:
+
+ ar = (AsyncResult)msg.obj;
+ lc = (LoadLinearFixedContext) ar.userObj;
+ result = (IccIoResult) ar.result;
+ response = lc.onLoaded;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ iccException = result.getException();
+
+ if (iccException != null) {
+ sendResult(response, null, iccException);
+ 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 {
+ mCi.iccIOForApp(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid),
+ lc.recordNum,
+ READ_RECORD_MODE_ABSOLUTE,
+ lc.recordSize, null, null, mAid,
+ obtainMessage(EVENT_READ_RECORD_DONE, lc));
+ }
+ }
+
+ break;
+
+ case EVENT_READ_BINARY_DONE:
+ ar = (AsyncResult)msg.obj;
+ response = (Message) ar.userObj;
+ result = (IccIoResult) ar.result;
+
+ if (ar.exception != null) {
+ sendResult(response, null, ar.exception);
+ break;
+ }
+
+ iccException = result.getException();
+
+ if (iccException != null) {
+ sendResult(response, null, iccException);
+ break;
+ }
+
+ sendResult(response, result.payload, null);
+ break;
+
+ }} catch (Exception exc) {
+ if (response != null) {
+ sendResult(response, null, exc);
+ } else {
+ loge("uncaught exception" + exc);
+ }
+ }
+ }
+
+ /**
+ * Returns the root path of the EF file.
+ * i.e returns MasterFile + DFfile as a string.
+ * Ex: For EF_ADN on a SIM, it will return "3F007F10"
+ * This function handles only EFids that are common to
+ * RUIM, SIM, USIM and other types of Icc cards.
+ *
+ * @param efId
+ * @return root path of the file.
+ */
+ protected String getCommonIccEFPath(int efid) {
+ switch(efid) {
+ case EF_ADN:
+ case EF_FDN:
+ case EF_MSISDN:
+ case EF_SDN:
+ case EF_EXT1:
+ case EF_EXT2:
+ case EF_EXT3:
+ return MF_SIM + DF_TELECOM;
+
+ case EF_ICCID:
+ case EF_PL:
+ return MF_SIM;
+ case EF_IMG:
+ return MF_SIM + DF_TELECOM + DF_GRAPHICS;
+ }
+ return null;
+ }
+
+ protected abstract String getEFPath(int efid);
+ protected abstract void logd(String s);
+
+ protected abstract void loge(String s);
+ protected void setAid(String aid) {
+ mAid = aid;
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/IccFileNotFound.java b/src/java/com/android/internal/telephony/IccFileNotFound.java
new file mode 100644
index 0000000..915cea6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccFileNotFound.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 IccFileNotFound extends IccException {
+ IccFileNotFound() {
+
+ }
+
+ IccFileNotFound(String s) {
+ super(s);
+ }
+
+ IccFileNotFound(int ef) {
+ super("ICC EF Not Found 0x" + Integer.toHexString(ef));
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccFileTypeMismatch.java b/src/java/com/android/internal/telephony/IccFileTypeMismatch.java
new file mode 100644
index 0000000..66fcfa9
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccFileTypeMismatch.java
@@ -0,0 +1,30 @@
+/*
+ * 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 IccFileTypeMismatch extends IccException {
+ public IccFileTypeMismatch() {
+
+ }
+
+ public IccFileTypeMismatch(String s) {
+ super(s);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccIoResult.java b/src/java/com/android/internal/telephony/IccIoResult.java
new file mode 100644
index 0000000..7043da5
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccIoResult.java
@@ -0,0 +1,70 @@
+/*
+ * 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
+IccIoResult {
+ public int sw1;
+ public int sw2;
+
+ public byte[] payload;
+
+ public IccIoResult(int sw1, int sw2, byte[] payload) {
+ this.sw1 = sw1;
+ this.sw2 = sw2;
+ this.payload = payload;
+ }
+
+ public IccIoResult(int sw1, int sw2, String hexString) {
+ this(sw1, sw2, IccUtils.hexStringToBytes(hexString));
+ }
+
+ public String toString() {
+ return "IccIoResponse 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 IccException getException() {
+ if (success()) return null;
+
+ switch (sw1) {
+ case 0x94:
+ if (sw2 == 0x08) {
+ return new IccFileTypeMismatch();
+ } else {
+ return new IccFileNotFound();
+ }
+ default:
+ return new IccException("sw1:" + sw1 + " sw2:" + sw2);
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
new file mode 100644
index 0000000..45562ca
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -0,0 +1,289 @@
+/*
+ * 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.pm.PackageManager;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ServiceManager;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * SimPhoneBookInterfaceManager to provide an inter-process communication to
+ * access ADN-like SIM records.
+ */
+public abstract class IccPhoneBookInterfaceManager extends IIccPhoneBook.Stub {
+ protected static final boolean DBG = true;
+
+ protected PhoneBase phone;
+ protected AdnRecordCache adnCache;
+ protected final Object mLock = new Object();
+ protected int recordSize[];
+ protected boolean success;
+ protected List<AdnRecord> records;
+
+ protected static final boolean ALLOW_SIM_OP_IN_UI_THREAD = false;
+
+ protected static final int EVENT_GET_SIZE_DONE = 1;
+ protected static final int EVENT_LOAD_DONE = 2;
+ protected static final int EVENT_UPDATE_DONE = 3;
+
+ protected Handler mBaseHandler = 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
+ logd("GET_RECORD_SIZE Size " + recordSize[0] +
+ " total " + recordSize[1] +
+ " #record " + recordSize[2]);
+ }
+ notifyPending(ar);
+ }
+ break;
+ case EVENT_UPDATE_DONE:
+ ar = (AsyncResult) msg.obj;
+ synchronized (mLock) {
+ success = (ar.exception == null);
+ notifyPending(ar);
+ }
+ break;
+ case EVENT_LOAD_DONE:
+ ar = (AsyncResult)msg.obj;
+ synchronized (mLock) {
+ if (ar.exception == null) {
+ records = (List<AdnRecord>) ar.result;
+ } else {
+ if(DBG) logd("Cannot load ADN records");
+ if (records != null) {
+ records.clear();
+ }
+ }
+ notifyPending(ar);
+ }
+ break;
+ }
+ }
+
+ private void notifyPending(AsyncResult ar) {
+ if (ar.userObj == null) {
+ return;
+ }
+ AtomicBoolean status = (AtomicBoolean) ar.userObj;
+ status.set(true);
+ mLock.notifyAll();
+ }
+ };
+
+ public IccPhoneBookInterfaceManager(PhoneBase phone) {
+ this.phone = phone;
+ }
+
+ public void dispose() {
+ }
+
+ protected void publish() {
+ //NOTE service "simphonebook" added by IccSmsInterfaceManagerProxy
+ ServiceManager.addService("simphonebook", this);
+ }
+
+ protected abstract void logd(String msg);
+
+ protected abstract void loge(String msg);
+
+ /**
+ * 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. Currently the email field
+ * if set in the ADN record is ignored.
+ * 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) logd("updateAdnRecordsInEfBySearch: efid=" + efid +
+ " ("+ oldTag + "," + oldPhoneNumber + ")"+ "==>" +
+ " ("+ newTag + "," + newPhoneNumber + ")"+ " pin2=" + pin2);
+
+ efid = updateEfForIccType(efid);
+
+ synchronized(mLock) {
+ checkThread();
+ success = false;
+ AtomicBoolean status = new AtomicBoolean(false);
+ Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, status);
+ AdnRecord oldAdn = new AdnRecord(oldTag, oldPhoneNumber);
+ AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
+ adnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
+ waitForResult(status);
+ }
+ 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. Currently the email field
+ * if set in the ADN record is ignored.
+ * 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) logd("updateAdnRecordsInEfByIndex: efid=" + efid +
+ " Index=" + index + " ==> " +
+ "("+ newTag + "," + newPhoneNumber + ")"+ " pin2=" + pin2);
+ synchronized(mLock) {
+ checkThread();
+ success = false;
+ AtomicBoolean status = new AtomicBoolean(false);
+ Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, status);
+ AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
+ adnCache.updateAdnByIndex(efid, newAdn, index, pin2, response);
+ waitForResult(status);
+ }
+ return success;
+ }
+
+ /**
+ * Get the capacity of records in efid
+ *
+ * @param efid the EF id of a ADN-like ICC
+ * @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 abstract int[] getAdnRecordsSize(int efid);
+
+ /**
+ * 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 ICC
+ * @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");
+ }
+
+ efid = updateEfForIccType(efid);
+ if (DBG) logd("getAdnRecordsInEF: efid=" + efid);
+
+ synchronized(mLock) {
+ checkThread();
+ AtomicBoolean status = new AtomicBoolean(false);
+ Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, status);
+ adnCache.requestLoadAllAdnLike(efid, adnCache.extensionEfForEf(efid), response);
+ waitForResult(status);
+ }
+ return records;
+ }
+
+ protected void checkThread() {
+ if (!ALLOW_SIM_OP_IN_UI_THREAD) {
+ // Make sure this isn't the UI thread, since it will block
+ if (mBaseHandler.getLooper().equals(Looper.myLooper())) {
+ loge("query() called on the main UI thread!");
+ throw new IllegalStateException(
+ "You cannot call query on this provder from the main UI thread.");
+ }
+ }
+ }
+
+ protected void waitForResult(AtomicBoolean status) {
+ while (!status.get()) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ logd("interrupted while trying to update by search");
+ }
+ }
+ }
+
+ private int updateEfForIccType(int efid) {
+ // Check if we are trying to read ADN records
+ if (efid == IccConstants.EF_ADN) {
+ if (phone.getIccCard().isApplicationOnIcc(IccCardApplication.AppType.APPTYPE_USIM)) {
+ return IccConstants.EF_PBR;
+ }
+ }
+ return efid;
+ }
+}
+
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManagerProxy.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManagerProxy.java
new file mode 100644
index 0000000..1c0fc52
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManagerProxy.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+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 IccPhoneBookInterfaceManagerProxy extends IIccPhoneBook.Stub {
+ private IccPhoneBookInterfaceManager mIccPhoneBookInterfaceManager;
+
+ public IccPhoneBookInterfaceManagerProxy(IccPhoneBookInterfaceManager
+ iccPhoneBookInterfaceManager) {
+ mIccPhoneBookInterfaceManager = iccPhoneBookInterfaceManager;
+ if(ServiceManager.getService("simphonebook") == null) {
+ ServiceManager.addService("simphonebook", this);
+ }
+ }
+
+ public void setmIccPhoneBookInterfaceManager(
+ IccPhoneBookInterfaceManager iccPhoneBookInterfaceManager) {
+ this.mIccPhoneBookInterfaceManager = iccPhoneBookInterfaceManager;
+ }
+
+ public boolean
+ updateAdnRecordsInEfBySearch (int efid,
+ String oldTag, String oldPhoneNumber,
+ String newTag, String newPhoneNumber,
+ String pin2) throws android.os.RemoteException {
+ return mIccPhoneBookInterfaceManager.updateAdnRecordsInEfBySearch(
+ efid, oldTag, oldPhoneNumber, newTag, newPhoneNumber, pin2);
+ }
+
+ public boolean
+ updateAdnRecordsInEfByIndex(int efid, String newTag,
+ String newPhoneNumber, int index, String pin2) throws android.os.RemoteException {
+ return mIccPhoneBookInterfaceManager.updateAdnRecordsInEfByIndex(efid,
+ newTag, newPhoneNumber, index, pin2);
+ }
+
+ public int[] getAdnRecordsSize(int efid) throws android.os.RemoteException {
+ return mIccPhoneBookInterfaceManager.getAdnRecordsSize(efid);
+ }
+
+ public List<AdnRecord> getAdnRecordsInEf(int efid) throws android.os.RemoteException {
+ return mIccPhoneBookInterfaceManager.getAdnRecordsInEf(efid);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccProvider.java b/src/java/com/android/internal/telephony/IccProvider.java
new file mode 100644
index 0000000..a66e19d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccProvider.java
@@ -0,0 +1,431 @@
+/*
+ * 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.ContentProvider;
+import android.content.UriMatcher;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+
+import com.android.internal.telephony.IccConstants;
+import com.android.internal.telephony.AdnRecord;
+import com.android.internal.telephony.IIccPhoneBook;
+
+
+/**
+ * {@hide}
+ */
+public class IccProvider extends ContentProvider {
+ private static final String TAG = "IccProvider";
+ private static final boolean DBG = false;
+
+
+ private static final String[] ADDRESS_BOOK_COLUMN_NAMES = new String[] {
+ "name",
+ "number",
+ "emails",
+ "_id"
+ };
+
+ 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_EMAILS = "emails";
+ private static final String STR_PIN2 = "pin2";
+
+ private static final UriMatcher URL_MATCHER =
+ new UriMatcher(UriMatcher.NO_MATCH);
+
+ static {
+ URL_MATCHER.addURI("icc", "adn", ADN);
+ URL_MATCHER.addURI("icc", "fdn", FDN);
+ URL_MATCHER.addURI("icc", "sdn", SDN);
+ }
+
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sort) {
+ switch (URL_MATCHER.match(url)) {
+ case ADN:
+ return loadFromEf(IccConstants.EF_ADN);
+
+ case FDN:
+ return loadFromEf(IccConstants.EF_FDN);
+
+ case SDN:
+ return loadFromEf(IccConstants.EF_SDN);
+
+ default:
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ }
+
+ @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 = IccConstants.EF_ADN;
+ break;
+
+ case FDN:
+ efType = IccConstants.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");
+ // TODO(): Read email instead of sending null.
+ boolean success = addIccRecordToEf(efType, tag, number, null, pin2);
+
+ if (!success) {
+ return null;
+ }
+
+ StringBuilder buf = new StringBuilder("content://icc/");
+ 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 = IccConstants.EF_ADN;
+ break;
+
+ case FDN:
+ efType = IccConstants.EF_FDN;
+ break;
+
+ default:
+ throw new UnsupportedOperationException(
+ "Cannot insert into URL: " + url);
+ }
+
+ // parse where clause
+ String tag = null;
+ String number = null;
+ String[] emails = 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_EMAILS.equals(key)) {
+ //TODO(): Email is null.
+ emails = null;
+ } else if (STR_PIN2.equals(key)) {
+ pin2 = normalizeValue(val);
+ }
+ }
+
+ if (TextUtils.isEmpty(number)) {
+ return 0;
+ }
+
+ if (efType == IccConstants.EF_FDN && TextUtils.isEmpty(pin2)) {
+ return 0;
+ }
+
+ boolean success = deleteIccRecordFromEf(efType, tag, number, emails, 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 = IccConstants.EF_ADN;
+ break;
+
+ case FDN:
+ efType = IccConstants.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[] emails = null;
+ String newTag = values.getAsString("newTag");
+ String newNumber = values.getAsString("newNumber");
+ String[] newEmails = null;
+ // TODO(): Update for email.
+ boolean success = updateIccRecordInEf(efType, tag, number,
+ newTag, newNumber, pin2);
+
+ if (!success) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ private MatrixCursor loadFromEf(int efType) {
+ if (DBG) log("loadFromEf: efType=" + efType);
+
+ List<AdnRecord> adnRecords = null;
+ try {
+ IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
+ ServiceManager.getService("simphonebook"));
+ if (iccIpb != null) {
+ adnRecords = iccIpb.getAdnRecordsInEf(efType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ } catch (SecurityException ex) {
+ if (DBG) log(ex.toString());
+ }
+
+ if (adnRecords != null) {
+ // Load the results
+ final int N = adnRecords.size();
+ final MatrixCursor cursor = new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES, N);
+ if (DBG) log("adnRecords.size=" + N);
+ for (int i = 0; i < N ; i++) {
+ loadRecord(adnRecords.get(i), cursor, i);
+ }
+ return cursor;
+ } else {
+ // No results to load
+ Log.w(TAG, "Cannot load ADN records");
+ return new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES);
+ }
+ }
+
+ private boolean
+ addIccRecordToEf(int efType, String name, String number, String[] emails, String pin2) {
+ if (DBG) log("addIccRecordToEf: efType=" + efType + ", name=" + name +
+ ", number=" + number + ", emails=" + emails);
+
+ 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 {
+ IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
+ ServiceManager.getService("simphonebook"));
+ if (iccIpb != null) {
+ success = iccIpb.updateAdnRecordsInEfBySearch(efType, "", "",
+ name, number, pin2);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ } catch (SecurityException ex) {
+ if (DBG) log(ex.toString());
+ }
+ if (DBG) log("addIccRecordToEf: " + success);
+ return success;
+ }
+
+ private boolean
+ updateIccRecordInEf(int efType, String oldName, String oldNumber,
+ String newName, String newNumber, String pin2) {
+ if (DBG) log("updateIccRecordInEf: efType=" + efType +
+ ", oldname=" + oldName + ", oldnumber=" + oldNumber +
+ ", newname=" + newName + ", newnumber=" + newNumber);
+ boolean success = false;
+
+ try {
+ IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
+ ServiceManager.getService("simphonebook"));
+ if (iccIpb != null) {
+ success = iccIpb.updateAdnRecordsInEfBySearch(efType,
+ oldName, oldNumber, newName, newNumber, pin2);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ } catch (SecurityException ex) {
+ if (DBG) log(ex.toString());
+ }
+ if (DBG) log("updateIccRecordInEf: " + success);
+ return success;
+ }
+
+
+ private boolean deleteIccRecordFromEf(int efType, String name, String number, String[] emails,
+ String pin2) {
+ if (DBG) log("deleteIccRecordFromEf: efType=" + efType +
+ ", name=" + name + ", number=" + number + ", emails=" + emails + ", pin2=" + pin2);
+
+ boolean success = false;
+
+ try {
+ IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
+ ServiceManager.getService("simphonebook"));
+ if (iccIpb != null) {
+ success = iccIpb.updateAdnRecordsInEfBySearch(efType,
+ name, number, "", "", pin2);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ } catch (SecurityException ex) {
+ if (DBG) log(ex.toString());
+ }
+ if (DBG) log("deleteIccRecordFromEf: " + success);
+ return success;
+ }
+
+ /**
+ * Loads an AdnRecord into a MatrixCursor. Must be called with mLock held.
+ *
+ * @param record the ADN record to load from
+ * @param cursor the cursor to receive the results
+ */
+ private void loadRecord(AdnRecord record, MatrixCursor cursor, int id) {
+ if (!record.isEmpty()) {
+ Object[] contact = new Object[4];
+ String alphaTag = record.getAlphaTag();
+ String number = record.getNumber();
+
+ if (DBG) log("loadRecord: " + alphaTag + ", " + number + ",");
+ contact[0] = alphaTag;
+ contact[1] = number;
+
+ String[] emails = record.getEmails();
+ if (emails != null) {
+ StringBuilder emailString = new StringBuilder();
+ for (String email: emails) {
+ if (DBG) log("Adding email:" + email);
+ emailString.append(email);
+ emailString.append(",");
+ }
+ contact[2] = emailString.toString();
+ }
+ contact[3] = id;
+ cursor.addRow(contact);
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(TAG, "[IccProvider] " + msg);
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/IccRecords.java b/src/java/com/android/internal/telephony/IccRecords.java
new file mode 100644
index 0000000..41c9d5a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccRecords.java
@@ -0,0 +1,422 @@
+/*
+ * 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.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+
+import com.android.internal.telephony.gsm.UsimServiceTable;
+import com.android.internal.telephony.ims.IsimRecords;
+
+/**
+ * {@hide}
+ */
+public abstract class IccRecords extends Handler implements IccConstants {
+
+ protected static final boolean DBG = true;
+ // ***** Instance Variables
+ protected boolean mDestroyed = false; // set to true once this object needs to be disposed of
+ protected Context mContext;
+ protected CommandsInterface mCi;
+ protected IccFileHandler mFh;
+ protected IccCard mParentCard;
+
+ protected RegistrantList recordsLoadedRegistrants = new RegistrantList();
+ protected RegistrantList mRecordsEventsRegistrants = new RegistrantList();
+ protected RegistrantList mNewSmsRegistrants = new RegistrantList();
+ protected RegistrantList mNetworkSelectionModeAutomaticRegistrants = new RegistrantList();
+
+ protected int recordsToLoad; // number of pending load requests
+
+ protected AdnRecordCache adnCache;
+
+ // ***** Cached SIM State; cleared on channel close
+
+ protected boolean recordsRequested = false; // true if we've made requests for the sim records
+
+ public String iccid;
+ protected String msisdn = null; // My mobile number
+ protected String msisdnTag = null;
+ protected String voiceMailNum = null;
+ protected String voiceMailTag = null;
+ protected String newVoiceMailNum = null;
+ protected String newVoiceMailTag = null;
+ protected boolean isVoiceMailFixed = false;
+ protected int countVoiceMessages = 0;
+
+ protected int mncLength = UNINITIALIZED;
+ protected int mailboxIndex = 0; // 0 is no mailbox dailing number associated
+
+ protected String spn;
+
+ // ***** Constants
+
+ // Markers for mncLength
+ protected static final int UNINITIALIZED = -1;
+ protected static final int UNKNOWN = 0;
+
+ // Bitmasks for SPN display rules.
+ protected static final int SPN_RULE_SHOW_SPN = 0x01;
+ protected static final int SPN_RULE_SHOW_PLMN = 0x02;
+
+ // ***** Event Constants
+ protected static final int EVENT_SET_MSISDN_DONE = 30;
+ public static final int EVENT_MWI = 0;
+ public static final int EVENT_CFI = 1;
+ public static final int EVENT_SPN = 2;
+
+ public static final int EVENT_GET_ICC_RECORD_DONE = 100;
+
+ /**
+ * Generic ICC record loaded callback. Subclasses can call EF load methods on
+ * {@link IccFileHandler} passing a Message for onLoaded with the what field set to
+ * {@link #EVENT_GET_ICC_RECORD_DONE} and the obj field set to an instance
+ * of this interface. The {@link #handleMessage} method in this class will print a
+ * log message using {@link #getEfName()} and decrement {@link #recordsToLoad}.
+ *
+ * If the record load was successful, {@link #onRecordLoaded} will be called with the result.
+ * Otherwise, an error log message will be output by {@link #handleMessage} and
+ * {@link #onRecordLoaded} will not be called.
+ */
+ public interface IccRecordLoaded {
+ String getEfName();
+ void onRecordLoaded(AsyncResult ar);
+ }
+
+ // ***** Constructor
+ public IccRecords(IccCard card, Context c, CommandsInterface ci) {
+ mContext = c;
+ mCi = ci;
+ mFh = card.getIccFileHandler();
+ mParentCard = card;
+ }
+
+ /**
+ * Call when the IccRecords object is no longer going to be used.
+ */
+ public void dispose() {
+ mDestroyed = true;
+ mParentCard = null;
+ mFh = null;
+ mCi = null;
+ mContext = null;
+ }
+
+ protected abstract void onRadioOffOrNotAvailable();
+ public abstract void onReady();
+
+ //***** Public Methods
+ public AdnRecordCache getAdnCache() {
+ return adnCache;
+ }
+
+ public IccCard getIccCard() {
+ return mParentCard;
+ }
+
+ public void registerForRecordsLoaded(Handler h, int what, Object obj) {
+ if (mDestroyed) {
+ return;
+ }
+
+ Registrant r = new Registrant(h, what, obj);
+ recordsLoadedRegistrants.add(r);
+
+ if (recordsToLoad == 0 && recordsRequested == true) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ public void unregisterForRecordsLoaded(Handler h) {
+ recordsLoadedRegistrants.remove(h);
+ }
+
+ public void registerForRecordsEvents(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mRecordsEventsRegistrants.add(r);
+ }
+ public void unregisterForRecordsEvents(Handler h) {
+ mRecordsEventsRegistrants.remove(h);
+ }
+
+ public void registerForNewSms(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mNewSmsRegistrants.add(r);
+ }
+ public void unregisterForNewSms(Handler h) {
+ mNewSmsRegistrants.remove(h);
+ }
+
+ public void registerForNetworkSelectionModeAutomatic(
+ Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mNetworkSelectionModeAutomaticRegistrants.add(r);
+ }
+ public void unregisterForNetworkSelectionModeAutomatic(Handler h) {
+ mNetworkSelectionModeAutomaticRegistrants.remove(h);
+ }
+
+ /**
+ * Get the International Mobile Subscriber ID (IMSI) on a SIM
+ * for GSM, UMTS and like networks. Default is null if IMSI is
+ * not supported or unavailable.
+ *
+ * @return null if SIM is not yet ready or unavailable
+ */
+ public String getIMSI() {
+ return null;
+ }
+
+ 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(mFh).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 (EF_SPN=0x6F46) or in RUIM (EF_RUIM_SPN=0x6F41)
+ * @return null if SIM is not yet ready or no RUIM entry
+ */
+ public String getServiceProviderName() {
+ 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 abstract void setVoiceMailNumber(String alphaTag, String voiceNumber,
+ Message onComplete);
+
+ 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 abstract void setVoiceMessageWaiting(int line, int countWaiting);
+
+ /** @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 getVoiceMessageCount() {
+ return countVoiceMessages;
+ }
+
+ /**
+ * 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 abstract void onRefresh(boolean fileChanged, int[] fileList);
+
+
+ public boolean getRecordsLoaded() {
+ if (recordsToLoad == 0 && recordsRequested == true) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ //***** Overridden from Handler
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_GET_ICC_RECORD_DONE:
+ try {
+ AsyncResult ar = (AsyncResult) msg.obj;
+ IccRecordLoaded recordLoaded = (IccRecordLoaded) ar.userObj;
+ if (DBG) log(recordLoaded.getEfName() + " LOADED");
+
+ if (ar.exception != null) {
+ loge("Record Load Exception: " + ar.exception);
+ } else {
+ recordLoaded.onRecordLoaded(ar);
+ }
+ }catch (RuntimeException exc) {
+ // I don't want these exceptions to be fatal
+ loge("Exception parsing SIM record: " + exc);
+ } finally {
+ // Count up record load responses even if they are fails
+ onRecordLoaded();
+ }
+ break;
+
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ protected abstract void onRecordLoaded();
+
+ protected abstract void onAllRecordsLoaded();
+
+ /**
+ * 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.
+ * Generally used for GSM/UMTS and the like SIMs.
+ */
+ public abstract int getDisplayRule(String plmn);
+
+ /**
+ * Return true if "Restriction of menu options for manual PLMN selection"
+ * bit is set or EF_CSP data is unavailable, return false otherwise.
+ * Generally used for GSM/UMTS and the like SIMs.
+ */
+ public boolean isCspPlmnEnabled() {
+ return false;
+ }
+
+ /**
+ * Returns the 5 or 6 digit MCC/MNC of the operator that
+ * provided the SIM card. Returns null of SIM is not yet ready
+ * or is not valid for the type of IccCard. Generally used for
+ * GSM/UMTS and the like SIMS
+ */
+ public String getOperatorNumeric() {
+ return null;
+ }
+
+ /**
+ * Get the current Voice call forwarding flag for GSM/UMTS and the like SIMs
+ *
+ * @return true if enabled
+ */
+ public boolean getVoiceCallForwardingFlag() {
+ return false;
+ }
+
+ /**
+ * Set the voice call forwarding flag for GSM/UMTS and the like SIMs
+ *
+ * @param line to enable/disable
+ * @param enable
+ */
+ public void setVoiceCallForwardingFlag(int line, boolean enable) {
+ }
+
+ /**
+ * Indicates wether SIM is in provisioned state or not.
+ * Overridden only if SIM can be dynamically provisioned via OTA.
+ *
+ * @return true if provisioned
+ */
+ public boolean isProvisioned () {
+ return true;
+ }
+
+ /**
+ * Write string to log file
+ *
+ * @param s is the string to write
+ */
+ protected abstract void log(String s);
+
+ /**
+ * Write error string to log file.
+ *
+ * @param s is the string to write
+ */
+ protected abstract void loge(String s);
+
+ /**
+ * Return an interface to retrieve the ISIM records for IMS, if available.
+ * @return the interface to retrieve the ISIM records, or null if not supported
+ */
+ public IsimRecords getIsimRecords() {
+ return null;
+ }
+
+ public UsimServiceTable getUsimServiceTable() {
+ return null;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccRefreshResponse.java b/src/java/com/android/internal/telephony/IccRefreshResponse.java
new file mode 100644
index 0000000..6806703
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccRefreshResponse.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * See also RIL_SimRefresh in include/telephony/ril.h
+ *
+ * {@hide}
+ */
+
+public class IccRefreshResponse {
+
+ public static final int REFRESH_RESULT_FILE_UPDATE = 0; /* Single file was updated */
+ public static final int REFRESH_RESULT_INIT = 1; /* The Icc has been initialized */
+ public static final int REFRESH_RESULT_RESET = 2; /* The Icc was reset */
+
+ public int refreshResult; /* Sim Refresh result */
+ public int efId; /* EFID */
+ public String aid; /* null terminated string, e.g.,
+ from 0xA0, 0x00 -> 0x41,
+ 0x30, 0x30, 0x30 */
+ /* Example: a0000000871002f310ffff89080000ff */
+
+ @Override
+ public String toString() {
+ return "{" + refreshResult + ", " + aid +", " + efId + "}";
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccServiceTable.java b/src/java/com/android/internal/telephony/IccServiceTable.java
new file mode 100644
index 0000000..ed74a11
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccServiceTable.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 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.util.Log;
+
+/**
+ * Wrapper class for an ICC EF containing a bit field of enabled services.
+ */
+public abstract class IccServiceTable {
+ protected final byte[] mServiceTable;
+
+ protected IccServiceTable(byte[] table) {
+ mServiceTable = table;
+ }
+
+ // Get the class name to use for log strings
+ protected abstract String getTag();
+
+ // Get the array of enums to use for toString
+ protected abstract Object[] getValues();
+
+ /**
+ * Returns if the specified service is available.
+ * @param service the service number as a zero-based offset (the enum ordinal)
+ * @return true if the service is available; false otherwise
+ */
+ protected boolean isAvailable(int service) {
+ int offset = service / 8;
+ if (offset >= mServiceTable.length) {
+ // Note: Enums are zero-based, but the TS service numbering is one-based
+ Log.e(getTag(), "isAvailable for service " + (service + 1) + " fails, max service is " +
+ (mServiceTable.length * 8));
+ return false;
+ }
+ int bit = service % 8;
+ return (mServiceTable[offset] & (1 << bit)) != 0;
+ }
+
+ public String toString() {
+ Object[] values = getValues();
+ int numBytes = mServiceTable.length;
+ StringBuilder builder = new StringBuilder(getTag()).append('[')
+ .append(numBytes * 8).append("]={ ");
+
+ boolean addComma = false;
+ for (int i = 0; i < numBytes; i++) {
+ byte currentByte = mServiceTable[i];
+ for (int bit = 0; bit < 8; bit++) {
+ if ((currentByte & (1 << bit)) != 0) {
+ if (addComma) {
+ builder.append(", ");
+ } else {
+ addComma = true;
+ }
+ int ordinal = (i * 8) + bit;
+ if (ordinal < values.length) {
+ builder.append(values[ordinal]);
+ } else {
+ builder.append('#').append(ordinal + 1); // service number (one-based)
+ }
+ }
+ }
+ }
+ return builder.append(" }").toString();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
new file mode 100644
index 0000000..5fef6de
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -0,0 +1,217 @@
+/*
+ * 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;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.util.HexDump;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
+
+/**
+ * IccSmsInterfaceManager to provide an inter-process communication to
+ * access Sms in Icc.
+ */
+public abstract class IccSmsInterfaceManager extends ISms.Stub {
+ protected PhoneBase mPhone;
+ protected Context mContext;
+ protected SMSDispatcher mDispatcher;
+
+ protected IccSmsInterfaceManager(PhoneBase phone){
+ mPhone = phone;
+ mContext = phone.getContext();
+ }
+
+ protected void enforceReceiveAndSend(String message) {
+ mContext.enforceCallingPermission(
+ "android.permission.RECEIVE_SMS", message);
+ mContext.enforceCallingPermission(
+ "android.permission.SEND_SMS", message);
+ }
+
+ /**
+ * Send a data based SMS to a specific application port.
+ *
+ * @param destAddr the address to send the message to
+ * @param scAddr is the service center address or null to use
+ * the current default SMSC
+ * @param destPort 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 successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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").
+ */
+ public void sendData(String destAddr, String scAddr, int destPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ mPhone.getContext().enforceCallingPermission(
+ "android.permission.SEND_SMS",
+ "Sending SMS message");
+ if (Log.isLoggable("SMS", Log.VERBOSE)) {
+ log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort=" +
+ destPort + " data='"+ HexDump.toHexString(data) + "' sentIntent=" +
+ sentIntent + " deliveryIntent=" + deliveryIntent);
+ }
+ mDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
+ }
+
+ /**
+ * Send a text based SMS.
+ *
+ * @param destAddr the address to send the message to
+ * @param scAddr 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 successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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").
+ */
+ public void sendText(String destAddr, String scAddr,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ mPhone.getContext().enforceCallingPermission(
+ "android.permission.SEND_SMS",
+ "Sending SMS message");
+ if (Log.isLoggable("SMS", Log.VERBOSE)) {
+ log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
+ " text='"+ text + "' sentIntent=" +
+ sentIntent + " deliveryIntent=" + deliveryIntent);
+ }
+ mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent);
+ }
+
+ /**
+ * Send a multi-part text based SMS.
+ *
+ * @param destAddr the address to send the message to
+ * @param scAddr 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 applications,
+ * 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 sendMultipartText(String destAddr, String scAddr, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
+ mPhone.getContext().enforceCallingPermission(
+ "android.permission.SEND_SMS",
+ "Sending SMS message");
+ if (Log.isLoggable("SMS", Log.VERBOSE)) {
+ int i = 0;
+ for (String part : parts) {
+ log("sendMultipartText: destAddr=" + destAddr + ", srAddr=" + scAddr +
+ ", part[" + (i++) + "]=" + part);
+ }
+ }
+ mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
+ (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents);
+ }
+
+ /**
+ * 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
+ */
+ protected 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] == STATUS_ON_ICC_FREE) {
+ ret.add(null);
+ } else {
+ ret.add(new SmsRawData(messages.get(i)));
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * 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.
+ */
+ protected byte[] makeSmsRecordData(int status, byte[] pdu) {
+ byte[] data = new byte[IccConstants.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 < IccConstants.SMS_RECORD_LENGTH; j++) {
+ data[j] = -1;
+ }
+
+ return data;
+ }
+
+ protected abstract void log(String msg);
+
+}
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java
new file mode 100644
index 0000000..54de508
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import android.app.PendingIntent;
+import android.os.ServiceManager;
+
+import java.util.List;
+
+public class IccSmsInterfaceManagerProxy extends ISms.Stub {
+ private IccSmsInterfaceManager mIccSmsInterfaceManager;
+
+ public IccSmsInterfaceManagerProxy(IccSmsInterfaceManager
+ iccSmsInterfaceManager) {
+ this.mIccSmsInterfaceManager = iccSmsInterfaceManager;
+ if(ServiceManager.getService("isms") == null) {
+ ServiceManager.addService("isms", this);
+ }
+ }
+
+ public void setmIccSmsInterfaceManager(IccSmsInterfaceManager iccSmsInterfaceManager) {
+ this.mIccSmsInterfaceManager = iccSmsInterfaceManager;
+ }
+
+ public boolean
+ updateMessageOnIccEf(int index, int status, byte[] pdu) throws android.os.RemoteException {
+ return mIccSmsInterfaceManager.updateMessageOnIccEf(index, status, pdu);
+ }
+
+ public boolean copyMessageToIccEf(int status, byte[] pdu,
+ byte[] smsc) throws android.os.RemoteException {
+ return mIccSmsInterfaceManager.copyMessageToIccEf(status, pdu, smsc);
+ }
+
+ public List<SmsRawData> getAllMessagesFromIccEf() throws android.os.RemoteException {
+ return mIccSmsInterfaceManager.getAllMessagesFromIccEf();
+ }
+
+ public void sendData(String destAddr, String scAddr, int destPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ mIccSmsInterfaceManager.sendData(destAddr, scAddr, destPort, data,
+ sentIntent, deliveryIntent);
+ }
+
+ public void sendText(String destAddr, String scAddr,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ mIccSmsInterfaceManager.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent);
+ }
+
+ public void sendMultipartText(String destAddr, String scAddr,
+ List<String> parts, List<PendingIntent> sentIntents,
+ List<PendingIntent> deliveryIntents) throws android.os.RemoteException {
+ mIccSmsInterfaceManager.sendMultipartText(destAddr, scAddr,
+ parts, sentIntents, deliveryIntents);
+ }
+
+ public boolean enableCellBroadcast(int messageIdentifier) throws android.os.RemoteException {
+ return mIccSmsInterfaceManager.enableCellBroadcast(messageIdentifier);
+ }
+
+ public boolean disableCellBroadcast(int messageIdentifier) throws android.os.RemoteException {
+ return mIccSmsInterfaceManager.disableCellBroadcast(messageIdentifier);
+ }
+
+ public boolean enableCellBroadcastRange(int startMessageId, int endMessageId)
+ throws android.os.RemoteException {
+ return mIccSmsInterfaceManager.enableCellBroadcastRange(startMessageId, endMessageId);
+ }
+
+ public boolean disableCellBroadcastRange(int startMessageId, int endMessageId)
+ throws android.os.RemoteException {
+ return mIccSmsInterfaceManager.disableCellBroadcastRange(startMessageId, endMessageId);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccUtils.java b/src/java/com/android/internal/telephony/IccUtils.java
new file mode 100644
index 0000000..a966f76
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccUtils.java
@@ -0,0 +1,530 @@
+/*
+ * 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.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.util.Log;
+
+import com.android.internal.telephony.GsmAlphabet;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+/**
+ * Various methods, useful for dealing with SIM data.
+ */
+public class IccUtils {
+ static final String LOG_TAG="IccUtils";
+
+ /**
+ * 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;
+ // Some PLMNs have 'f' as high nibble, ignore it
+ if (v == 0xf) continue;
+ if (v > 9) break;
+ ret.append((char)('0' + v));
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * Decode cdma byte into String.
+ */
+ public static String
+ cdmaBcdToString(byte[] data, int offset, int length) {
+ StringBuilder ret = new StringBuilder(length);
+
+ int count = 0;
+ for (int i = offset; count < length; i++) {
+ int v;
+ v = data[i] & 0xf;
+ if (v > 9) v = 0;
+ ret.append((char)('0' + v));
+
+ if (++count == length) break;
+
+ v = (data[i] >> 4) & 0xf;
+ if (v > 9) v = 0;
+ ret.append((char)('0' + v));
+ ++count;
+ }
+ 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
+ gsmBcdByteToInt(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 CDMA style BCD byte like {@link gsmBcdByteToInt}, but
+ * opposite nibble format. The least significant BCD digit
+ * is in the least significant nibble and the most significant
+ * is in the most significant nibble.
+ */
+ public static int
+ cdmaBcdByteToInt(byte b) {
+ int ret = 0;
+
+ // treat out-of-range BCD values as 0
+ if ((b & 0xf0) <= 0x90) {
+ ret = ((b >> 4) & 0xf) * 10;
+ }
+
+ if ((b & 0x0f) <= 0x09) {
+ ret += (b & 0xf);
+ }
+
+ 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 == 0) {
+ return "";
+ }
+ 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();
+ }
+
+ Resources resource = Resources.getSystem();
+ String defaultCharset = "";
+ try {
+ defaultCharset =
+ resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset);
+ } catch (NotFoundException e) {
+ // Ignore Exception and defaultCharset is set to a empty string.
+ }
+ return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
+ }
+
+ 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 of hexadecimal characters.
+ *
+ * @param bytes an array of bytes
+ *
+ * @return hex string representation of bytes array
+ */
+ 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;
+ }
+
+ /**
+ * 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++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01);
+ };
+
+ if (pixelIndex != numOfPixels) {
+ Log.e(LOG_TAG, "parse end and size error");
+ }
+ return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
+ }
+
+ private static int bitToRGB(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
+ * @return The color bitmap
+ */
+ public static Bitmap parseToRGB(byte[] data, int length,
+ boolean transparency) {
+ int valueIndex = 0;
+ int width = data[valueIndex++] & 0xFF;
+ int height = data[valueIndex++] & 0xFF;
+ int bits = data[valueIndex++] & 0xFF;
+ int colorNumber = data[valueIndex++] & 0xFF;
+ int clutOffset = ((data[valueIndex++] & 0xFF) << 8)
+ | (data[valueIndex++] & 0xFF);
+
+ int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber);
+ if (true == transparency) {
+ colorIndexArray[colorNumber - 1] = Color.TRANSPARENT;
+ }
+
+ int[] resultArray = null;
+ if (0 == (8 % bits)) {
+ resultArray = mapTo2OrderBitColor(data, valueIndex,
+ (width * height), colorIndexArray, bits);
+ } else {
+ resultArray = mapToNon2OrderBitColor(data, valueIndex,
+ (width * height), colorIndexArray, bits);
+ }
+
+ return Bitmap.createBitmap(resultArray, width, height,
+ Bitmap.Config.RGB_565);
+ }
+
+ private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex,
+ int length, int[] colorArray, int bits) {
+ if (0 != (8 % bits)) {
+ Log.e(LOG_TAG, "not event number of color");
+ return mapToNon2OrderBitColor(data, valueIndex, length, colorArray,
+ bits);
+ }
+
+ int mask = 0x01;
+ switch (bits) {
+ case 1:
+ mask = 0x01;
+ break;
+ case 2:
+ mask = 0x03;
+ break;
+ case 4:
+ mask = 0x0F;
+ break;
+ case 8:
+ mask = 0xFF;
+ break;
+ }
+
+ int[] resultArray = new int[length];
+ int resultIndex = 0;
+ int run = 8 / bits;
+ while (resultIndex < length) {
+ byte tempByte = data[valueIndex++];
+ for (int runIndex = 0; runIndex < run; ++runIndex) {
+ int offset = run - runIndex - 1;
+ resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits))
+ & mask];
+ }
+ }
+ return resultArray;
+ }
+
+ private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex,
+ int length, int[] colorArray, int bits) {
+ if (0 == (8 % bits)) {
+ Log.e(LOG_TAG, "not odd number of color");
+ return mapTo2OrderBitColor(data, valueIndex, length, colorArray,
+ bits);
+ }
+
+ int[] resultArray = new int[length];
+ // TODO fix me:
+ return resultArray;
+ }
+
+ private static int[] getCLUT(byte[] rawData, int offset, int number) {
+ if (null == rawData) {
+ return null;
+ }
+
+ int[] result = new int[number];
+ int endIndex = offset + (number * 3); // 1 color use 3 bytes
+ int valueIndex = offset;
+ int colorIndex = 0;
+ int alpha = 0xff << 24;
+ do {
+ result[colorIndex++] = alpha
+ | ((rawData[valueIndex++] & 0xFF) << 16)
+ | ((rawData[valueIndex++] & 0xFF) << 8)
+ | ((rawData[valueIndex++] & 0xFF));
+ } while (valueIndex < endIndex);
+ return result;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IccVmFixedException.java b/src/java/com/android/internal/telephony/IccVmFixedException.java
new file mode 100644
index 0000000..a75496f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccVmFixedException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * {@hide}
+ */
+public final class IccVmFixedException extends IccException {
+ IccVmFixedException()
+ {
+
+ }
+
+ public IccVmFixedException(String s)
+ {
+ super(s);
+ }
+} \ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/IccVmNotSupportedException.java b/src/java/com/android/internal/telephony/IccVmNotSupportedException.java
new file mode 100644
index 0000000..3c9d126
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IccVmNotSupportedException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * {@hide}
+ */
+public final class IccVmNotSupportedException extends IccException {
+ IccVmNotSupportedException()
+ {
+
+ }
+
+ public IccVmNotSupportedException(String s)
+ {
+ super(s);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/IntRangeManager.java b/src/java/com/android/internal/telephony/IntRangeManager.java
new file mode 100644
index 0000000..cc7774d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/IntRangeManager.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2011 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.Iterator;
+
+/**
+ * Clients can enable reception of SMS-CB messages for specific ranges of
+ * message identifiers (channels). This class keeps track of the currently
+ * enabled message identifiers and calls abstract methods to update the
+ * radio when the range of enabled message identifiers changes.
+ *
+ * An update is a call to {@link #startUpdate} followed by zero or more
+ * calls to {@link #addRange} followed by a call to {@link #finishUpdate}.
+ * Calls to {@link #enableRange} and {@link #disableRange} will perform
+ * an incremental update operation if the enabled ranges have changed.
+ * A full update operation (i.e. after a radio reset) can be performed
+ * by a call to {@link #updateRanges}.
+ *
+ * Clients are identified by String (the name associated with the User ID
+ * of the caller) so that a call to remove a range can be mapped to the
+ * client that enabled that range (or else rejected).
+ */
+public abstract class IntRangeManager {
+
+ /**
+ * Initial capacity for IntRange clients array list. There will be
+ * few cell broadcast listeners on a typical device, so this can be small.
+ */
+ private static final int INITIAL_CLIENTS_ARRAY_SIZE = 4;
+
+ /**
+ * One or more clients forming the continuous range [startId, endId].
+ * <p>When a client is added, the IntRange may merge with one or more
+ * adjacent IntRanges to form a single combined IntRange.
+ * <p>When a client is removed, the IntRange may divide into several
+ * non-contiguous IntRanges.
+ */
+ private class IntRange {
+ int startId;
+ int endId;
+ // sorted by earliest start id
+ final ArrayList<ClientRange> clients;
+
+ /**
+ * Create a new IntRange with a single client.
+ * @param startId the first id included in the range
+ * @param endId the last id included in the range
+ * @param client the client requesting the enabled range
+ */
+ IntRange(int startId, int endId, String client) {
+ this.startId = startId;
+ this.endId = endId;
+ clients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE);
+ clients.add(new ClientRange(startId, endId, client));
+ }
+
+ /**
+ * Create a new IntRange for an existing ClientRange.
+ * @param clientRange the initial ClientRange to add
+ */
+ IntRange(ClientRange clientRange) {
+ startId = clientRange.startId;
+ endId = clientRange.endId;
+ clients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE);
+ clients.add(clientRange);
+ }
+
+ /**
+ * Create a new IntRange from an existing IntRange. This is used for
+ * removing a ClientRange, because new IntRanges may need to be created
+ * for any gaps that open up after the ClientRange is removed. A copy
+ * is made of the elements of the original IntRange preceding the element
+ * that is being removed. The following elements will be added to this
+ * IntRange or to a new IntRange when a gap is found.
+ * @param intRange the original IntRange to copy elements from
+ * @param numElements the number of elements to copy from the original
+ */
+ IntRange(IntRange intRange, int numElements) {
+ this.startId = intRange.startId;
+ this.endId = intRange.endId;
+ this.clients = new ArrayList<ClientRange>(intRange.clients.size());
+ for (int i=0; i < numElements; i++) {
+ this.clients.add(intRange.clients.get(i));
+ }
+ }
+
+ /**
+ * Insert new ClientRange in order by start id.
+ * <p>If the new ClientRange is known to be sorted before or after the
+ * existing ClientRanges, or at a particular index, it can be added
+ * to the clients array list directly, instead of via this method.
+ * <p>Note that this can be changed from linear to binary search if the
+ * number of clients grows large enough that it would make a difference.
+ * @param range the new ClientRange to insert
+ */
+ void insert(ClientRange range) {
+ int len = clients.size();
+ for (int i=0; i < len; i++) {
+ ClientRange nextRange = clients.get(i);
+ if (range.startId <= nextRange.startId) {
+ // ignore duplicate ranges from the same client
+ if (!range.equals(nextRange)) {
+ clients.add(i, range);
+ }
+ return;
+ }
+ }
+ clients.add(range); // append to end of list
+ }
+ }
+
+ /**
+ * The message id range for a single client.
+ */
+ private class ClientRange {
+ final int startId;
+ final int endId;
+ final String client;
+
+ ClientRange(int startId, int endId, String client) {
+ this.startId = startId;
+ this.endId = endId;
+ this.client = client;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o != null && o instanceof ClientRange) {
+ ClientRange other = (ClientRange) o;
+ return startId == other.startId &&
+ endId == other.endId &&
+ client.equals(other.client);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return (startId * 31 + endId) * 31 + client.hashCode();
+ }
+ }
+
+ /**
+ * List of integer ranges, one per client, sorted by start id.
+ */
+ private ArrayList<IntRange> mRanges = new ArrayList<IntRange>();
+
+ protected IntRangeManager() {}
+
+ /**
+ * Enable a range for the specified client and update ranges
+ * if necessary. If {@link #finishUpdate} returns failure,
+ * false is returned and the range is not added.
+ *
+ * @param startId the first id included in the range
+ * @param endId the last id included in the range
+ * @param client the client requesting the enabled range
+ * @return true if successful, false otherwise
+ */
+ public synchronized boolean enableRange(int startId, int endId, String client) {
+ int len = mRanges.size();
+
+ // empty range list: add the initial IntRange
+ if (len == 0) {
+ if (tryAddSingleRange(startId, endId, true)) {
+ mRanges.add(new IntRange(startId, endId, client));
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ }
+
+ for (int startIndex = 0; startIndex < len; startIndex++) {
+ IntRange range = mRanges.get(startIndex);
+ if (startId < range.startId) {
+ // test if new range completely precedes this range
+ // note that [1, 4] and [5, 6] coalesce to [1, 6]
+ if ((endId + 1) < range.startId) {
+ // insert new int range before previous first range
+ if (tryAddSingleRange(startId, endId, true)) {
+ mRanges.add(startIndex, new IntRange(startId, endId, client));
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ } else if (endId <= range.endId) {
+ // extend the start of this range
+ if (tryAddSingleRange(startId, range.startId - 1, true)) {
+ range.startId = startId;
+ range.clients.add(0, new ClientRange(startId, endId, client));
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ } else {
+ // find last range that can coalesce into the new combined range
+ for (int endIndex = startIndex+1; endIndex < len; endIndex++) {
+ IntRange endRange = mRanges.get(endIndex);
+ if ((endId + 1) < endRange.startId) {
+ // try to add entire new range
+ if (tryAddSingleRange(startId, endId, true)) {
+ range.startId = startId;
+ range.endId = endId;
+ // insert new ClientRange before existing ranges
+ range.clients.add(0, new ClientRange(startId, endId, client));
+ // coalesce range with following ranges up to endIndex-1
+ // remove each range after adding its elements, so the index
+ // of the next range to join is always startIndex+1.
+ // i is the index if no elements were removed: we only care
+ // about the number of loop iterations, not the value of i.
+ int joinIndex = startIndex + 1;
+ for (int i = joinIndex; i < endIndex; i++) {
+ IntRange joinRange = mRanges.get(joinIndex);
+ range.clients.addAll(joinRange.clients);
+ mRanges.remove(joinRange);
+ }
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ } else if (endId <= endRange.endId) {
+ // add range from start id to start of last overlapping range,
+ // values from endRange.startId to endId are already enabled
+ if (tryAddSingleRange(startId, endRange.startId - 1, true)) {
+ range.startId = startId;
+ range.endId = endRange.endId;
+ // insert new ClientRange before existing ranges
+ range.clients.add(0, new ClientRange(startId, endId, client));
+ // coalesce range with following ranges up to endIndex
+ // remove each range after adding its elements, so the index
+ // of the next range to join is always startIndex+1.
+ // i is the index if no elements were removed: we only care
+ // about the number of loop iterations, not the value of i.
+ int joinIndex = startIndex + 1;
+ for (int i = joinIndex; i <= endIndex; i++) {
+ IntRange joinRange = mRanges.get(joinIndex);
+ range.clients.addAll(joinRange.clients);
+ mRanges.remove(joinRange);
+ }
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ }
+ }
+
+ // endId extends past all existing IntRanges: combine them all together
+ if (tryAddSingleRange(startId, endId, true)) {
+ range.startId = startId;
+ range.endId = endId;
+ // insert new ClientRange before existing ranges
+ range.clients.add(0, new ClientRange(startId, endId, client));
+ // coalesce range with following ranges up to len-1
+ // remove each range after adding its elements, so the index
+ // of the next range to join is always startIndex+1.
+ // i is the index if no elements were removed: we only care
+ // about the number of loop iterations, not the value of i.
+ int joinIndex = startIndex + 1;
+ for (int i = joinIndex; i < len; i++) {
+ IntRange joinRange = mRanges.get(joinIndex);
+ range.clients.addAll(joinRange.clients);
+ mRanges.remove(joinRange);
+ }
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ }
+ } else if ((startId + 1) <= range.endId) {
+ if (endId <= range.endId) {
+ // completely contained in existing range; no radio changes
+ range.insert(new ClientRange(startId, endId, client));
+ return true;
+ } else {
+ // find last range that can coalesce into the new combined range
+ int endIndex = startIndex;
+ for (int testIndex = startIndex+1; testIndex < len; testIndex++) {
+ IntRange testRange = mRanges.get(testIndex);
+ if ((endId + 1) < testRange.startId) {
+ break;
+ } else {
+ endIndex = testIndex;
+ }
+ }
+ // no adjacent IntRanges to combine
+ if (endIndex == startIndex) {
+ // add range from range.endId+1 to endId,
+ // values from startId to range.endId are already enabled
+ if (tryAddSingleRange(range.endId + 1, endId, true)) {
+ range.endId = endId;
+ range.insert(new ClientRange(startId, endId, client));
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ }
+ // get last range to coalesce into start range
+ IntRange endRange = mRanges.get(endIndex);
+ // Values from startId to range.endId have already been enabled.
+ // if endId > endRange.endId, then enable range from range.endId+1 to endId,
+ // else enable range from range.endId+1 to endRange.startId-1, because
+ // values from endRange.startId to endId have already been added.
+ int newRangeEndId = (endId <= endRange.endId) ? endRange.startId - 1 : endId;
+ if (tryAddSingleRange(range.endId + 1, newRangeEndId, true)) {
+ range.endId = endId;
+ // insert new ClientRange in place
+ range.insert(new ClientRange(startId, endId, client));
+ // coalesce range with following ranges up to endIndex-1
+ // remove each range after adding its elements, so the index
+ // of the next range to join is always startIndex+1 (joinIndex).
+ // i is the index if no elements had been removed: we only care
+ // about the number of loop iterations, not the value of i.
+ int joinIndex = startIndex + 1;
+ for (int i = joinIndex; i < endIndex; i++) {
+ IntRange joinRange = mRanges.get(joinIndex);
+ range.clients.addAll(joinRange.clients);
+ mRanges.remove(joinRange);
+ }
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ }
+ }
+ }
+
+ // append new range after existing IntRanges
+ if (tryAddSingleRange(startId, endId, true)) {
+ mRanges.add(new IntRange(startId, endId, client));
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ }
+
+ /**
+ * Disable a range for the specified client and update ranges
+ * if necessary. If {@link #finishUpdate} returns failure,
+ * false is returned and the range is not removed.
+ *
+ * @param startId the first id included in the range
+ * @param endId the last id included in the range
+ * @param client the client requesting to disable the range
+ * @return true if successful, false otherwise
+ */
+ public synchronized boolean disableRange(int startId, int endId, String client) {
+ int len = mRanges.size();
+
+ for (int i=0; i < len; i++) {
+ IntRange range = mRanges.get(i);
+ if (startId < range.startId) {
+ return false; // not found
+ } else if (endId <= range.endId) {
+ // found the IntRange that encloses the client range, if any
+ // search for it in the clients list
+ ArrayList<ClientRange> clients = range.clients;
+
+ // handle common case of IntRange containing one ClientRange
+ int crLength = clients.size();
+ if (crLength == 1) {
+ ClientRange cr = clients.get(0);
+ if (cr.startId == startId && cr.endId == endId && cr.client.equals(client)) {
+ // disable range in radio then remove the entire IntRange
+ if (tryAddSingleRange(startId, endId, false)) {
+ mRanges.remove(i);
+ return true;
+ } else {
+ return false; // failed to update radio
+ }
+ } else {
+ return false; // not found
+ }
+ }
+
+ // several ClientRanges: remove one, potentially splitting into many IntRanges.
+ // Save the original start and end id for the original IntRange
+ // in case the radio update fails and we have to revert it. If the
+ // update succeeds, we remove the client range and insert the new IntRanges.
+ int largestEndId = Integer.MIN_VALUE; // largest end identifier found
+ boolean updateStarted = false;
+
+ for (int crIndex=0; crIndex < crLength; crIndex++) {
+ ClientRange cr = clients.get(crIndex);
+ if (cr.startId == startId && cr.endId == endId && cr.client.equals(client)) {
+ // found the ClientRange to remove, check if it's the last in the list
+ if (crIndex == crLength - 1) {
+ if (range.endId == largestEndId) {
+ // no channels to remove from radio; return success
+ clients.remove(crIndex);
+ return true;
+ } else {
+ // disable the channels at the end and lower the end id
+ if (tryAddSingleRange(largestEndId + 1, range.endId, false)) {
+ clients.remove(crIndex);
+ range.endId = largestEndId;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ // copy the IntRange so that we can remove elements and modify the
+ // start and end id's in the copy, leaving the original unmodified
+ // until after the radio update succeeds
+ IntRange rangeCopy = new IntRange(range, crIndex);
+
+ if (crIndex == 0) {
+ // removing the first ClientRange, so we may need to increase
+ // the start id of the IntRange.
+ // We know there are at least two ClientRanges in the list,
+ // so clients.get(1) should always succeed.
+ int nextStartId = clients.get(1).startId;
+ if (nextStartId != range.startId) {
+ startUpdate();
+ updateStarted = true;
+ addRange(range.startId, nextStartId - 1, false);
+ rangeCopy.startId = nextStartId;
+ }
+ // init largestEndId
+ largestEndId = clients.get(1).endId;
+ }
+
+ // go through remaining ClientRanges, creating new IntRanges when
+ // there is a gap in the sequence. After radio update succeeds,
+ // remove the original IntRange and append newRanges to mRanges.
+ // Otherwise, leave the original IntRange in mRanges and return false.
+ ArrayList<IntRange> newRanges = new ArrayList<IntRange>();
+
+ IntRange currentRange = rangeCopy;
+ for (int nextIndex = crIndex + 1; nextIndex < crLength; nextIndex++) {
+ ClientRange nextCr = clients.get(nextIndex);
+ if (nextCr.startId > largestEndId + 1) {
+ if (!updateStarted) {
+ startUpdate();
+ updateStarted = true;
+ }
+ addRange(largestEndId + 1, nextCr.startId - 1, false);
+ currentRange.endId = largestEndId;
+ newRanges.add(currentRange);
+ currentRange = new IntRange(nextCr);
+ } else {
+ currentRange.clients.add(nextCr);
+ }
+ if (nextCr.endId > largestEndId) {
+ largestEndId = nextCr.endId;
+ }
+ }
+
+ // remove any channels between largestEndId and endId
+ if (largestEndId < endId) {
+ if (!updateStarted) {
+ startUpdate();
+ updateStarted = true;
+ }
+ addRange(largestEndId + 1, endId, false);
+ currentRange.endId = largestEndId;
+ }
+ newRanges.add(currentRange);
+
+ if (updateStarted && !finishUpdate()) {
+ return false; // failed to update radio
+ }
+
+ // replace the original IntRange with newRanges
+ mRanges.remove(i);
+ mRanges.addAll(i, newRanges);
+ return true;
+ } else {
+ // not the ClientRange to remove; save highest end ID seen so far
+ if (cr.endId > largestEndId) {
+ largestEndId = cr.endId;
+ }
+ }
+ }
+ }
+ }
+
+ return false; // not found
+ }
+
+ /**
+ * Perform a complete update operation (enable all ranges). Useful
+ * after a radio reset. Calls {@link #startUpdate}, followed by zero or
+ * more calls to {@link #addRange}, followed by {@link #finishUpdate}.
+ * @return true if successful, false otherwise
+ */
+ public boolean updateRanges() {
+ startUpdate();
+ Iterator<IntRange> iterator = mRanges.iterator();
+ if (iterator.hasNext()) {
+ IntRange range = iterator.next();
+ int start = range.startId;
+ int end = range.endId;
+ // accumulate ranges of [startId, endId]
+ while (iterator.hasNext()) {
+ IntRange nextNode = iterator.next();
+ // [startIdA, endIdA], [endIdA + 1, endIdB] -> [startIdA, endIdB]
+ if (nextNode.startId <= (end + 1)) {
+ if (nextNode.endId > end) {
+ end = nextNode.endId;
+ }
+ } else {
+ addRange(start, end, true);
+ start = nextNode.startId;
+ end = nextNode.endId;
+ }
+ }
+ // add final range
+ addRange(start, end, true);
+ }
+ return finishUpdate();
+ }
+
+ /**
+ * Enable or disable a single range of message identifiers.
+ * @param startId the first id included in the range
+ * @param endId the last id included in the range
+ * @param selected true to enable range, false to disable range
+ * @return true if successful, false otherwise
+ */
+ private boolean tryAddSingleRange(int startId, int endId, boolean selected) {
+ startUpdate();
+ addRange(startId, endId, selected);
+ return finishUpdate();
+ }
+
+ /**
+ * Returns whether the list of ranges is completely empty.
+ * @return true if there are no enabled ranges
+ */
+ public boolean isEmpty() {
+ return mRanges.isEmpty();
+ }
+
+ /**
+ * Called when the list of enabled ranges has changed. This will be
+ * followed by zero or more calls to {@link #addRange} followed by
+ * a call to {@link #finishUpdate}.
+ */
+ protected abstract void startUpdate();
+
+ /**
+ * Called after {@link #startUpdate} to indicate a range of enabled
+ * or disabled values.
+ *
+ * @param startId the first id included in the range
+ * @param endId the last id included in the range
+ * @param selected true to enable range, false to disable range
+ */
+ protected abstract void addRange(int startId, int endId, boolean selected);
+
+ /**
+ * Called to indicate the end of a range update started by the
+ * previous call to {@link #startUpdate}.
+ * @return true if successful, false otherwise
+ */
+ protected abstract boolean finishUpdate();
+}
diff --git a/src/java/com/android/internal/telephony/MccTable.java b/src/java/com/android/internal/telephony/MccTable.java
new file mode 100644
index 0000000..cb33521
--- /dev/null
+++ b/src/java/com/android/internal/telephony/MccTable.java
@@ -0,0 +1,572 @@
+/*
+ * 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.app.ActivityManagerNative;
+import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.net.wifi.WifiManager;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Locale;
+import libcore.icu.TimeZones;
+
+/**
+ * Mobile Country Code
+ *
+ * {@hide}
+ */
+public final class MccTable
+{
+ static final String LOG_TAG = "MccTable";
+
+ static ArrayList<MccEntry> table;
+
+ static class MccEntry implements Comparable<MccEntry>
+ {
+ int mcc;
+ String iso;
+ int smallestDigitsMnc;
+ String language;
+
+ MccEntry(int mnc, String iso, int smallestDigitsMCC) {
+ this(mnc, iso, smallestDigitsMCC, null);
+ }
+
+ MccEntry(int mnc, String iso, int smallestDigitsMCC, String language) {
+ this.mcc = mnc;
+ this.iso = iso;
+ this.smallestDigitsMnc = smallestDigitsMCC;
+ this.language = language;
+ }
+
+
+ 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);
+ }
+ }
+
+ /**
+ * Returns a default time zone ID for the given MCC.
+ * @param mcc Mobile Country Code
+ * @return default TimeZone ID, or null if not specified
+ */
+ public static String defaultTimeZoneForMcc(int mcc) {
+ MccEntry entry;
+
+ entry = entryForMcc(mcc);
+ if (entry == null || entry.iso == null) {
+ return null;
+ } else {
+ Locale locale;
+ if (entry.language == null) {
+ locale = new Locale(entry.iso);
+ } else {
+ locale = new Locale(entry.language, entry.iso);
+ }
+ String[] tz = TimeZones.forLocale(locale);
+ if (tz.length == 0) return null;
+ return tz[0];
+ }
+ }
+
+ /**
+ * 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
+ * an ISO 2-3 character language code if available.
+ * Returns null if unavailable.
+ */
+ public static String defaultLanguageForMcc(int mcc) {
+ MccEntry entry;
+
+ entry = entryForMcc(mcc);
+
+ if (entry == null) {
+ return null;
+ } else {
+ return entry.language;
+ }
+ }
+
+ /**
+ * Given a GSM Mobile Country Code, returns
+ * the smallest number of digits that M if available.
+ * Returns 2 if unavailable.
+ */
+ public static int
+ smallestDigitsMccForMnc(int mcc)
+ {
+ MccEntry entry;
+
+ entry = entryForMcc(mcc);
+
+ if (entry == null) {
+ return 2;
+ } else {
+ return entry.smallestDigitsMnc;
+ }
+ }
+
+ /**
+ * Updates MCC and MNC device configuration information for application retrieving
+ * correct version of resources. If either MCC or MNC is 0, they will be ignored (not set).
+ * @param context Context to act on.
+ * @param mccmnc truncated imsi with just the MCC and MNC - MNC assumed to be from 4th to end
+ */
+ public static void updateMccMncConfiguration(Context context, String mccmnc) {
+ if (!TextUtils.isEmpty(mccmnc)) {
+ int mcc, mnc;
+
+ try {
+ mcc = Integer.parseInt(mccmnc.substring(0,3));
+ mnc = Integer.parseInt(mccmnc.substring(3));
+ } catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "Error parsing IMSI");
+ return;
+ }
+
+ Log.d(LOG_TAG, "updateMccMncConfiguration: mcc=" + mcc + ", mnc=" + mnc);
+
+ if (mcc != 0) {
+ setTimezoneFromMccIfNeeded(context, mcc);
+ setLocaleFromMccIfNeeded(context, mcc);
+ setWifiCountryCodeFromMcc(context, mcc);
+ }
+ try {
+ Configuration config = ActivityManagerNative.getDefault().getConfiguration();
+ if (mcc != 0) {
+ config.mcc = mcc;
+ }
+ if (mnc != 0) {
+ config.mnc = mnc;
+ }
+ ActivityManagerNative.getDefault().updateConfiguration(config);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can't update configuration", e);
+ }
+ }
+ }
+
+ /**
+ * Utility code to set the system locale if it's not set already
+ * @param context Context to act on.
+ * @param language Two character language code desired
+ * @param country Two character country code desired
+ *
+ * {@hide}
+ */
+ public static void setSystemLocale(Context context, String language, String country) {
+ String l = SystemProperties.get("persist.sys.language");
+ String c = SystemProperties.get("persist.sys.country");
+
+ if (null == language) {
+ return; // no match possible
+ }
+ language = language.toLowerCase();
+ if (null == country) {
+ country = "";
+ }
+ country = country.toUpperCase();
+
+ if((null == l || 0 == l.length()) && (null == c || 0 == c.length())) {
+ try {
+ // try to find a good match
+ String[] locales = context.getAssets().getLocales();
+ final int N = locales.length;
+ String bestMatch = null;
+ for(int i = 0; i < N; i++) {
+ // only match full (lang + country) locales
+ if (locales[i]!=null && locales[i].length() >= 5 &&
+ locales[i].substring(0,2).equals(language)) {
+ if (locales[i].substring(3,5).equals(country)) {
+ bestMatch = locales[i];
+ break;
+ } else if (null == bestMatch) {
+ bestMatch = locales[i];
+ }
+ }
+ }
+ if (null != bestMatch) {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ Configuration config = am.getConfiguration();
+ config.locale = new Locale(bestMatch.substring(0,2),
+ bestMatch.substring(3,5));
+ config.userSetLocale = true;
+ am.updateConfiguration(config);
+ }
+ } catch (Exception e) {
+ // Intentionally left blank
+ }
+ }
+ }
+
+ /**
+ * If the timezone is not already set, set it based on the MCC of the SIM.
+ * @param context Context to act on.
+ * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
+ */
+ private static void setTimezoneFromMccIfNeeded(Context context, int mcc) {
+ String timezone = SystemProperties.get(ServiceStateTracker.TIMEZONE_PROPERTY);
+ if (timezone == null || timezone.length() == 0) {
+ String zoneId = defaultTimeZoneForMcc(mcc);
+ if (zoneId != null && zoneId.length() > 0) {
+ // Set time zone based on MCC
+ AlarmManager alarm =
+ (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ alarm.setTimeZone(zoneId);
+ Log.d(LOG_TAG, "timezone set to "+zoneId);
+ }
+ }
+ }
+
+ /**
+ * If the locale is not already set, set it based on the MCC of the SIM.
+ * @param context Context to act on.
+ * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
+ */
+ private static void setLocaleFromMccIfNeeded(Context context, int mcc) {
+ if (TelephonyManager.getLteOnCdmaModeStatic() == PhoneConstants.LTE_ON_CDMA_TRUE) {
+ // Avoid system locale is set from MCC table if CDMALTEPhone is used.
+ // The locale will be picked up based on EFpl/EFli once CSIM records are loaded.
+ return;
+ }
+ String language = MccTable.defaultLanguageForMcc(mcc);
+ String country = MccTable.countryCodeForMcc(mcc);
+
+ Log.d(LOG_TAG, "locale set to "+language+"_"+country);
+ setSystemLocale(context, language, country);
+ }
+
+ /**
+ * If the number of allowed wifi channels has not been set, set it based on
+ * the MCC of the SIM.
+ * @param context Context to act on.
+ * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
+ */
+ private static void setWifiCountryCodeFromMcc(Context context, int mcc) {
+ String country = MccTable.countryCodeForMcc(mcc);
+ if (!country.isEmpty()) {
+ Log.d(LOG_TAG, "WIFI_COUNTRY_CODE set to " + country);
+ WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ //persist
+ wM.setCountryCode(country, true);
+ }
+ }
+
+ 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.
+ *
+ */
+
+ table.add(new MccEntry(202,"gr",2)); //Greece
+ table.add(new MccEntry(204,"nl",2,"nl")); //Netherlands (Kingdom of the)
+ table.add(new MccEntry(206,"be",2)); //Belgium
+ table.add(new MccEntry(208,"fr",2,"fr")); //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,"es")); //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,"it")); //Italy
+ table.add(new MccEntry(225,"va",2,"it")); //Vatican City State
+ table.add(new MccEntry(226,"ro",2)); //Romania
+ table.add(new MccEntry(228,"ch",2,"de")); //Switzerland (Confederation of)
+ table.add(new MccEntry(230,"cz",2,"cs")); //Czech Republic
+ table.add(new MccEntry(231,"sk",2)); //Slovak Republic
+ table.add(new MccEntry(232,"at",2,"de")); //Austria
+ table.add(new MccEntry(234,"gb",2,"en")); //United Kingdom of Great Britain and Northern Ireland
+ table.add(new MccEntry(235,"gb",2,"en")); //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,"de")); //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,"en")); //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(289,"ge",2)); //Abkhazia (Georgia)
+ 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,"si",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(297,"me",2)); //Montenegro (Republic of)
+ table.add(new MccEntry(302,"ca",3,"")); //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,"en")); //United States of America
+ table.add(new MccEntry(311,"us",3,"en")); //United States of America
+ table.add(new MccEntry(312,"us",3,"en")); //United States of America
+ table.add(new MccEntry(313,"us",3,"en")); //United States of America
+ table.add(new MccEntry(314,"us",3,"en")); //United States of America
+ table.add(new MccEntry(315,"us",3,"en")); //United States of America
+ table.add(new MccEntry(316,"us",3,"en")); //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,"ai",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(423,"ps",2)); //Palestine
+ 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,"ja")); //Japan
+ table.add(new MccEntry(441,"jp",2,"ja")); //Japan
+ table.add(new MccEntry(450,"kr",2,"ko")); //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,"zh")); //China (People's Republic of)
+ table.add(new MccEntry(461,"cn",2,"zh")); //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,"en")); //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,"en")); //Singapore (Republic of)
+ table.add(new MccEntry(528,"bn",2)); //Brunei Darussalam
+ table.add(new MccEntry(530,"nz",2, "en")); //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,"en")); //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/src/java/com/android/internal/telephony/MmiCode.java b/src/java/com/android/internal/telephony/MmiCode.java
new file mode 100644
index 0000000..c71ff77
--- /dev/null
+++ b/src/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/src/java/com/android/internal/telephony/OperatorInfo.java b/src/java/com/android/internal/telephony/OperatorInfo.java
new file mode 100644
index 0000000..1999cb3
--- /dev/null
+++ b/src/java/com/android/internal/telephony/OperatorInfo.java
@@ -0,0 +1,149 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+
+/**
+ * {@hide}
+ */
+public class OperatorInfo implements Parcelable {
+ public enum State {
+ UNKNOWN,
+ AVAILABLE,
+ CURRENT,
+ FORBIDDEN;
+ }
+
+ private String operatorAlphaLong;
+ private String operatorAlphaShort;
+ private String operatorNumeric;
+
+ private State state = State.UNKNOWN;
+
+
+ public String
+ getOperatorAlphaLong() {
+ return operatorAlphaLong;
+ }
+
+ public String
+ getOperatorAlphaShort() {
+ return operatorAlphaShort;
+ }
+
+ public String
+ getOperatorNumeric() {
+ return operatorNumeric;
+ }
+
+ public State
+ getState() {
+ return state;
+ }
+
+ OperatorInfo(String operatorAlphaLong,
+ String operatorAlphaShort,
+ String operatorNumeric,
+ State state) {
+
+ this.operatorAlphaLong = operatorAlphaLong;
+ this.operatorAlphaShort = operatorAlphaShort;
+ this.operatorNumeric = operatorNumeric;
+
+ this.state = state;
+ }
+
+
+ public OperatorInfo(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 "OperatorInfo " + operatorAlphaLong
+ + "/" + operatorAlphaShort
+ + "/" + operatorNumeric
+ + "/" + state;
+ }
+
+ /**
+ * Parcelable interface implemented below.
+ * This is a simple effort to make OperatorInfo 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 OperatorInfo 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 OperatorInfo object, or an array thereof.
+ */
+ public static final Creator<OperatorInfo> CREATOR =
+ new Creator<OperatorInfo>() {
+ public OperatorInfo createFromParcel(Parcel in) {
+ OperatorInfo opInfo = new OperatorInfo(
+ in.readString(), /*operatorAlphaLong*/
+ in.readString(), /*operatorAlphaShort*/
+ in.readString(), /*operatorNumeric*/
+ (State) in.readSerializable()); /*state*/
+ return opInfo;
+ }
+
+ public OperatorInfo[] newArray(int size) {
+ return new OperatorInfo[size];
+ }
+ };
+}
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
new file mode 100644
index 0000000..34aa96c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -0,0 +1,1695 @@
+/*
+ * 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.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.telephony.CellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+
+import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.gsm.UsimServiceTable;
+import com.android.internal.telephony.ims.IsimRecords;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+
+import com.android.internal.telephony.PhoneConstants.*; // ????
+
+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;
+
+ public 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>
+ * <li>DORMANT = The data connection is still active,
+ but physical link is down</li>
+ * </ul>
+ */
+ NONE, DATAIN, DATAOUT, DATAINANDOUT, DORMANT;
+ };
+
+ enum SuppService {
+ UNKNOWN, SWITCH, SEPARATE, TRANSFER, CONFERENCE, REJECT, HANGUP;
+ };
+
+ // "Features" accessible through the connectivity manager
+ static final String FEATURE_ENABLE_MMS = "enableMMS";
+ static final String FEATURE_ENABLE_SUPL = "enableSUPL";
+ static final String FEATURE_ENABLE_DUN = "enableDUN";
+ static final String FEATURE_ENABLE_HIPRI = "enableHIPRI";
+ static final String FEATURE_ENABLE_DUN_ALWAYS = "enableDUNAlways";
+ static final String FEATURE_ENABLE_FOTA = "enableFOTA";
+ static final String FEATURE_ENABLE_IMS = "enableIMS";
+ static final String FEATURE_ENABLE_CBS = "enableCBS";
+
+ /**
+ * 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_DATA_ATTACHED = "dataAttached";
+ static final String REASON_DATA_DETACHED = "dataDetached";
+ static final String REASON_CDMA_DATA_ATTACHED = "cdmaDataAttached";
+ static final String REASON_CDMA_DATA_DETACHED = "cdmaDataDetached";
+ static final String REASON_APN_CHANGED = "apnChanged";
+ static final String REASON_APN_SWITCHED = "apnSwitched";
+ static final String REASON_APN_FAILED = "apnFailed";
+ 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";
+ static final String REASON_PS_RESTRICT_ENABLED = "psRestrictEnabled";
+ static final String REASON_PS_RESTRICT_DISABLED = "psRestrictDisabled";
+ static final String REASON_SIM_LOADED = "simLoaded";
+ static final String REASON_NW_TYPE_CHANGED = "nwTypeChanged";
+ static final String REASON_DATA_DEPENDENCY_MET = "dependencyMet";
+ static final String REASON_DATA_DEPENDENCY_UNMET = "dependencyUnmet";
+
+ // Used for band mode selection 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
+ // Note NT_* substitute RILConstants.NETWORK_MODE_* above the Phone
+ int NT_MODE_WCDMA_PREF = RILConstants.NETWORK_MODE_WCDMA_PREF;
+ int NT_MODE_GSM_ONLY = RILConstants.NETWORK_MODE_GSM_ONLY;
+ int NT_MODE_WCDMA_ONLY = RILConstants.NETWORK_MODE_WCDMA_ONLY;
+ int NT_MODE_GSM_UMTS = RILConstants.NETWORK_MODE_GSM_UMTS;
+
+ int NT_MODE_CDMA = RILConstants.NETWORK_MODE_CDMA;
+
+ int NT_MODE_CDMA_NO_EVDO = RILConstants.NETWORK_MODE_CDMA_NO_EVDO;
+ int NT_MODE_EVDO_NO_CDMA = RILConstants.NETWORK_MODE_EVDO_NO_CDMA;
+ int NT_MODE_GLOBAL = RILConstants.NETWORK_MODE_GLOBAL;
+
+ int NT_MODE_LTE_ONLY = RILConstants.NETWORK_MODE_LTE_ONLY;
+ int PREFERRED_NT_MODE = RILConstants.PREFERRED_NETWORK_MODE;
+
+
+ // Used for CDMA roaming mode
+ static final int CDMA_RM_HOME = 0; // Home Networks only, as defined in PRL
+ static final int CDMA_RM_AFFILIATED = 1; // Roaming an Affiliated networks, as defined in PRL
+ static final int CDMA_RM_ANY = 2; // Roaming on Any Network, as defined in PRL
+
+ // Used for CDMA subscription mode
+ static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // RUIM/SIM (default)
+ static final int CDMA_SUBSCRIPTION_NV = 1; // NV -> non-volatile memory
+
+ static final int PREFERRED_CDMA_SUBSCRIPTION = CDMA_SUBSCRIPTION_NV;
+
+ static final int TTY_MODE_OFF = 0;
+ static final int TTY_MODE_FULL = 1;
+ static final int TTY_MODE_HCO = 2;
+ static final int TTY_MODE_VCO = 3;
+
+ /**
+ * CDMA OTA PROVISION STATUS, the same as RIL_CDMA_OTA_Status in ril.h
+ */
+
+ public static final int CDMA_OTA_PROVISION_STATUS_SPL_UNLOCKED = 0;
+ public static final int CDMA_OTA_PROVISION_STATUS_SPC_RETRIES_EXCEEDED = 1;
+ public static final int CDMA_OTA_PROVISION_STATUS_A_KEY_EXCHANGED = 2;
+ public static final int CDMA_OTA_PROVISION_STATUS_SSD_UPDATED = 3;
+ public static final int CDMA_OTA_PROVISION_STATUS_NAM_DOWNLOADED = 4;
+ public static final int CDMA_OTA_PROVISION_STATUS_MDN_DOWNLOADED = 5;
+ public static final int CDMA_OTA_PROVISION_STATUS_IMSI_DOWNLOADED = 6;
+ public static final int CDMA_OTA_PROVISION_STATUS_PRL_DOWNLOADED = 7;
+ public static final int CDMA_OTA_PROVISION_STATUS_COMMITTED = 8;
+ public static final int CDMA_OTA_PROVISION_STATUS_OTAPA_STARTED = 9;
+ public static final int CDMA_OTA_PROVISION_STATUS_OTAPA_STOPPED = 10;
+ public static final int CDMA_OTA_PROVISION_STATUS_OTAPA_ABORTED = 11;
+
+
+ /**
+ * Get the current ServiceState. Use
+ * <code>registerForServiceStateChanged</code> to be informed of
+ * updates.
+ */
+ ServiceState getServiceState();
+
+ /**
+ * Get the current CellLocation.
+ */
+ CellLocation getCellLocation();
+
+ /**
+ * Get the current for the default apn DataState. No change notification
+ * exists at this interface -- use
+ * {@link android.telephony.PhoneStateListener} instead.
+ */
+ DataState getDataConnectionState();
+
+ /**
+ * Get the current DataState. No change notification exists at this
+ * interface -- use
+ * {@link android.telephony.PhoneStateListener} instead.
+ * @param apnType specify for which apn to get connection state info.
+ */
+ DataState getDataConnectionState(String apnType);
+
+ /**
+ * Get the current DataActivityState. No change notification exists at this
+ * interface -- use
+ * {@link android.telephony.TelephonyManager} instead.
+ */
+ DataActivityState getDataActivityState();
+
+ /**
+ * Gets the context for the phone, as set at initialization time.
+ */
+ Context getContext();
+
+ /**
+ * Disables the DNS check (i.e., allows "0.0.0.0").
+ * Useful for lab testing environment.
+ * @param b true disables the check, false enables.
+ */
+ void disableDnsCheck(boolean b);
+
+ /**
+ * Returns true if the DNS check is currently disabled.
+ */
+ boolean isDnsCheckDisabled();
+
+ /**
+ * Get current coarse-grained voice call state.
+ * Use {@link #registerForPreciseCallStateChanged(Handler, int, Object)
+ * registerForPreciseCallStateChanged()} 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();
+
+ /**
+ * Return a numerical identifier for the phone radio interface.
+ * @return PHONE_TYPE_XXX as defined above.
+ */
+ int getPhoneType();
+
+ /**
+ * Returns an array of string identifiers for the APN types serviced by the
+ * currently active.
+ * @return The string array will always return at least one entry, Phone.APN_TYPE_DEFAULT.
+ * TODO: Revisit if we always should return at least one entry.
+ */
+ String[] getActiveApnTypes();
+
+ /**
+ * Returns string for the active APN host.
+ * @return type as a string or null if none.
+ */
+ String getActiveApnHost(String apnType);
+
+ /**
+ * Return the LinkProperties for the named apn or null if not available
+ */
+ LinkProperties getLinkProperties(String apnType);
+
+ /**
+ * Return the LinkCapabilities
+ */
+ LinkCapabilities getLinkCapabilities(String apnType);
+
+ /**
+ * 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 as SignalStrength
+ */
+ SignalStrength getSignalStrength();
+
+ /**
+ * 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);
+
+ /**
+ * Register for getting notifications for change in the Call State {@link Call.State}
+ * This is called PreciseCallState because the call state is more precise than the
+ * {@link Phone.State} which can be obtained using the {@link PhoneStateListener}
+ *
+ * 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 registerForPreciseCallStateChanged(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for voice call state change notifications.
+ * Extraneous calls are tolerated silently.
+ */
+ void unregisterForPreciseCallStateChanged(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 out-band ringback tone is needed.<p>
+ *
+ * Messages received from this:
+ * Message.obj will be an AsyncResult
+ * AsyncResult.userObj = obj
+ * AsyncResult.result = boolean, true to start play ringback tone
+ * and false to stop. <p>
+ */
+ void registerForRingbackTone(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for ringback tone notification.
+ */
+
+ void unregisterForRingbackTone(Handler h);
+
+ /**
+ * Registers the handler to reset the uplink mute state to get
+ * uplink audio.
+ */
+ void registerForResendIncallMute(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for resend incall mute notifications.
+ */
+ void unregisterForResendIncallMute(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);
+
+ /**
+ * Registration point for Ecm timer reset
+ * @param h handler to notify
+ * @param what user-defined message code
+ * @param obj placed in Message.obj
+ */
+ public void registerForEcmTimerReset(Handler h, int what, Object obj);
+
+ /**
+ * Unregister for notification for Ecm timer reset
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForEcmTimerReset(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);
+
+ /**
+ * Register for notifications when a sInCall VoicePrivacy is enabled
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj);
+
+ /**
+ * Unegister for notifications when a sInCall VoicePrivacy is enabled
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForInCallVoicePrivacyOn(Handler h);
+
+ /**
+ * Register for notifications when a sInCall VoicePrivacy is disabled
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj);
+
+ /**
+ * Unegister for notifications when a sInCall VoicePrivacy is disabled
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForInCallVoicePrivacyOff(Handler h);
+
+ /**
+ * Register for notifications when CDMA OTA Provision status change
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForCdmaOtaStatusChange(Handler h, int what, Object obj);
+
+ /**
+ * Unegister for notifications when CDMA OTA Provision status change
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForCdmaOtaStatusChange(Handler h);
+
+ /**
+ * Registration point for subscription info ready
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ public void registerForSubscriptionInfoReady(Handler h, int what, Object obj);
+
+ /**
+ * Unregister for notifications for subscription info
+ * @param h Handler to be removed from the registrant list.
+ */
+ public void unregisterForSubscriptionInfoReady(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 getIccRecordsLoaded();
+
+ /**
+ * Returns the ICC card interface for this phone, or null
+ * if not applicable to underlying technology.
+ */
+ IccCard getIccCard();
+
+ /**
+ * Answers a ringing or waiting call. Active calls, if any, go on hold.
+ * Answering occurs asynchronously, and final notification occurs via
+ * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @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 #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @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 #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @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 #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @exception CallStateException if canConference() would return false.
+ * In these cases, this operation may not be performed.
+ */
+ void conference() throws CallStateException;
+
+ /**
+ * Enable or disable enhanced Voice Privacy (VP). If enhanced VP is
+ * disabled, normal VP is enabled.
+ *
+ * @param enable whether true or false to enable or disable.
+ * @param onComplete a callback message when the action is completed.
+ */
+ void enableEnhancedVoicePrivacy(boolean enable, Message onComplete);
+
+ /**
+ * Get the currently set Voice Privacy (VP) mode.
+ *
+ * @param onComplete a callback message when the action is completed.
+ */
+ void getEnhancedVoicePrivacy(Message onComplete);
+
+ /**
+ * 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 #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ *
+ * @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 #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ */
+ 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 #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ */
+ 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 #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}.
+ */
+ 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;
+
+ /**
+ * Initiate a new voice connection with supplementary User to User
+ * Information. 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, UUSInfo uusInfo) 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();
+
+ /**
+ * send burst DTMF tone, it can send the string as single character or multiple character
+ * ignore if there is no active call or not valid digits string.
+ * Valid digit means only includes characters ISO-LATIN characters 0-9, *, #
+ * The difference between sendDtmf and sendBurstDtmf is sendDtmf only sends one character,
+ * this api can send single character and multiple character, also, this api has response
+ * back to caller.
+ *
+ * @param dtmfString is string representing the dialing digit(s) in the active call
+ * @param on the DTMF ON length in milliseconds, or 0 for default
+ * @param off the DTMF OFF length in milliseconds, or 0 for default
+ * @param onComplete is the callback message when the action is processed by BP
+ *
+ */
+ void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete);
+
+ /**
+ * 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). For CDMA phones, the MDN is returned
+ * and {@link #getMsisdn()} will return the MSISDN on CDMA/LTE phones.<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 unread voicemail count. This count is shown when the voicemail
+ * notification is expanded.<p>
+ */
+ int getVoiceMessageCount();
+
+ /**
+ * 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.CommandsInterface.</code>
+ * @param onComplete a callback message when the action is completed.
+ * @see com.android.internal.telephony.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.CommandsInterface.</code>
+ * @param commandInterfaceCFAction is one of the valid call forwarding
+ * CF_ACTIONS, as defined in
+ * <code>com.android.internal.telephony.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.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.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.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>OperatorInfo</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(OperatorInfo, 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(OperatorInfo 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);
+
+ /**
+ * Gets the default SMSC address.
+ *
+ * @param result Callback message contains the SMSC address.
+ */
+ void getSmscAddress(Message result);
+
+ /**
+ * Sets the default SMSC address.
+ *
+ * @param address new SMSC address
+ * @param result Callback message is empty on completion
+ */
+ void setSmscAddress(String address, Message result);
+
+ /**
+ * 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 #registerForPreciseCallStateChanged(android.os.Handler, int,
+ * java.lang.Object) registerForPreciseCallStateChanged()}
+ * 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();
+
+ /**
+ * Enables or disables echo suppression.
+ */
+ void setEchoSuppressionEnabled(boolean enabled);
+
+ /**
+ * 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 Data Call 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 getDataCallList(Message response);
+
+ /**
+ * Update the ServiceState CellLocation for current network registration.
+ */
+ void updateServiceLocation();
+
+ /**
+ * 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);
+
+ /**
+ * Query the CDMA roaming preference setting
+ *
+ * @param response is callback message to report one of CDMA_RM_*
+ */
+ void queryCdmaRoamingPreference(Message response);
+
+ /**
+ * Requests to set the CDMA roaming preference
+ * @param cdmaRoamingType one of CDMA_RM_*
+ * @param response is callback message
+ */
+ void setCdmaRoamingPreference(int cdmaRoamingType, Message response);
+
+ /**
+ * Requests to set the CDMA subscription mode
+ * @param cdmaSubscriptionType one of CDMA_SUBSCRIPTION_*
+ * @param response is callback message
+ */
+ void setCdmaSubscription(int cdmaSubscriptionType, Message response);
+
+ /**
+ * If this is a simulated phone interface, returns a SimulatedRadioControl.
+ * @ return A SimulatedRadioControl if this is a simulated interface;
+ * otherwise, null.
+ */
+ SimulatedRadioControl getSimulatedRadioControl();
+
+ /**
+ * 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();
+
+ /**
+ * Report on whether data connectivity is allowed for an APN.
+ */
+ boolean isDataConnectivityPossible(String apnType);
+
+ /**
+ * Retrieves the unique device ID, e.g., IMEI for GSM phones and MEID for CDMA phones.
+ */
+ String getDeviceId();
+
+ /**
+ * Retrieves the software version number for the device, e.g., IMEI/SV
+ * for GSM phones.
+ */
+ String getDeviceSvn();
+
+ /**
+ * Retrieves the unique subscriber ID, e.g., IMSI for GSM phones.
+ */
+ String getSubscriberId();
+
+ /**
+ * Retrieves the serial number of the ICC, if applicable.
+ */
+ String getIccSerialNumber();
+
+ /* CDMA support methods */
+
+ /**
+ * Retrieves the MIN for CDMA phones.
+ */
+ String getCdmaMin();
+
+ /**
+ * Check if subscription data has been assigned to mMin
+ *
+ * return true if MIN info is ready; false otherwise.
+ */
+ boolean isMinInfoReady();
+
+ /**
+ * Retrieves PRL Version for CDMA phones
+ */
+ String getCdmaPrlVersion();
+
+ /**
+ * Retrieves the ESN for CDMA phones.
+ */
+ String getEsn();
+
+ /**
+ * Retrieves MEID for CDMA phones.
+ */
+ String getMeid();
+
+ /**
+ * Retrieves the MSISDN from the UICC. For GSM/UMTS phones, this is equivalent to
+ * {@link #getLine1Number()}. For CDMA phones, {@link #getLine1Number()} returns
+ * the MDN, so this method is provided to return the MSISDN on CDMA/LTE phones.
+ */
+ String getMsisdn();
+
+ /**
+ * Retrieves IMEI for phones. Returns null if IMEI is not set.
+ */
+ String getImei();
+
+ /**
+ * Retrieves the PhoneSubInfo of the Phone
+ */
+ public PhoneSubInfo getPhoneSubInfo();
+
+ /**
+ * Retrieves the IccSmsInterfaceManager of the Phone
+ */
+ public IccSmsInterfaceManager getIccSmsInterfaceManager();
+
+ /**
+ * Retrieves the IccPhoneBookInterfaceManager of the Phone
+ */
+ public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager();
+
+ /**
+ * setTTYMode
+ * sets a TTY mode option.
+ * @param ttyMode is a one of the following:
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_OFF}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_FULL}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_HCO}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
+ * @param onComplete a callback message when the action is completed
+ */
+ void setTTYMode(int ttyMode, Message onComplete);
+
+ /**
+ * queryTTYMode
+ * query the status of the TTY mode
+ *
+ * @param onComplete a callback message when the action is completed.
+ */
+ void queryTTYMode(Message onComplete);
+
+ /**
+ * Activate or deactivate cell broadcast SMS.
+ *
+ * @param activate
+ * 0 = activate, 1 = deactivate
+ * @param response
+ * Callback message is empty on completion
+ */
+ void activateCellBroadcastSms(int activate, Message response);
+
+ /**
+ * Query the current configuration of cdma cell broadcast SMS.
+ *
+ * @param response
+ * Callback message is empty on completion
+ */
+ void getCellBroadcastSmsConfig(Message response);
+
+ /**
+ * Configure cell broadcast SMS.
+ *
+ * TODO: Change the configValuesArray to a RIL_BroadcastSMSConfig
+ *
+ * @param response
+ * Callback message is empty on completion
+ */
+ public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response);
+
+ public void notifyDataActivity();
+
+ /**
+ * Returns the CDMA ERI icon index to display
+ */
+ public int getCdmaEriIconIndex();
+
+ /**
+ * Returns the CDMA ERI icon mode,
+ * 0 - ON
+ * 1 - FLASHING
+ */
+ public int getCdmaEriIconMode();
+
+ /**
+ * Returns the CDMA ERI text,
+ */
+ public String getCdmaEriText();
+
+ /**
+ * request to exit emergency call back mode
+ * the caller should use setOnECMModeExitResponse
+ * to receive the emergency callback mode exit response
+ */
+ void exitEmergencyCallbackMode();
+
+ /**
+ * this decides if the dial number is OTA(Over the air provision) number or not
+ * @param dialStr is string representing the dialing digit(s)
+ * @return true means the dialStr is OTA number, and false means the dialStr is not OTA number
+ */
+ boolean isOtaSpNumber(String dialStr);
+
+ /**
+ * Returns true if OTA Service Provisioning needs to be performed.
+ */
+ boolean needsOtaServiceProvisioning();
+
+ /**
+ * Register for notifications when CDMA call waiting comes
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForCallWaiting(Handler h, int what, Object obj);
+
+ /**
+ * Unegister for notifications when CDMA Call waiting comes
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForCallWaiting(Handler h);
+
+
+ /**
+ * Register for signal information 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 registerForSignalInfo(Handler h, int what, Object obj) ;
+ /**
+ * Unregisters for signal information notifications.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForSignalInfo(Handler h);
+
+ /**
+ * Register for display information 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 registerForDisplayInfo(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for display information notifications.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForDisplayInfo(Handler h) ;
+
+ /**
+ * Register for CDMA number information record notification from the network.
+ * Message.obj will contain an AsyncResult.
+ * AsyncResult.result will be a CdmaInformationRecords.CdmaNumberInfoRec
+ * instance.
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForNumberInfo(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for number information record notifications.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForNumberInfo(Handler h);
+
+ /**
+ * Register for CDMA redirected number information record notification
+ * from the network.
+ * Message.obj will contain an AsyncResult.
+ * AsyncResult.result will be a CdmaInformationRecords.CdmaRedirectingNumberInfoRec
+ * instance.
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForRedirectedNumberInfo(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for redirected number information record notification.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForRedirectedNumberInfo(Handler h);
+
+ /**
+ * Register for CDMA line control information record notification
+ * from the network.
+ * Message.obj will contain an AsyncResult.
+ * AsyncResult.result will be a CdmaInformationRecords.CdmaLineControlInfoRec
+ * instance.
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForLineControlInfo(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for line control information notifications.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForLineControlInfo(Handler h);
+
+ /**
+ * Register for CDMA T53 CLIR information record notifications
+ * from the network.
+ * Message.obj will contain an AsyncResult.
+ * AsyncResult.result will be a CdmaInformationRecords.CdmaT53ClirInfoRec
+ * instance.
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerFoT53ClirlInfo(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for T53 CLIR information record notification
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForT53ClirInfo(Handler h);
+
+ /**
+ * Register for CDMA T53 audio control information record notifications
+ * from the network.
+ * Message.obj will contain an AsyncResult.
+ * AsyncResult.result will be a CdmaInformationRecords.CdmaT53AudioControlInfoRec
+ * instance.
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+ void registerForT53AudioControlInfo(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for T53 audio control information record notifications.
+ * Extraneous calls are tolerated silently
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unregisterForT53AudioControlInfo(Handler h);
+
+ /**
+ * registers for exit emergency call back mode request response
+ *
+ * @param h Handler that receives the notification message.
+ * @param what User-defined message code.
+ * @param obj User object.
+ */
+
+ void setOnEcbModeExitResponse(Handler h, int what, Object obj);
+
+ /**
+ * Unregisters for exit emergency call back mode request response
+ *
+ * @param h Handler to be removed from the registrant list.
+ */
+ void unsetOnEcbModeExitResponse(Handler h);
+
+ /**
+ * Return if the current radio is LTE on CDMA. This
+ * is a tri-state return value as for a period of time
+ * the mode may be unknown.
+ *
+ * @return {@link #LTE_ON_CDMA_UNKNOWN}, {@link #LTE_ON_CDMA_FALSE} or {@link #LTE_ON_CDMA_TRUE}
+ */
+ public int getLteOnCdmaMode();
+
+ /**
+ * TODO: Adding a function for each property is not good.
+ * A fucntion of type getPhoneProp(propType) where propType is an
+ * enum of GSM+CDMA+LTE props would be a better approach.
+ *
+ * Get "Restriction of menu options for manual PLMN selection" bit
+ * status from EF_CSP data, this belongs to "Value Added Services Group".
+ * @return true if this bit is set or EF_CSP data is unavailable,
+ * false otherwise
+ */
+ boolean isCspPlmnEnabled();
+
+ /**
+ * Return an interface to retrieve the ISIM records for IMS, if available.
+ * @return the interface to retrieve the ISIM records, or null if not supported
+ */
+ IsimRecords getIsimRecords();
+
+ /**
+ * Request the ISIM application on the UICC to perform the AKA
+ * challenge/response algorithm for IMS authentication. The nonce string
+ * and challenge response are Base64 encoded Strings.
+ *
+ * @param nonce the nonce string to pass with the ISIM authentication request
+ * @param response a callback message with the String response in the obj field
+ */
+ void requestIsimAuthentication(String nonce, Message response);
+
+ /**
+ * 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
+ */
+ void setVoiceMessageWaiting(int line, int countWaiting);
+
+ /**
+ * Gets the USIM service table from the UICC, if present and available.
+ * @return an interface to the UsimServiceTable record, or null if not available
+ */
+ UsimServiceTable getUsimServiceTable();
+
+ /**
+ * Unregister from all events it registered for and dispose objects
+ * created by this object.
+ */
+ void dispose();
+
+ /**
+ * Remove references to external object stored in this object.
+ */
+ void removeReferences();
+}
diff --git a/src/java/com/android/internal/telephony/PhoneBase.java b/src/java/com/android/internal/telephony/PhoneBase.java
new file mode 100644
index 0000000..b55240a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneBase.java
@@ -0,0 +1,1189 @@
+/*
+ * 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.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.SharedPreferences;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.net.wifi.WifiManager;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.telephony.gsm.UsimServiceTable;
+import com.android.internal.telephony.ims.IsimRecords;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+import com.android.internal.telephony.gsm.SIMRecords;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicReference;
+
+
+/**
+ * (<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 extends Handler implements Phone {
+ private static final String LOG_TAG = "PHONE";
+ private static final boolean LOCAL_DEBUG = true;
+
+ // Key used to read and write the saved network selection numeric value
+ public static final String NETWORK_SELECTION_KEY = "network_selection_key";
+ // Key used to read and write the saved network selection operator name
+ public static final String NETWORK_SELECTION_NAME_KEY = "network_selection_name_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";
+
+ /* Event Constants */
+ protected static final int EVENT_RADIO_AVAILABLE = 1;
+ /** Supplementary Service Notification received. */
+ protected static final int EVENT_SSN = 2;
+ protected static final int EVENT_SIM_RECORDS_LOADED = 3;
+ protected static final int EVENT_MMI_DONE = 4;
+ protected static final int EVENT_RADIO_ON = 5;
+ protected static final int EVENT_GET_BASEBAND_VERSION_DONE = 6;
+ protected static final int EVENT_USSD = 7;
+ protected static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 8;
+ protected static final int EVENT_GET_IMEI_DONE = 9;
+ protected static final int EVENT_GET_IMEISV_DONE = 10;
+ protected static final int EVENT_GET_SIM_STATUS_DONE = 11;
+ protected static final int EVENT_SET_CALL_FORWARD_DONE = 12;
+ protected static final int EVENT_GET_CALL_FORWARD_DONE = 13;
+ protected static final int EVENT_CALL_RING = 14;
+ protected static final int EVENT_CALL_RING_CONTINUE = 15;
+
+ // Used to intercept the carrier selection calls so that
+ // we can save the values.
+ protected static final int EVENT_SET_NETWORK_MANUAL_COMPLETE = 16;
+ protected static final int EVENT_SET_NETWORK_AUTOMATIC_COMPLETE = 17;
+ protected static final int EVENT_SET_CLIR_COMPLETE = 18;
+ protected static final int EVENT_REGISTERED_TO_NETWORK = 19;
+ protected static final int EVENT_SET_VM_NUMBER_DONE = 20;
+ // Events for CDMA support
+ protected static final int EVENT_GET_DEVICE_IDENTITY_DONE = 21;
+ protected static final int EVENT_RUIM_RECORDS_LOADED = 22;
+ protected static final int EVENT_NV_READY = 23;
+ protected static final int EVENT_SET_ENHANCED_VP = 24;
+ protected static final int EVENT_EMERGENCY_CALLBACK_MODE_ENTER = 25;
+ protected static final int EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE = 26;
+ protected static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 27;
+ // other
+ protected static final int EVENT_SET_NETWORK_AUTOMATIC = 28;
+ protected static final int EVENT_NEW_ICC_SMS = 29;
+ protected static final int EVENT_ICC_RECORD_EVENTS = 30;
+
+ // Key used to read/write current CLIR setting
+ public static final String CLIR_KEY = "clir_key";
+
+ // Key used to read/write "disable DNS server check" pref (used for testing)
+ public static final String DNS_SERVER_CHECK_DISABLED_KEY = "dns_server_check_disabled_key";
+
+ /* Instance Variables */
+ public CommandsInterface mCM;
+ boolean mDnsCheckDisabled;
+ public DataConnectionTracker mDataConnectionTracker;
+ boolean mDoesRilSendMultipleCallRing;
+ int mCallRingContinueToken;
+ int mCallRingDelay;
+ public boolean mIsTheCurrentActivePhone = true;
+ boolean mIsVoiceCapable = true;
+ public IccRecords mIccRecords;
+ protected AtomicReference<IccCard> mIccCard = new AtomicReference<IccCard>();
+ public SmsStorageMonitor mSmsStorageMonitor;
+ public SmsUsageMonitor mSmsUsageMonitor;
+ public SMSDispatcher mSMS;
+
+ /**
+ * Set a system property, unless we're in unit test mode
+ */
+ public void
+ setSystemProperty(String property, String value) {
+ if(getUnitTestMode()) {
+ return;
+ }
+ SystemProperties.set(property, value);
+ }
+
+
+ protected final RegistrantList mPreciseCallStateRegistrants
+ = 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 final 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, CommandsInterface ci) {
+ this(notifier, context, ci, 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, CommandsInterface ci,
+ boolean unitTestMode) {
+ this.mNotifier = notifier;
+ this.mContext = context;
+ mLooper = Looper.myLooper();
+ mCM = ci;
+
+ setPropertiesByCarrier();
+
+ setUnitTestMode(unitTestMode);
+
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ mDnsCheckDisabled = sp.getBoolean(DNS_SERVER_CHECK_DISABLED_KEY, false);
+ mCM.setOnCallRing(this, EVENT_CALL_RING, null);
+
+ /* "Voice capable" means that this device supports circuit-switched
+ * (i.e. voice) phone calls over the telephony network, and is allowed
+ * to display the in-call UI while a cellular voice call is active.
+ * This will be false on "data only" devices which can't make voice
+ * calls and don't support any in-call UI.
+ */
+ mIsVoiceCapable = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_voice_capable);
+
+ /**
+ * Some RIL's don't always send RIL_UNSOL_CALL_RING so it needs
+ * to be generated locally. Ideally all ring tones should be loops
+ * and this wouldn't be necessary. But to minimize changes to upper
+ * layers it is requested that it be generated by lower layers.
+ *
+ * By default old phones won't have the property set but do generate
+ * the RIL_UNSOL_CALL_RING so the default if there is no property is
+ * true.
+ */
+ mDoesRilSendMultipleCallRing = SystemProperties.getBoolean(
+ TelephonyProperties.PROPERTY_RIL_SENDS_MULTIPLE_CALL_RING, true);
+ Log.d(LOG_TAG, "mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing);
+
+ mCallRingDelay = SystemProperties.getInt(
+ TelephonyProperties.PROPERTY_CALL_RING_DELAY, 3000);
+ Log.d(LOG_TAG, "mCallRingDelay=" + mCallRingDelay);
+
+ // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
+ mSmsStorageMonitor = new SmsStorageMonitor(this);
+ mSmsUsageMonitor = new SmsUsageMonitor(context);
+ }
+
+ public void dispose() {
+ synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+ mCM.unSetOnCallRing(this);
+ // Must cleanup all connectionS and needs to use sendMessage!
+ mDataConnectionTracker.cleanUpAllConnections(null);
+ mIsTheCurrentActivePhone = false;
+ // Dispose the SMS usage and storage monitors
+ mSmsStorageMonitor.dispose();
+ mSmsUsageMonitor.dispose();
+ }
+ }
+
+ public void removeReferences() {
+ mSmsStorageMonitor = null;
+ mSmsUsageMonitor = null;
+ mSMS = null;
+ mIccRecords = null;
+ mIccCard.set(null);
+ mDataConnectionTracker = null;
+ }
+
+ /**
+ * When overridden the derived class needs to call
+ * super.handleMessage(msg) so this method has a
+ * a chance to process the message.
+ *
+ * @param msg
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+
+ switch(msg.what) {
+ case EVENT_CALL_RING:
+ Log.d(LOG_TAG, "Event EVENT_CALL_RING Received state=" + getState());
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ PhoneConstants.State state = getState();
+ if ((!mDoesRilSendMultipleCallRing)
+ && ((state == PhoneConstants.State.RINGING) ||
+ (state == PhoneConstants.State.IDLE))) {
+ mCallRingContinueToken += 1;
+ sendIncomingCallRingNotification(mCallRingContinueToken);
+ } else {
+ notifyIncomingRing();
+ }
+ }
+ break;
+
+ case EVENT_CALL_RING_CONTINUE:
+ Log.d(LOG_TAG, "Event EVENT_CALL_RING_CONTINUE Received stat=" + getState());
+ if (getState() == PhoneConstants.State.RINGING) {
+ sendIncomingCallRingNotification(msg.arg1);
+ }
+ break;
+
+ default:
+ throw new RuntimeException("unexpected event not handled");
+ }
+ }
+
+ // Inherited documentation suffices.
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Disables the DNS check (i.e., allows "0.0.0.0").
+ * Useful for lab testing environment.
+ * @param b true disables the check, false enables.
+ */
+ public void disableDnsCheck(boolean b) {
+ mDnsCheckDisabled = b;
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean(DNS_SERVER_CHECK_DISABLED_KEY, b);
+ editor.apply();
+ }
+
+ /**
+ * Returns true if the DNS check is currently disabled.
+ */
+ public boolean isDnsCheckDisabled() {
+ return mDnsCheckDisabled;
+ }
+
+ // Inherited documentation suffices.
+ public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) {
+ checkCorrectThread(h);
+
+ mPreciseCallStateRegistrants.addUnique(h, what, obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForPreciseCallStateChanged(Handler h) {
+ mPreciseCallStateRegistrants.remove(h);
+ }
+
+ /**
+ * Subclasses of Phone probably want to replace this with a
+ * version scoped to their packages
+ */
+ protected void notifyPreciseCallStateChangedP() {
+ AsyncResult ar = new AsyncResult(null, this, null);
+ mPreciseCallStateRegistrants.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);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){
+ mCM.registerForInCallVoicePrivacyOn(h,what,obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForInCallVoicePrivacyOn(Handler h){
+ mCM.unregisterForInCallVoicePrivacyOn(h);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){
+ mCM.registerForInCallVoicePrivacyOff(h,what,obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForInCallVoicePrivacyOff(Handler h){
+ mCM.unregisterForInCallVoicePrivacyOff(h);
+ }
+
+ // 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);
+ }
+
+ /**
+ * 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.
+ */
+ public 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);
+ }
+ }
+
+ // 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);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForRingbackTone(Handler h, int what, Object obj) {
+ mCM.registerForRingbackTone(h,what,obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForRingbackTone(Handler h) {
+ mCM.unregisterForRingbackTone(h);
+ }
+
+ // Inherited documentation suffices.
+ public void registerForResendIncallMute(Handler h, int what, Object obj) {
+ mCM.registerForResendIncallMute(h,what,obj);
+ }
+
+ // Inherited documentation suffices.
+ public void unregisterForResendIncallMute(Handler h) {
+ mCM.unregisterForResendIncallMute(h);
+ }
+
+ public void setEchoSuppressionEnabled(boolean enabled) {
+ // no need for regular phone
+ }
+
+ /**
+ * 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");
+ }
+ }
+
+ /**
+ * Set the properties by matching the carrier string in
+ * a string-array resource
+ */
+ private void setPropertiesByCarrier() {
+ String carrier = SystemProperties.get("ro.carrier");
+
+ if (null == carrier || 0 == carrier.length() || "unknown".equals(carrier)) {
+ return;
+ }
+
+ CharSequence[] carrierLocales = mContext.
+ getResources().getTextArray(R.array.carrier_properties);
+
+ for (int i = 0; i < carrierLocales.length; i+=3) {
+ String c = carrierLocales[i].toString();
+ if (carrier.equals(c)) {
+ String l = carrierLocales[i+1].toString();
+
+ String language = l.substring(0, 2);
+ String country = "";
+ if (l.length() >=5) {
+ country = l.substring(3, 5);
+ }
+ MccTable.setSystemLocale(mContext, language, country);
+
+ if (!country.isEmpty()) {
+ try {
+ Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.WIFI_COUNTRY_CODE);
+ } catch (Settings.SettingNotFoundException e) {
+ // note this is not persisting
+ WifiManager wM = (WifiManager)
+ mContext.getSystemService(Context.WIFI_SERVICE);
+ wM.setCountryCode(country, false);
+ }
+ }
+ return;
+ }
+ }
+ }
+
+ /**
+ * Get state
+ */
+ public abstract PhoneConstants.State getState();
+
+ /**
+ * Retrieves the IccFileHandler of the Phone instance
+ */
+ public IccFileHandler getIccFileHandler(){
+ IccCard iccCard = mIccCard.get();
+ if (iccCard == null) return null;
+ return iccCard.getIccFileHandler();
+ }
+
+ /*
+ * Retrieves the Handler of the Phone instance
+ */
+ public Handler getHandler() {
+ return this;
+ }
+
+ /**
+ * Retrieves the ServiceStateTracker of the phone instance.
+ */
+ public ServiceStateTracker getServiceStateTracker() {
+ return null;
+ }
+
+ /**
+ * Get call tracker
+ */
+ public CallTracker getCallTracker() {
+ return null;
+ }
+
+ @Override
+ public IccCard getIccCard() {
+ return mIccCard.get();
+ }
+
+ @Override
+ public String getIccSerialNumber() {
+ return mIccRecords.iccid;
+ }
+
+ @Override
+ public boolean getIccRecordsLoaded() {
+ return mIccRecords.getRecordsLoaded();
+ }
+
+ @Override
+ public boolean getMessageWaitingIndicator() {
+ return mIccRecords.getVoiceMessageWaiting();
+ }
+
+ @Override
+ public boolean getCallForwardingIndicator() {
+ return mIccRecords.getVoiceCallForwardingFlag();
+ }
+
+ /**
+ * Query the status of the CDMA roaming preference
+ */
+ public void queryCdmaRoamingPreference(Message response) {
+ mCM.queryCdmaRoamingPreference(response);
+ }
+
+ /**
+ * Set the status of the CDMA roaming preference
+ */
+ public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+ mCM.setCdmaRoamingPreference(cdmaRoamingType, response);
+ }
+
+ /**
+ * Set the status of the CDMA subscription mode
+ */
+ public void setCdmaSubscription(int cdmaSubscriptionType, Message response) {
+ mCM.setCdmaSubscriptionSource(cdmaSubscriptionType, response);
+ }
+
+ /**
+ * Set the preferred Network Type: Global, CDMA only or GSM/UMTS only
+ */
+ public void setPreferredNetworkType(int networkType, Message response) {
+ mCM.setPreferredNetworkType(networkType, response);
+ }
+
+ public void getPreferredNetworkType(Message response) {
+ mCM.getPreferredNetworkType(response);
+ }
+
+ public void getSmscAddress(Message result) {
+ mCM.getSmscAddress(result);
+ }
+
+ public void setSmscAddress(String address, Message result) {
+ mCM.setSmscAddress(address, result);
+ }
+
+ public void setTTYMode(int ttyMode, Message onComplete) {
+ mCM.setTTYMode(ttyMode, onComplete);
+ }
+
+ public void queryTTYMode(Message onComplete) {
+ mCM.queryTTYMode(onComplete);
+ }
+
+ public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("enableEnhancedVoicePrivacy");
+ }
+
+ public void getEnhancedVoicePrivacy(Message onComplete) {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("getEnhancedVoicePrivacy");
+ }
+
+ public void setBandMode(int bandMode, Message response) {
+ mCM.setBandMode(bandMode, response);
+ }
+
+ public void queryAvailableBandMode(Message response) {
+ mCM.queryAvailableBandMode(response);
+ }
+
+ public void invokeOemRilRequestRaw(byte[] data, Message response) {
+ mCM.invokeOemRilRequestRaw(data, response);
+ }
+
+ public void invokeOemRilRequestStrings(String[] strings, Message response) {
+ mCM.invokeOemRilRequestStrings(strings, response);
+ }
+
+ public void notifyDataActivity() {
+ mNotifier.notifyDataActivity(this);
+ }
+
+ public void notifyMessageWaitingIndicator() {
+ // Do not notify voice mail waiting if device doesn't support voice
+ if (!mIsVoiceCapable)
+ return;
+
+ // This function is added to send the notification to DefaultPhoneNotifier.
+ mNotifier.notifyMessageWaitingChanged(this);
+ }
+
+ public void notifyDataConnection(String reason, String apnType,
+ PhoneConstants.DataState state) {
+ mNotifier.notifyDataConnection(this, reason, apnType, state);
+ }
+
+ public void notifyDataConnection(String reason, String apnType) {
+ mNotifier.notifyDataConnection(this, reason, apnType, getDataConnectionState(apnType));
+ }
+
+ public void notifyDataConnection(String reason) {
+ String types[] = getActiveApnTypes();
+ for (String apnType : types) {
+ mNotifier.notifyDataConnection(this, reason, apnType, getDataConnectionState(apnType));
+ }
+ }
+
+ public void notifyOtaspChanged(int otaspMode) {
+ mNotifier.notifyOtaspChanged(this, otaspMode);
+ }
+
+ /**
+ * @return true if a mobile originating emergency call is active
+ */
+ public boolean isInEmergencyCall() {
+ return false;
+ }
+
+ /**
+ * @return true if we are in the emergency call back mode. This is a period where
+ * the phone should be using as little power as possible and be ready to receive an
+ * incoming call from the emergency operator.
+ */
+ public boolean isInEcm() {
+ return false;
+ }
+
+ public abstract String getPhoneName();
+
+ public abstract int getPhoneType();
+
+ /** @hide */
+ public int getVoiceMessageCount(){
+ return 0;
+ }
+
+ /**
+ * Returns the CDMA ERI icon index to display
+ */
+ public int getCdmaEriIconIndex() {
+ logUnexpectedCdmaMethodCall("getCdmaEriIconIndex");
+ return -1;
+ }
+
+ /**
+ * Returns the CDMA ERI icon mode,
+ * 0 - ON
+ * 1 - FLASHING
+ */
+ public int getCdmaEriIconMode() {
+ logUnexpectedCdmaMethodCall("getCdmaEriIconMode");
+ return -1;
+ }
+
+ /**
+ * Returns the CDMA ERI text,
+ */
+ public String getCdmaEriText() {
+ logUnexpectedCdmaMethodCall("getCdmaEriText");
+ return "GSM nw, no ERI";
+ }
+
+ public String getCdmaMin() {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("getCdmaMin");
+ return null;
+ }
+
+ public boolean isMinInfoReady() {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("isMinInfoReady");
+ return false;
+ }
+
+ public String getCdmaPrlVersion(){
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("getCdmaPrlVersion");
+ return null;
+ }
+
+ public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete) {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("sendBurstDtmf");
+ }
+
+ public void exitEmergencyCallbackMode() {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("exitEmergencyCallbackMode");
+ }
+
+ public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj) {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("registerForCdmaOtaStatusChange");
+ }
+
+ public void unregisterForCdmaOtaStatusChange(Handler h) {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("unregisterForCdmaOtaStatusChange");
+ }
+
+ public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("registerForSubscriptionInfoReady");
+ }
+
+ public void unregisterForSubscriptionInfoReady(Handler h) {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("unregisterForSubscriptionInfoReady");
+ }
+
+ /**
+ * Returns true if OTA Service Provisioning needs to be performed.
+ * If not overridden return false.
+ */
+ public boolean needsOtaServiceProvisioning() {
+ return false;
+ }
+
+ /**
+ * Return true if number is an OTASP number.
+ * If not overridden return false.
+ */
+ public boolean isOtaSpNumber(String dialStr) {
+ return false;
+ }
+
+ public void registerForCallWaiting(Handler h, int what, Object obj){
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("registerForCallWaiting");
+ }
+
+ public void unregisterForCallWaiting(Handler h){
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("unregisterForCallWaiting");
+ }
+
+ public void registerForEcmTimerReset(Handler h, int what, Object obj) {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("registerForEcmTimerReset");
+ }
+
+ public void unregisterForEcmTimerReset(Handler h) {
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("unregisterForEcmTimerReset");
+ }
+
+ public void registerForSignalInfo(Handler h, int what, Object obj) {
+ mCM.registerForSignalInfo(h, what, obj);
+ }
+
+ public void unregisterForSignalInfo(Handler h) {
+ mCM.unregisterForSignalInfo(h);
+ }
+
+ public void registerForDisplayInfo(Handler h, int what, Object obj) {
+ mCM.registerForDisplayInfo(h, what, obj);
+ }
+
+ public void unregisterForDisplayInfo(Handler h) {
+ mCM.unregisterForDisplayInfo(h);
+ }
+
+ public void registerForNumberInfo(Handler h, int what, Object obj) {
+ mCM.registerForNumberInfo(h, what, obj);
+ }
+
+ public void unregisterForNumberInfo(Handler h) {
+ mCM.unregisterForNumberInfo(h);
+ }
+
+ public void registerForRedirectedNumberInfo(Handler h, int what, Object obj) {
+ mCM.registerForRedirectedNumberInfo(h, what, obj);
+ }
+
+ public void unregisterForRedirectedNumberInfo(Handler h) {
+ mCM.unregisterForRedirectedNumberInfo(h);
+ }
+
+ public void registerForLineControlInfo(Handler h, int what, Object obj) {
+ mCM.registerForLineControlInfo( h, what, obj);
+ }
+
+ public void unregisterForLineControlInfo(Handler h) {
+ mCM.unregisterForLineControlInfo(h);
+ }
+
+ public void registerFoT53ClirlInfo(Handler h, int what, Object obj) {
+ mCM.registerFoT53ClirlInfo(h, what, obj);
+ }
+
+ public void unregisterForT53ClirInfo(Handler h) {
+ mCM.unregisterForT53ClirInfo(h);
+ }
+
+ public void registerForT53AudioControlInfo(Handler h, int what, Object obj) {
+ mCM.registerForT53AudioControlInfo( h, what, obj);
+ }
+
+ public void unregisterForT53AudioControlInfo(Handler h) {
+ mCM.unregisterForT53AudioControlInfo(h);
+ }
+
+ public void setOnEcbModeExitResponse(Handler h, int what, Object obj){
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("setOnEcbModeExitResponse");
+ }
+
+ public void unsetOnEcbModeExitResponse(Handler h){
+ // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
+ logUnexpectedCdmaMethodCall("unsetOnEcbModeExitResponse");
+ }
+
+ public String[] getActiveApnTypes() {
+ return mDataConnectionTracker.getActiveApnTypes();
+ }
+
+ public String getActiveApnHost(String apnType) {
+ return mDataConnectionTracker.getActiveApnString(apnType);
+ }
+
+ public LinkProperties getLinkProperties(String apnType) {
+ return mDataConnectionTracker.getLinkProperties(apnType);
+ }
+
+ public LinkCapabilities getLinkCapabilities(String apnType) {
+ return mDataConnectionTracker.getLinkCapabilities(apnType);
+ }
+
+ public int enableApnType(String type) {
+ return mDataConnectionTracker.enableApnType(type);
+ }
+
+ public int disableApnType(String type) {
+ return mDataConnectionTracker.disableApnType(type);
+ }
+
+ public boolean isDataConnectivityPossible() {
+ return isDataConnectivityPossible(PhoneConstants.APN_TYPE_DEFAULT);
+ }
+
+ public boolean isDataConnectivityPossible(String apnType) {
+ return ((mDataConnectionTracker != null) &&
+ (mDataConnectionTracker.isDataPossible(apnType)));
+ }
+
+ /**
+ * Notify 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) {
+ if (!mIsVoiceCapable)
+ return;
+ AsyncResult ar = new AsyncResult(null, cn, null);
+ mNewRingingConnectionRegistrants.notifyRegistrants(ar);
+ }
+
+ /**
+ * Notify registrants of a RING event.
+ */
+ private void notifyIncomingRing() {
+ if (!mIsVoiceCapable)
+ return;
+ AsyncResult ar = new AsyncResult(null, this, null);
+ mIncomingRingRegistrants.notifyRegistrants(ar);
+ }
+
+ /**
+ * Send the incoming call Ring notification if conditions are right.
+ */
+ private void sendIncomingCallRingNotification(int token) {
+ if (mIsVoiceCapable && !mDoesRilSendMultipleCallRing &&
+ (token == mCallRingContinueToken)) {
+ Log.d(LOG_TAG, "Sending notifyIncomingRing");
+ notifyIncomingRing();
+ sendMessageDelayed(
+ obtainMessage(EVENT_CALL_RING_CONTINUE, token, 0), mCallRingDelay);
+ } else {
+ Log.d(LOG_TAG, "Ignoring ring notification request,"
+ + " mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing
+ + " token=" + token
+ + " mCallRingContinueToken=" + mCallRingContinueToken
+ + " mIsVoiceCapable=" + mIsVoiceCapable);
+ }
+ }
+
+ public boolean isCspPlmnEnabled() {
+ // This function should be overridden by the class GSMPhone.
+ // Not implemented in CDMAPhone.
+ logUnexpectedGsmMethodCall("isCspPlmnEnabled");
+ return false;
+ }
+
+ public IsimRecords getIsimRecords() {
+ Log.e(LOG_TAG, "getIsimRecords() is only supported on LTE devices");
+ return null;
+ }
+
+ public void requestIsimAuthentication(String nonce, Message result) {
+ Log.e(LOG_TAG, "requestIsimAuthentication() is only supported on LTE devices");
+ }
+
+ public String getMsisdn() {
+ logUnexpectedGsmMethodCall("getMsisdn");
+ return null;
+ }
+
+ /**
+ * Common error logger method for unexpected calls to CDMA-only methods.
+ */
+ private static void logUnexpectedCdmaMethodCall(String name)
+ {
+ Log.e(LOG_TAG, "Error! " + name + "() in PhoneBase should not be " +
+ "called, CDMAPhone inactive.");
+ }
+
+ public PhoneConstants.DataState getDataConnectionState() {
+ return getDataConnectionState(PhoneConstants.APN_TYPE_DEFAULT);
+ }
+
+ /**
+ * Common error logger method for unexpected calls to GSM/WCDMA-only methods.
+ */
+ private static void logUnexpectedGsmMethodCall(String name) {
+ Log.e(LOG_TAG, "Error! " + name + "() in PhoneBase should not be " +
+ "called, GSMPhone inactive.");
+ }
+
+ // Called by SimRecords which is constructed with a PhoneBase instead of a GSMPhone.
+ public void notifyCallForwardingIndicator() {
+ // This function should be overridden by the class GSMPhone. Not implemented in CDMAPhone.
+ Log.e(LOG_TAG, "Error! This function should never be executed, inactive CDMAPhone.");
+ }
+
+ public void notifyDataConnectionFailed(String reason, String apnType) {
+ mNotifier.notifyDataConnectionFailed(this, reason, apnType);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getLteOnCdmaMode() {
+ return mCM.getLteOnCdmaMode();
+ }
+
+ /**
+ * 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
+ */
+ @Override
+ public void setVoiceMessageWaiting(int line, int countWaiting) {
+ mIccRecords.setVoiceMessageWaiting(line, countWaiting);
+ }
+
+ /**
+ * Gets the USIM service table from the UICC, if present and available.
+ * @return an interface to the UsimServiceTable record, or null if not available
+ */
+ @Override
+ public UsimServiceTable getUsimServiceTable() {
+ return mIccRecords.getUsimServiceTable();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("PhoneBase:");
+ pw.println(" mCM=" + mCM);
+ pw.println(" mDnsCheckDisabled=" + mDnsCheckDisabled);
+ pw.println(" mDataConnectionTracker=" + mDataConnectionTracker);
+ pw.println(" mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing);
+ pw.println(" mCallRingContinueToken=" + mCallRingContinueToken);
+ pw.println(" mCallRingDelay=" + mCallRingDelay);
+ pw.println(" mIsTheCurrentActivePhone=" + mIsTheCurrentActivePhone);
+ pw.println(" mIsVoiceCapable=" + mIsVoiceCapable);
+ pw.println(" mIccRecords=" + mIccRecords);
+ pw.println(" mIccCard=" + mIccCard.get());
+ pw.println(" mSmsStorageMonitor=" + mSmsStorageMonitor);
+ pw.println(" mSmsUsageMonitor=" + mSmsUsageMonitor);
+ pw.println(" mSMS=" + mSMS);
+ pw.flush();
+ pw.println(" mLooper=" + mLooper);
+ pw.println(" mContext=" + mContext);
+ pw.println(" mNotifier=" + mNotifier);
+ pw.println(" mSimulatedRadioControl=" + mSimulatedRadioControl);
+ pw.println(" mUnitTestMode=" + mUnitTestMode);
+ pw.println(" isDnsCheckDisabled()=" + isDnsCheckDisabled());
+ pw.println(" getUnitTestMode()=" + getUnitTestMode());
+ pw.println(" getState()=" + getState());
+ pw.println(" getIccSerialNumber()=" + getIccSerialNumber());
+ pw.println(" getIccRecordsLoaded()=" + getIccRecordsLoaded());
+ pw.println(" getMessageWaitingIndicator()=" + getMessageWaitingIndicator());
+ pw.println(" getCallForwardingIndicator()=" + getCallForwardingIndicator());
+ pw.println(" isInEmergencyCall()=" + isInEmergencyCall());
+ pw.flush();
+ pw.println(" isInEcm()=" + isInEcm());
+ pw.println(" getPhoneName()=" + getPhoneName());
+ pw.println(" getPhoneType()=" + getPhoneType());
+ pw.println(" getVoiceMessageCount()=" + getVoiceMessageCount());
+ pw.println(" getActiveApnTypes()=" + getActiveApnTypes());
+ pw.println(" isDataConnectivityPossible()=" + isDataConnectivityPossible());
+ pw.println(" needsOtaServiceProvisioning=" + needsOtaServiceProvisioning());
+ }
+}
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
new file mode 100644
index 0000000..2c85dc6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -0,0 +1,213 @@
+/*
+ * 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.net.LocalServerSocket;
+import android.os.Looper;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.os.SystemProperties;
+
+import com.android.internal.telephony.cdma.CDMAPhone;
+import com.android.internal.telephony.cdma.CDMALTEPhone;
+import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
+import com.android.internal.telephony.gsm.GSMPhone;
+import com.android.internal.telephony.sip.SipPhone;
+import com.android.internal.telephony.sip.SipPhoneFactory;
+
+/**
+ * {@hide}
+ */
+public class PhoneFactory {
+ static final String LOG_TAG = "PHONE";
+ static final int SOCKET_OPEN_RETRY_MILLIS = 2 * 1000;
+ static final int SOCKET_OPEN_MAX_RETRY = 3;
+
+ //***** Class Variables
+
+ static private Phone sProxyPhone = null;
+ static private CommandsInterface sCommandsInterface = null;
+
+ static private boolean sMadeDefaults = false;
+ static private PhoneNotifier sPhoneNotifier;
+ static private Looper sLooper;
+ static private Context sContext;
+
+ static final int preferredCdmaSubscription =
+ CdmaSubscriptionSourceManager.PREFERRED_CDMA_SUBSCRIPTION;
+
+ //***** Class Methods
+
+ public static void makeDefaultPhones(Context context) {
+ makeDefaultPhone(context);
+ }
+
+ /**
+ * FIXME replace this with some other way of making these
+ * instances
+ */
+ public static void makeDefaultPhone(Context context) {
+ synchronized(Phone.class) {
+ if (!sMadeDefaults) {
+ sLooper = Looper.myLooper();
+ sContext = context;
+
+ if (sLooper == null) {
+ throw new RuntimeException(
+ "PhoneFactory.makeDefaultPhone 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();
+
+ // Get preferred network mode
+ int preferredNetworkMode = RILConstants.PREFERRED_NETWORK_MODE;
+ if (TelephonyManager.getLteOnCdmaModeStatic() == PhoneConstants.LTE_ON_CDMA_TRUE) {
+ preferredNetworkMode = Phone.NT_MODE_GLOBAL;
+ }
+ int networkMode = Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.PREFERRED_NETWORK_MODE, preferredNetworkMode);
+ Log.i(LOG_TAG, "Network Mode set to " + Integer.toString(networkMode));
+
+ // Get cdmaSubscription
+ // TODO: Change when the ril will provides a way to know at runtime
+ // the configuration, bug 4202572. And the ril issues the
+ // RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, bug 4295439.
+ int cdmaSubscription;
+ int lteOnCdma = TelephonyManager.getLteOnCdmaModeStatic();
+ switch (lteOnCdma) {
+ case PhoneConstants.LTE_ON_CDMA_FALSE:
+ cdmaSubscription = CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_NV;
+ Log.i(LOG_TAG, "lteOnCdma is 0 use SUBSCRIPTION_FROM_NV");
+ break;
+ case PhoneConstants.LTE_ON_CDMA_TRUE:
+ cdmaSubscription = CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM;
+ Log.i(LOG_TAG, "lteOnCdma is 1 use SUBSCRIPTION_FROM_RUIM");
+ break;
+ case PhoneConstants.LTE_ON_CDMA_UNKNOWN:
+ default:
+ //Get cdmaSubscription mode from Settings.System
+ cdmaSubscription = Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.PREFERRED_CDMA_SUBSCRIPTION,
+ preferredCdmaSubscription);
+ Log.i(LOG_TAG, "lteOnCdma not set, using PREFERRED_CDMA_SUBSCRIPTION");
+ break;
+ }
+ Log.i(LOG_TAG, "Cdma Subscription set to " + cdmaSubscription);
+
+ //reads the system properties and makes commandsinterface
+ sCommandsInterface = new RIL(context, networkMode, cdmaSubscription);
+
+ int phoneType = TelephonyManager.getPhoneType(networkMode);
+ if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ Log.i(LOG_TAG, "Creating GSMPhone");
+ sProxyPhone = new PhoneProxy(new GSMPhone(context,
+ sCommandsInterface, sPhoneNotifier));
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ switch (TelephonyManager.getLteOnCdmaModeStatic()) {
+ case PhoneConstants.LTE_ON_CDMA_TRUE:
+ Log.i(LOG_TAG, "Creating CDMALTEPhone");
+ sProxyPhone = new PhoneProxy(new CDMALTEPhone(context,
+ sCommandsInterface, sPhoneNotifier));
+ break;
+ case PhoneConstants.LTE_ON_CDMA_FALSE:
+ default:
+ Log.i(LOG_TAG, "Creating CDMAPhone");
+ sProxyPhone = new PhoneProxy(new CDMAPhone(context,
+ sCommandsInterface, sPhoneNotifier));
+ break;
+ }
+ }
+
+ sMadeDefaults = true;
+ }
+ }
+ }
+
+ public static Phone getDefaultPhone() {
+ if (sLooper != Looper.myLooper()) {
+ throw new RuntimeException(
+ "PhoneFactory.getDefaultPhone must be called from Looper thread");
+ }
+
+ if (!sMadeDefaults) {
+ throw new IllegalStateException("Default phones haven't been made yet!");
+ }
+ return sProxyPhone;
+ }
+
+ public static Phone getCdmaPhone() {
+ Phone phone;
+ synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+ switch (TelephonyManager.getLteOnCdmaModeStatic()) {
+ case PhoneConstants.LTE_ON_CDMA_TRUE: {
+ phone = new CDMALTEPhone(sContext, sCommandsInterface, sPhoneNotifier);
+ break;
+ }
+ case PhoneConstants.LTE_ON_CDMA_FALSE:
+ case PhoneConstants.LTE_ON_CDMA_UNKNOWN:
+ default: {
+ phone = new CDMAPhone(sContext, sCommandsInterface, sPhoneNotifier);
+ break;
+ }
+ }
+ }
+ return phone;
+ }
+
+ public static Phone getGsmPhone() {
+ synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+ Phone phone = new GSMPhone(sContext, sCommandsInterface, sPhoneNotifier);
+ return phone;
+ }
+ }
+
+ /**
+ * Makes a {@link SipPhone} object.
+ * @param sipUri the local SIP URI the phone runs on
+ * @return the {@code SipPhone} object or null if the SIP URI is not valid
+ */
+ public static SipPhone makeSipPhone(String sipUri) {
+ return SipPhoneFactory.makePhone(sipUri, sContext, sPhoneNotifier);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
new file mode 100644
index 0000000..efc7a13
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.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;
+
+import android.telephony.CellInfo;
+
+/**
+ * {@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);
+
+ /** TODO - reason should never be null */
+ public void notifyDataConnection(Phone sender, String reason, String apnType,
+ PhoneConstants.DataState state);
+
+ public void notifyDataConnectionFailed(Phone sender, String reason, String apnType);
+
+ public void notifyDataActivity(Phone sender);
+
+ public void notifyOtaspChanged(Phone sender, int otaspMode);
+
+ // TODO - trigger notifyCellInfo from ServiceStateTracker
+ public void notifyCellInfo(Phone sender, CellInfo cellInfo);
+}
diff --git a/src/java/com/android/internal/telephony/PhoneProxy.java b/src/java/com/android/internal/telephony/PhoneProxy.java
new file mode 100644
index 0000000..77135d4
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneProxy.java
@@ -0,0 +1,972 @@
+/*
+ * 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;
+
+
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.content.Intent;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.telephony.CellLocation;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.util.Log;
+
+import com.android.internal.telephony.cdma.CDMAPhone;
+import com.android.internal.telephony.gsm.GSMPhone;
+import com.android.internal.telephony.gsm.UsimServiceTable;
+import com.android.internal.telephony.ims.IsimRecords;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+import com.android.internal.telephony.CallManager;
+
+import java.util.List;
+
+public class PhoneProxy extends Handler implements Phone {
+ public final static Object lockForRadioTechnologyChange = new Object();
+
+ private Phone mActivePhone;
+ private CommandsInterface mCommandsInterface;
+ private IccSmsInterfaceManagerProxy mIccSmsInterfaceManagerProxy;
+ private IccPhoneBookInterfaceManagerProxy mIccPhoneBookInterfaceManagerProxy;
+ private PhoneSubInfoProxy mPhoneSubInfoProxy;
+
+ private boolean mResetModemOnRadioTechnologyChange = false;
+
+ private int mRilVersion;
+
+ private static final int EVENT_VOICE_RADIO_TECH_CHANGED = 1;
+ private static final int EVENT_RADIO_ON = 2;
+ private static final int EVENT_REQUEST_VOICE_RADIO_TECH_DONE = 3;
+ private static final int EVENT_RIL_CONNECTED = 4;
+
+ private static final String LOG_TAG = "PHONE";
+
+ //***** Class Methods
+ public PhoneProxy(Phone phone) {
+ mActivePhone = phone;
+ mResetModemOnRadioTechnologyChange = SystemProperties.getBoolean(
+ TelephonyProperties.PROPERTY_RESET_ON_RADIO_TECH_CHANGE, false);
+ mIccSmsInterfaceManagerProxy = new IccSmsInterfaceManagerProxy(
+ phone.getIccSmsInterfaceManager());
+ mIccPhoneBookInterfaceManagerProxy = new IccPhoneBookInterfaceManagerProxy(
+ phone.getIccPhoneBookInterfaceManager());
+ mPhoneSubInfoProxy = new PhoneSubInfoProxy(phone.getPhoneSubInfo());
+ mCommandsInterface = ((PhoneBase)mActivePhone).mCM;
+
+ mCommandsInterface.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
+ mCommandsInterface.registerForOn(this, EVENT_RADIO_ON, null);
+ mCommandsInterface.registerForVoiceRadioTechChanged(
+ this, EVENT_VOICE_RADIO_TECH_CHANGED, null);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+ switch(msg.what) {
+ case EVENT_RADIO_ON:
+ /* Proactively query voice radio technologies */
+ mCommandsInterface.getVoiceRadioTechnology(
+ this.obtainMessage(EVENT_REQUEST_VOICE_RADIO_TECH_DONE));
+ break;
+
+ case EVENT_RIL_CONNECTED:
+ if (ar.exception == null && ar.result != null) {
+ mRilVersion = (Integer) ar.result;
+ } else {
+ logd("Unexpected exception on EVENT_RIL_CONNECTED");
+ mRilVersion = -1;
+ }
+ break;
+
+ case EVENT_VOICE_RADIO_TECH_CHANGED:
+ case EVENT_REQUEST_VOICE_RADIO_TECH_DONE:
+
+ if (ar.exception == null) {
+ if ((ar.result != null) && (((int[]) ar.result).length != 0)) {
+ int newVoiceTech = ((int[]) ar.result)[0];
+ updatePhoneObject(newVoiceTech);
+ } else {
+ loge("Voice Radio Technology event " + msg.what + " has no tech!");
+ }
+ } else {
+ loge("Voice Radio Technology event " + msg.what + " exception!" + ar.exception);
+ }
+ break;
+
+ default:
+ loge("Error! This handler was not registered for this message type. Message: "
+ + msg.what);
+ break;
+ }
+ super.handleMessage(msg);
+ }
+
+ private static void logd(String msg) {
+ Log.d(LOG_TAG, "[PhoneProxy] " + msg);
+ }
+
+ private void logw(String msg) {
+ Log.w(LOG_TAG, "[PhoneProxy] " + msg);
+ }
+
+ private void loge(String msg) {
+ Log.e(LOG_TAG, "[PhoneProxy] " + msg);
+ }
+
+ private void updatePhoneObject(int newVoiceRadioTech) {
+
+ if (mActivePhone != null) {
+ if(mRilVersion == 6 && getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE) {
+ /*
+ * On v6 RIL, when LTE_ON_CDMA is TRUE, always create CDMALTEPhone
+ * irrespective of the voice radio tech reported.
+ */
+ if (mActivePhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+ logd("LTE ON CDMA property is set. Use CDMA Phone" +
+ " newVoiceRadioTech = " + newVoiceRadioTech +
+ " Active Phone = " + mActivePhone.getPhoneName());
+ return;
+ } else {
+ logd("LTE ON CDMA property is set. Switch to CDMALTEPhone" +
+ " newVoiceRadioTech = " + newVoiceRadioTech +
+ " Active Phone = " + mActivePhone.getPhoneName());
+ newVoiceRadioTech = ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT;
+ }
+ } else {
+ if ((ServiceState.isCdma(newVoiceRadioTech) &&
+ mActivePhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) ||
+ (ServiceState.isGsm(newVoiceRadioTech) &&
+ mActivePhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)) {
+ // Nothing changed. Keep phone as it is.
+ logd("Ignoring voice radio technology changed message." +
+ " newVoiceRadioTech = " + newVoiceRadioTech +
+ " Active Phone = " + mActivePhone.getPhoneName());
+ return;
+ }
+ }
+ }
+
+ if (newVoiceRadioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
+ // We need some voice phone object to be active always, so never
+ // delete the phone without anything to replace it with!
+ logd("Ignoring voice radio technology changed message. newVoiceRadioTech = Unknown."
+ + " Active Phone = " + mActivePhone.getPhoneName());
+ return;
+ }
+
+ boolean oldPowerState = false; // old power state to off
+ if (mResetModemOnRadioTechnologyChange) {
+ if (mCommandsInterface.getRadioState().isOn()) {
+ oldPowerState = true;
+ logd("Setting Radio Power to Off");
+ mCommandsInterface.setRadioPower(false, null);
+ }
+ }
+
+ deleteAndCreatePhone(newVoiceRadioTech);
+
+ if (mResetModemOnRadioTechnologyChange && oldPowerState) { // restore power state
+ logd("Resetting Radio");
+ mCommandsInterface.setRadioPower(oldPowerState, null);
+ }
+
+ // Set the new interfaces in the proxy's
+ mIccSmsInterfaceManagerProxy.setmIccSmsInterfaceManager(
+ mActivePhone.getIccSmsInterfaceManager());
+ mIccPhoneBookInterfaceManagerProxy.setmIccPhoneBookInterfaceManager(mActivePhone
+ .getIccPhoneBookInterfaceManager());
+ mPhoneSubInfoProxy.setmPhoneSubInfo(this.mActivePhone.getPhoneSubInfo());
+
+ mCommandsInterface = ((PhoneBase)mActivePhone).mCM;
+
+ // Send an Intent to the PhoneApp that we had a radio technology change
+ Intent intent = new Intent(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(PhoneConstants.PHONE_NAME_KEY, mActivePhone.getPhoneName());
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+
+ }
+
+ private void deleteAndCreatePhone(int newVoiceRadioTech) {
+
+ String outgoingPhoneName = "Unknown";
+ Phone oldPhone = mActivePhone;
+
+ if (oldPhone != null) {
+ outgoingPhoneName = ((PhoneBase) oldPhone).getPhoneName();
+ }
+
+ logd("Switching Voice Phone : " + outgoingPhoneName + " >>> "
+ + (ServiceState.isGsm(newVoiceRadioTech) ? "GSM" : "CDMA"));
+
+ if (oldPhone != null) {
+ CallManager.getInstance().unregisterPhone(oldPhone);
+ logd("Disposing old phone..");
+ oldPhone.dispose();
+ }
+
+ // Give the garbage collector a hint to start the garbage collection
+ // asap NOTE this has been disabled since radio technology change could
+ // happen during e.g. a multimedia playing and could slow the system.
+ // Tests needs to be done to see the effects of the GC call here when
+ // system is busy.
+ // System.gc();
+
+ if (ServiceState.isCdma(newVoiceRadioTech)) {
+ mActivePhone = PhoneFactory.getCdmaPhone();
+ } else if (ServiceState.isGsm(newVoiceRadioTech)) {
+ mActivePhone = PhoneFactory.getGsmPhone();
+ }
+
+ if (oldPhone != null) {
+ oldPhone.removeReferences();
+ }
+
+ if(mActivePhone != null) {
+ CallManager.getInstance().registerPhone(mActivePhone);
+ }
+
+ oldPhone = null;
+ }
+
+ public ServiceState getServiceState() {
+ return mActivePhone.getServiceState();
+ }
+
+ public CellLocation getCellLocation() {
+ return mActivePhone.getCellLocation();
+ }
+
+ public PhoneConstants.DataState getDataConnectionState() {
+ return mActivePhone.getDataConnectionState(PhoneConstants.APN_TYPE_DEFAULT);
+ }
+
+ public PhoneConstants.DataState getDataConnectionState(String apnType) {
+ return mActivePhone.getDataConnectionState(apnType);
+ }
+
+ public DataActivityState getDataActivityState() {
+ return mActivePhone.getDataActivityState();
+ }
+
+ public Context getContext() {
+ return mActivePhone.getContext();
+ }
+
+ public void disableDnsCheck(boolean b) {
+ mActivePhone.disableDnsCheck(b);
+ }
+
+ public boolean isDnsCheckDisabled() {
+ return mActivePhone.isDnsCheckDisabled();
+ }
+
+ public PhoneConstants.State getState() {
+ return mActivePhone.getState();
+ }
+
+ public String getPhoneName() {
+ return mActivePhone.getPhoneName();
+ }
+
+ public int getPhoneType() {
+ return mActivePhone.getPhoneType();
+ }
+
+ public String[] getActiveApnTypes() {
+ return mActivePhone.getActiveApnTypes();
+ }
+
+ public String getActiveApnHost(String apnType) {
+ return mActivePhone.getActiveApnHost(apnType);
+ }
+
+ public LinkProperties getLinkProperties(String apnType) {
+ return mActivePhone.getLinkProperties(apnType);
+ }
+
+ public LinkCapabilities getLinkCapabilities(String apnType) {
+ return mActivePhone.getLinkCapabilities(apnType);
+ }
+
+ public SignalStrength getSignalStrength() {
+ return mActivePhone.getSignalStrength();
+ }
+
+ public void registerForUnknownConnection(Handler h, int what, Object obj) {
+ mActivePhone.registerForUnknownConnection(h, what, obj);
+ }
+
+ public void unregisterForUnknownConnection(Handler h) {
+ mActivePhone.unregisterForUnknownConnection(h);
+ }
+
+ public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) {
+ mActivePhone.registerForPreciseCallStateChanged(h, what, obj);
+ }
+
+ public void unregisterForPreciseCallStateChanged(Handler h) {
+ mActivePhone.unregisterForPreciseCallStateChanged(h);
+ }
+
+ public void registerForNewRingingConnection(Handler h, int what, Object obj) {
+ mActivePhone.registerForNewRingingConnection(h, what, obj);
+ }
+
+ public void unregisterForNewRingingConnection(Handler h) {
+ mActivePhone.unregisterForNewRingingConnection(h);
+ }
+
+ public void registerForIncomingRing(Handler h, int what, Object obj) {
+ mActivePhone.registerForIncomingRing(h, what, obj);
+ }
+
+ public void unregisterForIncomingRing(Handler h) {
+ mActivePhone.unregisterForIncomingRing(h);
+ }
+
+ public void registerForDisconnect(Handler h, int what, Object obj) {
+ mActivePhone.registerForDisconnect(h, what, obj);
+ }
+
+ public void unregisterForDisconnect(Handler h) {
+ mActivePhone.unregisterForDisconnect(h);
+ }
+
+ public void registerForMmiInitiate(Handler h, int what, Object obj) {
+ mActivePhone.registerForMmiInitiate(h, what, obj);
+ }
+
+ public void unregisterForMmiInitiate(Handler h) {
+ mActivePhone.unregisterForMmiInitiate(h);
+ }
+
+ public void registerForMmiComplete(Handler h, int what, Object obj) {
+ mActivePhone.registerForMmiComplete(h, what, obj);
+ }
+
+ public void unregisterForMmiComplete(Handler h) {
+ mActivePhone.unregisterForMmiComplete(h);
+ }
+
+ public List<? extends MmiCode> getPendingMmiCodes() {
+ return mActivePhone.getPendingMmiCodes();
+ }
+
+ public void sendUssdResponse(String ussdMessge) {
+ mActivePhone.sendUssdResponse(ussdMessge);
+ }
+
+ public void registerForServiceStateChanged(Handler h, int what, Object obj) {
+ mActivePhone.registerForServiceStateChanged(h, what, obj);
+ }
+
+ public void unregisterForServiceStateChanged(Handler h) {
+ mActivePhone.unregisterForServiceStateChanged(h);
+ }
+
+ public void registerForSuppServiceNotification(Handler h, int what, Object obj) {
+ mActivePhone.registerForSuppServiceNotification(h, what, obj);
+ }
+
+ public void unregisterForSuppServiceNotification(Handler h) {
+ mActivePhone.unregisterForSuppServiceNotification(h);
+ }
+
+ public void registerForSuppServiceFailed(Handler h, int what, Object obj) {
+ mActivePhone.registerForSuppServiceFailed(h, what, obj);
+ }
+
+ public void unregisterForSuppServiceFailed(Handler h) {
+ mActivePhone.unregisterForSuppServiceFailed(h);
+ }
+
+ public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){
+ mActivePhone.registerForInCallVoicePrivacyOn(h,what,obj);
+ }
+
+ public void unregisterForInCallVoicePrivacyOn(Handler h){
+ mActivePhone.unregisterForInCallVoicePrivacyOn(h);
+ }
+
+ public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){
+ mActivePhone.registerForInCallVoicePrivacyOff(h,what,obj);
+ }
+
+ public void unregisterForInCallVoicePrivacyOff(Handler h){
+ mActivePhone.unregisterForInCallVoicePrivacyOff(h);
+ }
+
+ public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj) {
+ mActivePhone.registerForCdmaOtaStatusChange(h,what,obj);
+ }
+
+ public void unregisterForCdmaOtaStatusChange(Handler h) {
+ mActivePhone.unregisterForCdmaOtaStatusChange(h);
+ }
+
+ public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
+ mActivePhone.registerForSubscriptionInfoReady(h, what, obj);
+ }
+
+ public void unregisterForSubscriptionInfoReady(Handler h) {
+ mActivePhone.unregisterForSubscriptionInfoReady(h);
+ }
+
+ public void registerForEcmTimerReset(Handler h, int what, Object obj) {
+ mActivePhone.registerForEcmTimerReset(h,what,obj);
+ }
+
+ public void unregisterForEcmTimerReset(Handler h) {
+ mActivePhone.unregisterForEcmTimerReset(h);
+ }
+
+ public void registerForRingbackTone(Handler h, int what, Object obj) {
+ mActivePhone.registerForRingbackTone(h,what,obj);
+ }
+
+ public void unregisterForRingbackTone(Handler h) {
+ mActivePhone.unregisterForRingbackTone(h);
+ }
+
+ public void registerForResendIncallMute(Handler h, int what, Object obj) {
+ mActivePhone.registerForResendIncallMute(h,what,obj);
+ }
+
+ public void unregisterForResendIncallMute(Handler h) {
+ mActivePhone.unregisterForResendIncallMute(h);
+ }
+
+ public boolean getIccRecordsLoaded() {
+ return mActivePhone.getIccRecordsLoaded();
+ }
+
+ public IccCard getIccCard() {
+ return mActivePhone.getIccCard();
+ }
+
+ public void acceptCall() throws CallStateException {
+ mActivePhone.acceptCall();
+ }
+
+ public void rejectCall() throws CallStateException {
+ mActivePhone.rejectCall();
+ }
+
+ public void switchHoldingAndActive() throws CallStateException {
+ mActivePhone.switchHoldingAndActive();
+ }
+
+ public boolean canConference() {
+ return mActivePhone.canConference();
+ }
+
+ public void conference() throws CallStateException {
+ mActivePhone.conference();
+ }
+
+ public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
+ mActivePhone.enableEnhancedVoicePrivacy(enable, onComplete);
+ }
+
+ public void getEnhancedVoicePrivacy(Message onComplete) {
+ mActivePhone.getEnhancedVoicePrivacy(onComplete);
+ }
+
+ public boolean canTransfer() {
+ return mActivePhone.canTransfer();
+ }
+
+ public void explicitCallTransfer() throws CallStateException {
+ mActivePhone.explicitCallTransfer();
+ }
+
+ public void clearDisconnected() {
+ mActivePhone.clearDisconnected();
+ }
+
+ public Call getForegroundCall() {
+ return mActivePhone.getForegroundCall();
+ }
+
+ public Call getBackgroundCall() {
+ return mActivePhone.getBackgroundCall();
+ }
+
+ public Call getRingingCall() {
+ return mActivePhone.getRingingCall();
+ }
+
+ public Connection dial(String dialString) throws CallStateException {
+ return mActivePhone.dial(dialString);
+ }
+
+ public Connection dial(String dialString, UUSInfo uusInfo) throws CallStateException {
+ return mActivePhone.dial(dialString, uusInfo);
+ }
+
+ public boolean handlePinMmi(String dialString) {
+ return mActivePhone.handlePinMmi(dialString);
+ }
+
+ public boolean handleInCallMmiCommands(String command) throws CallStateException {
+ return mActivePhone.handleInCallMmiCommands(command);
+ }
+
+ public void sendDtmf(char c) {
+ mActivePhone.sendDtmf(c);
+ }
+
+ public void startDtmf(char c) {
+ mActivePhone.startDtmf(c);
+ }
+
+ public void stopDtmf() {
+ mActivePhone.stopDtmf();
+ }
+
+ public void setRadioPower(boolean power) {
+ mActivePhone.setRadioPower(power);
+ }
+
+ public boolean getMessageWaitingIndicator() {
+ return mActivePhone.getMessageWaitingIndicator();
+ }
+
+ public boolean getCallForwardingIndicator() {
+ return mActivePhone.getCallForwardingIndicator();
+ }
+
+ public String getLine1Number() {
+ return mActivePhone.getLine1Number();
+ }
+
+ public String getCdmaMin() {
+ return mActivePhone.getCdmaMin();
+ }
+
+ public boolean isMinInfoReady() {
+ return mActivePhone.isMinInfoReady();
+ }
+
+ public String getCdmaPrlVersion() {
+ return mActivePhone.getCdmaPrlVersion();
+ }
+
+ public String getLine1AlphaTag() {
+ return mActivePhone.getLine1AlphaTag();
+ }
+
+ public void setLine1Number(String alphaTag, String number, Message onComplete) {
+ mActivePhone.setLine1Number(alphaTag, number, onComplete);
+ }
+
+ public String getVoiceMailNumber() {
+ return mActivePhone.getVoiceMailNumber();
+ }
+
+ /** @hide */
+ public int getVoiceMessageCount(){
+ return mActivePhone.getVoiceMessageCount();
+ }
+
+ public String getVoiceMailAlphaTag() {
+ return mActivePhone.getVoiceMailAlphaTag();
+ }
+
+ public void setVoiceMailNumber(String alphaTag,String voiceMailNumber,
+ Message onComplete) {
+ mActivePhone.setVoiceMailNumber(alphaTag, voiceMailNumber, onComplete);
+ }
+
+ public void getCallForwardingOption(int commandInterfaceCFReason,
+ Message onComplete) {
+ mActivePhone.getCallForwardingOption(commandInterfaceCFReason,
+ onComplete);
+ }
+
+ public void setCallForwardingOption(int commandInterfaceCFReason,
+ int commandInterfaceCFAction, String dialingNumber,
+ int timerSeconds, Message onComplete) {
+ mActivePhone.setCallForwardingOption(commandInterfaceCFReason,
+ commandInterfaceCFAction, dialingNumber, timerSeconds, onComplete);
+ }
+
+ public void getOutgoingCallerIdDisplay(Message onComplete) {
+ mActivePhone.getOutgoingCallerIdDisplay(onComplete);
+ }
+
+ public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
+ Message onComplete) {
+ mActivePhone.setOutgoingCallerIdDisplay(commandInterfaceCLIRMode,
+ onComplete);
+ }
+
+ public void getCallWaiting(Message onComplete) {
+ mActivePhone.getCallWaiting(onComplete);
+ }
+
+ public void setCallWaiting(boolean enable, Message onComplete) {
+ mActivePhone.setCallWaiting(enable, onComplete);
+ }
+
+ public void getAvailableNetworks(Message response) {
+ mActivePhone.getAvailableNetworks(response);
+ }
+
+ public void setNetworkSelectionModeAutomatic(Message response) {
+ mActivePhone.setNetworkSelectionModeAutomatic(response);
+ }
+
+ public void selectNetworkManually(OperatorInfo network, Message response) {
+ mActivePhone.selectNetworkManually(network, response);
+ }
+
+ public void setPreferredNetworkType(int networkType, Message response) {
+ mActivePhone.setPreferredNetworkType(networkType, response);
+ }
+
+ public void getPreferredNetworkType(Message response) {
+ mActivePhone.getPreferredNetworkType(response);
+ }
+
+ public void getNeighboringCids(Message response) {
+ mActivePhone.getNeighboringCids(response);
+ }
+
+ public void setOnPostDialCharacter(Handler h, int what, Object obj) {
+ mActivePhone.setOnPostDialCharacter(h, what, obj);
+ }
+
+ public void setMute(boolean muted) {
+ mActivePhone.setMute(muted);
+ }
+
+ public boolean getMute() {
+ return mActivePhone.getMute();
+ }
+
+ public void setEchoSuppressionEnabled(boolean enabled) {
+ mActivePhone.setEchoSuppressionEnabled(enabled);
+ }
+
+ public void invokeOemRilRequestRaw(byte[] data, Message response) {
+ mActivePhone.invokeOemRilRequestRaw(data, response);
+ }
+
+ public void invokeOemRilRequestStrings(String[] strings, Message response) {
+ mActivePhone.invokeOemRilRequestStrings(strings, response);
+ }
+
+ public void getDataCallList(Message response) {
+ mActivePhone.getDataCallList(response);
+ }
+
+ public void updateServiceLocation() {
+ mActivePhone.updateServiceLocation();
+ }
+
+ public void enableLocationUpdates() {
+ mActivePhone.enableLocationUpdates();
+ }
+
+ public void disableLocationUpdates() {
+ mActivePhone.disableLocationUpdates();
+ }
+
+ public void setUnitTestMode(boolean f) {
+ mActivePhone.setUnitTestMode(f);
+ }
+
+ public boolean getUnitTestMode() {
+ return mActivePhone.getUnitTestMode();
+ }
+
+ public void setBandMode(int bandMode, Message response) {
+ mActivePhone.setBandMode(bandMode, response);
+ }
+
+ public void queryAvailableBandMode(Message response) {
+ mActivePhone.queryAvailableBandMode(response);
+ }
+
+ public boolean getDataRoamingEnabled() {
+ return mActivePhone.getDataRoamingEnabled();
+ }
+
+ public void setDataRoamingEnabled(boolean enable) {
+ mActivePhone.setDataRoamingEnabled(enable);
+ }
+
+ public void queryCdmaRoamingPreference(Message response) {
+ mActivePhone.queryCdmaRoamingPreference(response);
+ }
+
+ public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+ mActivePhone.setCdmaRoamingPreference(cdmaRoamingType, response);
+ }
+
+ public void setCdmaSubscription(int cdmaSubscriptionType, Message response) {
+ mActivePhone.setCdmaSubscription(cdmaSubscriptionType, response);
+ }
+
+ public SimulatedRadioControl getSimulatedRadioControl() {
+ return mActivePhone.getSimulatedRadioControl();
+ }
+
+ public int enableApnType(String type) {
+ return mActivePhone.enableApnType(type);
+ }
+
+ public int disableApnType(String type) {
+ return mActivePhone.disableApnType(type);
+ }
+
+ public boolean isDataConnectivityPossible() {
+ return mActivePhone.isDataConnectivityPossible(PhoneConstants.APN_TYPE_DEFAULT);
+ }
+
+ public boolean isDataConnectivityPossible(String apnType) {
+ return mActivePhone.isDataConnectivityPossible(apnType);
+ }
+
+ public String getDeviceId() {
+ return mActivePhone.getDeviceId();
+ }
+
+ public String getDeviceSvn() {
+ return mActivePhone.getDeviceSvn();
+ }
+
+ public String getSubscriberId() {
+ return mActivePhone.getSubscriberId();
+ }
+
+ public String getIccSerialNumber() {
+ return mActivePhone.getIccSerialNumber();
+ }
+
+ public String getEsn() {
+ return mActivePhone.getEsn();
+ }
+
+ public String getMeid() {
+ return mActivePhone.getMeid();
+ }
+
+ public String getMsisdn() {
+ return mActivePhone.getMsisdn();
+ }
+
+ public String getImei() {
+ return mActivePhone.getImei();
+ }
+
+ public PhoneSubInfo getPhoneSubInfo(){
+ return mActivePhone.getPhoneSubInfo();
+ }
+
+ public IccSmsInterfaceManager getIccSmsInterfaceManager(){
+ return mActivePhone.getIccSmsInterfaceManager();
+ }
+
+ public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
+ return mActivePhone.getIccPhoneBookInterfaceManager();
+ }
+
+ public void setTTYMode(int ttyMode, Message onComplete) {
+ mActivePhone.setTTYMode(ttyMode, onComplete);
+ }
+
+ public void queryTTYMode(Message onComplete) {
+ mActivePhone.queryTTYMode(onComplete);
+ }
+
+ public void activateCellBroadcastSms(int activate, Message response) {
+ mActivePhone.activateCellBroadcastSms(activate, response);
+ }
+
+ public void getCellBroadcastSmsConfig(Message response) {
+ mActivePhone.getCellBroadcastSmsConfig(response);
+ }
+
+ public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
+ mActivePhone.setCellBroadcastSmsConfig(configValuesArray, response);
+ }
+
+ public void notifyDataActivity() {
+ mActivePhone.notifyDataActivity();
+ }
+
+ public void getSmscAddress(Message result) {
+ mActivePhone.getSmscAddress(result);
+ }
+
+ public void setSmscAddress(String address, Message result) {
+ mActivePhone.setSmscAddress(address, result);
+ }
+
+ public int getCdmaEriIconIndex() {
+ return mActivePhone.getCdmaEriIconIndex();
+ }
+
+ public String getCdmaEriText() {
+ return mActivePhone.getCdmaEriText();
+ }
+
+ public int getCdmaEriIconMode() {
+ return mActivePhone.getCdmaEriIconMode();
+ }
+
+ public Phone getActivePhone() {
+ return mActivePhone;
+ }
+
+ public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete){
+ mActivePhone.sendBurstDtmf(dtmfString, on, off, onComplete);
+ }
+
+ public void exitEmergencyCallbackMode(){
+ mActivePhone.exitEmergencyCallbackMode();
+ }
+
+ public boolean needsOtaServiceProvisioning(){
+ return mActivePhone.needsOtaServiceProvisioning();
+ }
+
+ public boolean isOtaSpNumber(String dialStr){
+ return mActivePhone.isOtaSpNumber(dialStr);
+ }
+
+ public void registerForCallWaiting(Handler h, int what, Object obj){
+ mActivePhone.registerForCallWaiting(h,what,obj);
+ }
+
+ public void unregisterForCallWaiting(Handler h){
+ mActivePhone.unregisterForCallWaiting(h);
+ }
+
+ public void registerForSignalInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForSignalInfo(h,what,obj);
+ }
+
+ public void unregisterForSignalInfo(Handler h) {
+ mActivePhone.unregisterForSignalInfo(h);
+ }
+
+ public void registerForDisplayInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForDisplayInfo(h,what,obj);
+ }
+
+ public void unregisterForDisplayInfo(Handler h) {
+ mActivePhone.unregisterForDisplayInfo(h);
+ }
+
+ public void registerForNumberInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForNumberInfo(h, what, obj);
+ }
+
+ public void unregisterForNumberInfo(Handler h) {
+ mActivePhone.unregisterForNumberInfo(h);
+ }
+
+ public void registerForRedirectedNumberInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForRedirectedNumberInfo(h, what, obj);
+ }
+
+ public void unregisterForRedirectedNumberInfo(Handler h) {
+ mActivePhone.unregisterForRedirectedNumberInfo(h);
+ }
+
+ public void registerForLineControlInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForLineControlInfo( h, what, obj);
+ }
+
+ public void unregisterForLineControlInfo(Handler h) {
+ mActivePhone.unregisterForLineControlInfo(h);
+ }
+
+ public void registerFoT53ClirlInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerFoT53ClirlInfo(h, what, obj);
+ }
+
+ public void unregisterForT53ClirInfo(Handler h) {
+ mActivePhone.unregisterForT53ClirInfo(h);
+ }
+
+ public void registerForT53AudioControlInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForT53AudioControlInfo( h, what, obj);
+ }
+
+ public void unregisterForT53AudioControlInfo(Handler h) {
+ mActivePhone.unregisterForT53AudioControlInfo(h);
+ }
+
+ public void setOnEcbModeExitResponse(Handler h, int what, Object obj){
+ mActivePhone.setOnEcbModeExitResponse(h,what,obj);
+ }
+
+ public void unsetOnEcbModeExitResponse(Handler h){
+ mActivePhone.unsetOnEcbModeExitResponse(h);
+ }
+
+ public boolean isCspPlmnEnabled() {
+ return mActivePhone.isCspPlmnEnabled();
+ }
+
+ public IsimRecords getIsimRecords() {
+ return mActivePhone.getIsimRecords();
+ }
+
+ public void requestIsimAuthentication(String nonce, Message response) {
+ mActivePhone.requestIsimAuthentication(nonce, response);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getLteOnCdmaMode() {
+ return mActivePhone.getLteOnCdmaMode();
+ }
+
+ @Override
+ public void setVoiceMessageWaiting(int line, int countWaiting) {
+ mActivePhone.setVoiceMessageWaiting(line, countWaiting);
+ }
+
+ @Override
+ public UsimServiceTable getUsimServiceTable() {
+ return mActivePhone.getUsimServiceTable();
+ }
+
+ public void dispose() {
+ mCommandsInterface.unregisterForOn(this);
+ mCommandsInterface.unregisterForVoiceRadioTechChanged(this);
+ mCommandsInterface.unregisterForRilConnected(this);
+ }
+
+ public void removeReferences() {
+ mActivePhone = null;
+ mCommandsInterface = null;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/PhoneStateIntentReceiver.java b/src/java/com/android/internal/telephony/PhoneStateIntentReceiver.java
new file mode 100644
index 0000000..89084ac
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneStateIntentReceiver.java
@@ -0,0 +1,203 @@
+/*
+ * 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.os.Handler;
+import android.os.Message;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+/**
+ *
+ * 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 = "PHONE";
+ private static final boolean DBG = false;
+
+ 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;
+
+ PhoneConstants.State mPhoneState = PhoneConstants.State.IDLE;
+ ServiceState mServiceState = new ServiceState();
+ SignalStrength mSignalStrength = new SignalStrength();
+
+ 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 PhoneConstants.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 as an asu 0..31
+ *
+ * Throws RuntimeException if client has not called notifySignalStrength()
+ */
+ public int getSignalStrengthLevelAsu() {
+ // TODO: use new SignalStrength instead of asu
+ if ((mWants & NOTIF_SIGNAL) == 0) {
+ throw new RuntimeException
+ ("client must call notifySignalStrength(int)");
+ }
+ return mSignalStrength.getAsuLevel();
+ }
+
+ /**
+ * 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)");
+ }
+ return mSignalStrength.getDbm();
+ }
+
+ public void notifyPhoneCallState(int eventWhat) {
+ mWants |= NOTIF_PHONE;
+ mPhoneStateEventWhat = eventWhat;
+ mFilter.addAction(TelephonyManager.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)) {
+ mSignalStrength = SignalStrength.newFromBundle(intent.getExtras());
+
+ if (mTarget != null && getNotifySignalStrength()) {
+ Message message = Message.obtain(mTarget, mAsuEventWhat);
+ mTarget.sendMessage(message);
+ }
+ } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
+ if (DBG) Log.d(LOG_TAG, "onReceiveIntent: ACTION_PHONE_STATE_CHANGED, state="
+ + intent.getStringExtra(PhoneConstants.STATE_KEY));
+ String phoneState = intent.getStringExtra(PhoneConstants.STATE_KEY);
+ mPhoneState = (PhoneConstants.State) Enum.valueOf(
+ PhoneConstants.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/src/java/com/android/internal/telephony/PhoneSubInfo.java b/src/java/com/android/internal/telephony/PhoneSubInfo.java
new file mode 100755
index 0000000..e8449ce
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneSubInfo.java
@@ -0,0 +1,206 @@
+/*
+ * 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 java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.ims.IsimRecords;
+
+public class PhoneSubInfo extends IPhoneSubInfo.Stub {
+ static final String LOG_TAG = "PHONE";
+ private Phone mPhone;
+ private Context mContext;
+ private static final String READ_PHONE_STATE =
+ android.Manifest.permission.READ_PHONE_STATE;
+ // TODO: change getCompleteVoiceMailNumber() to require READ_PRIVILEGED_PHONE_STATE
+ private static final String CALL_PRIVILEGED =
+ android.Manifest.permission.CALL_PRIVILEGED;
+ private static final String READ_PRIVILEGED_PHONE_STATE =
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+
+ public PhoneSubInfo(Phone phone) {
+ mPhone = phone;
+ mContext = phone.getContext();
+ }
+
+ public void dispose() {
+ }
+
+ protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
+ Log.d(LOG_TAG, "PhoneSubInfo finalized");
+ }
+
+ /**
+ * Retrieves the unique device ID, e.g., IMEI for GSM phones and MEID for CDMA 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 subscriber 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 ICC, if applicable.
+ */
+ public String getIccSerialNumber() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return mPhone.getIccSerialNumber();
+ }
+
+ /**
+ * 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 MSISDN string.
+ */
+ public String getMsisdn() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ return mPhone.getMsisdn();
+ }
+
+ /**
+ * Retrieves the voice mail number.
+ */
+ public String getVoiceMailNumber() {
+ mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
+ String number = PhoneNumberUtils.extractNetworkPortion(mPhone.getVoiceMailNumber());
+ Log.d(LOG_TAG, "VM: PhoneSubInfo.getVoiceMailNUmber: "); // + number);
+ return number;
+ }
+
+ /**
+ * Retrieves the complete voice mail number.
+ *
+ * @hide
+ */
+ public String getCompleteVoiceMailNumber() {
+ mContext.enforceCallingOrSelfPermission(CALL_PRIVILEGED,
+ "Requires CALL_PRIVILEGED");
+ String number = mPhone.getVoiceMailNumber();
+ Log.d(LOG_TAG, "VM: PhoneSubInfo.getCompleteVoiceMailNUmber: "); // + number);
+ return number;
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
+ * @return the IMPI, or null if not present or not loaded
+ */
+ public String getIsimImpi() {
+ mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+ "Requires READ_PRIVILEGED_PHONE_STATE");
+ IsimRecords isim = mPhone.getIsimRecords();
+ if (isim != null) {
+ return isim.getIsimImpi();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the IMS home network domain name that was loaded from the ISIM.
+ * @return the IMS domain name, or null if not present or not loaded
+ */
+ public String getIsimDomain() {
+ mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+ "Requires READ_PRIVILEGED_PHONE_STATE");
+ IsimRecords isim = mPhone.getIsimRecords();
+ if (isim != null) {
+ return isim.getIsimDomain();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the IMS public user identities (IMPU) that were loaded from the ISIM.
+ * @return an array of IMPU strings, with one IMPU per string, or null if
+ * not present or not loaded
+ */
+ public String[] getIsimImpu() {
+ mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+ "Requires READ_PRIVILEGED_PHONE_STATE");
+ IsimRecords isim = mPhone.getIsimRecords();
+ if (isim != null) {
+ return isim.getIsimImpu();
+ } else {
+ return null;
+ }
+ }
+
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump PhoneSubInfo from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ pw.println("Phone Subscriber Info:");
+ pw.println(" Phone Type = " + mPhone.getPhoneName());
+ pw.println(" Device ID = " + mPhone.getDeviceId());
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoProxy.java b/src/java/com/android/internal/telephony/PhoneSubInfoProxy.java
new file mode 100755
index 0000000..bd130de
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoProxy.java
@@ -0,0 +1,133 @@
+/*
+ * 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.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.ServiceManager;
+
+
+public class PhoneSubInfoProxy extends IPhoneSubInfo.Stub {
+ private PhoneSubInfo mPhoneSubInfo;
+
+ public PhoneSubInfoProxy(PhoneSubInfo phoneSubInfo) {
+ mPhoneSubInfo = phoneSubInfo;
+ if(ServiceManager.getService("iphonesubinfo") == null) {
+ ServiceManager.addService("iphonesubinfo", this);
+ }
+ }
+
+ public void setmPhoneSubInfo(PhoneSubInfo phoneSubInfo) {
+ this.mPhoneSubInfo = phoneSubInfo;
+ }
+
+ public String getDeviceId() {
+ return mPhoneSubInfo.getDeviceId();
+ }
+
+ public String getDeviceSvn() {
+ return mPhoneSubInfo.getDeviceSvn();
+ }
+
+ /**
+ * Retrieves the unique subscriber ID, e.g., IMSI for GSM phones.
+ */
+ public String getSubscriberId() {
+ return mPhoneSubInfo.getSubscriberId();
+ }
+
+ /**
+ * Retrieves the serial number of the ICC, if applicable.
+ */
+ public String getIccSerialNumber() {
+ return mPhoneSubInfo.getIccSerialNumber();
+ }
+
+ /**
+ * Retrieves the phone number string for line 1.
+ */
+ public String getLine1Number() {
+ return mPhoneSubInfo.getLine1Number();
+ }
+
+ /**
+ * Retrieves the alpha identifier for line 1.
+ */
+ public String getLine1AlphaTag() {
+ return mPhoneSubInfo.getLine1AlphaTag();
+ }
+
+ /**
+ * Retrieves the MSISDN Number.
+ */
+ public String getMsisdn() {
+ return mPhoneSubInfo.getMsisdn();
+ }
+
+ /**
+ * Retrieves the voice mail number.
+ */
+ public String getVoiceMailNumber() {
+ return mPhoneSubInfo.getVoiceMailNumber();
+ }
+
+ /**
+ * Retrieves the complete voice mail number.
+ */
+ public String getCompleteVoiceMailNumber() {
+ return mPhoneSubInfo.getCompleteVoiceMailNumber();
+ }
+
+ /**
+ * Retrieves the alpha identifier associated with the voice mail number.
+ */
+ public String getVoiceMailAlphaTag() {
+ return mPhoneSubInfo.getVoiceMailAlphaTag();
+ }
+
+ /**
+ * Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
+ * @return the IMPI, or null if not present or not loaded
+ */
+ public String getIsimImpi() {
+ return mPhoneSubInfo.getIsimImpi();
+ }
+
+ /**
+ * Returns the IMS home network domain name that was loaded from the ISIM.
+ * @return the IMS domain name, or null if not present or not loaded
+ */
+ public String getIsimDomain() {
+ return mPhoneSubInfo.getIsimDomain();
+ }
+
+ /**
+ * Returns the IMS public user identities (IMPU) that were loaded from the ISIM.
+ * @return an array of IMPU strings, with one IMPU per string, or null if
+ * not present or not loaded
+ */
+ public String[] getIsimImpu() {
+ return mPhoneSubInfo.getIsimImpu();
+ }
+
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mPhoneSubInfo.dump(fd, pw, args);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
new file mode 100644
index 0000000..b14f6c8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -0,0 +1,3861 @@
+/*
+ * 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 static com.android.internal.telephony.RILConstants.*;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_EDGE;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_GPRS;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_UMTS;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_HSDPA;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_HSUPA;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_HSPA;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.PowerManager.WakeLock;
+import android.telephony.NeighboringCellInfo;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+import com.android.internal.telephony.gsm.SuppServiceNotification;
+import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
+import com.android.internal.telephony.cdma.CdmaInformationRecords;
+import com.android.internal.telephony.IccRefreshResponse;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * {@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++;
+ mResult = null;
+ }
+ }
+ }
+
+ 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, Object ret) {
+ CommandException ex;
+
+ ex = CommandException.fromRilErrno(error);
+
+ if (RIL.RILJ_LOGD) Log.d(LOG_TAG, serialString() + "< "
+ + RIL.requestToString(mRequest)
+ + " error: " + ex);
+
+ if (mResult != null) {
+ AsyncResult.forMessage(mResult, ret, 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";
+ static final boolean RILJ_LOGD = true;
+ static final boolean RILJ_LOGV = false; // STOP SHIP if true
+
+ /**
+ * Wake lock timeout should be longer than the longest timeout in
+ * the vendor ril.
+ */
+ private static final int DEFAULT_WAKE_LOCK_TIMEOUT = 60000;
+
+ //***** Instance Variables
+
+ LocalSocket mSocket;
+ HandlerThread mSenderThread;
+ RILSender mSender;
+ Thread mReceiverThread;
+ RILReceiver mReceiver;
+ WakeLock mWakeLock;
+ int mWakeLockTimeout;
+ // The number of requests pending to be sent out, it increases before calling
+ // EVENT_SEND and decreases while handling EVENT_SEND. It gets cleared while
+ // WAKE_LOCK_TIMEOUT occurs.
+ int mRequestMessagesPending;
+ // The number of requests sent out but waiting for response. It increases while
+ // sending request and decreases while handling response. It should match
+ // mRequestList.size() unless there are requests no replied while
+ // WAKE_LOCK_TIMEOUT occurs.
+ int mRequestMessagesWaiting;
+
+ //I'd rather this be LinkedList or something
+ ArrayList<RILRequest> mRequestsList = new ArrayList<RILRequest>();
+
+ Object mLastNITZTimeInfo;
+
+ // When we are testing emergency calls
+ AtomicBoolean mTestingEmergencyCall = new AtomicBoolean(false);
+
+ //***** 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;
+
+ // The number of the required config values for broadcast SMS stored in the C struct
+ // RIL_CDMA_BroadcastServiceInfo
+ private static final int CDMA_BSI_NO_OF_INTS_STRUCT = 3;
+
+ private static final int CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES = 31;
+
+ BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ 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 extends Handler implements Runnable {
+ public RILSender(Looper looper) {
+ super(looper);
+ }
+
+ // Only allocated once
+ byte[] dataLength = new byte[4];
+
+ //***** Runnable implementation
+ public void
+ run() {
+ //setup if needed
+ }
+
+
+ //***** Handler implementation
+ @Override 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, null);
+ rr.release();
+ if (mRequestMessagesPending > 0)
+ mRequestMessagesPending--;
+ alreadySubtracted = true;
+ return;
+ }
+
+ synchronized (mRequestsList) {
+ mRequestsList.add(rr);
+ mRequestMessagesWaiting++;
+ }
+
+ if (mRequestMessagesPending > 0)
+ 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, null);
+ 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, null);
+ rr.release();
+ }
+ } finally {
+ // Note: We are "Done" only if there are no outstanding
+ // requests or replies. Thus this code path will only release
+ // the wake lock on errors.
+ releaseWakeLockIfDone();
+ }
+
+ if (!alreadySubtracted && mRequestMessagesPending > 0) {
+ 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.
+ synchronized (mWakeLock) {
+ if (mWakeLock.isHeld()) {
+ // The timer of WAKE_LOCK_TIMEOUT is reset with each
+ // new send request. So when WAKE_LOCK_TIMEOUT occurs
+ // all requests in mRequestList already waited at
+ // least DEFAULT_WAKE_LOCK_TIMEOUT but no response.
+ // Reset mRequestMessagesWaiting to enable
+ // releaseWakeLockIfDone().
+ //
+ // Note: Keep mRequestList so that delayed response
+ // can still be handled when response finally comes.
+ if (mRequestMessagesWaiting != 0) {
+ Log.d(LOG_TAG, "NOTE: mReqWaiting is NOT 0 but"
+ + mRequestMessagesWaiting + " at TIMEOUT, reset!"
+ + " There still msg waitng for response");
+
+ mRequestMessagesWaiting = 0;
+
+ if (RILJ_LOGD) {
+ synchronized (mRequestsList) {
+ int count = mRequestsList.size();
+ Log.d(LOG_TAG, "WAKE_LOCK_TIMEOUT " +
+ " mRequestList=" + count);
+
+ for (int i = 0; i < count; i++) {
+ rr = mRequestsList.get(i);
+ Log.d(LOG_TAG, i + ": [" + rr.mSerial + "] "
+ + requestToString(rr.mRequest));
+ }
+ }
+ }
+ }
+ // mRequestMessagesPending shows how many
+ // requests are waiting to be sent (and before
+ // to be added in request list) since star the
+ // WAKE_LOCK_TIMEOUT timer. Since WAKE_LOCK_TIMEOUT
+ // is the expected time to get response, all requests
+ // should already sent out (i.e.
+ // mRequestMessagesPending is 0 )while TIMEOUT occurs.
+ if (mRequestMessagesPending != 0) {
+ Log.e(LOG_TAG, "ERROR: mReqPending is NOT 0 but"
+ + mRequestMessagesPending + " at TIMEOUT, reset!");
+ mRequestMessagesPending = 0;
+
+ }
+ 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 +
+ "Exception:" + tr.toString());
+ }
+
+ 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
+ clearRequestsList(RADIO_NOT_AVAILABLE, false);
+ }} catch (Throwable tr) {
+ Log.e(LOG_TAG,"Uncaught exception", tr);
+ }
+
+ /* We're disconnected so we don't know the ril version */
+ notifyRegistrantsRilConnectionChanged(-1);
+ }
+ }
+
+
+
+ //***** Constructors
+
+ public RIL(Context context, int preferredNetworkType, int cdmaSubscription) {
+ super(context);
+ if (RILJ_LOGD) {
+ riljLog("RIL(context, preferredNetworkType=" + preferredNetworkType +
+ " cdmaSubscription=" + cdmaSubscription + ")");
+ }
+ mCdmaSubscription = cdmaSubscription;
+ mPreferredNetworkType = preferredNetworkType;
+ mPhoneType = RILConstants.NO_PHONE;
+
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+ mWakeLock.setReferenceCounted(false);
+ mWakeLockTimeout = SystemProperties.getInt(TelephonyProperties.PROPERTY_WAKE_LOCK_TIMEOUT,
+ DEFAULT_WAKE_LOCK_TIMEOUT);
+ mRequestMessagesPending = 0;
+ mRequestMessagesWaiting = 0;
+
+ mSenderThread = new HandlerThread("RILSender");
+ mSenderThread.start();
+
+ Looper looper = mSenderThread.getLooper();
+ mSender = new RILSender(looper);
+
+ ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
+ riljLog("Not starting RILReceiver: wifi-only");
+ } else {
+ riljLog("Starting RILReceiver");
+ 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
+
+ public void getVoiceRadioTechnology(Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_VOICE_RADIO_TECH, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ @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
+ getIccCardStatus(Message result) {
+ //Note: This RIL request has not been renamed to ICC,
+ // but this request is also valid for SIM and RUIM
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_SIM_STATUS, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ @Override public void
+ supplyIccPin(String pin, Message result) {
+ supplyIccPinForApp(pin, null, result);
+ }
+
+ @Override public void
+ supplyIccPinForApp(String pin, String aid, Message result) {
+ //Note: This RIL request has not been renamed to ICC,
+ // but this request is also valid for SIM and RUIM
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PIN, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(2);
+ rr.mp.writeString(pin);
+ rr.mp.writeString(aid);
+
+ send(rr);
+ }
+
+ @Override public void
+ supplyIccPuk(String puk, String newPin, Message result) {
+ supplyIccPukForApp(puk, newPin, null, result);
+ }
+
+ @Override public void
+ supplyIccPukForApp(String puk, String newPin, String aid, Message result) {
+ //Note: This RIL request has not been renamed to ICC,
+ // but this request is also valid for SIM and RUIM
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PUK, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(3);
+ rr.mp.writeString(puk);
+ rr.mp.writeString(newPin);
+ rr.mp.writeString(aid);
+
+ send(rr);
+ }
+
+ @Override public void
+ supplyIccPin2(String pin, Message result) {
+ supplyIccPin2ForApp(pin, null, result);
+ }
+
+ @Override public void
+ supplyIccPin2ForApp(String pin, String aid, Message result) {
+ //Note: This RIL request has not been renamed to ICC,
+ // but this request is also valid for SIM and RUIM
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PIN2, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(2);
+ rr.mp.writeString(pin);
+ rr.mp.writeString(aid);
+
+ send(rr);
+ }
+
+ @Override public void
+ supplyIccPuk2(String puk2, String newPin2, Message result) {
+ supplyIccPuk2ForApp(puk2, newPin2, null, result);
+ }
+
+ @Override public void
+ supplyIccPuk2ForApp(String puk, String newPin2, String aid, Message result) {
+ //Note: This RIL request has not been renamed to ICC,
+ // but this request is also valid for SIM and RUIM
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ENTER_SIM_PUK2, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(3);
+ rr.mp.writeString(puk);
+ rr.mp.writeString(newPin2);
+ rr.mp.writeString(aid);
+
+ send(rr);
+ }
+
+ @Override public void
+ changeIccPin(String oldPin, String newPin, Message result) {
+ changeIccPinForApp(oldPin, newPin, null, result);
+ }
+
+ @Override public void
+ changeIccPinForApp(String oldPin, String newPin, String aid, Message result) {
+ //Note: This RIL request has not been renamed to ICC,
+ // but this request is also valid for SIM and RUIM
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CHANGE_SIM_PIN, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(3);
+ rr.mp.writeString(oldPin);
+ rr.mp.writeString(newPin);
+ rr.mp.writeString(aid);
+
+ send(rr);
+ }
+
+ @Override public void
+ changeIccPin2(String oldPin2, String newPin2, Message result) {
+ changeIccPin2ForApp(oldPin2, newPin2, null, result);
+ }
+
+ @Override public void
+ changeIccPin2ForApp(String oldPin2, String newPin2, String aid, Message result) {
+ //Note: This RIL request has not been renamed to ICC,
+ // but this request is also valid for SIM and RUIM
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CHANGE_SIM_PIN2, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeInt(3);
+ rr.mp.writeString(oldPin2);
+ rr.mp.writeString(newPin2);
+ rr.mp.writeString(aid);
+
+ 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_LOGD) 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_LOGD) 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ @Deprecated public void
+ getPDPContextList(Message result) {
+ getDataCallList(result);
+ }
+
+ public void
+ getDataCallList(Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_DATA_CALL_LIST, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ dial (String address, int clirMode, Message result) {
+ dial(address, clirMode, null, result);
+ }
+
+ public void
+ dial(String address, int clirMode, UUSInfo uusInfo, Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);
+
+ rr.mp.writeString(address);
+ rr.mp.writeInt(clirMode);
+ rr.mp.writeInt(0); // UUS information is absent
+
+ if (uusInfo == null) {
+ rr.mp.writeInt(0); // UUS information is absent
+ } else {
+ rr.mp.writeInt(1); // UUS information is present
+ rr.mp.writeInt(uusInfo.getType());
+ rr.mp.writeInt(uusInfo.getDcs());
+ rr.mp.writeByteArray(uusInfo.getUserData());
+ }
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getIMSI(Message result) {
+ getIMSIForApp(null, result);
+ }
+
+ public void
+ getIMSIForApp(String aid, Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMSI, result);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeString(aid);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() +
+ "> getIMSI: " + requestToString(rr.mRequest)
+ + " aid: " + aid);
+
+ send(rr);
+ }
+
+ public void
+ getIMEI(Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMEI, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getIMEISV(Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMEISV, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ public void
+ hangupConnection (int gsmIndex, Message result) {
+ if (RILJ_LOGD) riljLog("hangupConnection: gsmIndex=" + gsmIndex);
+
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_HANGUP, result);
+
+ if (RILJ_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ conference (Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_CONFERENCE, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ public void setPreferredVoicePrivacy(boolean enable, Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE,
+ result);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(enable ? 1:0);
+
+ send(rr);
+ }
+
+ public void getPreferredVoicePrivacy(Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE,
+ result);
+ send(rr);
+ }
+
+ public void
+ separateConnection (int gsmIndex, Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SEPARATE_CONNECTION, result);
+
+ if (RILJ_LOGD) 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ rejectCall (Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_UDUB, result);
+
+ if (RILJ_LOGD) 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_LOGD) 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * @deprecated
+ */
+ public void
+ getLastPdpFailCause (Message result) {
+ getLastDataCallFailCause (result);
+ }
+
+ /**
+ * The preferred new alternative to getLastPdpFailCause
+ */
+ public void
+ getLastDataCallFailCause (Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE, result);
+
+ if (RILJ_LOGD) 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_LOGD) 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getSignalStrength (Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SIGNAL_STRENGTH, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getVoiceRegistrationState (Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_VOICE_REGISTRATION_STATE, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getDataRegistrationState (Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_DATA_REGISTRATION_STATE, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getOperator(Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_OPERATOR, result);
+
+ if (RILJ_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ sendBurstDtmf(String dtmfString, int on, int off, Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_BURST_DTMF, result);
+
+ rr.mp.writeInt(3);
+ rr.mp.writeString(dtmfString);
+ rr.mp.writeString(Integer.toString(on));
+ rr.mp.writeString(Integer.toString(off));
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " : " + dtmfString);
+
+ 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ sendCdmaSms(byte[] pdu, Message result) {
+ int address_nbr_of_digits;
+ int subaddr_nbr_of_digits;
+ int bearerDataLength;
+ ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+ DataInputStream dis = new DataInputStream(bais);
+
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_CDMA_SEND_SMS, result);
+
+ try {
+ rr.mp.writeInt(dis.readInt()); //teleServiceId
+ rr.mp.writeByte((byte) dis.readInt()); //servicePresent
+ rr.mp.writeInt(dis.readInt()); //serviceCategory
+ rr.mp.writeInt(dis.read()); //address_digit_mode
+ rr.mp.writeInt(dis.read()); //address_nbr_mode
+ rr.mp.writeInt(dis.read()); //address_ton
+ rr.mp.writeInt(dis.read()); //address_nbr_plan
+ address_nbr_of_digits = (byte) dis.read();
+ rr.mp.writeByte((byte) address_nbr_of_digits);
+ for(int i=0; i < address_nbr_of_digits; i++){
+ rr.mp.writeByte(dis.readByte()); // address_orig_bytes[i]
+ }
+ rr.mp.writeInt(dis.read()); //subaddressType
+ rr.mp.writeByte((byte) dis.read()); //subaddr_odd
+ subaddr_nbr_of_digits = (byte) dis.read();
+ rr.mp.writeByte((byte) subaddr_nbr_of_digits);
+ for(int i=0; i < subaddr_nbr_of_digits; i++){
+ rr.mp.writeByte(dis.readByte()); //subaddr_orig_bytes[i]
+ }
+
+ bearerDataLength = dis.read();
+ rr.mp.writeInt(bearerDataLength);
+ for(int i=0; i < bearerDataLength; i++){
+ rr.mp.writeByte(dis.readByte()); //bearerData[i]
+ }
+ }catch (IOException ex){
+ if (RILJ_LOGD) riljLog("sendSmsCdma: conversion from input stream to object failed: "
+ + ex);
+ }
+
+ if (RILJ_LOGD) 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 (false) {
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest)
+ + " " + index);
+ }
+
+ send(rr);
+ }
+
+ public void deleteSmsOnRuim(int index, Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM,
+ response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(index);
+
+ if (false) {
+ if (RILJ_LOGD) 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 (false) {
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest)
+ + " " + status);
+ }
+
+ send(rr);
+ }
+
+ public void writeSmsToRuim(int status, String pdu, Message response) {
+ status = translateStatus(status);
+
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM,
+ response);
+
+ rr.mp.writeInt(status);
+ rr.mp.writeString(pdu);
+
+ if (false) {
+ if (RILJ_LOGD) 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_ICC_READ:
+ return 1;
+ case SmsManager.STATUS_ON_ICC_UNREAD:
+ return 0;
+ case SmsManager.STATUS_ON_ICC_SENT:
+ return 3;
+ case SmsManager.STATUS_ON_ICC_UNSENT:
+ return 2;
+ }
+
+ // Default to READ.
+ return 1;
+ }
+
+ public void
+ setupDataCall(String radioTechnology, String profile, String apn,
+ String user, String password, String authType, String protocol,
+ Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SETUP_DATA_CALL, result);
+
+ rr.mp.writeInt(7);
+
+ rr.mp.writeString(radioTechnology);
+ rr.mp.writeString(profile);
+ rr.mp.writeString(apn);
+ rr.mp.writeString(user);
+ rr.mp.writeString(password);
+ rr.mp.writeString(authType);
+ rr.mp.writeString(protocol);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest) + " " + radioTechnology + " "
+ + profile + " " + apn + " " + user + " "
+ + password + " " + authType + " " + protocol);
+
+ send(rr);
+ }
+
+ public void
+ deactivateDataCall(int cid, int reason, Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_DEACTIVATE_DATA_CALL, result);
+
+ rr.mp.writeInt(2);
+ rr.mp.writeString(Integer.toString(cid));
+ rr.mp.writeString(Integer.toString(reason));
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " +
+ requestToString(rr.mRequest) + " " + cid + " " + reason);
+
+ 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_LOGD) {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + (on ? " on" : " off"));
+ }
+
+ 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 (RILJ_LOGD) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ acknowledgeLastIncomingGsmSms(boolean success, int cause, Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SMS_ACKNOWLEDGE, result);
+
+ rr.mp.writeInt(2);
+ rr.mp.writeInt(success ? 1 : 0);
+ rr.mp.writeInt(cause);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + success + " " + cause);
+
+ send(rr);
+ }
+
+ public void
+ acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE, result);
+
+ rr.mp.writeInt(success ? 0 : 1); //RIL_CDMA_SMS_ErrorClass
+ // cause code according to X.S004-550E
+ rr.mp.writeInt(cause);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + success + " " + cause);
+
+ send(rr);
+ }
+
+ public void
+ acknowledgeIncomingGsmSmsWithPdu(boolean success, String ackPdu, Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU, result);
+
+ rr.mp.writeInt(2);
+ rr.mp.writeString(success ? "1" : "0");
+ rr.mp.writeString(ackPdu);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + ' ' + success + " [" + ackPdu + ']');
+
+ send(rr);
+ }
+
+ public void
+ iccIO (int command, int fileid, String path, int p1, int p2, int p3,
+ String data, String pin2, Message result) {
+ iccIOForApp(command, fileid, path, p1, p2, p3, data, pin2, null, result);
+ }
+ public void
+ iccIOForApp (int command, int fileid, String path, int p1, int p2, int p3,
+ String data, String pin2, String aid, Message result) {
+ //Note: This RIL request has not been renamed to ICC,
+ // but this request is also valid for SIM and RUIM
+ 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);
+ rr.mp.writeString(aid);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> iccIO: "
+ + requestToString(rr.mRequest)
+ + " 0x" + Integer.toHexString(command)
+ + " 0x" + Integer.toHexString(fileid) + " "
+ + " path: " + path + ","
+ + p1 + "," + p2 + "," + p3
+ + " aid: " + aid);
+
+ send(rr);
+ }
+
+ public void
+ getCLIR(Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_GET_CLIR, result);
+
+ if (RILJ_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ public void
+ getBasebandVersion (Message response) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_BASEBAND_VERSION, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ @Override
+ public void
+ queryFacilityLock(String facility, String password, int serviceClass,
+ Message response) {
+ queryFacilityLockForApp(facility, password, serviceClass, null, response);
+ }
+
+ @Override
+ public void
+ queryFacilityLockForApp(String facility, String password, int serviceClass, String appId,
+ Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_QUERY_FACILITY_LOCK, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ // count strings
+ rr.mp.writeInt(4);
+
+ rr.mp.writeString(facility);
+ rr.mp.writeString(password);
+
+ rr.mp.writeString(Integer.toString(serviceClass));
+ rr.mp.writeString(appId);
+
+ send(rr);
+ }
+
+ @Override
+ public void
+ setFacilityLock (String facility, boolean lockState, String password,
+ int serviceClass, Message response) {
+ setFacilityLockForApp(facility, lockState, password, serviceClass, null, response);
+ }
+
+ @Override
+ public void
+ setFacilityLockForApp(String facility, boolean lockState, String password,
+ int serviceClass, String appId, Message response) {
+ String lockString;
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SET_FACILITY_LOCK, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ // count strings
+ rr.mp.writeInt(5);
+
+ rr.mp.writeString(facility);
+ lockString = (lockState)?"1":"0";
+ rr.mp.writeString(lockString);
+ rr.mp.writeString(password);
+ rr.mp.writeString(Integer.toString(serviceClass));
+ rr.mp.writeString(appId);
+
+ send(rr);
+
+ }
+
+ public void
+ sendUSSD (String ussdString, Message response) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_SEND_USSD, response);
+
+ if (RILJ_LOGD) 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_LOGD) riljLog(rr.serialString()
+ + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+
+ public void resetRadio(Message result) {
+ RILRequest rr
+ = RILRequest.obtain(RIL_REQUEST_RESET_RADIO, result);
+
+ if (RILJ_LOGD) 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + "[" + IccUtils.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_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ rr.mp.writeString(contents);
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendEnvelopeWithStatus(String contents, Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + '[' + contents + ']');
+
+ 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_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ int[] param = new int[1];
+ param[0] = accept ? 1 : 0;
+ rr.mp.writeIntArray(param);
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setCurrentPreferredNetworkType() {
+ if (RILJ_LOGD) riljLog("setCurrentPreferredNetworkType: " + mSetPreferredNetworkType);
+ setPreferredNetworkType(mSetPreferredNetworkType, null);
+ }
+ private int mSetPreferredNetworkType;
+
+ /**
+ * {@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);
+
+ mSetPreferredNetworkType = networkType;
+ mPreferredNetworkType = networkType;
+
+ if (RILJ_LOGD) 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_LOGD) 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_LOGD) 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_LOGD) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest) + ": " + enable);
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void getSmscAddress(Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_SMSC_ADDRESS, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setSmscAddress(String address, Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_SET_SMSC_ADDRESS, result);
+
+ rr.mp.writeString(address);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " : " + address);
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void reportSmsMemoryStatus(boolean available, Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_REPORT_SMS_MEMORY_STATUS, result);
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(available ? 1 : 0);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest) + ": " + available);
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void reportStkServiceIsRunning(Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING, result);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void getGsmBroadcastConfig(Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GSM_GET_BROADCAST_CONFIG, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GSM_SET_BROADCAST_CONFIG, response);
+
+ int numOfConfig = config.length;
+ rr.mp.writeInt(numOfConfig);
+
+ for(int i = 0; i < numOfConfig; i++) {
+ rr.mp.writeInt(config[i].getFromServiceId());
+ rr.mp.writeInt(config[i].getToServiceId());
+ rr.mp.writeInt(config[i].getFromCodeScheme());
+ rr.mp.writeInt(config[i].getToCodeScheme());
+ rr.mp.writeInt(config[i].isSelected() ? 1 : 0);
+ }
+
+ if (RILJ_LOGD) {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " with " + numOfConfig + " configs : ");
+ for (int i = 0; i < numOfConfig; i++) {
+ riljLog(config[i].toString());
+ }
+ }
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setGsmBroadcastActivation(boolean activate, Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_GSM_BROADCAST_ACTIVATION, response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(activate ? 0 : 1);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ 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_LOGD) 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 RadioState getRadioStateFromInt(int stateInt) {
+ RadioState state;
+
+ /* RIL_RadioState ril.h */
+ switch(stateInt) {
+ case 0: state = RadioState.RADIO_OFF; break;
+ case 1: state = RadioState.RADIO_UNAVAILABLE; break;
+ case 10: state = RadioState.RADIO_ON; break;
+
+ default:
+ throw new RuntimeException(
+ "Unrecognized RIL_RadioState: " + stateInt);
+ }
+ return state;
+ }
+
+ private void switchToRadioState(RadioState newState) {
+ 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 pending 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++;
+
+ mSender.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
+ Message msg = mSender.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
+ mSender.sendMessageDelayed(msg, mWakeLockTimeout);
+ }
+ }
+
+ private void
+ releaseWakeLockIfDone() {
+ synchronized (mWakeLock) {
+ if (mWakeLock.isHeld() &&
+ (mRequestMessagesPending == 0) &&
+ (mRequestMessagesWaiting == 0)) {
+ mSender.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
+ mWakeLock.release();
+ }
+ }
+ }
+
+ private void
+ send(RILRequest rr) {
+ Message msg;
+
+ if (mSocket == null) {
+ rr.onError(RADIO_NOT_AVAILABLE, null);
+ rr.release();
+ return;
+ }
+
+ msg = mSender.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();
+ }
+
+ /**
+ * Release each request in mReqeustsList then clear the list
+ * @param error is the RIL_Errno sent back
+ * @param loggable true means to print all requests in mRequestslist
+ */
+ private void clearRequestsList(int error, boolean loggable) {
+ RILRequest rr;
+ synchronized (mRequestsList) {
+ int count = mRequestsList.size();
+ if (RILJ_LOGD && loggable) {
+ Log.d(LOG_TAG, "WAKE_LOCK_TIMEOUT " +
+ " mReqPending=" + mRequestMessagesPending +
+ " mRequestList=" + count);
+ }
+
+ for (int i = 0; i < count ; i++) {
+ rr = mRequestsList.get(i);
+ if (RILJ_LOGD && loggable) {
+ Log.d(LOG_TAG, i + ": [" + rr.mSerial + "] " +
+ requestToString(rr.mRequest));
+ }
+ rr.onError(error, null);
+ rr.release();
+ }
+ mRequestsList.clear();
+ mRequestMessagesWaiting = 0;
+ }
+ }
+
+ 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);
+ if (mRequestMessagesWaiting > 0)
+ mRequestMessagesWaiting--;
+ 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;
+ }
+
+ Object ret = null;
+
+ if (error == 0 || p.dataAvail() > 0) {
+ // either command succeeds or command fails but with data payload
+ 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 = responseIccCardStatus(p); break;
+ case RIL_REQUEST_ENTER_SIM_PIN: ret = responseInts(p); break;
+ case RIL_REQUEST_ENTER_SIM_PUK: ret = responseInts(p); break;
+ case RIL_REQUEST_ENTER_SIM_PIN2: ret = responseInts(p); break;
+ case RIL_REQUEST_ENTER_SIM_PUK2: ret = responseInts(p); break;
+ case RIL_REQUEST_CHANGE_SIM_PIN: ret = responseInts(p); break;
+ case RIL_REQUEST_CHANGE_SIM_PIN2: ret = responseInts(p); break;
+ case RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION: ret = responseInts(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: {
+ if (mTestingEmergencyCall.getAndSet(false)) {
+ if (mEmergencyCallbackModeRegistrant != null) {
+ riljLog("testing emergency call, notify ECM Registrants");
+ mEmergencyCallbackModeRegistrant.notifyRegistrant();
+ }
+ }
+ 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 = responseSignalStrength(p); break;
+ case RIL_REQUEST_VOICE_REGISTRATION_STATE: ret = responseStrings(p); break;
+ case RIL_REQUEST_DATA_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_DATA_CALL: ret = responseSetupDataCall(p); break;
+ case RIL_REQUEST_SIM_IO: ret = responseICC_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_DATA_CALL: ret = responseVoid(p); break;
+ case RIL_REQUEST_QUERY_FACILITY_LOCK: ret = responseInts(p); break;
+ case RIL_REQUEST_SET_FACILITY_LOCK: ret = responseInts(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 = responseOperatorInfos(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_DATA_CALL_FAIL_CAUSE: ret = responseInts(p); break;
+ case RIL_REQUEST_DATA_CALL_LIST: ret = responseDataCallList(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 = responseGetPreferredNetworkType(p); break;
+ case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS: ret = responseCellList(p); break;
+ case RIL_REQUEST_SET_LOCATION_UPDATES: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE: ret = responseInts(p); break;
+ case RIL_REQUEST_SET_TTY_MODE: ret = responseVoid(p); break;
+ case RIL_REQUEST_QUERY_TTY_MODE: ret = responseInts(p); break;
+ case RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE: ret = responseInts(p); break;
+ case RIL_REQUEST_CDMA_FLASH: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_BURST_DTMF: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_SEND_SMS: ret = responseSMS(p); break;
+ case RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE: ret = responseVoid(p); break;
+ case RIL_REQUEST_GSM_GET_BROADCAST_CONFIG: ret = responseGmsBroadcastConfig(p); break;
+ case RIL_REQUEST_GSM_SET_BROADCAST_CONFIG: ret = responseVoid(p); break;
+ case RIL_REQUEST_GSM_BROADCAST_ACTIVATION: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG: ret = responseCdmaBroadcastConfig(p); break;
+ case RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_BROADCAST_ACTIVATION: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_SUBSCRIPTION: ret = responseStrings(p); break;
+ case RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM: ret = responseInts(p); break;
+ case RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM: ret = responseVoid(p); break;
+ case RIL_REQUEST_DEVICE_IDENTITY: ret = responseStrings(p); break;
+ case RIL_REQUEST_GET_SMSC_ADDRESS: ret = responseString(p); break;
+ case RIL_REQUEST_SET_SMSC_ADDRESS: ret = responseVoid(p); break;
+ case RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE: ret = responseVoid(p); break;
+ case RIL_REQUEST_REPORT_SMS_MEMORY_STATUS: ret = responseVoid(p); break;
+ case RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING: ret = responseVoid(p); break;
+ case RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE: ret = responseInts(p); break;
+ case RIL_REQUEST_ISIM_AUTHENTICATION: ret = responseString(p); break;
+ case RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU: ret = responseVoid(p); break;
+ case RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS: ret = responseICC_IO(p); break;
+ case RIL_REQUEST_VOICE_RADIO_TECH: ret = responseInts(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 (error != 0) {
+ rr.onError(error, ret);
+ rr.release();
+ return;
+ }
+
+ if (RILJ_LOGD) 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:
+ if (!RILJ_LOGV) {
+ // If not versbose logging just return and don't display IMSI and IMEI, 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 if (req == RIL_REQUEST_GET_NEIGHBORING_CELL_IDS) {
+ ArrayList<NeighboringCellInfo> cells;
+ cells = (ArrayList<NeighboringCellInfo>) ret;
+ sb = new StringBuilder(" ");
+ for (NeighboringCellInfo cell : cells) {
+ sb.append(cell).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_VOICE_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 = responseSignalStrength(p); break;
+ case RIL_UNSOL_DATA_CALL_LIST_CHANGED: ret = responseDataCallList(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 = responseSimRefresh(p); break;
+ case RIL_UNSOL_CALL_RING: ret = responseCallRing(p); break;
+ case RIL_UNSOL_RESTRICTED_STATE_CHANGED: ret = responseInts(p); break;
+ case RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED: ret = responseVoid(p); break;
+ case RIL_UNSOL_RESPONSE_CDMA_NEW_SMS: ret = responseCdmaSms(p); break;
+ case RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS: ret = responseRaw(p); break;
+ case RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL: ret = responseVoid(p); break;
+ case RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE: ret = responseVoid(p); break;
+ case RIL_UNSOL_CDMA_CALL_WAITING: ret = responseCdmaCallWaiting(p); break;
+ case RIL_UNSOL_CDMA_OTA_PROVISION_STATUS: ret = responseInts(p); break;
+ case RIL_UNSOL_CDMA_INFO_REC: ret = responseCdmaInformationRecord(p); break;
+ case RIL_UNSOL_OEM_HOOK_RAW: ret = responseRaw(p); break;
+ case RIL_UNSOL_RINGBACK_TONE: ret = responseInts(p); break;
+ case RIL_UNSOL_RESEND_INCALL_MUTE: ret = responseVoid(p); break;
+ case RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED: ret = responseInts(p); break;
+ case RIL_UNSOl_CDMA_PRL_CHANGED: ret = responseInts(p); break;
+ case RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE: ret = responseVoid(p); break;
+ case RIL_UNSOL_RIL_CONNECTED: ret = responseInts(p); break;
+ case RIL_UNSOL_VOICE_RADIO_TECH_CHANGED: ret = responseInts(p); break;
+
+ default:
+ throw new RuntimeException("Unrecognized unsol response: " + response);
+ //break; (implied)
+ }} catch (Throwable tr) {
+ Log.e(LOG_TAG, "Exception processing unsol response: " + response +
+ "Exception:" + tr.toString());
+ return;
+ }
+
+ switch(response) {
+ case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED:
+ /* has bonus radio state int */
+ RadioState newState = getRadioStateFromInt(p.readInt());
+ if (RILJ_LOGD) unsljLogMore(response, newState.toString());
+
+ switchToRadioState(newState);
+ break;
+ case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED:
+ if (RILJ_LOGD) unsljLog(response);
+
+ mCallStateRegistrants
+ .notifyRegistrants(new AsyncResult(null, null, null));
+ break;
+ case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED:
+ if (RILJ_LOGD) unsljLog(response);
+
+ mVoiceNetworkStateRegistrants
+ .notifyRegistrants(new AsyncResult(null, null, null));
+ break;
+ case RIL_UNSOL_RESPONSE_NEW_SMS: {
+ if (RILJ_LOGD) unsljLog(response);
+
+ // FIXME this should move up a layer
+ String a[] = new String[2];
+
+ a[1] = (String)ret;
+
+ SmsMessage sms;
+
+ sms = SmsMessage.newFromCMT(a);
+ if (mGsmSmsRegistrant != null) {
+ mGsmSmsRegistrant
+ .notifyRegistrant(new AsyncResult(null, sms, null));
+ }
+ break;
+ }
+ case RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mSmsStatusRegistrant != null) {
+ mSmsStatusRegistrant.notifyRegistrant(
+ new AsyncResult(null, ret, null));
+ }
+ break;
+ case RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ int[] smsIndex = (int[])ret;
+
+ if(smsIndex.length == 1) {
+ if (mSmsOnSimRegistrant != null) {
+ mSmsOnSimRegistrant.
+ notifyRegistrant(new AsyncResult(null, smsIndex, null));
+ }
+ } else {
+ if (RILJ_LOGD) 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_LOGD) unsljLogMore(response, resp[0]);
+ if (mUSSDRegistrant != null) {
+ mUSSDRegistrant.notifyRegistrant(
+ new AsyncResult (null, resp, null));
+ }
+ break;
+ case RIL_UNSOL_NITZ_TIME_RECEIVED:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ // has bonus long containing milliseconds since boot that the NITZ
+ // time was received
+ long nitzReceiveTime = p.readLong();
+
+ Object[] result = new Object[2];
+
+ result[0] = ret;
+ result[1] = Long.valueOf(nitzReceiveTime);
+
+ boolean ignoreNitz = SystemProperties.getBoolean(
+ TelephonyProperties.PROPERTY_IGNORE_NITZ, false);
+
+ if (ignoreNitz) {
+ if (RILJ_LOGD) riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
+ } else {
+ 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 (RILJ_LOGV) unsljLogvRet(response, ret);
+
+ if (mSignalStrengthRegistrant != null) {
+ mSignalStrengthRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+ case RIL_UNSOL_DATA_CALL_LIST_CHANGED:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ mDataNetworkStateRegistrants.notifyRegistrants(new AsyncResult(null, ret, null));
+ break;
+
+ case RIL_UNSOL_SUPP_SVC_NOTIFICATION:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mSsnRegistrant != null) {
+ mSsnRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_STK_SESSION_END:
+ if (RILJ_LOGD) unsljLog(response);
+
+ if (mCatSessionEndRegistrant != null) {
+ mCatSessionEndRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_STK_PROACTIVE_COMMAND:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mCatProCmdRegistrant != null) {
+ mCatProCmdRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_STK_EVENT_NOTIFY:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mCatEventRegistrant != null) {
+ mCatEventRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_STK_CALL_SETUP:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mCatCallSetUpRegistrant != null) {
+ mCatCallSetUpRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_SIM_SMS_STORAGE_FULL:
+ if (RILJ_LOGD) unsljLog(response);
+
+ if (mIccSmsFullRegistrant != null) {
+ mIccSmsFullRegistrant.notifyRegistrant();
+ }
+ break;
+
+ case RIL_UNSOL_SIM_REFRESH:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mIccRefreshRegistrants != null) {
+ mIccRefreshRegistrants.notifyRegistrants(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_CALL_RING:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mRingRegistrant != null) {
+ mRingRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_RESTRICTED_STATE_CHANGED:
+ if (RILJ_LOGD) unsljLogvRet(response, ret);
+ if (mRestrictedStateRegistrant != null) {
+ mRestrictedStateRegistrant.notifyRegistrant(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED:
+ if (RILJ_LOGD) unsljLog(response);
+
+ if (mIccStatusChangedRegistrants != null) {
+ mIccStatusChangedRegistrants.notifyRegistrants();
+ }
+ break;
+
+ case RIL_UNSOL_RESPONSE_CDMA_NEW_SMS:
+ if (RILJ_LOGD) unsljLog(response);
+
+ SmsMessage sms = (SmsMessage) ret;
+
+ if (mCdmaSmsRegistrant != null) {
+ mCdmaSmsRegistrant
+ .notifyRegistrant(new AsyncResult(null, sms, null));
+ }
+ break;
+
+ case RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS:
+ if (RILJ_LOGD) unsljLog(response);
+
+ if (mGsmBroadcastSmsRegistrant != null) {
+ mGsmBroadcastSmsRegistrant
+ .notifyRegistrant(new AsyncResult(null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL:
+ if (RILJ_LOGD) unsljLog(response);
+
+ if (mIccSmsFullRegistrant != null) {
+ mIccSmsFullRegistrant.notifyRegistrant();
+ }
+ break;
+
+ case RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE:
+ if (RILJ_LOGD) unsljLog(response);
+
+ if (mEmergencyCallbackModeRegistrant != null) {
+ mEmergencyCallbackModeRegistrant.notifyRegistrant();
+ }
+ break;
+
+ case RIL_UNSOL_CDMA_CALL_WAITING:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mCallWaitingInfoRegistrants != null) {
+ mCallWaitingInfoRegistrants.notifyRegistrants(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_CDMA_OTA_PROVISION_STATUS:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mOtaProvisionRegistrants != null) {
+ mOtaProvisionRegistrants.notifyRegistrants(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_CDMA_INFO_REC:
+ ArrayList<CdmaInformationRecords> listInfoRecs;
+
+ try {
+ listInfoRecs = (ArrayList<CdmaInformationRecords>)ret;
+ } catch (ClassCastException e) {
+ Log.e(LOG_TAG, "Unexpected exception casting to listInfoRecs", e);
+ break;
+ }
+
+ for (CdmaInformationRecords rec : listInfoRecs) {
+ if (RILJ_LOGD) unsljLogRet(response, rec);
+ notifyRegistrantsCdmaInfoRec(rec);
+ }
+ break;
+
+ case RIL_UNSOL_OEM_HOOK_RAW:
+ if (RILJ_LOGD) unsljLogvRet(response, IccUtils.bytesToHexString((byte[])ret));
+ if (mUnsolOemHookRawRegistrant != null) {
+ mUnsolOemHookRawRegistrant.notifyRegistrant(new AsyncResult(null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_RINGBACK_TONE:
+ if (RILJ_LOGD) unsljLogvRet(response, ret);
+ if (mRingbackToneRegistrants != null) {
+ boolean playtone = (((int[])ret)[0] == 1);
+ mRingbackToneRegistrants.notifyRegistrants(
+ new AsyncResult (null, playtone, null));
+ }
+ break;
+
+ case RIL_UNSOL_RESEND_INCALL_MUTE:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mResendIncallMuteRegistrants != null) {
+ mResendIncallMuteRegistrants.notifyRegistrants(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_VOICE_RADIO_TECH_CHANGED:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mVoiceRadioTechChangedRegistrants != null) {
+ mVoiceRadioTechChangedRegistrants.notifyRegistrants(
+ new AsyncResult(null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mCdmaSubscriptionChangedRegistrants != null) {
+ mCdmaSubscriptionChangedRegistrants.notifyRegistrants(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOl_CDMA_PRL_CHANGED:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mCdmaPrlChangedRegistrants != null) {
+ mCdmaPrlChangedRegistrants.notifyRegistrants(
+ new AsyncResult (null, ret, null));
+ }
+ break;
+
+ case RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE:
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ if (mExitEmergencyCallbackModeRegistrants != null) {
+ mExitEmergencyCallbackModeRegistrants.notifyRegistrants(
+ new AsyncResult (null, null, null));
+ }
+ break;
+
+ case RIL_UNSOL_RIL_CONNECTED: {
+ if (RILJ_LOGD) unsljLogRet(response, ret);
+
+ // Initial conditions
+ setRadioPower(false, null);
+ setPreferredNetworkType(mPreferredNetworkType, null);
+ setCdmaSubscriptionSource(mCdmaSubscription, null);
+ notifyRegistrantsRilConnectionChanged(((int[])ret)[0]);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Notifiy all registrants that the ril has connected or disconnected.
+ *
+ * @param rilVer is the version of the ril or -1 if disconnected.
+ */
+ private void notifyRegistrantsRilConnectionChanged(int rilVer) {
+ mRilVersion = rilVer;
+ if (mRilConnectedRegistrants != null) {
+ mRilConnectedRegistrants.notifyRegistrants(
+ new AsyncResult (null, new Integer(rilVer), null));
+ }
+ }
+
+ 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
+ responseCdmaSms(Parcel p) {
+ SmsMessage sms;
+ sms = SmsMessage.newFromParcel(p);
+
+ return sms;
+ }
+
+ 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, errorCode;
+ String ackPDU;
+
+ messageRef = p.readInt();
+ ackPDU = p.readString();
+ errorCode = p.readInt();
+
+ SmsResponse response = new SmsResponse(messageRef, ackPDU, errorCode);
+
+ return response;
+ }
+
+
+ private Object
+ responseICC_IO(Parcel p) {
+ int sw1, sw2;
+ byte data[] = null;
+ Message ret;
+
+ sw1 = p.readInt();
+ sw2 = p.readInt();
+
+ String s = p.readString();
+
+ if (RILJ_LOGV) riljLog("< iccIO: "
+ + " 0x" + Integer.toHexString(sw1)
+ + " 0x" + Integer.toHexString(sw2) + " "
+ + s);
+
+ return new IccIoResult(sw1, sw2, s);
+ }
+
+ private Object
+ responseIccCardStatus(Parcel p) {
+ IccCardApplication ca;
+
+ IccCardStatus status = new IccCardStatus();
+ status.setCardState(p.readInt());
+ status.setUniversalPinState(p.readInt());
+ status.setGsmUmtsSubscriptionAppIndex(p.readInt());
+ status.setCdmaSubscriptionAppIndex(p.readInt());
+ status.setImsSubscriptionAppIndex(p.readInt());
+ int numApplications = p.readInt();
+
+ // limit to maximum allowed applications
+ if (numApplications > IccCardStatus.CARD_MAX_APPS) {
+ numApplications = IccCardStatus.CARD_MAX_APPS;
+ }
+ status.setNumApplications(numApplications);
+
+ for (int i = 0 ; i < numApplications ; i++) {
+ ca = new IccCardApplication();
+ ca.app_type = ca.AppTypeFromRILInt(p.readInt());
+ ca.app_state = ca.AppStateFromRILInt(p.readInt());
+ ca.perso_substate = ca.PersoSubstateFromRILInt(p.readInt());
+ ca.aid = p.readString();
+ ca.app_label = p.readString();
+ ca.pin1_replaced = p.readInt();
+ ca.pin1 = ca.PinStateFromRILInt(p.readInt());
+ ca.pin2 = ca.PinStateFromRILInt(p.readInt());
+ status.addApplication(ca);
+ }
+ return status;
+ }
+
+ private Object
+ responseSimRefresh(Parcel p) {
+ IccRefreshResponse response = new IccRefreshResponse();
+
+ response.refreshResult = p.readInt();
+ response.efId = p.readInt();
+ response.aid = p.readString();
+ return response;
+ }
+
+ private Object
+ responseCallList(Parcel p) {
+ int num;
+ int voiceSettings;
+ ArrayList<DriverCall> response;
+ DriverCall dc;
+
+ num = p.readInt();
+ response = new ArrayList<DriverCall>(num);
+
+ if (RILJ_LOGV) {
+ riljLog("responseCallList: num=" + num +
+ " mEmergencyCallbackModeRegistrant=" + mEmergencyCallbackModeRegistrant +
+ " mTestingEmergencyCall=" + mTestingEmergencyCall.get());
+ }
+ 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();
+ voiceSettings = p.readInt();
+ dc.isVoice = (0 == voiceSettings) ? false : true;
+ dc.isVoicePrivacy = (0 != p.readInt());
+ dc.number = p.readString();
+ int np = p.readInt();
+ dc.numberPresentation = DriverCall.presentationFromCLIP(np);
+ dc.name = p.readString();
+ dc.namePresentation = p.readInt();
+ int uusInfoPresent = p.readInt();
+ if (uusInfoPresent == 1) {
+ dc.uusInfo = new UUSInfo();
+ dc.uusInfo.setType(p.readInt());
+ dc.uusInfo.setDcs(p.readInt());
+ byte[] userData = p.createByteArray();
+ dc.uusInfo.setUserData(userData);
+ riljLogv(String.format("Incoming UUS : type=%d, dcs=%d, length=%d",
+ dc.uusInfo.getType(), dc.uusInfo.getDcs(),
+ dc.uusInfo.getUserData().length));
+ riljLogv("Incoming UUS : data (string)="
+ + new String(dc.uusInfo.getUserData()));
+ riljLogv("Incoming UUS : data (hex): "
+ + IccUtils.bytesToHexString(dc.uusInfo.getUserData()));
+ } else {
+ riljLogv("Incoming UUS : NOT present!");
+ }
+
+ // Make sure there's a leading + on addresses with a TOA of 145
+ dc.number = PhoneNumberUtils.stringFromStringAndTOA(dc.number, dc.TOA);
+
+ response.add(dc);
+
+ if (dc.isVoicePrivacy) {
+ mVoicePrivacyOnRegistrants.notifyRegistrants();
+ riljLog("InCall VoicePrivacy is enabled");
+ } else {
+ mVoicePrivacyOffRegistrants.notifyRegistrants();
+ riljLog("InCall VoicePrivacy is disabled");
+ }
+ }
+
+ Collections.sort(response);
+
+ if ((num == 0) && mTestingEmergencyCall.getAndSet(false)) {
+ if (mEmergencyCallbackModeRegistrant != null) {
+ riljLog("responseCallList: call ended, testing emergency call," +
+ " notify ECM Registrants");
+ mEmergencyCallbackModeRegistrant.notifyRegistrant();
+ }
+ }
+
+ return response;
+ }
+
+ private DataCallState getDataCallState(Parcel p, int version) {
+ DataCallState dataCall = new DataCallState();
+
+ dataCall.version = version;
+ if (version < 5) {
+ dataCall.cid = p.readInt();
+ dataCall.active = p.readInt();
+ dataCall.type = p.readString();
+ String addresses = p.readString();
+ if (!TextUtils.isEmpty(addresses)) {
+ dataCall.addresses = addresses.split(" ");
+ }
+ } else {
+ dataCall.status = p.readInt();
+ dataCall.suggestedRetryTime = p.readInt();
+ dataCall.cid = p.readInt();
+ dataCall.active = p.readInt();
+ dataCall.type = p.readString();
+ dataCall.ifname = p.readString();
+ if ((dataCall.status == DataConnection.FailCause.NONE.getErrorCode()) &&
+ TextUtils.isEmpty(dataCall.ifname)) {
+ throw new RuntimeException("getDataCallState, no ifname");
+ }
+ String addresses = p.readString();
+ if (!TextUtils.isEmpty(addresses)) {
+ dataCall.addresses = addresses.split(" ");
+ }
+ String dnses = p.readString();
+ if (!TextUtils.isEmpty(dnses)) {
+ dataCall.dnses = dnses.split(" ");
+ }
+ String gateways = p.readString();
+ if (!TextUtils.isEmpty(gateways)) {
+ dataCall.gateways = gateways.split(" ");
+ }
+ }
+ return dataCall;
+ }
+
+ private Object
+ responseDataCallList(Parcel p) {
+ ArrayList<DataCallState> response;
+
+ int ver = p.readInt();
+ int num = p.readInt();
+ riljLog("responseDataCallList ver=" + ver + " num=" + num);
+
+ response = new ArrayList<DataCallState>(num);
+ for (int i = 0; i < num; i++) {
+ response.add(getDataCallState(p, ver));
+ }
+
+ return response;
+ }
+
+ private Object
+ responseSetupDataCall(Parcel p) {
+ int ver = p.readInt();
+ int num = p.readInt();
+ if (RILJ_LOGV) riljLog("responseSetupDataCall ver=" + ver + " num=" + num);
+
+ DataCallState dataCall;
+
+ if (ver < 5) {
+ dataCall = new DataCallState();
+ dataCall.version = ver;
+ dataCall.cid = Integer.parseInt(p.readString());
+ dataCall.ifname = p.readString();
+ if (TextUtils.isEmpty(dataCall.ifname)) {
+ throw new RuntimeException(
+ "RIL_REQUEST_SETUP_DATA_CALL response, no ifname");
+ }
+ String addresses = p.readString();
+ if (!TextUtils.isEmpty(addresses)) {
+ dataCall.addresses = addresses.split(" ");
+ }
+ if (num >= 4) {
+ String dnses = p.readString();
+ if (RILJ_LOGD) riljLog("responseSetupDataCall got dnses=" + dnses);
+ if (!TextUtils.isEmpty(dnses)) {
+ dataCall.dnses = dnses.split(" ");
+ }
+ }
+ if (num >= 5) {
+ String gateways = p.readString();
+ if (RILJ_LOGD) riljLog("responseSetupDataCall got gateways=" + gateways);
+ if (!TextUtils.isEmpty(gateways)) {
+ dataCall.gateways = gateways.split(" ");
+ }
+ }
+ } else {
+ if (num != 1) {
+ throw new RuntimeException(
+ "RIL_REQUEST_SETUP_DATA_CALL response expecting 1 RIL_Data_Call_response_v5"
+ + " got " + num);
+ }
+ dataCall = getDataCallState(p, ver);
+ }
+
+ return dataCall;
+ }
+
+ private Object
+ responseOperatorInfos(Parcel p) {
+ String strings[] = (String [])responseStrings(p);
+ ArrayList<OperatorInfo> 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<OperatorInfo>(strings.length / 4);
+
+ for (int i = 0 ; i < strings.length ; i += 4) {
+ ret.add (
+ new OperatorInfo(
+ strings[i+0],
+ strings[i+1],
+ strings[i+2],
+ strings[i+3]));
+ }
+
+ return ret;
+ }
+
+ private Object
+ responseCellList(Parcel p) {
+ int num, rssi;
+ String location;
+ ArrayList<NeighboringCellInfo> response;
+ NeighboringCellInfo cell;
+
+ num = p.readInt();
+ response = new ArrayList<NeighboringCellInfo>();
+
+ // Determine the radio access type
+ String radioString = SystemProperties.get(
+ TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE, "unknown");
+ int radioType;
+ if (radioString.equals("GPRS")) {
+ radioType = NETWORK_TYPE_GPRS;
+ } else if (radioString.equals("EDGE")) {
+ radioType = NETWORK_TYPE_EDGE;
+ } else if (radioString.equals("UMTS")) {
+ radioType = NETWORK_TYPE_UMTS;
+ } else if (radioString.equals("HSDPA")) {
+ radioType = NETWORK_TYPE_HSDPA;
+ } else if (radioString.equals("HSUPA")) {
+ radioType = NETWORK_TYPE_HSUPA;
+ } else if (radioString.equals("HSPA")) {
+ radioType = NETWORK_TYPE_HSPA;
+ } else {
+ radioType = NETWORK_TYPE_UNKNOWN;
+ }
+
+ // Interpret the location based on radio access type
+ if (radioType != NETWORK_TYPE_UNKNOWN) {
+ for (int i = 0 ; i < num ; i++) {
+ rssi = p.readInt();
+ location = p.readString();
+ cell = new NeighboringCellInfo(rssi, location, radioType);
+ response.add(cell);
+ }
+ }
+ return response;
+ }
+
+ private Object responseGetPreferredNetworkType(Parcel p) {
+ int [] response = (int[]) responseInts(p);
+
+ if (response.length >= 1) {
+ // Since this is the response for getPreferredNetworkType
+ // we'll assume that it should be the value we want the
+ // vendor ril to take if we reestablish a connection to it.
+ mPreferredNetworkType = response[0];
+ }
+ return response;
+ }
+
+ private Object responseGmsBroadcastConfig(Parcel p) {
+ int num;
+ ArrayList<SmsBroadcastConfigInfo> response;
+ SmsBroadcastConfigInfo info;
+
+ num = p.readInt();
+ response = new ArrayList<SmsBroadcastConfigInfo>(num);
+
+ for (int i = 0; i < num; i++) {
+ int fromId = p.readInt();
+ int toId = p.readInt();
+ int fromScheme = p.readInt();
+ int toScheme = p.readInt();
+ boolean selected = (p.readInt() == 1);
+
+ info = new SmsBroadcastConfigInfo(fromId, toId, fromScheme,
+ toScheme, selected);
+ response.add(info);
+ }
+ return response;
+ }
+
+ private Object
+ responseCdmaBroadcastConfig(Parcel p) {
+ int numServiceCategories;
+ int response[];
+
+ numServiceCategories = p.readInt();
+
+ if (numServiceCategories == 0) {
+ // TODO: The logic of providing default values should
+ // not be done by this transport layer. And needs to
+ // be done by the vendor ril or application logic.
+ int numInts;
+ numInts = CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES * CDMA_BSI_NO_OF_INTS_STRUCT + 1;
+ response = new int[numInts];
+
+ // Faking a default record for all possible records.
+ response[0] = CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES;
+
+ // Loop over CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES set 'english' as
+ // default language and selection status to false for all.
+ for (int i = 1; i < numInts; i += CDMA_BSI_NO_OF_INTS_STRUCT ) {
+ response[i + 0] = i / CDMA_BSI_NO_OF_INTS_STRUCT;
+ response[i + 1] = 1;
+ response[i + 2] = 0;
+ }
+ } else {
+ int numInts;
+ numInts = (numServiceCategories * CDMA_BSI_NO_OF_INTS_STRUCT) + 1;
+ response = new int[numInts];
+
+ response[0] = numServiceCategories;
+ for (int i = 1 ; i < numInts; i++) {
+ response[i] = p.readInt();
+ }
+ }
+
+ return response;
+ }
+
+ private Object
+ responseSignalStrength(Parcel p) {
+ int numInts = 12;
+ int response[];
+
+ /* TODO: Add SignalStrength class to match RIL_SignalStrength */
+ response = new int[numInts];
+ for (int i = 0 ; i < numInts ; i++) {
+ response[i] = p.readInt();
+ }
+
+ return response;
+ }
+
+ private ArrayList<CdmaInformationRecords>
+ responseCdmaInformationRecord(Parcel p) {
+ int numberOfInfoRecs;
+ ArrayList<CdmaInformationRecords> response;
+
+ /**
+ * Loop through all of the information records unmarshalling them
+ * and converting them to Java Objects.
+ */
+ numberOfInfoRecs = p.readInt();
+ response = new ArrayList<CdmaInformationRecords>(numberOfInfoRecs);
+
+ for (int i = 0; i < numberOfInfoRecs; i++) {
+ CdmaInformationRecords InfoRec = new CdmaInformationRecords(p);
+ response.add(InfoRec);
+ }
+
+ return response;
+ }
+
+ private Object
+ responseCdmaCallWaiting(Parcel p) {
+ CdmaCallWaitingNotification notification = new CdmaCallWaitingNotification();
+
+ notification.number = p.readString();
+ notification.numberPresentation = notification.presentationFromCLIP(p.readInt());
+ notification.name = p.readString();
+ notification.namePresentation = notification.numberPresentation;
+ notification.isPresent = p.readInt();
+ notification.signalType = p.readInt();
+ notification.alertPitch = p.readInt();
+ notification.signal = p.readInt();
+ notification.numberType = p.readInt();
+ notification.numberPlan = p.readInt();
+
+ return notification;
+ }
+
+ private Object
+ responseCallRing(Parcel p){
+ char response[] = new char[4];
+
+ response[0] = (char) p.readInt(); // isPresent
+ response[1] = (char) p.readInt(); // signalType
+ response[2] = (char) p.readInt(); // alertPitch
+ response[3] = (char) p.readInt(); // signal
+
+ return response;
+ }
+
+ private void
+ notifyRegistrantsCdmaInfoRec(CdmaInformationRecords infoRec) {
+ int response = RIL_UNSOL_CDMA_INFO_REC;
+ if (infoRec.record instanceof CdmaInformationRecords.CdmaDisplayInfoRec) {
+ if (mDisplayInfoRegistrants != null) {
+ if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+ mDisplayInfoRegistrants.notifyRegistrants(
+ new AsyncResult (null, infoRec.record, null));
+ }
+ } else if (infoRec.record instanceof CdmaInformationRecords.CdmaSignalInfoRec) {
+ if (mSignalInfoRegistrants != null) {
+ if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+ mSignalInfoRegistrants.notifyRegistrants(
+ new AsyncResult (null, infoRec.record, null));
+ }
+ } else if (infoRec.record instanceof CdmaInformationRecords.CdmaNumberInfoRec) {
+ if (mNumberInfoRegistrants != null) {
+ if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+ mNumberInfoRegistrants.notifyRegistrants(
+ new AsyncResult (null, infoRec.record, null));
+ }
+ } else if (infoRec.record instanceof CdmaInformationRecords.CdmaRedirectingNumberInfoRec) {
+ if (mRedirNumInfoRegistrants != null) {
+ if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+ mRedirNumInfoRegistrants.notifyRegistrants(
+ new AsyncResult (null, infoRec.record, null));
+ }
+ } else if (infoRec.record instanceof CdmaInformationRecords.CdmaLineControlInfoRec) {
+ if (mLineControlInfoRegistrants != null) {
+ if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+ mLineControlInfoRegistrants.notifyRegistrants(
+ new AsyncResult (null, infoRec.record, null));
+ }
+ } else if (infoRec.record instanceof CdmaInformationRecords.CdmaT53ClirInfoRec) {
+ if (mT53ClirInfoRegistrants != null) {
+ if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+ mT53ClirInfoRegistrants.notifyRegistrants(
+ new AsyncResult (null, infoRec.record, null));
+ }
+ } else if (infoRec.record instanceof CdmaInformationRecords.CdmaT53AudioControlInfoRec) {
+ if (mT53AudCntrlInfoRegistrants != null) {
+ if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+ mT53AudCntrlInfoRegistrants.notifyRegistrants(
+ new AsyncResult (null, infoRec.record, null));
+ }
+ }
+ }
+
+ 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 "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_VOICE_REGISTRATION_STATE: return "VOICE_REGISTRATION_STATE";
+ case RIL_REQUEST_DATA_REGISTRATION_STATE: return "DATA_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_DATA_CALL: return "SETUP_DATA_CALL";
+ 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_DATA_CALL: return "DEACTIVATE_DATA_CALL";
+ 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_DATA_CALL_FAIL_CAUSE: return "LAST_DATA_CALL_FAIL_CAUSE";
+ case RIL_REQUEST_DATA_CALL_LIST: return "DATA_CALL_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 "REQUEST_STK_GET_PROFILE";
+ case RIL_REQUEST_STK_SET_PROFILE: return "REQUEST_STK_SET_PROFILE";
+ case RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND: return "REQUEST_STK_SEND_ENVELOPE_COMMAND";
+ case RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE: return "REQUEST_STK_SEND_TERMINAL_RESPONSE";
+ case RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM: return "REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM";
+ case RIL_REQUEST_EXPLICIT_CALL_TRANSFER: return "REQUEST_EXPLICIT_CALL_TRANSFER";
+ case RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE: return "REQUEST_SET_PREFERRED_NETWORK_TYPE";
+ case RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE: return "REQUEST_GET_PREFERRED_NETWORK_TYPE";
+ case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS: return "REQUEST_GET_NEIGHBORING_CELL_IDS";
+ case RIL_REQUEST_SET_LOCATION_UPDATES: return "REQUEST_SET_LOCATION_UPDATES";
+ case RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE: return "RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE";
+ case RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE: return "RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE";
+ case RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE: return "RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE";
+ case RIL_REQUEST_SET_TTY_MODE: return "RIL_REQUEST_SET_TTY_MODE";
+ case RIL_REQUEST_QUERY_TTY_MODE: return "RIL_REQUEST_QUERY_TTY_MODE";
+ case RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE: return "RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE";
+ case RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE: return "RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE";
+ case RIL_REQUEST_CDMA_FLASH: return "RIL_REQUEST_CDMA_FLASH";
+ case RIL_REQUEST_CDMA_BURST_DTMF: return "RIL_REQUEST_CDMA_BURST_DTMF";
+ case RIL_REQUEST_CDMA_SEND_SMS: return "RIL_REQUEST_CDMA_SEND_SMS";
+ case RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE: return "RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE";
+ case RIL_REQUEST_GSM_GET_BROADCAST_CONFIG: return "RIL_REQUEST_GSM_GET_BROADCAST_CONFIG";
+ case RIL_REQUEST_GSM_SET_BROADCAST_CONFIG: return "RIL_REQUEST_GSM_SET_BROADCAST_CONFIG";
+ case RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG: return "RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG";
+ case RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG: return "RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG";
+ case RIL_REQUEST_GSM_BROADCAST_ACTIVATION: return "RIL_REQUEST_GSM_BROADCAST_ACTIVATION";
+ case RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY: return "RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY";
+ case RIL_REQUEST_CDMA_BROADCAST_ACTIVATION: return "RIL_REQUEST_CDMA_BROADCAST_ACTIVATION";
+ case RIL_REQUEST_CDMA_SUBSCRIPTION: return "RIL_REQUEST_CDMA_SUBSCRIPTION";
+ case RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM: return "RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM";
+ case RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM: return "RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM";
+ case RIL_REQUEST_DEVICE_IDENTITY: return "RIL_REQUEST_DEVICE_IDENTITY";
+ case RIL_REQUEST_GET_SMSC_ADDRESS: return "RIL_REQUEST_GET_SMSC_ADDRESS";
+ case RIL_REQUEST_SET_SMSC_ADDRESS: return "RIL_REQUEST_SET_SMSC_ADDRESS";
+ case RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE: return "REQUEST_EXIT_EMERGENCY_CALLBACK_MODE";
+ case RIL_REQUEST_REPORT_SMS_MEMORY_STATUS: return "RIL_REQUEST_REPORT_SMS_MEMORY_STATUS";
+ case RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING: return "RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING";
+ case RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE: return "RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE";
+ case RIL_REQUEST_ISIM_AUTHENTICATION: return "RIL_REQUEST_ISIM_AUTHENTICATION";
+ case RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU: return "RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU";
+ case RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS: return "RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS";
+ case RIL_REQUEST_VOICE_RADIO_TECH: return "RIL_REQUEST_VOICE_RADIO_TECH";
+ default: return "<unknown request>";
+ }
+ }
+
+ static String
+ responseToString(int request)
+ {
+/*
+ cat libs/telephony/ril_unsol_commands.h \
+ | egrep "^ *{RIL_" \
+ | sed -re 's/\{RIL_([^,]+),[^,]+,([^}]+).+/case RIL_\1: return "\1";/'
+*/
+ switch(request) {
+ case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED: return "UNSOL_RESPONSE_RADIO_STATE_CHANGED";
+ case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED: return "UNSOL_RESPONSE_CALL_STATE_CHANGED";
+ case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: return "UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED";
+ case RIL_UNSOL_RESPONSE_NEW_SMS: return "UNSOL_RESPONSE_NEW_SMS";
+ case RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT: return "UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT";
+ case RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM: return "UNSOL_RESPONSE_NEW_SMS_ON_SIM";
+ case RIL_UNSOL_ON_USSD: return "UNSOL_ON_USSD";
+ case RIL_UNSOL_ON_USSD_REQUEST: return "UNSOL_ON_USSD_REQUEST";
+ case RIL_UNSOL_NITZ_TIME_RECEIVED: return "UNSOL_NITZ_TIME_RECEIVED";
+ case RIL_UNSOL_SIGNAL_STRENGTH: return "UNSOL_SIGNAL_STRENGTH";
+ case RIL_UNSOL_DATA_CALL_LIST_CHANGED: return "UNSOL_DATA_CALL_LIST_CHANGED";
+ case RIL_UNSOL_SUPP_SVC_NOTIFICATION: return "UNSOL_SUPP_SVC_NOTIFICATION";
+ case RIL_UNSOL_STK_SESSION_END: return "UNSOL_STK_SESSION_END";
+ case RIL_UNSOL_STK_PROACTIVE_COMMAND: return "UNSOL_STK_PROACTIVE_COMMAND";
+ case RIL_UNSOL_STK_EVENT_NOTIFY: return "UNSOL_STK_EVENT_NOTIFY";
+ case RIL_UNSOL_STK_CALL_SETUP: return "UNSOL_STK_CALL_SETUP";
+ case RIL_UNSOL_SIM_SMS_STORAGE_FULL: return "UNSOL_SIM_SMS_STORAGE_FULL";
+ case RIL_UNSOL_SIM_REFRESH: return "UNSOL_SIM_REFRESH";
+ case RIL_UNSOL_CALL_RING: return "UNSOL_CALL_RING";
+ case RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED: return "UNSOL_RESPONSE_SIM_STATUS_CHANGED";
+ case RIL_UNSOL_RESPONSE_CDMA_NEW_SMS: return "UNSOL_RESPONSE_CDMA_NEW_SMS";
+ case RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS: return "UNSOL_RESPONSE_NEW_BROADCAST_SMS";
+ case RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL: return "UNSOL_CDMA_RUIM_SMS_STORAGE_FULL";
+ case RIL_UNSOL_RESTRICTED_STATE_CHANGED: return "UNSOL_RESTRICTED_STATE_CHANGED";
+ case RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE: return "UNSOL_ENTER_EMERGENCY_CALLBACK_MODE";
+ case RIL_UNSOL_CDMA_CALL_WAITING: return "UNSOL_CDMA_CALL_WAITING";
+ case RIL_UNSOL_CDMA_OTA_PROVISION_STATUS: return "UNSOL_CDMA_OTA_PROVISION_STATUS";
+ case RIL_UNSOL_CDMA_INFO_REC: return "UNSOL_CDMA_INFO_REC";
+ case RIL_UNSOL_OEM_HOOK_RAW: return "UNSOL_OEM_HOOK_RAW";
+ case RIL_UNSOL_RINGBACK_TONE: return "UNSOL_RINGBACK_TONG";
+ case RIL_UNSOL_RESEND_INCALL_MUTE: return "UNSOL_RESEND_INCALL_MUTE";
+ case RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED: return "CDMA_SUBSCRIPTION_SOURCE_CHANGED";
+ case RIL_UNSOl_CDMA_PRL_CHANGED: return "UNSOL_CDMA_PRL_CHANGED";
+ case RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE: return "UNSOL_EXIT_EMERGENCY_CALLBACK_MODE";
+ case RIL_UNSOL_RIL_CONNECTED: return "UNSOL_RIL_CONNECTED";
+ case RIL_UNSOL_VOICE_RADIO_TECH_CHANGED: return "UNSOL_VOICE_RADIO_TECH_CHANGED";
+ default: return "<unknown reponse>";
+ }
+ }
+
+ private void riljLog(String msg) {
+ Log.d(LOG_TAG, msg);
+ }
+
+ private void riljLogv(String msg) {
+ Log.v(LOG_TAG, msg);
+ }
+
+ private void unsljLog(int response) {
+ riljLog("[UNSL]< " + responseToString(response));
+ }
+
+ private void unsljLogMore(int response, String more) {
+ riljLog("[UNSL]< " + responseToString(response) + " " + more);
+ }
+
+ private void unsljLogRet(int response, Object ret) {
+ riljLog("[UNSL]< " + responseToString(response) + " " + retToString(response, ret));
+ }
+
+ private void unsljLogvRet(int response, Object ret) {
+ riljLogv("[UNSL]< " + responseToString(response) + " " + retToString(response, ret));
+ }
+
+
+ // ***** Methods for CDMA support
+ public void
+ getDeviceIdentity(Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_DEVICE_IDENTITY, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void
+ getCDMASubscription(Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_SUBSCRIPTION, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ @Override
+ public void setPhoneType(int phoneType) { // Called by CDMAPhone and GSMPhone constructor
+ if (RILJ_LOGD) riljLog("setPhoneType=" + phoneType + " old value=" + mPhoneType);
+ mPhoneType = phoneType;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void queryCdmaRoamingPreference(Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE, response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(cdmaRoamingType);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " : " + cdmaRoamingType);
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setCdmaSubscriptionSource(int cdmaSubscription , Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE, response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(cdmaSubscription);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " : " + cdmaSubscription);
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void getCdmaSubscriptionSource(Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void queryTTYMode(Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_QUERY_TTY_MODE, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setTTYMode(int ttyMode, Message response) {
+ RILRequest rr = RILRequest.obtain(
+ RILConstants.RIL_REQUEST_SET_TTY_MODE, response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(ttyMode);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " : " + ttyMode);
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void
+ sendCDMAFeatureCode(String FeatureCode, Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_FLASH, response);
+
+ rr.mp.writeString(FeatureCode);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " : " + FeatureCode);
+
+ send(rr);
+ }
+
+ public void getCdmaBroadcastConfig(Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG, response);
+
+ send(rr);
+ }
+
+ // TODO: Change the configValuesArray to a RIL_BroadcastSMSConfig
+ public void setCdmaBroadcastConfig(int[] configValuesArray, Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG, response);
+
+ for(int i = 0; i < configValuesArray.length; i++) {
+ rr.mp.writeInt(configValuesArray[i]);
+ }
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void setCdmaBroadcastActivation(boolean activate, Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_BROADCAST_ACTIVATION, response);
+
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(activate ? 0 :1);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void exitEmergencyCallbackMode(Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, response);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ public void requestIsimAuthentication(String nonce, Message response) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_ISIM_AUTHENTICATION, response);
+
+ rr.mp.writeString(nonce);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+ send(rr);
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.internal.telephony.BaseCommands#testingEmergencyCall()
+ */
+ @Override
+ public void testingEmergencyCall() {
+ if (RILJ_LOGD) riljLog("testingEmergencyCall");
+ mTestingEmergencyCall.set(true);
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("RIL:");
+ pw.println(" mSocket=" + mSocket);
+ pw.println(" mSenderThread=" + mSenderThread);
+ pw.println(" mSender=" + mSender);
+ pw.println(" mReceiverThread=" + mReceiverThread);
+ pw.println(" mReceiver=" + mReceiver);
+ pw.println(" mWakeLock=" + mWakeLock);
+ pw.println(" mWakeLockTimeout=" + mWakeLockTimeout);
+ synchronized (mRequestsList) {
+ pw.println(" mRequestMessagesPending=" + mRequestMessagesPending);
+ pw.println(" mRequestMessagesWaiting=" + mRequestMessagesWaiting);
+ int count = mRequestsList.size();
+ pw.println(" mRequestList count=" + count);
+ for (int i = 0; i < count; i++) {
+ RILRequest rr = mRequestsList.get(i);
+ pw.println(" [" + rr.mSerial + "] " + requestToString(rr.mRequest));
+ }
+ }
+ pw.println(" mLastNITZTimeInfo=" + mLastNITZTimeInfo);
+ pw.println(" mTestingEmergencyCall=" + mTestingEmergencyCall.get());
+ }
+}
diff --git a/src/java/com/android/internal/telephony/RestrictedState.java b/src/java/com/android/internal/telephony/RestrictedState.java
new file mode 100644
index 0000000..ad2b88d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RestrictedState.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;
+
+import android.telephony.ServiceState;
+
+public class RestrictedState {
+
+ /**
+ * Set true to block packet data access due to restriction
+ */
+ private boolean mPsRestricted;
+ /**
+ * Set true to block all normal voice/SMS/USSD/SS/AV64 due to restriction
+ */
+ private boolean mCsNormalRestricted;
+ /**
+ * Set true to block emergency call due to restriction
+ */
+ private boolean mCsEmergencyRestricted;
+
+ public RestrictedState() {
+ setPsRestricted(false);
+ setCsNormalRestricted(false);
+ setCsEmergencyRestricted(false);
+ }
+
+ /**
+ * @param csEmergencyRestricted the csEmergencyRestricted to set
+ */
+ public void setCsEmergencyRestricted(boolean csEmergencyRestricted) {
+ mCsEmergencyRestricted = csEmergencyRestricted;
+ }
+
+ /**
+ * @return the csEmergencyRestricted
+ */
+ public boolean isCsEmergencyRestricted() {
+ return mCsEmergencyRestricted;
+ }
+
+ /**
+ * @param csNormalRestricted the csNormalRestricted to set
+ */
+ public void setCsNormalRestricted(boolean csNormalRestricted) {
+ mCsNormalRestricted = csNormalRestricted;
+ }
+
+ /**
+ * @return the csNormalRestricted
+ */
+ public boolean isCsNormalRestricted() {
+ return mCsNormalRestricted;
+ }
+
+ /**
+ * @param psRestricted the psRestricted to set
+ */
+ public void setPsRestricted(boolean psRestricted) {
+ mPsRestricted = psRestricted;
+ }
+
+ /**
+ * @return the psRestricted
+ */
+ public boolean isPsRestricted() {
+ return mPsRestricted;
+ }
+
+ public boolean isCsRestricted() {
+ return mCsNormalRestricted && mCsEmergencyRestricted;
+ }
+
+ @Override
+ public boolean equals (Object o) {
+ RestrictedState s;
+
+ try {
+ s = (RestrictedState) o;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ return mPsRestricted == s.mPsRestricted
+ && mCsNormalRestricted == s.mCsNormalRestricted
+ && mCsEmergencyRestricted == s.mCsEmergencyRestricted;
+ }
+
+ @Override
+ public String toString() {
+ String csString = "none";
+
+ if (mCsEmergencyRestricted && mCsNormalRestricted) {
+ csString = "all";
+ } else if (mCsEmergencyRestricted && !mCsNormalRestricted) {
+ csString = "emergency";
+ } else if (!mCsEmergencyRestricted && mCsNormalRestricted) {
+ csString = "normal call";
+ }
+
+ return "Restricted State CS: " + csString + " PS:" + mPsRestricted;
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/RetryManager.java b/src/java/com/android/internal/telephony/RetryManager.java
new file mode 100644
index 0000000..250d99e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RetryManager.java
@@ -0,0 +1,410 @@
+/**
+ * Copyright (C) 2009 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.util.Log;
+import android.util.Pair;
+import android.text.TextUtils;
+
+import java.util.Random;
+import java.util.ArrayList;
+
+/**
+ * Retry manager allows a simple way to declare a series of
+ * retry timeouts. After creating a RetryManager the configure
+ * method is used to define the sequence. A simple linear series
+ * may be initialized using configure with three integer parameters
+ * The other configure method allows a series to be declared using
+ * a string.
+ *<p>
+ * The format of the configuration string is a series of parameters
+ * separated by a comma. There are two name value pair parameters plus a series
+ * of delay times. The units of of these delay times is unspecified.
+ * The name value pairs which may be specified are:
+ *<ul>
+ *<li>max_retries=<value>
+ *<li>default_randomizationTime=<value>
+ *</ul>
+ *<p>
+ * max_retries is the number of times that incrementRetryCount
+ * maybe called before isRetryNeeded will return false. if value
+ * is infinite then isRetryNeeded will always return true.
+ *
+ * default_randomizationTime will be used as the randomizationTime
+ * for delay times which have no supplied randomizationTime. If
+ * default_randomizationTime is not defined it defaults to 0.
+ *<p>
+ * The other parameters define The series of delay times and each
+ * may have an optional randomization value separated from the
+ * delay time by a colon.
+ *<p>
+ * Examples:
+ * <ul>
+ * <li>3 retries with no randomization value which means its 0:
+ * <ul><li><code>"1000, 2000, 3000"</code></ul>
+ *
+ * <li>10 retries with a 500 default randomization value for each and
+ * the 4..10 retries all using 3000 as the delay:
+ * <ul><li><code>"max_retries=10, default_randomization=500, 1000, 2000, 3000"</code></ul>
+ *
+ * <li>4 retries with a 100 as the default randomization value for the first 2 values and
+ * the other two having specified values of 500:
+ * <ul><li><code>"default_randomization=100, 1000, 2000, 4000:500, 5000:500"</code></ul>
+ *
+ * <li>Infinite number of retries with the first one at 1000, the second at 2000 all
+ * others will be at 3000.
+ * <ul><li><code>"max_retries=infinite,1000,2000,3000</code></ul>
+ * </ul>
+ *
+ * {@hide}
+ */
+public class RetryManager {
+ static public final String LOG_TAG = "GSM";
+ static public final boolean DBG = true;
+ static public final boolean VDBG = false;
+
+ /**
+ * Retry record with times in milli-seconds
+ */
+ private static class RetryRec {
+ RetryRec(int delayTime, int randomizationTime) {
+ mDelayTime = delayTime;
+ mRandomizationTime = randomizationTime;
+ }
+
+ int mDelayTime;
+ int mRandomizationTime;
+ }
+
+ /** The array of retry records */
+ private ArrayList<RetryRec> mRetryArray = new ArrayList<RetryRec>();
+
+ /** When true isRetryNeeded() will always return true */
+ private boolean mRetryForever;
+
+ /**
+ * The maximum number of retries to attempt before
+ * isRetryNeeded returns false
+ */
+ private int mMaxRetryCount;
+
+ /** The current number of retries */
+ private int mRetryCount;
+
+ /** Random number generator */
+ private Random rng = new Random();
+
+ private String mConfig;
+
+ /** Constructor */
+ public RetryManager() {
+ if (VDBG) log("constructor");
+ }
+
+ public String toString() {
+ String ret = "RetryManager: forever=" + mRetryForever + ", maxRetry=" + mMaxRetryCount +
+ ", retry=" + mRetryCount + ",\n " + mConfig;
+ for (RetryRec r : mRetryArray) {
+ ret += "\n " + r.mDelayTime + ":" + r.mRandomizationTime;
+ }
+ return ret;
+ }
+
+ /**
+ * Configure for a simple linear sequence of times plus
+ * a random value.
+ *
+ * @param maxRetryCount is the maximum number of retries
+ * before isRetryNeeded returns false.
+ * @param retryTime is a time that will be returned by getRetryTime.
+ * @param randomizationTime a random value between 0 and
+ * randomizationTime will be added to retryTime. this
+ * parameter may be 0.
+ * @return true if successful
+ */
+ public boolean configure(int maxRetryCount, int retryTime, int randomizationTime) {
+ Pair<Boolean, Integer> value;
+
+ if (VDBG) log("configure: " + maxRetryCount + ", " + retryTime + "," + randomizationTime);
+
+ if (!validateNonNegativeInt("maxRetryCount", maxRetryCount)) {
+ return false;
+ }
+
+ if (!validateNonNegativeInt("retryTime", retryTime)) {
+ return false;
+ }
+
+ if (!validateNonNegativeInt("randomizationTime", randomizationTime)) {
+ return false;
+ }
+
+ mMaxRetryCount = maxRetryCount;
+ resetRetryCount();
+ mRetryArray.clear();
+ mRetryArray.add(new RetryRec(retryTime, randomizationTime));
+
+ return true;
+ }
+
+ /**
+ * Configure for using string which allow arbitrary
+ * sequences of times. See class comments for the
+ * string format.
+ *
+ * @return true if successful
+ */
+ public boolean configure(String configStr) {
+ // Strip quotes if present.
+ if ((configStr.startsWith("\"") && configStr.endsWith("\""))) {
+ configStr = configStr.substring(1, configStr.length()-1);
+ }
+ if (VDBG) log("configure: '" + configStr + "'");
+ mConfig = configStr;
+
+ if (!TextUtils.isEmpty(configStr)) {
+ int defaultRandomization = 0;
+
+ if (VDBG) log("configure: not empty");
+
+ mMaxRetryCount = 0;
+ resetRetryCount();
+ mRetryArray.clear();
+
+ String strArray[] = configStr.split(",");
+ for (int i = 0; i < strArray.length; i++) {
+ if (VDBG) log("configure: strArray[" + i + "]='" + strArray[i] + "'");
+ Pair<Boolean, Integer> value;
+ String splitStr[] = strArray[i].split("=", 2);
+ splitStr[0] = splitStr[0].trim();
+ if (VDBG) log("configure: splitStr[0]='" + splitStr[0] + "'");
+ if (splitStr.length > 1) {
+ splitStr[1] = splitStr[1].trim();
+ if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
+ if (TextUtils.equals(splitStr[0], "default_randomization")) {
+ value = parseNonNegativeInt(splitStr[0], splitStr[1]);
+ if (!value.first) return false;
+ defaultRandomization = value.second;
+ } else if (TextUtils.equals(splitStr[0], "max_retries")) {
+ if (TextUtils.equals("infinite",splitStr[1])) {
+ mRetryForever = true;
+ } else {
+ value = parseNonNegativeInt(splitStr[0], splitStr[1]);
+ if (!value.first) return false;
+ mMaxRetryCount = value.second;
+ }
+ } else {
+ Log.e(LOG_TAG, "Unrecognized configuration name value pair: "
+ + strArray[i]);
+ return false;
+ }
+ } else {
+ /**
+ * Assume a retry time with an optional randomization value
+ * following a ":"
+ */
+ splitStr = strArray[i].split(":", 2);
+ splitStr[0] = splitStr[0].trim();
+ RetryRec rr = new RetryRec(0, 0);
+ value = parseNonNegativeInt("delayTime", splitStr[0]);
+ if (!value.first) return false;
+ rr.mDelayTime = value.second;
+
+ // Check if optional randomization value present
+ if (splitStr.length > 1) {
+ splitStr[1] = splitStr[1].trim();
+ if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
+ value = parseNonNegativeInt("randomizationTime", splitStr[1]);
+ if (!value.first) return false;
+ rr.mRandomizationTime = value.second;
+ } else {
+ rr.mRandomizationTime = defaultRandomization;
+ }
+ mRetryArray.add(rr);
+ }
+ }
+ if (mRetryArray.size() > mMaxRetryCount) {
+ mMaxRetryCount = mRetryArray.size();
+ if (VDBG) log("configure: setting mMaxRetryCount=" + mMaxRetryCount);
+ }
+ if (VDBG) log("configure: true");
+ return true;
+ } else {
+ if (VDBG) log("configure: false it's empty");
+ return false;
+ }
+ }
+
+ /**
+ * Report whether data reconnection should be retried
+ *
+ * @return {@code true} if the max retries has not been reached. {@code
+ * false} otherwise.
+ */
+ public boolean isRetryNeeded() {
+ boolean retVal = mRetryForever || (mRetryCount < mMaxRetryCount);
+ if (DBG) log("isRetryNeeded: " + retVal);
+ return retVal;
+ }
+
+ /**
+ * Return the timer that should be used to trigger the data reconnection
+ */
+ public int getRetryTimer() {
+ int index;
+ if (mRetryCount < mRetryArray.size()) {
+ index = mRetryCount;
+ } else {
+ index = mRetryArray.size() - 1;
+ }
+
+ int retVal;
+ if ((index >= 0) && (index < mRetryArray.size())) {
+ retVal = mRetryArray.get(index).mDelayTime + nextRandomizationTime(index);
+ } else {
+ retVal = 0;
+ }
+
+ if (DBG) log("getRetryTimer: " + retVal);
+ return retVal;
+ }
+
+ /**
+ * @return retry count
+ */
+ public int getRetryCount() {
+ if (DBG) log("getRetryCount: " + mRetryCount);
+ return mRetryCount;
+ }
+
+ /**
+ * Increase the retry counter, does not change retry forever.
+ */
+ public void increaseRetryCount() {
+ mRetryCount++;
+ if (mRetryCount > mMaxRetryCount) {
+ mRetryCount = mMaxRetryCount;
+ }
+ if (DBG) log("increaseRetryCount: " + mRetryCount);
+ }
+
+ /**
+ * Set retry count to the specified value
+ */
+ public void setRetryCount(int count) {
+ mRetryCount = count;
+ if (mRetryCount > mMaxRetryCount) {
+ mRetryCount = mMaxRetryCount;
+ }
+
+ if (mRetryCount < 0) {
+ mRetryCount = 0;
+ }
+
+ if (DBG) log("setRetryCount: " + mRetryCount);
+ }
+
+ /**
+ * Set retry forever to the specified value
+ */
+ public void setRetryForever(boolean retryForever) {
+ mRetryForever = retryForever;
+ if (DBG) log("setRetryForever: " + mRetryForever);
+ }
+
+ /**
+ * Clear the data-retry counter
+ */
+ public void resetRetryCount() {
+ mRetryCount = 0;
+ if (DBG) log("resetRetryCount: " + mRetryCount);
+ }
+
+ /**
+ * Retry forever using last timeout time.
+ */
+ public void retryForeverUsingLastTimeout() {
+ mRetryCount = mMaxRetryCount;
+ mRetryForever = true;
+ if (DBG) log("retryForeverUsingLastTimeout: " + mRetryForever + ", " + mRetryCount);
+ }
+
+ /**
+ * @return true if retrying forever
+ */
+ public boolean isRetryForever() {
+ if (DBG) log("isRetryForever: " + mRetryForever);
+ return mRetryForever;
+ }
+
+ /**
+ * Parse an integer validating the value is not negative.
+ *
+ * @param name
+ * @param stringValue
+ * @return Pair.first == true if stringValue an integer >= 0
+ */
+ private Pair<Boolean, Integer> parseNonNegativeInt(String name, String stringValue) {
+ int value;
+ Pair<Boolean, Integer> retVal;
+ try {
+ value = Integer.parseInt(stringValue);
+ retVal = new Pair<Boolean, Integer>(validateNonNegativeInt(name, value), value);
+ } catch (NumberFormatException e) {
+ Log.e(LOG_TAG, name + " bad value: " + stringValue, e);
+ retVal = new Pair<Boolean, Integer>(false, 0);
+ }
+ if (VDBG) log("parseNonNetativeInt: " + name + ", " + stringValue + ", "
+ + retVal.first + ", " + retVal.second);
+ return retVal;
+ }
+
+ /**
+ * Validate an integer is >= 0 and logs an error if not
+ *
+ * @param name
+ * @param value
+ * @return Pair.first
+ */
+ private boolean validateNonNegativeInt(String name, int value) {
+ boolean retVal;
+ if (value < 0) {
+ Log.e(LOG_TAG, name + " bad value: is < 0");
+ retVal = false;
+ } else {
+ retVal = true;
+ }
+ if (VDBG) log("validateNonNegative: " + name + ", " + value + ", " + retVal);
+ return retVal;
+ }
+
+ /**
+ * Return next random number for the index
+ */
+ private int nextRandomizationTime(int index) {
+ int randomTime = mRetryArray.get(index).mRandomizationTime;
+ if (randomTime == 0) {
+ return 0;
+ } else {
+ return rng.nextInt(randomTime);
+ }
+ }
+
+ private void log(String s) {
+ Log.d(LOG_TAG, "[RM] " + s);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
new file mode 100644
index 0000000..4a6c5dc
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.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;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.provider.Telephony;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.SmsCbMessage;
+import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+import android.text.Html;
+import android.text.Spanned;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.util.HexDump;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Random;
+
+import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
+import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
+import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
+import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE;
+import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU;
+import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF;
+
+public abstract class SMSDispatcher extends Handler {
+ static final String TAG = "SMS"; // accessed from inner class
+ private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
+
+ /** Permission required to receive SMS and SMS-CB messages. */
+ public static final String RECEIVE_SMS_PERMISSION = "android.permission.RECEIVE_SMS";
+
+ /** Permission required to receive ETWS and CMAS emergency broadcasts. */
+ public static final String RECEIVE_EMERGENCY_BROADCAST_PERMISSION =
+ "android.permission.RECEIVE_EMERGENCY_BROADCAST";
+
+ /** Permission required to send SMS to short codes without user confirmation. */
+ private static final String SEND_SMS_NO_CONFIRMATION_PERMISSION =
+ "android.permission.SEND_SMS_NO_CONFIRMATION";
+
+ /** Query projection for checking for duplicate message segments. */
+ private static final String[] PDU_PROJECTION = new String[] {
+ "pdu"
+ };
+
+ /** Query projection for combining concatenated message segments. */
+ private static final String[] PDU_SEQUENCE_PORT_PROJECTION = new String[] {
+ "pdu",
+ "sequence",
+ "destination_port"
+ };
+
+ private static final int PDU_COLUMN = 0;
+ private static final int SEQUENCE_COLUMN = 1;
+ private static final int DESTINATION_PORT_COLUMN = 2;
+
+ /** New SMS received. */
+ protected static final int EVENT_NEW_SMS = 1;
+
+ /** SMS send complete. */
+ protected static final int EVENT_SEND_SMS_COMPLETE = 2;
+
+ /** Retry sending a previously failed SMS message */
+ private static final int EVENT_SEND_RETRY = 3;
+
+ /** Confirmation required for sending a large number of messages. */
+ private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4;
+
+ /** Send the user confirmed SMS */
+ static final int EVENT_SEND_CONFIRMED_SMS = 5; // accessed from inner class
+
+ /** Don't send SMS (user did not confirm). */
+ static final int EVENT_STOP_SENDING = 7; // accessed from inner class
+
+ /** Confirmation required for third-party apps sending to an SMS short code. */
+ private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8;
+
+ /** Confirmation required for third-party apps sending to an SMS short code. */
+ private static final int EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE = 9;
+
+ protected final Phone mPhone;
+ protected final Context mContext;
+ protected final ContentResolver mResolver;
+ protected final CommandsInterface mCm;
+ protected final SmsStorageMonitor mStorageMonitor;
+ protected final TelephonyManager mTelephonyManager;
+
+ protected final WapPushOverSms mWapPush;
+
+ protected static 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;
+ /** single part SMS */
+ private static final int SINGLE_PART_SMS = 1;
+ /** Message sending queue limit */
+ private static final int MO_MSG_QUEUE_LIMIT = 5;
+
+ /**
+ * Message reference for a CONCATENATED_8_BIT_REFERENCE or
+ * CONCATENATED_16_BIT_REFERENCE message set. Should be
+ * incremented for each set of concatenated messages.
+ * Static field shared by all dispatcher objects.
+ */
+ private static int sConcatenatedRef = new Random().nextInt(256);
+
+ /** Outgoing message counter. Shared by all dispatchers. */
+ private final SmsUsageMonitor mUsageMonitor;
+
+ /** Number of outgoing SmsTrackers waiting for user confirmation. */
+ private int mPendingTrackerCount;
+
+ /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
+ private PowerManager.WakeLock mWakeLock;
+
+ /**
+ * Hold the wake lock for 5 seconds, which should be enough time for
+ * any receiver(s) to grab its own wake lock.
+ */
+ private static final int WAKE_LOCK_TIMEOUT = 5000;
+
+ /* Flags indicating whether the current device allows sms service */
+ protected boolean mSmsCapable = true;
+ protected boolean mSmsReceiveDisabled;
+ protected boolean mSmsSendDisabled;
+
+ protected int mRemainingMessages = -1;
+
+ protected static int getNextConcatenatedRef() {
+ sConcatenatedRef += 1;
+ return sConcatenatedRef;
+ }
+
+ /**
+ * Create a new SMS dispatcher.
+ * @param phone the Phone to use
+ * @param storageMonitor the SmsStorageMonitor to use
+ * @param usageMonitor the SmsUsageMonitor to use
+ */
+ protected SMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
+ SmsUsageMonitor usageMonitor) {
+ mPhone = phone;
+ mWapPush = new WapPushOverSms(phone, this);
+ mContext = phone.getContext();
+ mResolver = mContext.getContentResolver();
+ mCm = phone.mCM;
+ mStorageMonitor = storageMonitor;
+ mUsageMonitor = usageMonitor;
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+
+ createWakelock();
+
+ mSmsCapable = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_sms_capable);
+ mSmsReceiveDisabled = !SystemProperties.getBoolean(
+ TelephonyProperties.PROPERTY_SMS_RECEIVE, mSmsCapable);
+ mSmsSendDisabled = !SystemProperties.getBoolean(
+ TelephonyProperties.PROPERTY_SMS_SEND, mSmsCapable);
+ Log.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable + " format=" + getFormat()
+ + " mSmsReceiveDisabled=" + mSmsReceiveDisabled
+ + " mSmsSendDisabled=" + mSmsSendDisabled);
+ }
+
+ /** Unregister for incoming SMS events. */
+ public abstract void dispose();
+
+ /**
+ * The format of the message PDU in the associated broadcast intent.
+ * This will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
+ * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
+ *
+ * Note: All applications which handle incoming SMS messages by processing the
+ * SMS_RECEIVED_ACTION broadcast intent MUST pass the "format" extra from the intent
+ * into the new methods in {@link android.telephony.SmsMessage} which take an
+ * extra format parameter. This is required in order to correctly decode the PDU on
+ * devices which require support for both 3GPP and 3GPP2 formats at the same time,
+ * such as CDMA/LTE devices and GSM/CDMA world phones.
+ *
+ * @return the format of the message PDU
+ */
+ protected abstract String getFormat();
+
+ @Override
+ protected void finalize() {
+ Log.d(TAG, "SMSDispatcher finalized");
+ }
+
+
+ /* 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. */
+ protected 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 (false) {
+ Log.d(TAG, "New SMS Message Received");
+ }
+
+ SmsMessage sms;
+
+ ar = (AsyncResult) msg.obj;
+
+ if (ar.exception != null) {
+ Log.e(TAG, "Exception processing incoming SMS. Exception:" + ar.exception);
+ return;
+ }
+
+ sms = (SmsMessage) ar.result;
+ try {
+ int result = dispatchMessage(sms.mWrappedSmsMessage);
+ if (result != Activity.RESULT_OK) {
+ // RESULT_OK means that message was broadcast for app(s) to handle.
+ // Any other result, we should ack here.
+ boolean handled = (result == Intents.RESULT_SMS_HANDLED);
+ notifyAndAcknowledgeLastIncomingSms(handled, result, null);
+ }
+ } catch (RuntimeException ex) {
+ Log.e(TAG, "Exception dispatching message", ex);
+ notifyAndAcknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null);
+ }
+
+ break;
+
+ case EVENT_SEND_SMS_COMPLETE:
+ // An outbound SMS has been successfully transferred, or failed.
+ handleSendComplete((AsyncResult) msg.obj);
+ break;
+
+ case EVENT_SEND_RETRY:
+ sendSms((SmsTracker) msg.obj);
+ break;
+
+ case EVENT_SEND_LIMIT_REACHED_CONFIRMATION:
+ handleReachSentLimit((SmsTracker)(msg.obj));
+ break;
+
+ case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE:
+ handleConfirmShortCode(false, (SmsTracker)(msg.obj));
+ break;
+
+ case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE:
+ handleConfirmShortCode(true, (SmsTracker)(msg.obj));
+ break;
+
+ case EVENT_SEND_CONFIRMED_SMS:
+ {
+ SmsTracker tracker = (SmsTracker) msg.obj;
+ if (tracker.isMultipart()) {
+ sendMultipartSms(tracker);
+ } else {
+ sendSms(tracker);
+ }
+ mPendingTrackerCount--;
+ break;
+ }
+
+ case EVENT_STOP_SENDING:
+ {
+ SmsTracker tracker = (SmsTracker) msg.obj;
+ if (tracker.mSentIntent != null) {
+ try {
+ tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
+ } catch (CanceledException ex) {
+ Log.e(TAG, "failed to send RESULT_ERROR_LIMIT_EXCEEDED");
+ }
+ }
+ mPendingTrackerCount--;
+ break;
+ }
+ }
+ }
+
+ private void createWakelock() {
+ PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher");
+ mWakeLock.setReferenceCounted(true);
+ }
+
+ /**
+ * Grabs a wake lock and sends intent as an ordered broadcast.
+ * The resultReceiver will check for errors and ACK/NACK back
+ * to the RIL.
+ *
+ * @param intent intent to broadcast
+ * @param permission Receivers are required to have this permission
+ */
+ public void dispatch(Intent intent, String permission) {
+ // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
+ // receivers time to take their own wake locks.
+ mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
+ mContext.sendOrderedBroadcast(intent, permission, mResultReceiver,
+ this, Activity.RESULT_OK, null, 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.
+ */
+ protected void handleSendComplete(AsyncResult ar) {
+ SmsTracker tracker = (SmsTracker) ar.userObj;
+ PendingIntent sentIntent = tracker.mSentIntent;
+
+ if (ar.exception == null) {
+ if (false) {
+ 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 {
+ if (mRemainingMessages > -1) {
+ mRemainingMessages--;
+ }
+
+ if (mRemainingMessages == 0) {
+ Intent sendNext = new Intent();
+ sendNext.putExtra(SEND_NEXT_MSG_EXTRA, true);
+ sentIntent.send(mContext, Activity.RESULT_OK, sendNext);
+ } else {
+ sentIntent.send(Activity.RESULT_OK);
+ }
+ } catch (CanceledException ex) {}
+ }
+ } else {
+ if (false) {
+ Log.d(TAG, "SMS send failed");
+ }
+
+ int ss = mPhone.getServiceState().getState();
+
+ if (ss != ServiceState.STATE_IN_SERVICE) {
+ handleNotInService(ss, tracker.mSentIntent);
+ } 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) {
+ int error = RESULT_ERROR_GENERIC_FAILURE;
+
+ if (((CommandException)(ar.exception)).getCommandError()
+ == CommandException.Error.FDN_CHECK_FAILURE) {
+ error = RESULT_ERROR_FDN_CHECK_FAILURE;
+ }
+ // Done retrying; return an error to the app.
+ try {
+ Intent fillIn = new Intent();
+ if (ar.result != null) {
+ fillIn.putExtra("errorCode", ((SmsResponse)ar.result).errorCode);
+ }
+ if (mRemainingMessages > -1) {
+ mRemainingMessages--;
+ }
+
+ if (mRemainingMessages == 0) {
+ fillIn.putExtra(SEND_NEXT_MSG_EXTRA, true);
+ }
+
+ tracker.mSentIntent.send(mContext, error, fillIn);
+ } 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 sentIntent the PendingIntent to send the error to
+ */
+ protected static void handleNotInService(int ss, PendingIntent sentIntent) {
+ if (sentIntent != null) {
+ try {
+ if (ss == ServiceState.STATE_POWER_OFF) {
+ sentIntent.send(RESULT_ERROR_RADIO_OFF);
+ } else {
+ sentIntent.send(RESULT_ERROR_NO_SERVICE);
+ }
+ } catch (CanceledException ex) {}
+ }
+ }
+
+ /**
+ * Dispatches an incoming SMS messages.
+ *
+ * @param sms the incoming message from the phone
+ * @return a result code from {@link Telephony.Sms.Intents}, or
+ * {@link Activity#RESULT_OK} if the message has been broadcast
+ * to applications
+ */
+ public abstract int dispatchMessage(SmsMessageBase sms);
+
+ /**
+ * Dispatch a normal incoming SMS. This is called from the format-specific
+ * {@link #dispatchMessage(SmsMessageBase)} if no format-specific handling is required.
+ *
+ * @param sms
+ * @return
+ */
+ protected int dispatchNormalMessage(SmsMessageBase sms) {
+ SmsHeader smsHeader = sms.getUserDataHeader();
+
+ // See if message is partial or port addressed.
+ if ((smsHeader == null) || (smsHeader.concatRef == null)) {
+ // Message is not partial (not part of concatenated sequence).
+ byte[][] pdus = new byte[1][];
+ pdus[0] = sms.getPdu();
+
+ if (smsHeader != null && smsHeader.portAddrs != null) {
+ if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
+ // GSM-style WAP indication
+ return mWapPush.dispatchWapPdu(sms.getUserData());
+ } else {
+ // The message was sent to a port, so concoct a URI for it.
+ dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
+ }
+ } else {
+ // Normal short and non-port-addressed message, dispatch it.
+ dispatchPdus(pdus);
+ }
+ return Activity.RESULT_OK;
+ } else {
+ // Process the message part.
+ SmsHeader.ConcatRef concatRef = smsHeader.concatRef;
+ SmsHeader.PortAddrs portAddrs = smsHeader.portAddrs;
+ return processMessagePart(sms.getPdu(), sms.getOriginatingAddress(),
+ concatRef.refNumber, concatRef.seqNumber, concatRef.msgCount,
+ sms.getTimestampMillis(), (portAddrs != null ? portAddrs.destPort : -1), false);
+ }
+ }
+
+ /**
+ * If this is the last part send the parts out to the application, otherwise
+ * the part is stored for later processing. Handles both 3GPP concatenated messages
+ * as well as 3GPP2 format WAP push messages processed by
+ * {@link com.android.internal.telephony.cdma.CdmaSMSDispatcher#processCdmaWapPdu}.
+ *
+ * @param pdu the message PDU, or the datagram portion of a CDMA WDP datagram segment
+ * @param address the originating address
+ * @param referenceNumber distinguishes concatenated messages from the same sender
+ * @param sequenceNumber the order of this segment in the message
+ * (starting at 0 for CDMA WDP datagrams and 1 for concatenated messages).
+ * @param messageCount the number of segments in the message
+ * @param timestamp the service center timestamp in millis
+ * @param destPort the destination port for the message, or -1 for no destination port
+ * @param isCdmaWapPush true if pdu is a CDMA WDP datagram segment and not an SM PDU
+ *
+ * @return a result code from {@link Telephony.Sms.Intents}, or
+ * {@link Activity#RESULT_OK} if the message has been broadcast
+ * to applications
+ */
+ protected int processMessagePart(byte[] pdu, String address, int referenceNumber,
+ int sequenceNumber, int messageCount, long timestamp, int destPort,
+ boolean isCdmaWapPush) {
+ byte[][] pdus = null;
+ Cursor cursor = null;
+ try {
+ // used by several query selection arguments
+ String refNumber = Integer.toString(referenceNumber);
+ String seqNumber = Integer.toString(sequenceNumber);
+
+ // Check for duplicate message segment
+ cursor = mResolver.query(mRawUri, PDU_PROJECTION,
+ "address=? AND reference_number=? AND sequence=?",
+ new String[] {address, refNumber, seqNumber}, null);
+
+ // moveToNext() returns false if no duplicates were found
+ if (cursor.moveToNext()) {
+ Log.w(TAG, "Discarding duplicate message segment from address=" + address
+ + " refNumber=" + refNumber + " seqNumber=" + seqNumber);
+ String oldPduString = cursor.getString(PDU_COLUMN);
+ byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString);
+ if (!Arrays.equals(oldPdu, pdu)) {
+ Log.e(TAG, "Warning: dup message segment PDU of length " + pdu.length
+ + " is different from existing PDU of length " + oldPdu.length);
+ }
+ return Intents.RESULT_SMS_HANDLED;
+ }
+ cursor.close();
+
+ // not a dup, query for all other segments of this concatenated message
+ String where = "address=? AND reference_number=?";
+ String[] whereArgs = new String[] {address, refNumber};
+ cursor = mResolver.query(mRawUri, PDU_SEQUENCE_PORT_PROJECTION, where, whereArgs, null);
+
+ int cursorCount = cursor.getCount();
+ if (cursorCount != messageCount - 1) {
+ // We don't have all the parts yet, store this one away
+ ContentValues values = new ContentValues();
+ values.put("date", timestamp);
+ values.put("pdu", HexDump.toHexString(pdu));
+ values.put("address", address);
+ values.put("reference_number", referenceNumber);
+ values.put("count", messageCount);
+ values.put("sequence", sequenceNumber);
+ if (destPort != -1) {
+ values.put("destination_port", destPort);
+ }
+ mResolver.insert(mRawUri, values);
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ // All the parts are in place, deal with them
+ pdus = new byte[messageCount][];
+ for (int i = 0; i < cursorCount; i++) {
+ cursor.moveToNext();
+ int cursorSequence = cursor.getInt(SEQUENCE_COLUMN);
+ // GSM sequence numbers start at 1; CDMA WDP datagram sequence numbers start at 0
+ if (!isCdmaWapPush) {
+ cursorSequence--;
+ }
+ pdus[cursorSequence] = HexDump.hexStringToByteArray(
+ cursor.getString(PDU_COLUMN));
+
+ // Read the destination port from the first segment (needed for CDMA WAP PDU).
+ // It's not a bad idea to prefer the port from the first segment for 3GPP as well.
+ if (cursorSequence == 0 && !cursor.isNull(DESTINATION_PORT_COLUMN)) {
+ destPort = cursor.getInt(DESTINATION_PORT_COLUMN);
+ }
+ }
+ // This one isn't in the DB, so add it
+ // GSM sequence numbers start at 1; CDMA WDP datagram sequence numbers start at 0
+ if (isCdmaWapPush) {
+ pdus[sequenceNumber] = pdu;
+ } else {
+ pdus[sequenceNumber - 1] = pdu;
+ }
+
+ // Remove the parts from the database
+ mResolver.delete(mRawUri, where, whereArgs);
+ } catch (SQLException e) {
+ Log.e(TAG, "Can't access multipart SMS database", e);
+ return Intents.RESULT_SMS_GENERIC_ERROR;
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+
+ // Special handling for CDMA WDP datagrams
+ if (isCdmaWapPush) {
+ // Build up the data stream
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ for (int i = 0; i < messageCount; i++) {
+ // reassemble the (WSP-)pdu
+ output.write(pdus[i], 0, pdus[i].length);
+ }
+ byte[] datagram = output.toByteArray();
+
+ // Dispatch the PDU to applications
+ if (destPort == SmsHeader.PORT_WAP_PUSH) {
+ // Handle the PUSH
+ return mWapPush.dispatchWapPdu(datagram);
+ } else {
+ pdus = new byte[1][];
+ pdus[0] = datagram;
+ // The messages were sent to any other WAP port
+ dispatchPortAddressedPdus(pdus, destPort);
+ return Activity.RESULT_OK;
+ }
+ }
+
+ // Dispatch the PDUs to applications
+ if (destPort != -1) {
+ if (destPort == SmsHeader.PORT_WAP_PUSH) {
+ // Build up the data stream
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ for (int i = 0; i < messageCount; i++) {
+ SmsMessage msg = SmsMessage.createFromPdu(pdus[i], getFormat());
+ byte[] data = msg.getUserData();
+ output.write(data, 0, data.length);
+ }
+ // Handle the PUSH
+ return mWapPush.dispatchWapPdu(output.toByteArray());
+ } else {
+ // The messages were sent to a port, so concoct a URI for it
+ dispatchPortAddressedPdus(pdus, destPort);
+ }
+ } else {
+ // The messages were not sent to a port
+ dispatchPdus(pdus);
+ }
+ return Activity.RESULT_OK;
+ }
+
+ /**
+ * Dispatches standard PDUs to interested applications
+ *
+ * @param pdus The raw PDUs making up the message
+ */
+ protected void dispatchPdus(byte[][] pdus) {
+ Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
+ intent.putExtra("pdus", pdus);
+ intent.putExtra("format", getFormat());
+ dispatch(intent, RECEIVE_SMS_PERMISSION);
+ }
+
+ /**
+ * Dispatches port addressed PDUs to interested applications
+ *
+ * @param pdus The raw PDUs making up the message
+ * @param port The destination port of the messages
+ */
+ protected 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);
+ intent.putExtra("format", getFormat());
+ dispatch(intent, RECEIVE_SMS_PERMISSION);
+ }
+
+ /**
+ * Send a data based SMS to a specific application port.
+ *
+ * @param destAddr the address to send the message to
+ * @param scAddr is the service center address or null to use
+ * the current default SMSC
+ * @param destPort 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 successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * <code>RESULT_ERROR_NO_SERVICE</code><br>.
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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").
+ */
+ protected abstract void sendData(String destAddr, String scAddr, int destPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent);
+
+ /**
+ * Send a text based SMS.
+ *
+ * @param destAddr the address to send the message to
+ * @param scAddr 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 successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * <code>RESULT_ERROR_NO_SERVICE</code><br>.
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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").
+ */
+ protected abstract void sendText(String destAddr, String scAddr,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent);
+
+ /**
+ * Calculate the number of septets needed to encode the message.
+ *
+ * @param messageBody the message to encode
+ * @param use7bitOnly ignore (but still count) illegal characters if true
+ * @return TextEncodingDetails
+ */
+ protected abstract TextEncodingDetails calculateLength(CharSequence messageBody,
+ boolean use7bitOnly);
+
+ /**
+ * Send a multi-part text based SMS.
+ *
+ * @param destAddr the address to send the message to
+ * @param scAddr 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>
+ * <code>RESULT_ERROR_NO_SERVICE</code>.
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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").
+ */
+ protected void sendMultipartText(String destAddr, String scAddr,
+ ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
+ ArrayList<PendingIntent> deliveryIntents) {
+
+ int refNumber = getNextConcatenatedRef() & 0x00FF;
+ int msgCount = parts.size();
+ int encoding = SmsConstants.ENCODING_UNKNOWN;
+
+ mRemainingMessages = msgCount;
+
+ TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
+ for (int i = 0; i < msgCount; i++) {
+ TextEncodingDetails details = calculateLength(parts.get(i), false);
+ if (encoding != details.codeUnitSize
+ && (encoding == SmsConstants.ENCODING_UNKNOWN
+ || encoding == SmsConstants.ENCODING_7BIT)) {
+ encoding = details.codeUnitSize;
+ }
+ encodingForParts[i] = details;
+ }
+
+ for (int i = 0; i < msgCount; i++) {
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = refNumber;
+ concatRef.seqNumber = i + 1; // 1-based sequence
+ concatRef.msgCount = msgCount;
+ // TODO: We currently set this to true since our messaging app will never
+ // send more than 255 parts (it converts the message to MMS well before that).
+ // However, we should support 3rd party messaging apps that might need 16-bit
+ // references
+ // Note: It's not sufficient to just flip this bit to true; it will have
+ // ripple effects (several calculations assume 8-bit ref).
+ concatRef.isEightBits = true;
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.concatRef = concatRef;
+
+ // Set the national language tables for 3GPP 7-bit encoding, if enabled.
+ if (encoding == SmsConstants.ENCODING_7BIT) {
+ smsHeader.languageTable = encodingForParts[i].languageTable;
+ smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
+ }
+
+ PendingIntent sentIntent = null;
+ if (sentIntents != null && sentIntents.size() > i) {
+ sentIntent = sentIntents.get(i);
+ }
+
+ PendingIntent deliveryIntent = null;
+ if (deliveryIntents != null && deliveryIntents.size() > i) {
+ deliveryIntent = deliveryIntents.get(i);
+ }
+
+ sendNewSubmitPdu(destAddr, scAddr, parts.get(i), smsHeader, encoding,
+ sentIntent, deliveryIntent, (i == (msgCount - 1)));
+ }
+
+ }
+
+ /**
+ * Create a new SubmitPdu and send it.
+ */
+ protected abstract void sendNewSubmitPdu(String destinationAddress, String scAddress,
+ String message, SmsHeader smsHeader, int encoding,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart);
+
+ /**
+ * Send a SMS
+ *
+ * @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>Intent</code> is
+ * broadcast when the message is successfully 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>
+ * <code>RESULT_ERROR_NO_SERVICE</code>.
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * 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").
+ * @param destAddr the destination phone number (for short code confirmation)
+ */
+ protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
+ PendingIntent deliveryIntent, String destAddr) {
+ if (mSmsSendDisabled) {
+ if (sentIntent != null) {
+ try {
+ sentIntent.send(RESULT_ERROR_NO_SERVICE);
+ } catch (CanceledException ex) {}
+ }
+ Log.d(TAG, "Device does not support sending sms.");
+ return;
+ }
+
+ if (pdu == null) {
+ if (sentIntent != null) {
+ try {
+ sentIntent.send(RESULT_ERROR_NULL_PDU);
+ } catch (CanceledException ex) {}
+ }
+ return;
+ }
+
+ HashMap<String, Object> map = new HashMap<String, Object>();
+ map.put("smsc", smsc);
+ map.put("pdu", pdu);
+
+ // Get calling app package name via UID from Binder call
+ PackageManager pm = mContext.getPackageManager();
+ String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());
+
+ if (packageNames == null || packageNames.length == 0) {
+ // Refuse to send SMS if we can't get the calling package name.
+ Log.e(TAG, "Can't get calling app package name: refusing to send SMS");
+ if (sentIntent != null) {
+ try {
+ sentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
+ } catch (CanceledException ex) {
+ Log.e(TAG, "failed to send error result");
+ }
+ }
+ return;
+ }
+
+ String appPackage = packageNames[0];
+
+ // Strip non-digits from destination phone number before checking for short codes
+ // and before displaying the number to the user if confirmation is required.
+ SmsTracker tracker = new SmsTracker(map, sentIntent, deliveryIntent, appPackage,
+ PhoneNumberUtils.extractNetworkPortion(destAddr));
+
+ // checkDestination() returns true if the destination is not a premium short code or the
+ // sending app is approved to send to short codes. Otherwise, a message is sent to our
+ // handler with the SmsTracker to request user confirmation before sending.
+ if (checkDestination(tracker)) {
+ // check for excessive outgoing SMS usage by this app
+ if (!mUsageMonitor.check(appPackage, SINGLE_PART_SMS)) {
+ sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
+ return;
+ }
+
+ int ss = mPhone.getServiceState().getState();
+
+ if (ss != ServiceState.STATE_IN_SERVICE) {
+ handleNotInService(ss, tracker.mSentIntent);
+ } else {
+ sendSms(tracker);
+ }
+ }
+ }
+
+ /**
+ * Check if destination is a potential premium short code and sender is not pre-approved to
+ * send to short codes.
+ *
+ * @param tracker the tracker for the SMS to send
+ * @return true if the destination is approved; false if user confirmation event was sent
+ */
+ boolean checkDestination(SmsTracker tracker) {
+ if (mContext.checkCallingOrSelfPermission(SEND_SMS_NO_CONFIRMATION_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true; // app is pre-approved to send to short codes
+ } else {
+ String countryIso = mTelephonyManager.getSimCountryIso();
+ if (countryIso == null || countryIso.length() != 2) {
+ Log.e(TAG, "Can't get SIM country code: trying network country code");
+ countryIso = mTelephonyManager.getNetworkCountryIso();
+ }
+
+ switch (mUsageMonitor.checkDestination(tracker.mDestAddress, countryIso)) {
+ case SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE:
+ sendMessage(obtainMessage(EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE,
+ tracker));
+ return false; // wait for user confirmation before sending
+
+ case SmsUsageMonitor.CATEGORY_PREMIUM_SHORT_CODE:
+ sendMessage(obtainMessage(EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE,
+ tracker));
+ return false; // wait for user confirmation before sending
+
+ case SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE:
+ case SmsUsageMonitor.CATEGORY_FREE_SHORT_CODE:
+ case SmsUsageMonitor.CATEGORY_STANDARD_SHORT_CODE:
+ default:
+ return true; // destination is not a premium short code
+ }
+ }
+ }
+
+ /**
+ * Deny sending an SMS if the outgoing queue limit is reached. Used when the message
+ * must be confirmed by the user due to excessive usage or potential premium SMS detected.
+ * @param tracker the SmsTracker for the message to send
+ * @return true if the message was denied; false to continue with send confirmation
+ */
+ private boolean denyIfQueueLimitReached(SmsTracker tracker) {
+ if (mPendingTrackerCount >= MO_MSG_QUEUE_LIMIT) {
+ // Deny sending message when the queue limit is reached.
+ try {
+ tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
+ } catch (CanceledException ex) {
+ Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
+ }
+ return true;
+ }
+ mPendingTrackerCount++;
+ return false;
+ }
+
+ /**
+ * Returns the label for the specified app package name.
+ * @param appPackage the package name of the app requesting to send an SMS
+ * @return the label for the specified app, or the package name if getApplicationInfo() fails
+ */
+ private CharSequence getAppLabel(String appPackage) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ApplicationInfo appInfo = pm.getApplicationInfo(appPackage, 0);
+ return appInfo.loadLabel(pm);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "PackageManager Name Not Found for package " + appPackage);
+ return appPackage; // fall back to package name if we can't get app label
+ }
+ }
+
+ /**
+ * Post an alert when SMS needs confirmation due to excessive usage.
+ * @param tracker an SmsTracker for the current message.
+ */
+ protected void handleReachSentLimit(SmsTracker tracker) {
+ if (denyIfQueueLimitReached(tracker)) {
+ return; // queue limit reached; error was returned to caller
+ }
+
+ CharSequence appLabel = getAppLabel(tracker.mAppPackage);
+ Resources r = Resources.getSystem();
+ Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel));
+
+ ConfirmDialogListener listener = new ConfirmDialogListener(tracker);
+
+ AlertDialog d = new AlertDialog.Builder(mContext)
+ .setTitle(R.string.sms_control_title)
+ .setIcon(R.drawable.stat_sys_warning)
+ .setMessage(messageText)
+ .setPositiveButton(r.getString(R.string.sms_control_yes), listener)
+ .setNegativeButton(r.getString(R.string.sms_control_no), listener)
+ .setOnCancelListener(listener)
+ .create();
+
+ d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ d.show();
+ }
+
+ /**
+ * Post an alert for user confirmation when sending to a potential short code.
+ * @param isPremium true if the destination is known to be a premium short code
+ * @param tracker the SmsTracker for the current message.
+ */
+ protected void handleConfirmShortCode(boolean isPremium, SmsTracker tracker) {
+ if (denyIfQueueLimitReached(tracker)) {
+ return; // queue limit reached; error was returned to caller
+ }
+
+ int messageId;
+ int titleId;
+ if (isPremium) {
+ messageId = R.string.sms_premium_short_code_confirm_message;
+ titleId = R.string.sms_premium_short_code_confirm_title;
+ } else {
+ messageId = R.string.sms_short_code_confirm_message;
+ titleId = R.string.sms_short_code_confirm_title;
+ }
+
+ CharSequence appLabel = getAppLabel(tracker.mAppPackage);
+ Resources r = Resources.getSystem();
+ Spanned messageText = Html.fromHtml(r.getString(messageId, appLabel, tracker.mDestAddress));
+
+ ConfirmDialogListener listener = new ConfirmDialogListener(tracker);
+
+ AlertDialog d = new AlertDialog.Builder(mContext)
+ .setTitle(titleId)
+ .setIcon(R.drawable.stat_sys_warning)
+ .setMessage(messageText)
+ .setPositiveButton(r.getString(R.string.sms_short_code_confirm_allow), listener)
+ .setNegativeButton(r.getString(R.string.sms_short_code_confirm_deny), listener)
+// TODO: add third button for "Report malicious app" feature
+// .setNeutralButton(r.getString(R.string.sms_short_code_confirm_report), listener)
+ .setOnCancelListener(listener)
+ .create();
+
+ d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ d.show();
+ }
+
+ /**
+ * Send the message along to the radio.
+ *
+ * @param tracker holds the SMS message to send
+ */
+ protected abstract void sendSms(SmsTracker tracker);
+
+ /**
+ * Send the multi-part SMS based on multipart Sms tracker
+ *
+ * @param tracker holds the multipart Sms tracker ready to be sent
+ */
+ private void sendMultipartSms(SmsTracker tracker) {
+ ArrayList<String> parts;
+ ArrayList<PendingIntent> sentIntents;
+ ArrayList<PendingIntent> deliveryIntents;
+
+ HashMap<String, Object> map = tracker.mData;
+
+ String destinationAddress = (String) map.get("destination");
+ String scAddress = (String) map.get("scaddress");
+
+ parts = (ArrayList<String>) map.get("parts");
+ sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents");
+ deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents");
+
+ // check if in service
+ int ss = mPhone.getServiceState().getState();
+ if (ss != ServiceState.STATE_IN_SERVICE) {
+ for (int i = 0, count = parts.size(); i < count; i++) {
+ PendingIntent sentIntent = null;
+ if (sentIntents != null && sentIntents.size() > i) {
+ sentIntent = sentIntents.get(i);
+ }
+ handleNotInService(ss, sentIntent);
+ }
+ return;
+ }
+
+ sendMultipartText(destinationAddress, scAddress, parts, sentIntents, deliveryIntents);
+ }
+
+ /**
+ * Send an acknowledge message.
+ * @param success indicates that last message was successfully received.
+ * @param result result code indicating any error
+ * @param response callback message sent when operation completes.
+ */
+ protected abstract void acknowledgeLastIncomingSms(boolean success,
+ int result, Message response);
+
+ /**
+ * Notify interested apps if the framework has rejected an incoming SMS,
+ * and send an acknowledge message to the network.
+ * @param success indicates that last message was successfully received.
+ * @param result result code indicating any error
+ * @param response callback message sent when operation completes.
+ */
+ private void notifyAndAcknowledgeLastIncomingSms(boolean success,
+ int result, Message response) {
+ if (!success) {
+ // broadcast SMS_REJECTED_ACTION intent
+ Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
+ intent.putExtra("result", result);
+ mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
+ mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
+ }
+ acknowledgeLastIncomingSms(success, result, response);
+ }
+
+ /**
+ * Keeps track of an SMS that has been sent to the RIL, until it has
+ * successfully been sent, or we're done trying.
+ *
+ */
+ protected static final class SmsTracker {
+ // fields need to be public for derived SmsDispatchers
+ public final HashMap<String, Object> mData;
+ public int mRetryCount;
+ public int mMessageRef;
+
+ public final PendingIntent mSentIntent;
+ public final PendingIntent mDeliveryIntent;
+
+ public final String mAppPackage;
+ public final String mDestAddress;
+
+ public SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
+ PendingIntent deliveryIntent, String appPackage, String destAddr) {
+ mData = data;
+ mSentIntent = sentIntent;
+ mDeliveryIntent = deliveryIntent;
+ mRetryCount = 0;
+ mAppPackage = appPackage;
+ mDestAddress = destAddr;
+ }
+
+ /**
+ * Returns whether this tracker holds a multi-part SMS.
+ * @return true if the tracker holds a multi-part SMS; false otherwise
+ */
+ protected boolean isMultipart() {
+ HashMap map = mData;
+ return map.containsKey("parts");
+ }
+ }
+
+ /**
+ * Dialog listener for SMS confirmation dialog.
+ */
+ private final class ConfirmDialogListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+
+ private final SmsTracker mTracker;
+
+ ConfirmDialogListener(SmsTracker tracker) {
+ mTracker = tracker;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ Log.d(TAG, "CONFIRM sending SMS");
+ sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS, mTracker));
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ Log.d(TAG, "DENY sending SMS");
+ sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
+ }
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ Log.d(TAG, "dialog dismissed: don't send SMS");
+ sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
+ }
+ }
+
+ private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Assume the intent is one of the SMS receive intents that
+ // was sent as an ordered broadcast. Check result and ACK.
+ int rc = getResultCode();
+ boolean success = (rc == Activity.RESULT_OK)
+ || (rc == Intents.RESULT_SMS_HANDLED);
+
+ // For a multi-part message, this only ACKs the last part.
+ // Previous parts were ACK'd as they were received.
+ acknowledgeLastIncomingSms(success, rc, null);
+ }
+ };
+
+ protected void dispatchBroadcastMessage(SmsCbMessage message) {
+ if (message.isEmergencyMessage()) {
+ Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
+ intent.putExtra("message", message);
+ Log.d(TAG, "Dispatching emergency SMS CB");
+ dispatch(intent, RECEIVE_EMERGENCY_BROADCAST_PERMISSION);
+ } else {
+ Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
+ intent.putExtra("message", message);
+ Log.d(TAG, "Dispatching SMS CB");
+ dispatch(intent, RECEIVE_SMS_PERMISSION);
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
new file mode 100644
index 0000000..e4cfb23
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -0,0 +1,538 @@
+/*
+ * 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.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * {@hide}
+ */
+public abstract class ServiceStateTracker extends Handler {
+
+ protected CommandsInterface cm;
+
+ public ServiceState ss;
+ protected ServiceState newSS;
+
+ public SignalStrength mSignalStrength;
+
+ // TODO - this should not be public
+ public RestrictedState mRestrictedState = new RestrictedState();
+
+ /* The otaspMode passed to PhoneStateListener#onOtaspChanged */
+ static public final int OTASP_UNINITIALIZED = 0;
+ static public final int OTASP_UNKNOWN = 1;
+ static public final int OTASP_NEEDED = 2;
+ static public final int OTASP_NOT_NEEDED = 3;
+
+ /**
+ * 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.
+ */
+ protected int[] pollingContext;
+ protected boolean mDesiredPowerState;
+
+ /**
+ * Values correspond to ServiceState.RIL_RADIO_TECHNOLOGY_ definitions.
+ */
+ protected int mRilRadioTechnology = 0;
+ protected int mNewRilRadioTechnology = 0;
+
+ /**
+ * By default, strength polling is enabled. However, if we're
+ * getting unsolicited signal strength updates from the radio, set
+ * value to true and don't bother polling any more.
+ */
+ protected boolean dontPollSignalStrength = false;
+
+ protected RegistrantList mRoamingOnRegistrants = new RegistrantList();
+ protected RegistrantList mRoamingOffRegistrants = new RegistrantList();
+ protected RegistrantList mAttachedRegistrants = new RegistrantList();
+ protected RegistrantList mDetachedRegistrants = new RegistrantList();
+ protected RegistrantList mNetworkAttachedRegistrants = new RegistrantList();
+ protected RegistrantList mPsRestrictEnabledRegistrants = new RegistrantList();
+ protected RegistrantList mPsRestrictDisabledRegistrants = new RegistrantList();
+
+ /* Radio power off pending flag and tag counter */
+ private boolean mPendingRadioPowerOffAfterDataOff = false;
+ private int mPendingRadioPowerOffAfterDataOffTag = 0;
+
+ protected static final boolean DBG = true;
+
+ /** Signal strength poll rate. */
+ protected static final int POLL_PERIOD_MILLIS = 20 * 1000;
+
+ /** Waiting period before recheck gprs and voice registration. */
+ public static final int DEFAULT_GPRS_CHECK_PERIOD_MILLIS = 60 * 1000;
+
+ /** GSM events */
+ protected static final int EVENT_RADIO_STATE_CHANGED = 1;
+ protected static final int EVENT_NETWORK_STATE_CHANGED = 2;
+ protected static final int EVENT_GET_SIGNAL_STRENGTH = 3;
+ protected static final int EVENT_POLL_STATE_REGISTRATION = 4;
+ protected static final int EVENT_POLL_STATE_GPRS = 5;
+ protected static final int EVENT_POLL_STATE_OPERATOR = 6;
+ protected static final int EVENT_POLL_SIGNAL_STRENGTH = 10;
+ protected static final int EVENT_NITZ_TIME = 11;
+ protected static final int EVENT_SIGNAL_STRENGTH_UPDATE = 12;
+ protected static final int EVENT_RADIO_AVAILABLE = 13;
+ protected static final int EVENT_POLL_STATE_NETWORK_SELECTION_MODE = 14;
+ protected static final int EVENT_GET_LOC_DONE = 15;
+ protected static final int EVENT_SIM_RECORDS_LOADED = 16;
+ protected static final int EVENT_SIM_READY = 17;
+ protected static final int EVENT_LOCATION_UPDATES_ENABLED = 18;
+ protected static final int EVENT_GET_PREFERRED_NETWORK_TYPE = 19;
+ protected static final int EVENT_SET_PREFERRED_NETWORK_TYPE = 20;
+ protected static final int EVENT_RESET_PREFERRED_NETWORK_TYPE = 21;
+ protected static final int EVENT_CHECK_REPORT_GPRS = 22;
+ protected static final int EVENT_RESTRICTED_STATE_CHANGED = 23;
+
+ /** CDMA events */
+ protected static final int EVENT_POLL_STATE_REGISTRATION_CDMA = 24;
+ protected static final int EVENT_POLL_STATE_OPERATOR_CDMA = 25;
+ protected static final int EVENT_RUIM_READY = 26;
+ protected static final int EVENT_RUIM_RECORDS_LOADED = 27;
+ protected static final int EVENT_POLL_SIGNAL_STRENGTH_CDMA = 28;
+ protected static final int EVENT_GET_SIGNAL_STRENGTH_CDMA = 29;
+ protected static final int EVENT_NETWORK_STATE_CHANGED_CDMA = 30;
+ protected static final int EVENT_GET_LOC_DONE_CDMA = 31;
+ protected static final int EVENT_SIGNAL_STRENGTH_UPDATE_CDMA = 32;
+ protected static final int EVENT_NV_LOADED = 33;
+ protected static final int EVENT_POLL_STATE_CDMA_SUBSCRIPTION = 34;
+ protected static final int EVENT_NV_READY = 35;
+ protected static final int EVENT_ERI_FILE_LOADED = 36;
+ protected static final int EVENT_OTA_PROVISION_STATUS_CHANGE = 37;
+ protected static final int EVENT_SET_RADIO_POWER_OFF = 38;
+ protected static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 39;
+ protected static final int EVENT_CDMA_PRL_VERSION_CHANGED = 40;
+ protected static final int EVENT_RADIO_ON = 41;
+
+
+ protected 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.
+ */
+ protected static final String[] GMT_COUNTRY_CODES = {
+ "bf", // Burkina Faso
+ "ci", // Cote d'Ivoire
+ "eh", // Western Sahara
+ "fo", // Faroe Islands, Denmark
+ "gb", // United Kingdom of Great Britain and Northern Ireland
+ "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
+ };
+
+ /** Reason for registration denial. */
+ protected static final String REGISTRATION_DENIED_GEN = "General";
+ protected static final String REGISTRATION_DENIED_AUTH = "Authentication Failure";
+
+ public ServiceStateTracker() {
+ }
+
+ public boolean getDesiredPowerState() {
+ return mDesiredPowerState;
+ }
+
+ /**
+ * 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
+ */
+ public void registerForRoamingOn(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ mRoamingOnRegistrants.add(r);
+
+ if (ss.getRoaming()) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForRoamingOn(Handler h) {
+ mRoamingOnRegistrants.remove(h);
+ }
+
+ /**
+ * 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
+ */
+ public void registerForRoamingOff(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ mRoamingOffRegistrants.add(r);
+
+ if (!ss.getRoaming()) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForRoamingOff(Handler h) {
+ mRoamingOffRegistrants.remove(h);
+ }
+
+ /**
+ * Re-register network by toggling preferred network type.
+ * This is a work-around 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.
+ */
+ public void reRegisterNetwork(Message onComplete) {
+ cm.getPreferredNetworkType(
+ obtainMessage(EVENT_GET_PREFERRED_NETWORK_TYPE, onComplete));
+ }
+
+ public void
+ setRadioPower(boolean power) {
+ mDesiredPowerState = power;
+
+ setPowerStateToDesired();
+ }
+
+ /**
+ * These two flags manage the behavior of the cell lock -- the
+ * lock should be held if either flag is true. The intention is
+ * to allow temporary acquisition of the lock to get a single
+ * update. Such a lock grab and release can thus be made to not
+ * interfere with more permanent lock holds -- in other words, the
+ * lock will only be released if both flags are false, and so
+ * releases by temporary users will only affect the lock state if
+ * there is no continuous user.
+ */
+ private boolean mWantContinuousLocationUpdates;
+ private boolean mWantSingleLocationUpdate;
+
+ public void enableSingleLocationUpdate() {
+ if (mWantSingleLocationUpdate || mWantContinuousLocationUpdates) return;
+ mWantSingleLocationUpdate = true;
+ cm.setLocationUpdates(true, obtainMessage(EVENT_LOCATION_UPDATES_ENABLED));
+ }
+
+ public void enableLocationUpdates() {
+ if (mWantSingleLocationUpdate || mWantContinuousLocationUpdates) return;
+ mWantContinuousLocationUpdates = true;
+ cm.setLocationUpdates(true, obtainMessage(EVENT_LOCATION_UPDATES_ENABLED));
+ }
+
+ protected void disableSingleLocationUpdate() {
+ mWantSingleLocationUpdate = false;
+ if (!mWantSingleLocationUpdate && !mWantContinuousLocationUpdates) {
+ cm.setLocationUpdates(false, null);
+ }
+ }
+
+ public void disableLocationUpdates() {
+ mWantContinuousLocationUpdates = false;
+ if (!mWantSingleLocationUpdate && !mWantContinuousLocationUpdates) {
+ cm.setLocationUpdates(false, null);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_SET_RADIO_POWER_OFF:
+ synchronized(this) {
+ if (mPendingRadioPowerOffAfterDataOff &&
+ (msg.arg1 == mPendingRadioPowerOffAfterDataOffTag)) {
+ if (DBG) log("EVENT_SET_RADIO_OFF, turn radio off now.");
+ hangupAndPowerOff();
+ mPendingRadioPowerOffAfterDataOffTag += 1;
+ mPendingRadioPowerOffAfterDataOff = false;
+ } else {
+ log("EVENT_SET_RADIO_OFF is stale arg1=" + msg.arg1 +
+ "!= tag=" + mPendingRadioPowerOffAfterDataOffTag);
+ }
+ }
+ break;
+
+ default:
+ log("Unhandled message with number: " + msg.what);
+ break;
+ }
+ }
+
+ protected abstract Phone getPhone();
+ protected abstract void handlePollStateResult(int what, AsyncResult ar);
+ protected abstract void updateSpnDisplay();
+ protected abstract void setPowerStateToDesired();
+ protected abstract void log(String s);
+ protected abstract void loge(String s);
+
+ public abstract int getCurrentDataConnectionState();
+ public abstract boolean isConcurrentVoiceAndDataAllowed();
+
+ /**
+ * Registration point for transition into DataConnection attached.
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ public void registerForDataConnectionAttached(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ mAttachedRegistrants.add(r);
+
+ if (getCurrentDataConnectionState() == ServiceState.STATE_IN_SERVICE) {
+ r.notifyRegistrant();
+ }
+ }
+ public void unregisterForDataConnectionAttached(Handler h) {
+ mAttachedRegistrants.remove(h);
+ }
+
+ /**
+ * Registration point for transition into DataConnection detached.
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ public void registerForDataConnectionDetached(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ mDetachedRegistrants.add(r);
+
+ if (getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE) {
+ r.notifyRegistrant();
+ }
+ }
+ public void unregisterForDataConnectionDetached(Handler h) {
+ mDetachedRegistrants.remove(h);
+ }
+
+ /**
+ * Registration point for transition into network attached.
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj in Message.obj
+ */
+ public void registerForNetworkAttached(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+
+ mNetworkAttachedRegistrants.add(r);
+ if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
+ r.notifyRegistrant();
+ }
+ }
+ public void unregisterForNetworkAttached(Handler h) {
+ mNetworkAttachedRegistrants.remove(h);
+ }
+
+ /**
+ * Registration point for transition into packet service restricted zone.
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ public void registerForPsRestrictedEnabled(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ mPsRestrictEnabledRegistrants.add(r);
+
+ if (mRestrictedState.isPsRestricted()) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForPsRestrictedEnabled(Handler h) {
+ mPsRestrictEnabledRegistrants.remove(h);
+ }
+
+ /**
+ * Registration point for transition out of packet service restricted zone.
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ public void registerForPsRestrictedDisabled(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ mPsRestrictDisabledRegistrants.add(r);
+
+ if (mRestrictedState.isPsRestricted()) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForPsRestrictedDisabled(Handler h) {
+ mPsRestrictDisabledRegistrants.remove(h);
+ }
+
+ /**
+ * Clean up existing voice and data connection then turn off radio power.
+ *
+ * Hang up the existing voice calls to decrease call drop rate.
+ */
+ public void powerOffRadioSafely(DataConnectionTracker dcTracker) {
+ synchronized (this) {
+ if (!mPendingRadioPowerOffAfterDataOff) {
+ // To minimize race conditions we call cleanUpAllConnections on
+ // both if else paths instead of before this isDisconnected test.
+ if (dcTracker.isDisconnected()) {
+ // To minimize race conditions we do this after isDisconnected
+ dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
+ if (DBG) log("Data disconnected, turn off radio right away.");
+ hangupAndPowerOff();
+ } else {
+ dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
+ Message msg = Message.obtain(this);
+ msg.what = EVENT_SET_RADIO_POWER_OFF;
+ msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
+ if (sendMessageDelayed(msg, 30000)) {
+ if (DBG) log("Wait upto 30s for data to disconnect, then turn off radio.");
+ mPendingRadioPowerOffAfterDataOff = true;
+ } else {
+ log("Cannot send delayed Msg, turn off radio right away.");
+ hangupAndPowerOff();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * process the pending request to turn radio off after data is disconnected
+ *
+ * return true if there is pending request to process; false otherwise.
+ */
+ public boolean processPendingRadioPowerOffAfterDataOff() {
+ synchronized(this) {
+ if (mPendingRadioPowerOffAfterDataOff) {
+ if (DBG) log("Process pending request to turn radio off.");
+ mPendingRadioPowerOffAfterDataOffTag += 1;
+ hangupAndPowerOff();
+ mPendingRadioPowerOffAfterDataOff = false;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Hang up all voice call and turn off radio. Implemented by derived class.
+ */
+ protected abstract void hangupAndPowerOff();
+
+ /** Cancel a pending (if any) pollState() operation */
+ protected void cancelPollState() {
+ // This will effectively cancel the rest of the poll requests.
+ pollingContext = new int[1];
+ }
+
+ /**
+ * Return true if time zone needs fixing.
+ *
+ * @param phoneBase
+ * @param operatorNumeric
+ * @param prevOperatorNumeric
+ * @param needToFixTimeZone
+ * @return true if time zone needs to be fixed
+ */
+ protected boolean shouldFixTimeZoneNow(PhoneBase phoneBase, String operatorNumeric,
+ String prevOperatorNumeric, boolean needToFixTimeZone) {
+ // Return false if the mcc isn't valid as we don't know where we are.
+ // Return true if we have an IccCard and the mcc changed or we
+ // need to fix it because when the NITZ time came in we didn't
+ // know the country code.
+
+ // If mcc is invalid then we'll return false
+ int mcc;
+ try {
+ mcc = Integer.parseInt(operatorNumeric.substring(0, 3));
+ } catch (Exception e) {
+ if (DBG) {
+ log("shouldFixTimeZoneNow: no mcc, operatorNumeric=" + operatorNumeric +
+ " retVal=false");
+ }
+ return false;
+ }
+
+ // If prevMcc is invalid will make it different from mcc
+ // so we'll return true if the card exists.
+ int prevMcc;
+ try {
+ prevMcc = Integer.parseInt(prevOperatorNumeric.substring(0, 3));
+ } catch (Exception e) {
+ prevMcc = mcc + 1;
+ }
+
+ // Determine if the Icc card exists
+ IccCard iccCard = phoneBase.getIccCard();
+ boolean iccCardExist = (iccCard != null) && iccCard.getState().iccCardExist();
+
+ // Determine retVal
+ boolean retVal = ((iccCardExist && (mcc != prevMcc)) || needToFixTimeZone);
+ if (DBG) {
+ long ctm = System.currentTimeMillis();
+ log("shouldFixTimeZoneNow: retVal=" + retVal +
+ " iccCard=" + iccCard +
+ " iccCard.state=" + (iccCard == null ? "null" : iccCard.getState().toString()) +
+ " iccCardExist=" + iccCardExist +
+ " operatorNumeric=" + operatorNumeric + " mcc=" + mcc +
+ " prevOperatorNumeric=" + prevOperatorNumeric + " prevMcc=" + prevMcc +
+ " needToFixTimeZone=" + needToFixTimeZone +
+ " ltod=" + TimeUtils.logTimeOfDay(ctm));
+ }
+ return retVal;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("ServiceStateTracker:");
+ pw.println(" ss=" + ss);
+ pw.println(" newSS=" + newSS);
+ pw.println(" mSignalStrength=" + mSignalStrength);
+ pw.println(" mRestrictedState=" + mRestrictedState);
+ pw.println(" pollingContext=" + pollingContext);
+ pw.println(" mDesiredPowerState=" + mDesiredPowerState);
+ pw.println(" mRilRadioTechnology=" + mRilRadioTechnology);
+ pw.println(" mNewRilRadioTechnology=" + mNewRilRadioTechnology);
+ pw.println(" dontPollSignalStrength=" + dontPollSignalStrength);
+ pw.println(" mPendingRadioPowerOffAfterDataOff=" + mPendingRadioPowerOffAfterDataOff);
+ pw.println(" mPendingRadioPowerOffAfterDataOffTag=" + mPendingRadioPowerOffAfterDataOffTag);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/SmsAddress.java b/src/java/com/android/internal/telephony/SmsAddress.java
new file mode 100644
index 0000000..b3892cb
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SmsAddress.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+public abstract class SmsAddress {
+ // From TS 23.040 9.1.2.5 and TS 24.008 table 10.5.118
+ // and C.S0005-D table 2.7.1.3.2.4-2
+ public static final int TON_UNKNOWN = 0;
+ public static final int TON_INTERNATIONAL = 1;
+ public static final int TON_NATIONAL = 2;
+ public static final int TON_NETWORK = 3;
+ public static final int TON_SUBSCRIBER = 4;
+ public static final int TON_ALPHANUMERIC = 5;
+ public static final int TON_ABBREVIATED = 6;
+
+ public int ton;
+ public String address;
+ public byte[] origBytes;
+
+ /**
+ * Returns the address of the SMS message in String form or null if unavailable
+ */
+ public String getAddressString() {
+ return address;
+ }
+
+ /**
+ * Returns true if this is an alphanumeric address
+ */
+ public boolean isAlphanumeric() {
+ return ton == TON_ALPHANUMERIC;
+ }
+
+ /**
+ * Returns true if this is a network address
+ */
+ public boolean isNetworkSpecific() {
+ return ton == TON_NETWORK;
+ }
+
+ 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;
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/SmsHeader.java b/src/java/com/android/internal/telephony/SmsHeader.java
new file mode 100644
index 0000000..30d57b8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SmsHeader.java
@@ -0,0 +1,289 @@
+/*
+ * 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 com.android.internal.telephony.SmsConstants;
+import com.android.internal.util.HexDump;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import java.util.ArrayList;
+
+/**
+ * SMS user data header, as specified in TS 23.040 9.2.3.24.
+ */
+public class SmsHeader {
+
+ // TODO(cleanup): this data structure is generally referred to as
+ // the 'user data header' or UDH, and so the class name should
+ // change to reflect this...
+
+ /** SMS user data header information element identifiers.
+ * (see TS 23.040 9.2.3.24)
+ */
+ public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE = 0x00;
+ public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION = 0x01;
+ public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT = 0x04;
+ public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05;
+ public static final int ELT_ID_SMSC_CONTROL_PARAMS = 0x06;
+ public static final int ELT_ID_UDH_SOURCE_INDICATION = 0x07;
+ public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE = 0x08;
+ public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL = 0x09;
+ public static final int ELT_ID_TEXT_FORMATTING = 0x0A;
+ public static final int ELT_ID_PREDEFINED_SOUND = 0x0B;
+ public static final int ELT_ID_USER_DEFINED_SOUND = 0x0C;
+ public static final int ELT_ID_PREDEFINED_ANIMATION = 0x0D;
+ public static final int ELT_ID_LARGE_ANIMATION = 0x0E;
+ public static final int ELT_ID_SMALL_ANIMATION = 0x0F;
+ public static final int ELT_ID_LARGE_PICTURE = 0x10;
+ public static final int ELT_ID_SMALL_PICTURE = 0x11;
+ public static final int ELT_ID_VARIABLE_PICTURE = 0x12;
+ public static final int ELT_ID_USER_PROMPT_INDICATOR = 0x13;
+ public static final int ELT_ID_EXTENDED_OBJECT = 0x14;
+ public static final int ELT_ID_REUSED_EXTENDED_OBJECT = 0x15;
+ public static final int ELT_ID_COMPRESSION_CONTROL = 0x16;
+ public static final int ELT_ID_OBJECT_DISTR_INDICATOR = 0x17;
+ public static final int ELT_ID_STANDARD_WVG_OBJECT = 0x18;
+ public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT = 0x19;
+ public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD = 0x1A;
+ public static final int ELT_ID_RFC_822_EMAIL_HEADER = 0x20;
+ public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT = 0x21;
+ public static final int ELT_ID_REPLY_ADDRESS_ELEMENT = 0x22;
+ public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION = 0x23;
+ public static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24;
+ public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25;
+
+ public static final int PORT_WAP_PUSH = 2948;
+ public static final int PORT_WAP_WSP = 9200;
+
+ public static class PortAddrs {
+ public int destPort;
+ public int origPort;
+ public boolean areEightBits;
+ }
+
+ public static class ConcatRef {
+ public int refNumber;
+ public int seqNumber;
+ public int msgCount;
+ public boolean isEightBits;
+ }
+
+ /**
+ * A header element that is not explicitly parsed, meaning not
+ * PortAddrs or ConcatRef.
+ */
+ public static class MiscElt {
+ public int id;
+ public byte[] data;
+ }
+
+ public PortAddrs portAddrs;
+ public ConcatRef concatRef;
+ public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>();
+
+ /** 7 bit national language locking shift table, or 0 for GSM default 7 bit alphabet. */
+ public int languageTable;
+
+ /** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */
+ public int languageShiftTable;
+
+ public SmsHeader() {}
+
+ /**
+ * Create structured SmsHeader object from serialized byte array representation.
+ * (see TS 23.040 9.2.3.24)
+ * @param data is user data header bytes
+ * @return SmsHeader object
+ */
+ public static SmsHeader fromByteArray(byte[] data) {
+ ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+ SmsHeader smsHeader = new SmsHeader();
+ while (inStream.available() > 0) {
+ /**
+ * NOTE: as defined in the spec, ConcatRef and PortAddr
+ * fields should not reoccur, but if they do the last
+ * occurrence is to be used. Also, for ConcatRef
+ * elements, if the count is zero, sequence is zero, or
+ * sequence is larger than count, the entire element is to
+ * be ignored.
+ */
+ int id = inStream.read();
+ int length = inStream.read();
+ ConcatRef concatRef;
+ PortAddrs portAddrs;
+ switch (id) {
+ case ELT_ID_CONCATENATED_8_BIT_REFERENCE:
+ concatRef = new ConcatRef();
+ concatRef.refNumber = inStream.read();
+ concatRef.msgCount = inStream.read();
+ concatRef.seqNumber = inStream.read();
+ concatRef.isEightBits = true;
+ if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
+ concatRef.seqNumber <= concatRef.msgCount) {
+ smsHeader.concatRef = concatRef;
+ }
+ break;
+ case ELT_ID_CONCATENATED_16_BIT_REFERENCE:
+ concatRef = new ConcatRef();
+ concatRef.refNumber = (inStream.read() << 8) | inStream.read();
+ concatRef.msgCount = inStream.read();
+ concatRef.seqNumber = inStream.read();
+ concatRef.isEightBits = false;
+ if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
+ concatRef.seqNumber <= concatRef.msgCount) {
+ smsHeader.concatRef = concatRef;
+ }
+ break;
+ case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT:
+ portAddrs = new PortAddrs();
+ portAddrs.destPort = inStream.read();
+ portAddrs.origPort = inStream.read();
+ portAddrs.areEightBits = true;
+ smsHeader.portAddrs = portAddrs;
+ break;
+ case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT:
+ portAddrs = new PortAddrs();
+ portAddrs.destPort = (inStream.read() << 8) | inStream.read();
+ portAddrs.origPort = (inStream.read() << 8) | inStream.read();
+ portAddrs.areEightBits = false;
+ smsHeader.portAddrs = portAddrs;
+ break;
+ case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
+ smsHeader.languageShiftTable = inStream.read();
+ break;
+ case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
+ smsHeader.languageTable = inStream.read();
+ break;
+ default:
+ MiscElt miscElt = new MiscElt();
+ miscElt.id = id;
+ miscElt.data = new byte[length];
+ inStream.read(miscElt.data, 0, length);
+ smsHeader.miscEltList.add(miscElt);
+ }
+ }
+ return smsHeader;
+ }
+
+ /**
+ * Create serialized byte array representation from structured SmsHeader object.
+ * (see TS 23.040 9.2.3.24)
+ * @return Byte array representing the SmsHeader
+ */
+ public static byte[] toByteArray(SmsHeader smsHeader) {
+ if ((smsHeader.portAddrs == null) &&
+ (smsHeader.concatRef == null) &&
+ (smsHeader.miscEltList.isEmpty()) &&
+ (smsHeader.languageShiftTable == 0) &&
+ (smsHeader.languageTable == 0)) {
+ return null;
+ }
+
+ ByteArrayOutputStream outStream =
+ new ByteArrayOutputStream(SmsConstants.MAX_USER_DATA_BYTES);
+ ConcatRef concatRef = smsHeader.concatRef;
+ if (concatRef != null) {
+ if (concatRef.isEightBits) {
+ outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE);
+ outStream.write(3);
+ outStream.write(concatRef.refNumber);
+ } else {
+ outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE);
+ outStream.write(4);
+ outStream.write(concatRef.refNumber >>> 8);
+ outStream.write(concatRef.refNumber & 0x00FF);
+ }
+ outStream.write(concatRef.msgCount);
+ outStream.write(concatRef.seqNumber);
+ }
+ PortAddrs portAddrs = smsHeader.portAddrs;
+ if (portAddrs != null) {
+ if (portAddrs.areEightBits) {
+ outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT);
+ outStream.write(2);
+ outStream.write(portAddrs.destPort);
+ outStream.write(portAddrs.origPort);
+ } else {
+ outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT);
+ outStream.write(4);
+ outStream.write(portAddrs.destPort >>> 8);
+ outStream.write(portAddrs.destPort & 0x00FF);
+ outStream.write(portAddrs.origPort >>> 8);
+ outStream.write(portAddrs.origPort & 0x00FF);
+ }
+ }
+ if (smsHeader.languageShiftTable != 0) {
+ outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT);
+ outStream.write(1);
+ outStream.write(smsHeader.languageShiftTable);
+ }
+ if (smsHeader.languageTable != 0) {
+ outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT);
+ outStream.write(1);
+ outStream.write(smsHeader.languageTable);
+ }
+ for (MiscElt miscElt : smsHeader.miscEltList) {
+ outStream.write(miscElt.id);
+ outStream.write(miscElt.data.length);
+ outStream.write(miscElt.data, 0, miscElt.data.length);
+ }
+ return outStream.toByteArray();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("UserDataHeader ");
+ builder.append("{ ConcatRef ");
+ if (concatRef == null) {
+ builder.append("unset");
+ } else {
+ builder.append("{ refNumber=" + concatRef.refNumber);
+ builder.append(", msgCount=" + concatRef.msgCount);
+ builder.append(", seqNumber=" + concatRef.seqNumber);
+ builder.append(", isEightBits=" + concatRef.isEightBits);
+ builder.append(" }");
+ }
+ builder.append(", PortAddrs ");
+ if (portAddrs == null) {
+ builder.append("unset");
+ } else {
+ builder.append("{ destPort=" + portAddrs.destPort);
+ builder.append(", origPort=" + portAddrs.origPort);
+ builder.append(", areEightBits=" + portAddrs.areEightBits);
+ builder.append(" }");
+ }
+ if (languageShiftTable != 0) {
+ builder.append(", languageShiftTable=" + languageShiftTable);
+ }
+ if (languageTable != 0) {
+ builder.append(", languageTable=" + languageTable);
+ }
+ for (MiscElt miscElt : miscEltList) {
+ builder.append(", MiscElt ");
+ builder.append("{ id=" + miscElt.id);
+ builder.append(", length=" + miscElt.data.length);
+ builder.append(", data=" + HexDump.toHexString(miscElt.data));
+ builder.append(" }");
+ }
+ builder.append(" }");
+ return builder.toString();
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/SmsMessageBase.java b/src/java/com/android/internal/telephony/SmsMessageBase.java
new file mode 100644
index 0000000..22d8cd8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SmsMessageBase.java
@@ -0,0 +1,350 @@
+/*
+ * 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;
+
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import java.util.Arrays;
+
+import android.provider.Telephony;
+
+/**
+ * Base class declaring the specific methods and members for SmsMessage.
+ * {@hide}
+ */
+public abstract class SmsMessageBase {
+ private static final String LOG_TAG = "SMS";
+
+ /** {@hide} The address of the SMSC. May be null */
+ protected String scAddress;
+
+ /** {@hide} The address of the sender */
+ protected SmsAddress originatingAddress;
+
+ /** {@hide} The message body as a string. May be null if the message isn't text */
+ protected String messageBody;
+
+ /** {@hide} */
+ protected String pseudoSubject;
+
+ /** {@hide} Non-null if this is an email gateway message */
+ protected String emailFrom;
+
+ /** {@hide} Non-null if this is an email gateway message */
+ protected String emailBody;
+
+ /** {@hide} */
+ protected boolean isEmail;
+
+ /** {@hide} */
+ protected long scTimeMillis;
+
+ /** {@hide} The raw PDU of the message */
+ protected byte[] mPdu;
+
+ /** {@hide} The raw bytes for the user data section of the message */
+ protected byte[] userData;
+
+ /** {@hide} */
+ protected SmsHeader userDataHeader;
+
+ // "Message Waiting Indication Group"
+ // 23.038 Section 4
+ /** {@hide} */
+ protected boolean isMwi;
+
+ /** {@hide} */
+ protected boolean mwiSense;
+
+ /** {@hide} */
+ protected boolean mwiDontStore;
+
+ /**
+ * Indicates status for messages stored on the ICC.
+ */
+ protected int statusOnIcc = -1;
+
+ /**
+ * Record index of message in the EF.
+ */
+ protected int indexOnIcc = -1;
+
+ /** TP-Message-Reference - Message Reference of sent message. @hide */
+ public int messageRef;
+
+ // TODO(): This class is duplicated in SmsMessage.java. Refactor accordingly.
+ public static abstract class SubmitPduBase {
+ public byte[] encodedScAddress; // Null if not applicable.
+ public byte[] encodedMessage;
+
+ public String toString() {
+ return "SubmitPdu: encodedScAddress = "
+ + Arrays.toString(encodedScAddress)
+ + ", encodedMessage = "
+ + Arrays.toString(encodedMessage);
+ }
+ }
+
+ /**
+ * 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 abstract SmsConstants.MessageClass getMessageClass();
+
+ /**
+ * 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 abstract int getProtocolIdentifier();
+
+ /**
+ * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+ * SMS
+ */
+ public abstract boolean isReplace();
+
+ /**
+ * 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 abstract boolean isCphsMwiMessage();
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) clear message
+ */
+ public abstract boolean isMWIClearMessage();
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) set message
+ */
+ public abstract boolean isMWISetMessage();
+
+ /**
+ * returns true if this message is a "Message Waiting Indication Group:
+ * Discard Message" notification and should not be stored.
+ */
+ public abstract boolean isMwiDontStore();
+
+ /**
+ * 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
+ *
+ * {@hide}
+ */
+ public SmsHeader getUserDataHeader() {
+ return userDataHeader;
+ }
+
+ /**
+ * TODO(cleanup): The term PDU is used in a seemingly non-unique
+ * manner -- for example, what is the difference between this byte
+ * array and the contents of SubmitPdu objects. Maybe a more
+ * illustrative term would be appropriate.
+ */
+
+ /**
+ * Returns the raw PDU for the message.
+ */
+ public byte[] getPdu() {
+ return mPdu;
+ }
+
+ /**
+ * For an SMS-STATUS-REPORT message, this returns the status field from
+ * the status report. This field indicates the status of a previously
+ * 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 abstract int getStatus();
+
+ /**
+ * Return true iff the message is a SMS-STATUS-REPORT message.
+ */
+ public abstract boolean isStatusReportMessage();
+
+ /**
+ * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+ * this message.
+ */
+ public abstract boolean isReplyPathPresent();
+
+ /**
+ * Returns the status of the message on the ICC (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the ICC. These are:
+ * SmsManager.STATUS_ON_ICC_FREE
+ * SmsManager.STATUS_ON_ICC_READ
+ * SmsManager.STATUS_ON_ICC_UNREAD
+ * SmsManager.STATUS_ON_ICC_SEND
+ * SmsManager.STATUS_ON_ICC_UNSENT
+ */
+ public int getStatusOnIcc() {
+ return statusOnIcc;
+ }
+
+ /**
+ * Returns the record index of the message on the ICC (1-based index).
+ * @return the record index of the message on the ICC, or -1 if this
+ * SmsMessage was not created from a ICC SMS EF record.
+ */
+ public int getIndexOnIcc() {
+ return indexOnIcc;
+ }
+
+ protected void parseMessageBody() {
+ // originatingAddress could be null if this message is from a status
+ // report.
+ if (originatingAddress != null && originatingAddress.couldBeEmailGateway()) {
+ extractEmailAddressFromMessageBody();
+ }
+ }
+
+ /**
+ * Try to parse this message as an email gateway message
+ * There are two ways specified in TS 23.040 Section 3.8 :
+ * - 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." (which is supported here)
+ * - Multiple addresses separated by commas, no spaces, Subject field delimited
+ * by '()' or '##' and '#' Section 9.2.3.24.11 (which are NOT supported here)
+ */
+ protected void extractEmailAddressFromMessageBody() {
+
+ /* Some carriers may use " /" delimiter as below
+ *
+ * 1. [x@y][ ]/[subject][ ]/[body]
+ * -or-
+ * 2. [x@y][ ]/[body]
+ */
+ String[] parts = messageBody.split("( /)|( )", 2);
+ if (parts.length < 2) return;
+ emailFrom = parts[0];
+ emailBody = parts[1];
+ isEmail = Telephony.Mms.isEmailAddress(emailFrom);
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/SmsRawData.java b/src/java/com/android/internal/telephony/SmsRawData.java
new file mode 100644
index 0000000..891d942
--- /dev/null
+++ b/src/java/com/android/internal/telephony/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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * 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/src/java/com/android/internal/telephony/SmsResponse.java b/src/java/com/android/internal/telephony/SmsResponse.java
new file mode 100644
index 0000000..a7c2840
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SmsResponse.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+/**
+ * Object returned by the RIL upon successful completion of sendSMS.
+ * Contains message reference and ackPdu.
+ *
+ */
+public class SmsResponse {
+ /** Message reference of the just-sent SMS. */
+ int messageRef;
+ /** ackPdu for the just-sent SMS. */
+ String ackPdu;
+ /**
+ * errorCode: See 3GPP 27.005, 3.2.5 for GSM/UMTS,
+ * 3GPP2 N.S0005 (IS-41C) Table 171 for CDMA, -1 if unknown or not applicable.
+ */
+ int errorCode;
+
+ public SmsResponse(int messageRef, String ackPdu, int errorCode) {
+ this.messageRef = messageRef;
+ this.ackPdu = ackPdu;
+ this.errorCode = errorCode;
+ }
+
+ public String toString() {
+ String ret = "{ messageRef = " + messageRef
+ + ", errorCode = " + errorCode
+ + ", ackPdu = " + ackPdu
+ + "}";
+ return ret;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/SmsStorageMonitor.java b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
new file mode 100644
index 0000000..0c06ffc
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2011 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.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.provider.Telephony.Sms.Intents;
+import android.util.Log;
+
+/**
+ * Monitors the device and ICC storage, and sends the appropriate events.
+ *
+ * This code was formerly part of {@link SMSDispatcher}, and has been moved
+ * into a separate class to support instantiation of multiple SMSDispatchers on
+ * dual-mode devices that require support for both 3GPP and 3GPP2 format messages.
+ */
+public final class SmsStorageMonitor extends Handler {
+ private static final String TAG = "SmsStorageMonitor";
+
+ /** SIM/RUIM storage is full */
+ private static final int EVENT_ICC_FULL = 1;
+
+ /** Memory status reporting is acknowledged by RIL */
+ private static final int EVENT_REPORT_MEMORY_STATUS_DONE = 2;
+
+ /** Radio is ON */
+ private static final int EVENT_RADIO_ON = 3;
+
+ /** Context from phone object passed to constructor. */
+ private final Context mContext;
+
+ /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
+ private PowerManager.WakeLock mWakeLock;
+
+ private boolean mReportMemoryStatusPending;
+
+ final CommandsInterface mCm; // accessed from inner class
+ boolean mStorageAvailable = true; // accessed from inner class
+
+ /**
+ * Hold the wake lock for 5 seconds, which should be enough time for
+ * any receiver(s) to grab its own wake lock.
+ */
+ private static final int WAKE_LOCK_TIMEOUT = 5000;
+
+ /**
+ * Creates an SmsStorageMonitor and registers for events.
+ * @param phone the Phone to use
+ */
+ public SmsStorageMonitor(PhoneBase phone) {
+ mContext = phone.getContext();
+ mCm = phone.mCM;
+
+ createWakelock();
+
+ mCm.setOnIccSmsFull(this, EVENT_ICC_FULL, null);
+ mCm.registerForOn(this, EVENT_RADIO_ON, null);
+
+ // Register for device storage intents. Use these to notify the RIL
+ // that storage for SMS is or is not available.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_DEVICE_STORAGE_FULL);
+ filter.addAction(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
+ mContext.registerReceiver(mResultReceiver, filter);
+ }
+
+ public void dispose() {
+ mCm.unSetOnIccSmsFull(this);
+ mCm.unregisterForOn(this);
+ mContext.unregisterReceiver(mResultReceiver);
+ }
+
+ /**
+ * 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_ICC_FULL:
+ handleIccFull();
+ break;
+
+ case EVENT_REPORT_MEMORY_STATUS_DONE:
+ ar = (AsyncResult) msg.obj;
+ if (ar.exception != null) {
+ mReportMemoryStatusPending = true;
+ Log.v(TAG, "Memory status report to modem pending : mStorageAvailable = "
+ + mStorageAvailable);
+ } else {
+ mReportMemoryStatusPending = false;
+ }
+ break;
+
+ case EVENT_RADIO_ON:
+ if (mReportMemoryStatusPending) {
+ Log.v(TAG, "Sending pending memory status report : mStorageAvailable = "
+ + mStorageAvailable);
+ mCm.reportSmsMemoryStatus(mStorageAvailable,
+ obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+ }
+ break;
+ }
+ }
+
+ private void createWakelock() {
+ PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SmsStorageMonitor");
+ mWakeLock.setReferenceCounted(true);
+ }
+
+ /**
+ * Called when SIM_FULL message is received from the RIL. Notifies interested
+ * parties that SIM storage for SMS messages is full.
+ */
+ private void handleIccFull() {
+ // broadcast SIM_FULL intent
+ Intent intent = new Intent(Intents.SIM_FULL_ACTION);
+ mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
+ mContext.sendBroadcast(intent, SMSDispatcher.RECEIVE_SMS_PERMISSION);
+ }
+
+ /** Returns whether or not there is storage available for an incoming SMS. */
+ public boolean isStorageAvailable() {
+ return mStorageAvailable;
+ }
+
+ private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) {
+ mStorageAvailable = false;
+ mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+ } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) {
+ mStorageAvailable = true;
+ mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+ }
+ }
+ };
+}
diff --git a/src/java/com/android/internal/telephony/SmsUsageMonitor.java b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
new file mode 100644
index 0000000..1804d97
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2011 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.ContentResolver;
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Implement the per-application based SMS control, which limits the number of
+ * SMS/MMS messages an app can send in the checking period.
+ *
+ * This code was formerly part of {@link SMSDispatcher}, and has been moved
+ * into a separate class to support instantiation of multiple SMSDispatchers on
+ * dual-mode devices that require support for both 3GPP and 3GPP2 format messages.
+ */
+public class SmsUsageMonitor {
+ private static final String TAG = "SmsUsageMonitor";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ /** Default checking period for SMS sent without user permission. */
+ private static final int DEFAULT_SMS_CHECK_PERIOD = 1800000; // 30 minutes
+
+ /** Default number of SMS sent in checking period without user permission. */
+ private static final int DEFAULT_SMS_MAX_COUNT = 30;
+
+ /** Return value from {@link #checkDestination} for regular phone numbers. */
+ static final int CATEGORY_NOT_SHORT_CODE = 0;
+
+ /** Return value from {@link #checkDestination} for free (no cost) short codes. */
+ static final int CATEGORY_FREE_SHORT_CODE = 1;
+
+ /** Return value from {@link #checkDestination} for standard rate (non-premium) short codes. */
+ static final int CATEGORY_STANDARD_SHORT_CODE = 2;
+
+ /** Return value from {@link #checkDestination} for possible premium short codes. */
+ static final int CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3;
+
+ /** Return value from {@link #checkDestination} for premium short codes. */
+ static final int CATEGORY_PREMIUM_SHORT_CODE = 4;
+
+ private final int mCheckPeriod;
+ private final int mMaxAllowed;
+
+ private final HashMap<String, ArrayList<Long>> mSmsStamp =
+ new HashMap<String, ArrayList<Long>>();
+
+ /** Context for retrieving regexes from XML resource. */
+ private final Context mContext;
+
+ /** Country code for the cached short code pattern matcher. */
+ private String mCurrentCountry;
+
+ /** Cached short code pattern matcher for {@link #mCurrentCountry}. */
+ private ShortCodePatternMatcher mCurrentPatternMatcher;
+
+ /** Cached short code regex patterns from secure settings for {@link #mCurrentCountry}. */
+ private String mSettingsShortCodePatterns;
+
+ /** Handler for responding to content observer updates. */
+ private final SettingsObserverHandler mSettingsObserverHandler;
+
+ /** XML tag for root element. */
+ private static final String TAG_SHORTCODES = "shortcodes";
+
+ /** XML tag for short code patterns for a specific country. */
+ private static final String TAG_SHORTCODE = "shortcode";
+
+ /** XML attribute for the country code. */
+ private static final String ATTR_COUNTRY = "country";
+
+ /** XML attribute for the short code regex pattern. */
+ private static final String ATTR_PATTERN = "pattern";
+
+ /** XML attribute for the premium short code regex pattern. */
+ private static final String ATTR_PREMIUM = "premium";
+
+ /** XML attribute for the free short code regex pattern. */
+ private static final String ATTR_FREE = "free";
+
+ /** XML attribute for the standard rate short code regex pattern. */
+ private static final String ATTR_STANDARD = "standard";
+
+ /**
+ * SMS short code regex pattern matcher for a specific country.
+ */
+ private static final class ShortCodePatternMatcher {
+ private final Pattern mShortCodePattern;
+ private final Pattern mPremiumShortCodePattern;
+ private final Pattern mFreeShortCodePattern;
+ private final Pattern mStandardShortCodePattern;
+
+ ShortCodePatternMatcher(String shortCodeRegex, String premiumShortCodeRegex,
+ String freeShortCodeRegex, String standardShortCodeRegex) {
+ mShortCodePattern = (shortCodeRegex != null ? Pattern.compile(shortCodeRegex) : null);
+ mPremiumShortCodePattern = (premiumShortCodeRegex != null ?
+ Pattern.compile(premiumShortCodeRegex) : null);
+ mFreeShortCodePattern = (freeShortCodeRegex != null ?
+ Pattern.compile(freeShortCodeRegex) : null);
+ mStandardShortCodePattern = (standardShortCodeRegex != null ?
+ Pattern.compile(standardShortCodeRegex) : null);
+ }
+
+ int getNumberCategory(String phoneNumber) {
+ if (mFreeShortCodePattern != null && mFreeShortCodePattern.matcher(phoneNumber)
+ .matches()) {
+ return CATEGORY_FREE_SHORT_CODE;
+ }
+ if (mStandardShortCodePattern != null && mStandardShortCodePattern.matcher(phoneNumber)
+ .matches()) {
+ return CATEGORY_STANDARD_SHORT_CODE;
+ }
+ if (mPremiumShortCodePattern != null && mPremiumShortCodePattern.matcher(phoneNumber)
+ .matches()) {
+ return CATEGORY_PREMIUM_SHORT_CODE;
+ }
+ if (mShortCodePattern != null && mShortCodePattern.matcher(phoneNumber).matches()) {
+ return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
+ }
+ return CATEGORY_NOT_SHORT_CODE;
+ }
+ }
+
+ /**
+ * Observe the secure setting for updated regex patterns.
+ */
+ private static class SettingsObserver extends ContentObserver {
+ private final int mWhat;
+ private final Handler mHandler;
+
+ SettingsObserver(Handler handler, int what) {
+ super(handler);
+ mHandler = handler;
+ mWhat = what;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mHandler.obtainMessage(mWhat).sendToTarget();
+ }
+ }
+
+ /**
+ * Handler to update regex patterns when secure setting for the current country is updated.
+ */
+ private class SettingsObserverHandler extends Handler {
+ /** Current content observer, or null. */
+ SettingsObserver mSettingsObserver;
+
+ /** Current country code to watch for settings updates. */
+ private String mCountryIso;
+
+ /** Request to start observing a secure setting. */
+ static final int OBSERVE_SETTING = 1;
+
+ /** Handler event for updated secure settings. */
+ static final int SECURE_SETTINGS_CHANGED = 2;
+
+ /** Send a message to this handler requesting to observe the setting for a new country. */
+ void observeSettingForCountry(String countryIso) {
+ obtainMessage(OBSERVE_SETTING, countryIso).sendToTarget();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case OBSERVE_SETTING:
+ if (msg.obj != null && msg.obj instanceof String) {
+ mCountryIso = (String) msg.obj;
+ String settingName = getSettingNameForCountry(mCountryIso);
+ ContentResolver resolver = mContext.getContentResolver();
+
+ if (mSettingsObserver != null) {
+ if (VDBG) log("Unregistering old content observer");
+ resolver.unregisterContentObserver(mSettingsObserver);
+ }
+
+ mSettingsObserver = new SettingsObserver(this, SECURE_SETTINGS_CHANGED);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(settingName), false, mSettingsObserver);
+ if (VDBG) log("Registered content observer for " + settingName);
+ }
+ break;
+
+ case SECURE_SETTINGS_CHANGED:
+ loadPatternsFromSettings(mCountryIso);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Create SMS usage monitor.
+ * @param context the context to use to load resources and get TelephonyManager service
+ */
+ public SmsUsageMonitor(Context context) {
+ mContext = context;
+ ContentResolver resolver = context.getContentResolver();
+
+ mMaxAllowed = Settings.Secure.getInt(resolver,
+ Settings.Secure.SMS_OUTGOING_CHECK_MAX_COUNT,
+ DEFAULT_SMS_MAX_COUNT);
+
+ mCheckPeriod = Settings.Secure.getInt(resolver,
+ Settings.Secure.SMS_OUTGOING_CHECK_INTERVAL_MS,
+ DEFAULT_SMS_CHECK_PERIOD);
+
+ mSettingsObserverHandler = new SettingsObserverHandler();
+ }
+
+ /**
+ * Return a pattern matcher object for the specified country.
+ * @param country the country to search for
+ * @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found
+ */
+ private ShortCodePatternMatcher getPatternMatcher(String country) {
+ int id = com.android.internal.R.xml.sms_short_codes;
+ XmlResourceParser parser = mContext.getResources().getXml(id);
+
+ try {
+ return getPatternMatcher(country, parser);
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "XML parser exception reading short code pattern resource", e);
+ } catch (IOException e) {
+ Log.e(TAG, "I/O exception reading short code pattern resource", e);
+ } finally {
+ parser.close();
+ }
+ return null; // country not found
+ }
+
+ /**
+ * Return a pattern matcher object for the specified country from a secure settings string.
+ * @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found
+ */
+ private static ShortCodePatternMatcher getPatternMatcher(String country, String settingsPattern) {
+ // embed pattern tag into an XML document.
+ String document = "<shortcodes>" + settingsPattern + "</shortcodes>";
+ if (VDBG) log("loading updated patterns from: " + document);
+
+ try {
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ XmlPullParser parser = factory.newPullParser();
+ parser.setInput(new StringReader(document));
+ return getPatternMatcher(country, parser);
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "XML parser exception reading short code pattern from settings", e);
+ } catch (IOException e) {
+ Log.e(TAG, "I/O exception reading short code pattern from settings", e);
+ }
+ return null; // country not found
+ }
+
+ /**
+ * Return a pattern matcher object for the specified country and pattern XML parser.
+ * @param country the country to search for
+ * @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found
+ */
+ private static ShortCodePatternMatcher getPatternMatcher(String country, XmlPullParser parser)
+ throws XmlPullParserException, IOException
+ {
+ XmlUtils.beginDocument(parser, TAG_SHORTCODES);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ String element = parser.getName();
+ if (element == null) break;
+
+ if (element.equals(TAG_SHORTCODE)) {
+ String currentCountry = parser.getAttributeValue(null, ATTR_COUNTRY);
+ if (country.equals(currentCountry)) {
+ String pattern = parser.getAttributeValue(null, ATTR_PATTERN);
+ String premium = parser.getAttributeValue(null, ATTR_PREMIUM);
+ String free = parser.getAttributeValue(null, ATTR_FREE);
+ String standard = parser.getAttributeValue(null, ATTR_STANDARD);
+ return new ShortCodePatternMatcher(pattern, premium, free, standard);
+ }
+ } else {
+ Log.e(TAG, "Error: skipping unknown XML tag " + element);
+ }
+ }
+ return null; // country not found
+ }
+
+ /** Clear the SMS application list for disposal. */
+ void dispose() {
+ mSmsStamp.clear();
+ }
+
+ /**
+ * Check to see if an application is allowed to send new SMS messages, and confirm with
+ * user if the send limit was reached or if a non-system app is potentially sending to a
+ * premium SMS short code or number.
+ *
+ * @param appName the package name of the app requesting to send an SMS
+ * @param smsWaiting the number of new messages desired to send
+ * @return true if application is allowed to send the requested number
+ * of new sms messages
+ */
+ public boolean check(String appName, int smsWaiting) {
+ synchronized (mSmsStamp) {
+ removeExpiredTimestamps();
+
+ ArrayList<Long> sentList = mSmsStamp.get(appName);
+ if (sentList == null) {
+ sentList = new ArrayList<Long>();
+ mSmsStamp.put(appName, sentList);
+ }
+
+ return isUnderLimit(sentList, smsWaiting);
+ }
+ }
+
+ /**
+ * Check if the destination is a possible premium short code.
+ * NOTE: the caller is expected to strip non-digits from the destination number with
+ * {@link PhoneNumberUtils#extractNetworkPortion} before calling this method.
+ * This happens in {@link SMSDispatcher#sendRawPdu} so that we use the same phone number
+ * for testing and in the user confirmation dialog if the user needs to confirm the number.
+ * This makes it difficult for malware to fool the user or the short code pattern matcher
+ * by using non-ASCII characters to make the number appear to be different from the real
+ * destination phone number.
+ *
+ * @param destAddress the destination address to test for possible short code
+ * @return {@link #CATEGORY_NOT_SHORT_CODE}, {@link #CATEGORY_FREE_SHORT_CODE},
+ * {@link #CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE}, or {@link #CATEGORY_PREMIUM_SHORT_CODE}.
+ */
+ public int checkDestination(String destAddress, String countryIso) {
+ synchronized (mSettingsObserverHandler) {
+ // always allow emergency numbers
+ if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) {
+ return CATEGORY_NOT_SHORT_CODE;
+ }
+
+ ShortCodePatternMatcher patternMatcher = null;
+
+ if (countryIso != null) {
+ // query secure settings and initialize content observer for updated regex patterns
+ if (mCurrentCountry == null || !countryIso.equals(mCurrentCountry)) {
+ loadPatternsFromSettings(countryIso);
+ mSettingsObserverHandler.observeSettingForCountry(countryIso);
+ }
+
+ if (countryIso.equals(mCurrentCountry)) {
+ patternMatcher = mCurrentPatternMatcher;
+ } else {
+ patternMatcher = getPatternMatcher(countryIso);
+ mCurrentCountry = countryIso;
+ mCurrentPatternMatcher = patternMatcher; // may be null if not found
+ }
+ }
+
+ if (patternMatcher != null) {
+ return patternMatcher.getNumberCategory(destAddress);
+ } else {
+ // Generic rule: numbers of 5 digits or less are considered potential short codes
+ Log.e(TAG, "No patterns for \"" + countryIso + "\": using generic short code rule");
+ if (destAddress.length() <= 5) {
+ return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
+ } else {
+ return CATEGORY_NOT_SHORT_CODE;
+ }
+ }
+ }
+ }
+
+ private static String getSettingNameForCountry(String countryIso) {
+ return Settings.Secure.SMS_SHORT_CODES_PREFIX + countryIso;
+ }
+
+ /**
+ * Load regex patterns from secure settings if present.
+ * @param countryIso the country to search for
+ */
+ void loadPatternsFromSettings(String countryIso) {
+ synchronized (mSettingsObserverHandler) {
+ if (VDBG) log("loadPatternsFromSettings(" + countryIso + ") called");
+ String settingsPatterns = Settings.Secure.getString(
+ mContext.getContentResolver(), getSettingNameForCountry(countryIso));
+ if (settingsPatterns != null && !settingsPatterns.equals(
+ mSettingsShortCodePatterns)) {
+ // settings pattern string has changed: update the pattern matcher
+ mSettingsShortCodePatterns = settingsPatterns;
+ ShortCodePatternMatcher matcher = getPatternMatcher(countryIso, settingsPatterns);
+ if (matcher != null) {
+ mCurrentCountry = countryIso;
+ mCurrentPatternMatcher = matcher;
+ }
+ } else if (settingsPatterns == null && mSettingsShortCodePatterns != null) {
+ // pattern string was removed: caller will load default patterns from XML resource
+ mCurrentCountry = null;
+ mCurrentPatternMatcher = null;
+ mSettingsShortCodePatterns = null;
+ }
+ }
+ }
+
+ /**
+ * Remove keys containing only old timestamps. This can happen if an SMS app is used
+ * to send messages and then uninstalled.
+ */
+ private void removeExpiredTimestamps() {
+ long beginCheckPeriod = System.currentTimeMillis() - mCheckPeriod;
+
+ synchronized (mSmsStamp) {
+ Iterator<Map.Entry<String, ArrayList<Long>>> iter = mSmsStamp.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry<String, ArrayList<Long>> entry = iter.next();
+ ArrayList<Long> oldList = entry.getValue();
+ if (oldList.isEmpty() || oldList.get(oldList.size() - 1) < beginCheckPeriod) {
+ iter.remove();
+ }
+ }
+ }
+ }
+
+ private boolean isUnderLimit(ArrayList<Long> sent, int smsWaiting) {
+ Long ct = System.currentTimeMillis();
+ long beginCheckPeriod = ct - mCheckPeriod;
+
+ if (VDBG) log("SMS send size=" + sent.size() + " time=" + ct);
+
+ while (!sent.isEmpty() && sent.get(0) < beginCheckPeriod) {
+ sent.remove(0);
+ }
+
+ if ((sent.size() + smsWaiting) <= mMaxAllowed) {
+ for (int i = 0; i < smsWaiting; i++ ) {
+ sent.add(ct);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/TelephonyCapabilities.java b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
new file mode 100644
index 0000000..a9e9376
--- /dev/null
+++ b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 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.util.Log;
+
+import com.android.internal.telephony.Phone;
+
+/**
+ * Utilities that check if the phone supports specified capabilities.
+ */
+public class TelephonyCapabilities {
+ private static final String LOG_TAG = "TelephonyCapabilities";
+
+ /** This class is never instantiated. */
+ private TelephonyCapabilities() {
+ }
+
+ /**
+ * Return true if the current phone supports ECM ("Emergency Callback
+ * Mode"), which is a feature where the device goes into a special
+ * state for a short period of time after making an outgoing emergency
+ * call.
+ *
+ * (On current devices, that state lasts 5 minutes. It prevents data
+ * usage by other apps, to avoid conflicts with any possible incoming
+ * calls. It also puts up a notification in the status bar, showing a
+ * countdown while ECM is active, and allowing the user to exit ECM.)
+ *
+ * Currently this is assumed to be true for CDMA phones, and false
+ * otherwise.
+ */
+ public static boolean supportsEcm(Phone phone) {
+ return (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
+ }
+
+ /**
+ * Return true if the current phone supports Over The Air Service
+ * Provisioning (OTASP)
+ *
+ * Currently this is assumed to be true for CDMA phones, and false
+ * otherwise.
+ *
+ * TODO: Watch out: this is also highly carrier-specific, since the
+ * OTASP procedure is different from one carrier to the next, *and* the
+ * different carriers may want very different onscreen UI as well.
+ * The procedure may even be different for different devices with the
+ * same carrier.
+ *
+ * So we eventually will need a much more flexible, pluggable design.
+ * This method here is just a placeholder to reduce hardcoded
+ * "if (CDMA)" checks sprinkled throughout the phone app.
+ */
+ public static boolean supportsOtasp(Phone phone) {
+ return (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
+ }
+
+ /**
+ * Return true if the current phone can retrieve the voice message count.
+ *
+ * Currently this is assumed to be true on CDMA phones and false otherwise.
+ */
+ public static boolean supportsVoiceMessageCount(Phone phone) {
+ return (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
+ }
+
+ /**
+ * Return true if this phone allows the user to select which
+ * network to use.
+ *
+ * Currently this is assumed to be true only on GSM phones.
+ *
+ * TODO: Should CDMA phones allow this as well?
+ */
+ public static boolean supportsNetworkSelection(Phone phone) {
+ return (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM);
+ }
+
+ /**
+ * Returns a resource ID for a label to use when displaying the
+ * "device id" of the current device. (This is currently used as the
+ * title of the "device id" dialog.)
+ *
+ * This is specific to the device's telephony technology: the device
+ * id is called "IMEI" on GSM phones and "MEID" on CDMA phones.
+ */
+ public static int getDeviceIdLabel(Phone phone) {
+ if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
+ return com.android.internal.R.string.imei;
+ } else if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+ return com.android.internal.R.string.meid;
+ } else {
+ Log.w(LOG_TAG, "getDeviceIdLabel: no known label for phone "
+ + phone.getPhoneName());
+ return 0;
+ }
+ }
+
+ /**
+ * Return true if the current phone supports the ability to explicitly
+ * manage the state of a conference call (i.e. view the participants,
+ * and hangup or separate individual callers.)
+ *
+ * The in-call screen's "Manage conference" UI is available only on
+ * devices that support this feature.
+ *
+ * Currently this is assumed to be true on GSM phones and false otherwise.
+ */
+ public static boolean supportsConferenceCallManagement(Phone phone) {
+ return ((phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
+ || (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP));
+ }
+
+ /**
+ * Return true if the current phone supports explicit "Hold" and
+ * "Unhold" actions for an active call. (If so, the in-call UI will
+ * provide onscreen "Hold" / "Unhold" buttons.)
+ *
+ * Currently this is assumed to be true on GSM phones and false
+ * otherwise. (In particular, CDMA has no concept of "putting a call
+ * on hold.")
+ */
+ public static boolean supportsHoldAndUnhold(Phone phone) {
+ return ((phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
+ || (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP));
+ }
+
+ /**
+ * Return true if the current phone supports distinct "Answer & Hold"
+ * and "Answer & End" behaviors in the call-waiting scenario. If so,
+ * the in-call UI may provide separate buttons or menu items for these
+ * two actions.
+ *
+ * Currently this is assumed to be true on GSM phones and false
+ * otherwise. (In particular, CDMA has no concept of explicitly
+ * managing the background call, or "putting a call on hold.")
+ *
+ * TODO: It might be better to expose this capability in a more
+ * generic form, like maybe "supportsExplicitMultipleLineManagement()"
+ * rather than focusing specifically on call-waiting behavior.
+ */
+ public static boolean supportsAnswerAndHold(Phone phone) {
+ return ((phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
+ || (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP));
+ }
+
+ /**
+ * Return true if phones with the given phone type support ADN
+ * (Abbreviated Dialing Numbers).
+ *
+ * Currently this returns true when the phone type is GSM
+ * ({@link Phone#PHONE_TYPE_GSM}).
+ *
+ * This is using int for an argument for letting apps outside
+ * Phone process access to it, while other methods in this class is
+ * using Phone object.
+ *
+ * TODO: Theoretically phones other than GSM may have the ADN capability.
+ * Consider having better check here, or have better capability as part
+ * of public API, with which the argument should be replaced with
+ * something more appropriate.
+ */
+ public static boolean supportsAdn(int phoneType) {
+ return phoneType == PhoneConstants.PHONE_TYPE_GSM;
+ }
+
+ /**
+ * Returns true if the device can distinguish the phone's dialing state
+ * (Call.State.DIALING/ALERTING) and connected state (Call.State.ACTIVE).
+ *
+ * Currently this returns true for GSM phones as we cannot know when a CDMA
+ * phone has transitioned from dialing/active to connected.
+ */
+ public static boolean canDistinguishDialingAndConnected(int phoneType) {
+ return phoneType == PhoneConstants.PHONE_TYPE_GSM;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/UUSInfo.java b/src/java/com/android/internal/telephony/UUSInfo.java
new file mode 100644
index 0000000..801b845
--- /dev/null
+++ b/src/java/com/android/internal/telephony/UUSInfo.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010 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;
+
+public class UUSInfo {
+
+ /*
+ * User-to-User signaling Info activation types derived from 3GPP 23.087
+ * v8.0
+ */
+
+ public static final int UUS_TYPE1_IMPLICIT = 0;
+
+ public static final int UUS_TYPE1_REQUIRED = 1;
+
+ public static final int UUS_TYPE1_NOT_REQUIRED = 2;
+
+ public static final int UUS_TYPE2_REQUIRED = 3;
+
+ public static final int UUS_TYPE2_NOT_REQUIRED = 4;
+
+ public static final int UUS_TYPE3_REQUIRED = 5;
+
+ public static final int UUS_TYPE3_NOT_REQUIRED = 6;
+
+ /*
+ * User-to-User Signaling Information data coding schemes. Possible values
+ * for Octet 3 (Protocol Discriminator field) in the UUIE. The values have
+ * been specified in section 10.5.4.25 of 3GPP TS 24.008
+ */
+
+ public static final int UUS_DCS_USP = 0; /* User specified protocol */
+
+ public static final int UUS_DCS_OSIHLP = 1; /* OSI higher layer protocol */
+
+ public static final int UUS_DCS_X244 = 2; /* X.244 */
+
+ public static final int UUS_DCS_RMCF = 3; /*
+ * Reserved for system management
+ * convergence function
+ */
+
+ public static final int UUS_DCS_IA5c = 4; /* IA5 characters */
+
+ private int uusType;
+
+ private int uusDcs;
+
+ private byte[] uusData;
+
+ public UUSInfo() {
+ this.uusType = UUS_TYPE1_IMPLICIT;
+ this.uusDcs = UUS_DCS_IA5c;
+ this.uusData = null;
+ }
+
+ public UUSInfo(int uusType, int uusDcs, byte[] uusData) {
+ this.uusType = uusType;
+ this.uusDcs = uusDcs;
+ this.uusData = uusData;
+ }
+
+ public int getDcs() {
+ return uusDcs;
+ }
+
+ public void setDcs(int uusDcs) {
+ this.uusDcs = uusDcs;
+ }
+
+ public int getType() {
+ return uusType;
+ }
+
+ public void setType(int uusType) {
+ this.uusType = uusType;
+ }
+
+ public byte[] getUserData() {
+ return uusData;
+ }
+
+ public void setUserData(byte[] uusData) {
+ this.uusData = uusData;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/WapPushManagerParams.java b/src/java/com/android/internal/telephony/WapPushManagerParams.java
new file mode 100644
index 0000000..11e5ff9
--- /dev/null
+++ b/src/java/com/android/internal/telephony/WapPushManagerParams.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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;
+
+/**
+ * WapPushManager constant value definitions
+ */
+public class WapPushManagerParams {
+ /**
+ * Application type activity
+ */
+ public static final int APP_TYPE_ACTIVITY = 0;
+
+ /**
+ * Application type service
+ */
+ public static final int APP_TYPE_SERVICE = 1;
+
+ /**
+ * Process Message return value
+ * Message is handled
+ */
+ public static final int MESSAGE_HANDLED = 0x1;
+
+ /**
+ * Process Message return value
+ * Application ID or content type was not found in the application ID table
+ */
+ public static final int APP_QUERY_FAILED = 0x2;
+
+ /**
+ * Process Message return value
+ * Receiver application signature check failed
+ */
+ public static final int SIGNATURE_NO_MATCH = 0x4;
+
+ /**
+ * Process Message return value
+ * Receiver application was not found
+ */
+ public static final int INVALID_RECEIVER_NAME = 0x8;
+
+ /**
+ * Process Message return value
+ * Unknown exception
+ */
+ public static final int EXCEPTION_CAUGHT = 0x10;
+
+ /**
+ * Process Message return value
+ * Need further processing after WapPushManager message processing
+ */
+ public static final int FURTHER_PROCESSING = 0x8000;
+
+}
+
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
new file mode 100755
index 0000000..e2779dc
--- /dev/null
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -0,0 +1,275 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.provider.Telephony;
+import android.provider.Telephony.Sms.Intents;
+import android.util.Log;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * WAP push handler class.
+ *
+ * @hide
+ */
+public class WapPushOverSms {
+ private static final String LOG_TAG = "WAP PUSH";
+
+ private final Context mContext;
+ private WspTypeDecoder pduDecoder;
+ private SMSDispatcher mSmsDispatcher;
+
+ /**
+ * Hold the wake lock for 5 seconds, which should be enough time for
+ * any receiver(s) to grab its own wake lock.
+ */
+ private final int WAKE_LOCK_TIMEOUT = 5000;
+
+ private final int BIND_RETRY_INTERVAL = 1000;
+ /**
+ * A handle to WapPushManager interface
+ */
+ private WapPushConnection mWapConn = null;
+ private class WapPushConnection implements ServiceConnection {
+ private IWapPushManager mWapPushMan;
+ private Context mOwner;
+
+ public WapPushConnection(Context ownerContext) {
+ mOwner = ownerContext;
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mWapPushMan = IWapPushManager.Stub.asInterface(service);
+ if (false) Log.v(LOG_TAG, "wappush manager connected to " +
+ mOwner.hashCode());
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mWapPushMan = null;
+ if (false) Log.v(LOG_TAG, "wappush manager disconnected.");
+ // WapPushManager must be always attached.
+ rebindWapPushManager();
+ }
+
+ /**
+ * bind WapPushManager
+ */
+ public void bindWapPushManager() {
+ if (mWapPushMan != null) return;
+
+ final ServiceConnection wapPushConnection = this;
+
+ mOwner.bindService(new Intent(IWapPushManager.class.getName()),
+ wapPushConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ /**
+ * rebind WapPushManager
+ * This method is called when WapPushManager is disconnected unexpectedly.
+ */
+ private void rebindWapPushManager() {
+ if (mWapPushMan != null) return;
+
+ final ServiceConnection wapPushConnection = this;
+ new Thread() {
+ public void run() {
+ while (mWapPushMan == null) {
+ mOwner.bindService(new Intent(IWapPushManager.class.getName()),
+ wapPushConnection, Context.BIND_AUTO_CREATE);
+ try {
+ Thread.sleep(BIND_RETRY_INTERVAL);
+ } catch (InterruptedException e) {
+ if (false) Log.v(LOG_TAG, "sleep interrupted.");
+ }
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * Returns interface to WapPushManager
+ */
+ public IWapPushManager getWapPushManager() {
+ return mWapPushMan;
+ }
+ }
+
+ public WapPushOverSms(Phone phone, SMSDispatcher smsDispatcher) {
+ mSmsDispatcher = smsDispatcher;
+ mContext = phone.getContext();
+ mWapConn = new WapPushConnection(mContext);
+ mWapConn.bindWapPushManager();
+ }
+
+
+ /**
+ * 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
+ * @return a result code from {@link Telephony.Sms.Intents}, or
+ * {@link Activity#RESULT_OK} if the message has been broadcast
+ * to applications
+ */
+ public int dispatchWapPdu(byte[] pdu) {
+
+ if (false) Log.d(LOG_TAG, "Rx: " + IccUtils.bytesToHexString(pdu));
+
+ int index = 0;
+ int transactionId = pdu[index++] & 0xFF;
+ int pduType = pdu[index++] & 0xFF;
+ int headerLength = 0;
+
+ if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) &&
+ (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
+ if (false) Log.w(LOG_TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ pduDecoder = new WspTypeDecoder(pdu);
+
+ /**
+ * 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.
+ */
+ if (pduDecoder.decodeUintvarInteger(index) == false) {
+ if (false) Log.w(LOG_TAG, "Received PDU. Header Length error.");
+ return Intents.RESULT_SMS_GENERIC_ERROR;
+ }
+ headerLength = (int)pduDecoder.getValue32();
+ index += pduDecoder.getDecodedDataLength();
+
+ 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
+ */
+ if (pduDecoder.decodeContentType(index) == false) {
+ if (false) Log.w(LOG_TAG, "Received PDU. Header Content-Type error.");
+ return Intents.RESULT_SMS_GENERIC_ERROR;
+ }
+
+ String mimeType = pduDecoder.getValueString();
+ long binaryContentType = pduDecoder.getValue32();
+ index += pduDecoder.getDecodedDataLength();
+
+ byte[] header = new byte[headerLength];
+ System.arraycopy(pdu, headerStartIndex, header, 0, header.length);
+
+ byte[] intentData;
+
+ if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO)) {
+ intentData = pdu;
+ } else {
+ int dataIndex = headerStartIndex + headerLength;
+ intentData = new byte[pdu.length - dataIndex];
+ System.arraycopy(pdu, dataIndex, intentData, 0, intentData.length);
+ }
+
+ /**
+ * Seek for application ID field in WSP header.
+ * If application ID is found, WapPushManager substitute the message
+ * processing. Since WapPushManager is optional module, if WapPushManager
+ * is not found, legacy message processing will be continued.
+ */
+ if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) {
+ index = (int) pduDecoder.getValue32();
+ pduDecoder.decodeXWapApplicationId(index);
+ String wapAppId = pduDecoder.getValueString();
+ if (wapAppId == null) {
+ wapAppId = Integer.toString((int) pduDecoder.getValue32());
+ }
+
+ String contentType = ((mimeType == null) ?
+ Long.toString(binaryContentType) : mimeType);
+ if (false) Log.v(LOG_TAG, "appid found: " + wapAppId + ":" + contentType);
+
+ try {
+ boolean processFurther = true;
+ IWapPushManager wapPushMan = mWapConn.getWapPushManager();
+
+ if (wapPushMan == null) {
+ if (false) Log.w(LOG_TAG, "wap push manager not found!");
+ } else {
+ Intent intent = new Intent();
+ intent.putExtra("transactionId", transactionId);
+ intent.putExtra("pduType", pduType);
+ intent.putExtra("header", header);
+ intent.putExtra("data", intentData);
+ intent.putExtra("contentTypeParameters",
+ pduDecoder.getContentParameters());
+
+ int procRet = wapPushMan.processMessage(wapAppId, contentType, intent);
+ if (false) Log.v(LOG_TAG, "procRet:" + procRet);
+ if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0
+ && (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) {
+ processFurther = false;
+ }
+ }
+ if (!processFurther) {
+ return Intents.RESULT_SMS_HANDLED;
+ }
+ } catch (RemoteException e) {
+ if (false) Log.w(LOG_TAG, "remote func failed...");
+ }
+ }
+ if (false) Log.v(LOG_TAG, "fall back to existing handler");
+
+ if (mimeType == null) {
+ if (false) Log.w(LOG_TAG, "Header Content-Type error.");
+ return Intents.RESULT_SMS_GENERIC_ERROR;
+ }
+
+ String permission;
+
+ if (mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_MMS)) {
+ permission = "android.permission.RECEIVE_MMS";
+ } else {
+ permission = "android.permission.RECEIVE_WAP_PUSH";
+ }
+
+ Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION);
+ intent.setType(mimeType);
+ intent.putExtra("transactionId", transactionId);
+ intent.putExtra("pduType", pduType);
+ intent.putExtra("header", header);
+ intent.putExtra("data", intentData);
+ intent.putExtra("contentTypeParameters", pduDecoder.getContentParameters());
+
+ mSmsDispatcher.dispatch(intent, permission);
+
+ return Activity.RESULT_OK;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/WspTypeDecoder.java b/src/java/com/android/internal/telephony/WspTypeDecoder.java
new file mode 100755
index 0000000..73260fb
--- /dev/null
+++ b/src/java/com/android/internal/telephony/WspTypeDecoder.java
@@ -0,0 +1,731 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+
+/**
+ * Implement the WSP data type decoder.
+ *
+ * @hide
+ */
+public class WspTypeDecoder {
+
+ private static final int WAP_PDU_SHORT_LENGTH_MAX = 30;
+ private static final int WAP_PDU_LENGTH_QUOTE = 31;
+
+ public static final int PDU_TYPE_PUSH = 0x06;
+ public static final int PDU_TYPE_CONFIRMED_PUSH = 0x07;
+
+ private final static HashMap<Integer, String> WELL_KNOWN_MIME_TYPES =
+ new HashMap<Integer, String>();
+
+ private final static HashMap<Integer, String> WELL_KNOWN_PARAMETERS =
+ new HashMap<Integer, String>();
+
+ public static final int PARAMETER_ID_X_WAP_APPLICATION_ID = 0x2f;
+ private static final int Q_VALUE = 0x00;
+
+ static {
+ WELL_KNOWN_MIME_TYPES.put(0x00, "*/*");
+ WELL_KNOWN_MIME_TYPES.put(0x01, "text/*");
+ WELL_KNOWN_MIME_TYPES.put(0x02, "text/html");
+ WELL_KNOWN_MIME_TYPES.put(0x03, "text/plain");
+ WELL_KNOWN_MIME_TYPES.put(0x04, "text/x-hdml");
+ WELL_KNOWN_MIME_TYPES.put(0x05, "text/x-ttml");
+ WELL_KNOWN_MIME_TYPES.put(0x06, "text/x-vCalendar");
+ WELL_KNOWN_MIME_TYPES.put(0x07, "text/x-vCard");
+ WELL_KNOWN_MIME_TYPES.put(0x08, "text/vnd.wap.wml");
+ WELL_KNOWN_MIME_TYPES.put(0x09, "text/vnd.wap.wmlscript");
+ WELL_KNOWN_MIME_TYPES.put(0x0A, "text/vnd.wap.wta-event");
+ WELL_KNOWN_MIME_TYPES.put(0x0B, "multipart/*");
+ WELL_KNOWN_MIME_TYPES.put(0x0C, "multipart/mixed");
+ WELL_KNOWN_MIME_TYPES.put(0x0D, "multipart/form-data");
+ WELL_KNOWN_MIME_TYPES.put(0x0E, "multipart/byterantes");
+ WELL_KNOWN_MIME_TYPES.put(0x0F, "multipart/alternative");
+ WELL_KNOWN_MIME_TYPES.put(0x10, "application/*");
+ WELL_KNOWN_MIME_TYPES.put(0x11, "application/java-vm");
+ WELL_KNOWN_MIME_TYPES.put(0x12, "application/x-www-form-urlencoded");
+ WELL_KNOWN_MIME_TYPES.put(0x13, "application/x-hdmlc");
+ WELL_KNOWN_MIME_TYPES.put(0x14, "application/vnd.wap.wmlc");
+ WELL_KNOWN_MIME_TYPES.put(0x15, "application/vnd.wap.wmlscriptc");
+ WELL_KNOWN_MIME_TYPES.put(0x16, "application/vnd.wap.wta-eventc");
+ WELL_KNOWN_MIME_TYPES.put(0x17, "application/vnd.wap.uaprof");
+ WELL_KNOWN_MIME_TYPES.put(0x18, "application/vnd.wap.wtls-ca-certificate");
+ WELL_KNOWN_MIME_TYPES.put(0x19, "application/vnd.wap.wtls-user-certificate");
+ WELL_KNOWN_MIME_TYPES.put(0x1A, "application/x-x509-ca-cert");
+ WELL_KNOWN_MIME_TYPES.put(0x1B, "application/x-x509-user-cert");
+ WELL_KNOWN_MIME_TYPES.put(0x1C, "image/*");
+ WELL_KNOWN_MIME_TYPES.put(0x1D, "image/gif");
+ WELL_KNOWN_MIME_TYPES.put(0x1E, "image/jpeg");
+ WELL_KNOWN_MIME_TYPES.put(0x1F, "image/tiff");
+ WELL_KNOWN_MIME_TYPES.put(0x20, "image/png");
+ WELL_KNOWN_MIME_TYPES.put(0x21, "image/vnd.wap.wbmp");
+ WELL_KNOWN_MIME_TYPES.put(0x22, "application/vnd.wap.multipart.*");
+ WELL_KNOWN_MIME_TYPES.put(0x23, "application/vnd.wap.multipart.mixed");
+ WELL_KNOWN_MIME_TYPES.put(0x24, "application/vnd.wap.multipart.form-data");
+ WELL_KNOWN_MIME_TYPES.put(0x25, "application/vnd.wap.multipart.byteranges");
+ WELL_KNOWN_MIME_TYPES.put(0x26, "application/vnd.wap.multipart.alternative");
+ WELL_KNOWN_MIME_TYPES.put(0x27, "application/xml");
+ WELL_KNOWN_MIME_TYPES.put(0x28, "text/xml");
+ WELL_KNOWN_MIME_TYPES.put(0x29, "application/vnd.wap.wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x2A, "application/x-x968-cross-cert");
+ WELL_KNOWN_MIME_TYPES.put(0x2B, "application/x-x968-ca-cert");
+ WELL_KNOWN_MIME_TYPES.put(0x2C, "application/x-x968-user-cert");
+ WELL_KNOWN_MIME_TYPES.put(0x2D, "text/vnd.wap.si");
+ WELL_KNOWN_MIME_TYPES.put(0x2E, "application/vnd.wap.sic");
+ WELL_KNOWN_MIME_TYPES.put(0x2F, "text/vnd.wap.sl");
+ WELL_KNOWN_MIME_TYPES.put(0x30, "application/vnd.wap.slc");
+ WELL_KNOWN_MIME_TYPES.put(0x31, "text/vnd.wap.co");
+ WELL_KNOWN_MIME_TYPES.put(0x32, "application/vnd.wap.coc");
+ WELL_KNOWN_MIME_TYPES.put(0x33, "application/vnd.wap.multipart.related");
+ WELL_KNOWN_MIME_TYPES.put(0x34, "application/vnd.wap.sia");
+ WELL_KNOWN_MIME_TYPES.put(0x35, "text/vnd.wap.connectivity-xml");
+ WELL_KNOWN_MIME_TYPES.put(0x36, "application/vnd.wap.connectivity-wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x37, "application/pkcs7-mime");
+ WELL_KNOWN_MIME_TYPES.put(0x38, "application/vnd.wap.hashed-certificate");
+ WELL_KNOWN_MIME_TYPES.put(0x39, "application/vnd.wap.signed-certificate");
+ WELL_KNOWN_MIME_TYPES.put(0x3A, "application/vnd.wap.cert-response");
+ WELL_KNOWN_MIME_TYPES.put(0x3B, "application/xhtml+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x3C, "application/wml+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x3D, "text/css");
+ WELL_KNOWN_MIME_TYPES.put(0x3E, "application/vnd.wap.mms-message");
+ WELL_KNOWN_MIME_TYPES.put(0x3F, "application/vnd.wap.rollover-certificate");
+ WELL_KNOWN_MIME_TYPES.put(0x40, "application/vnd.wap.locc+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x41, "application/vnd.wap.loc+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x42, "application/vnd.syncml.dm+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x43, "application/vnd.syncml.dm+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x44, "application/vnd.syncml.notification");
+ WELL_KNOWN_MIME_TYPES.put(0x45, "application/vnd.wap.xhtml+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x46, "application/vnd.wv.csp.cir");
+ WELL_KNOWN_MIME_TYPES.put(0x47, "application/vnd.oma.dd+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x48, "application/vnd.oma.drm.message");
+ WELL_KNOWN_MIME_TYPES.put(0x49, "application/vnd.oma.drm.content");
+ WELL_KNOWN_MIME_TYPES.put(0x4A, "application/vnd.oma.drm.rights+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x4B, "application/vnd.oma.drm.rights+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x4C, "application/vnd.wv.csp+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x4D, "application/vnd.wv.csp+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x4E, "application/vnd.syncml.ds.notification");
+ WELL_KNOWN_MIME_TYPES.put(0x4F, "audio/*");
+ WELL_KNOWN_MIME_TYPES.put(0x50, "video/*");
+ WELL_KNOWN_MIME_TYPES.put(0x51, "application/vnd.oma.dd2+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x52, "application/mikey");
+ WELL_KNOWN_MIME_TYPES.put(0x53, "application/vnd.oma.dcd");
+ WELL_KNOWN_MIME_TYPES.put(0x54, "application/vnd.oma.dcdc");
+
+ WELL_KNOWN_MIME_TYPES.put(0x0201, "application/vnd.uplanet.cacheop-wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x0202, "application/vnd.uplanet.signal");
+ WELL_KNOWN_MIME_TYPES.put(0x0203, "application/vnd.uplanet.alert-wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x0204, "application/vnd.uplanet.list-wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x0205, "application/vnd.uplanet.listcmd-wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x0206, "application/vnd.uplanet.channel-wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x0207, "application/vnd.uplanet.provisioning-status-uri");
+ WELL_KNOWN_MIME_TYPES.put(0x0208, "x-wap.multipart/vnd.uplanet.header-set");
+ WELL_KNOWN_MIME_TYPES.put(0x0209, "application/vnd.uplanet.bearer-choice-wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x020A, "application/vnd.phonecom.mmc-wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x020B, "application/vnd.nokia.syncset+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x020C, "image/x-up-wpng");
+ WELL_KNOWN_MIME_TYPES.put(0x0300, "application/iota.mmc-wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x0301, "application/iota.mmc-xml");
+ WELL_KNOWN_MIME_TYPES.put(0x0302, "application/vnd.syncml+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x0303, "application/vnd.syncml+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x0304, "text/vnd.wap.emn+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x0305, "text/calendar");
+ WELL_KNOWN_MIME_TYPES.put(0x0306, "application/vnd.omads-email+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x0307, "application/vnd.omads-file+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x0308, "application/vnd.omads-folder+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x0309, "text/directory;profile=vCard");
+ WELL_KNOWN_MIME_TYPES.put(0x030A, "application/vnd.wap.emn+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x030B, "application/vnd.nokia.ipdc-purchase-response");
+ WELL_KNOWN_MIME_TYPES.put(0x030C, "application/vnd.motorola.screen3+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x030D, "application/vnd.motorola.screen3+gzip");
+ WELL_KNOWN_MIME_TYPES.put(0x030E, "application/vnd.cmcc.setting+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x030F, "application/vnd.cmcc.bombing+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x0310, "application/vnd.docomo.pf");
+ WELL_KNOWN_MIME_TYPES.put(0x0311, "application/vnd.docomo.ub");
+ WELL_KNOWN_MIME_TYPES.put(0x0312, "application/vnd.omaloc-supl-init");
+ WELL_KNOWN_MIME_TYPES.put(0x0313, "application/vnd.oma.group-usage-list+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x0314, "application/oma-directory+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x0315, "application/vnd.docomo.pf2");
+ WELL_KNOWN_MIME_TYPES.put(0x0316, "application/vnd.oma.drm.roap-trigger+wbxml");
+ WELL_KNOWN_MIME_TYPES.put(0x0317, "application/vnd.sbm.mid2");
+ WELL_KNOWN_MIME_TYPES.put(0x0318, "application/vnd.wmf.bootstrap");
+ WELL_KNOWN_MIME_TYPES.put(0x0319, "application/vnc.cmcc.dcd+xml");
+ WELL_KNOWN_MIME_TYPES.put(0x031A, "application/vnd.sbm.cid");
+ WELL_KNOWN_MIME_TYPES.put(0x031B, "application/vnd.oma.bcast.provisioningtrigger");
+
+ WELL_KNOWN_PARAMETERS.put(0x00, "Q");
+ WELL_KNOWN_PARAMETERS.put(0x01, "Charset");
+ WELL_KNOWN_PARAMETERS.put(0x02, "Level");
+ WELL_KNOWN_PARAMETERS.put(0x03, "Type");
+ WELL_KNOWN_PARAMETERS.put(0x07, "Differences");
+ WELL_KNOWN_PARAMETERS.put(0x08, "Padding");
+ WELL_KNOWN_PARAMETERS.put(0x09, "Type");
+ WELL_KNOWN_PARAMETERS.put(0x0E, "Max-Age");
+ WELL_KNOWN_PARAMETERS.put(0x10, "Secure");
+ WELL_KNOWN_PARAMETERS.put(0x11, "SEC");
+ WELL_KNOWN_PARAMETERS.put(0x12, "MAC");
+ WELL_KNOWN_PARAMETERS.put(0x13, "Creation-date");
+ WELL_KNOWN_PARAMETERS.put(0x14, "Modification-date");
+ WELL_KNOWN_PARAMETERS.put(0x15, "Read-date");
+ WELL_KNOWN_PARAMETERS.put(0x16, "Size");
+ WELL_KNOWN_PARAMETERS.put(0x17, "Name");
+ WELL_KNOWN_PARAMETERS.put(0x18, "Filename");
+ WELL_KNOWN_PARAMETERS.put(0x19, "Start");
+ WELL_KNOWN_PARAMETERS.put(0x1A, "Start-info");
+ WELL_KNOWN_PARAMETERS.put(0x1B, "Comment");
+ WELL_KNOWN_PARAMETERS.put(0x1C, "Domain");
+ WELL_KNOWN_PARAMETERS.put(0x1D, "Path");
+ }
+
+ public static final String CONTENT_TYPE_B_PUSH_CO = "application/vnd.wap.coc";
+ public static final String CONTENT_TYPE_B_MMS = "application/vnd.wap.mms-message";
+ public static final String CONTENT_TYPE_B_PUSH_SYNCML_NOTI = "application/vnd.syncml.notification";
+
+ byte[] wspData;
+ int dataLength;
+ long unsigned32bit;
+ String stringValue;
+
+ HashMap<String, String> contentParameters;
+
+ public WspTypeDecoder(byte[] pdu) {
+ wspData = pdu;
+ }
+
+ /**
+ * Decode the "Text-string" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Text-string" in this pdu
+ *
+ * @return false when error(not a Text-string) occur
+ * return value can be retrieved by getValueString() method length of data in pdu can be
+ * retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeTextString(int startIndex) {
+ int index = startIndex;
+ while (wspData[index] != 0) {
+ index++;
+ }
+ dataLength = index - startIndex + 1;
+ if (wspData[startIndex] == 127) {
+ stringValue = new String(wspData, startIndex + 1, dataLength - 2);
+ } else {
+ stringValue = new String(wspData, startIndex, dataLength - 1);
+ }
+ return true;
+ }
+
+ /**
+ * Decode the "Token-text" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Token-text" in this pdu
+ *
+ * @return always true
+ * return value can be retrieved by getValueString() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeTokenText(int startIndex) {
+ int index = startIndex;
+ while (wspData[index] != 0) {
+ index++;
+ }
+ dataLength = index - startIndex + 1;
+ stringValue = new String(wspData, startIndex, dataLength - 1);
+
+ return true;
+ }
+
+ /**
+ * Decode the "Short-integer" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Short-integer" in this pdu
+ *
+ * @return false when error(not a Short-integer) occur
+ * return value can be retrieved by getValue32() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeShortInteger(int startIndex) {
+ if ((wspData[startIndex] & 0x80) == 0) {
+ return false;
+ }
+ unsigned32bit = wspData[startIndex] & 0x7f;
+ dataLength = 1;
+ return true;
+ }
+
+ /**
+ * Decode the "Long-integer" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Long-integer" in this pdu
+ *
+ * @return false when error(not a Long-integer) occur
+ * return value can be retrieved by getValue32() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeLongInteger(int startIndex) {
+ int lengthMultiOctet = wspData[startIndex] & 0xff;
+
+ if (lengthMultiOctet > WAP_PDU_SHORT_LENGTH_MAX) {
+ return false;
+ }
+ unsigned32bit = 0;
+ for (int i = 1; i <= lengthMultiOctet; i++) {
+ unsigned32bit = (unsigned32bit << 8) | (wspData[startIndex + i] & 0xff);
+ }
+ dataLength = 1 + lengthMultiOctet;
+ return true;
+ }
+
+ /**
+ * Decode the "Integer-Value" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Integer-Value" in this pdu
+ *
+ * @return false when error(not a Integer-Value) occur
+ * return value can be retrieved by getValue32() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeIntegerValue(int startIndex) {
+ if (decodeShortInteger(startIndex) == true) {
+ return true;
+ }
+ return decodeLongInteger(startIndex);
+ }
+
+ /**
+ * Decode the "Uintvar-integer" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Uintvar-integer" in this pdu
+ *
+ * @return false when error(not a Uintvar-integer) occur
+ * return value can be retrieved by getValue32() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeUintvarInteger(int startIndex) {
+ int index = startIndex;
+
+ unsigned32bit = 0;
+ while ((wspData[index] & 0x80) != 0) {
+ if ((index - startIndex) >= 4) {
+ return false;
+ }
+ unsigned32bit = (unsigned32bit << 7) | (wspData[index] & 0x7f);
+ index++;
+ }
+ unsigned32bit = (unsigned32bit << 7) | (wspData[index] & 0x7f);
+ dataLength = index - startIndex + 1;
+ return true;
+ }
+
+ /**
+ * Decode the "Value-length" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Value-length" in this pdu
+ *
+ * @return false when error(not a Value-length) occur
+ * return value can be retrieved by getValue32() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeValueLength(int startIndex) {
+ if ((wspData[startIndex] & 0xff) > WAP_PDU_LENGTH_QUOTE) {
+ return false;
+ }
+ if (wspData[startIndex] < WAP_PDU_LENGTH_QUOTE) {
+ unsigned32bit = wspData[startIndex];
+ dataLength = 1;
+ } else {
+ decodeUintvarInteger(startIndex + 1);
+ dataLength++;
+ }
+ return true;
+ }
+
+ /**
+ * Decode the "Extension-media" type for WSP PDU.
+ *
+ * @param startIndex The starting position of the "Extension-media" in this PDU.
+ *
+ * @return false on error, such as if there is no Extension-media at startIndex.
+ * Side-effects: updates stringValue (available with
+ * getValueString()), which will be null on error. The length of the
+ * data in the PDU is available with getValue32(), 0 on error.
+ */
+ public boolean decodeExtensionMedia(int startIndex) {
+ int index = startIndex;
+ dataLength = 0;
+ stringValue = null;
+ int length = wspData.length;
+ boolean rtrn = index < length;
+
+ while (index < length && wspData[index] != 0) {
+ index++;
+ }
+
+ dataLength = index - startIndex + 1;
+ stringValue = new String(wspData, startIndex, dataLength - 1);
+
+ return rtrn;
+ }
+
+ /**
+ * Decode the "Constrained-encoding" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Constrained-encoding" in this pdu
+ *
+ * @return false when error(not a Constrained-encoding) occur
+ * return value can be retrieved first by getValueString() and second by getValue32() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeConstrainedEncoding(int startIndex) {
+ if (decodeShortInteger(startIndex) == true) {
+ stringValue = null;
+ return true;
+ }
+ return decodeExtensionMedia(startIndex);
+ }
+
+ /**
+ * Decode the "Content-type" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Content-type" in this pdu
+ *
+ * @return false when error(not a Content-type) occurs
+ * If a content type exists in the headers (either as inline string, or as well-known
+ * value), getValueString() will return it. If a 'well known value' is encountered that
+ * cannot be mapped to a string mime type, getValueString() will return null, and
+ * getValue32() will return the unknown content type value.
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ * Any content type parameters will be accessible via getContentParameters()
+ */
+ public boolean decodeContentType(int startIndex) {
+ int mediaPrefixLength;
+ contentParameters = new HashMap<String, String>();
+
+ try {
+ if (decodeValueLength(startIndex) == false) {
+ boolean found = decodeConstrainedEncoding(startIndex);
+ if (found) {
+ expandWellKnownMimeType();
+ }
+ return found;
+ }
+ int headersLength = (int) unsigned32bit;
+ mediaPrefixLength = getDecodedDataLength();
+ if (decodeIntegerValue(startIndex + mediaPrefixLength) == true) {
+ dataLength += mediaPrefixLength;
+ int readLength = dataLength;
+ stringValue = null;
+ expandWellKnownMimeType();
+ long wellKnownValue = unsigned32bit;
+ String mimeType = stringValue;
+ if (readContentParameters(startIndex + dataLength,
+ (headersLength - (dataLength - mediaPrefixLength)), 0)) {
+ dataLength += readLength;
+ unsigned32bit = wellKnownValue;
+ stringValue = mimeType;
+ return true;
+ }
+ return false;
+ }
+ if (decodeExtensionMedia(startIndex + mediaPrefixLength) == true) {
+ dataLength += mediaPrefixLength;
+ int readLength = dataLength;
+ expandWellKnownMimeType();
+ long wellKnownValue = unsigned32bit;
+ String mimeType = stringValue;
+ if (readContentParameters(startIndex + dataLength,
+ (headersLength - (dataLength - mediaPrefixLength)), 0)) {
+ dataLength += readLength;
+ unsigned32bit = wellKnownValue;
+ stringValue = mimeType;
+ return true;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ //something doesn't add up
+ return false;
+ }
+ return false;
+ }
+
+ private boolean readContentParameters(int startIndex, int leftToRead, int accumulator) {
+
+ int totalRead = 0;
+
+ if (leftToRead > 0) {
+ byte nextByte = wspData[startIndex];
+ String value = null;
+ String param = null;
+ if ((nextByte & 0x80) == 0x00 && nextByte > 31) { // untyped
+ decodeTokenText(startIndex);
+ param = stringValue;
+ totalRead += dataLength;
+ } else { // typed
+ if (decodeIntegerValue(startIndex)) {
+ totalRead += dataLength;
+ int wellKnownParameterValue = (int) unsigned32bit;
+ param = WELL_KNOWN_PARAMETERS.get(wellKnownParameterValue);
+ if (param == null) {
+ param = "unassigned/0x" + Long.toHexString(wellKnownParameterValue);
+ }
+ // special case for the "Q" parameter, value is a uintvar
+ if (wellKnownParameterValue == Q_VALUE) {
+ if (decodeUintvarInteger(startIndex + totalRead)) {
+ totalRead += dataLength;
+ value = String.valueOf(unsigned32bit);
+ contentParameters.put(param, value);
+ return readContentParameters(startIndex + totalRead, leftToRead
+ - totalRead, accumulator + totalRead);
+ } else {
+ return false;
+ }
+ }
+ } else {
+ return false;
+ }
+ }
+
+ if (decodeNoValue(startIndex + totalRead)) {
+ totalRead += dataLength;
+ value = null;
+ } else if (decodeIntegerValue(startIndex + totalRead)) {
+ totalRead += dataLength;
+ int intValue = (int) unsigned32bit;
+ if (intValue == 0) {
+ value = "";
+ } else {
+ value = String.valueOf(intValue);
+ }
+ } else {
+ decodeTokenText(startIndex + totalRead);
+ totalRead += dataLength;
+ value = stringValue;
+ if (value.startsWith("\"")) {
+ // quoted string, so remove the quote
+ value = value.substring(1);
+ }
+ }
+ contentParameters.put(param, value);
+ return readContentParameters(startIndex + totalRead, leftToRead - totalRead,
+ accumulator + totalRead);
+
+ } else {
+ dataLength = accumulator;
+ return true;
+ }
+ }
+
+ /**
+ * Check if the next byte is No-Value
+ *
+ * @param startIndex The starting position of the "Content length" in this pdu
+ *
+ * @return true if and only if the next byte is 0x00
+ */
+ private boolean decodeNoValue(int startIndex) {
+ if (wspData[startIndex] == 0) {
+ dataLength = 1;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Populate stringValue with the mime type corresponding to the value in unsigned32bit
+ *
+ * Sets unsigned32bit to -1 if stringValue is already populated
+ */
+ private void expandWellKnownMimeType() {
+ if (stringValue == null) {
+ int binaryContentType = (int) unsigned32bit;
+ stringValue = WELL_KNOWN_MIME_TYPES.get(binaryContentType);
+ } else {
+ unsigned32bit = -1;
+ }
+ }
+
+ /**
+ * Decode the "Content length" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Content length" in this pdu
+ *
+ * @return false when error(not a Content length) occur
+ * return value can be retrieved by getValue32() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeContentLength(int startIndex) {
+ return decodeIntegerValue(startIndex);
+ }
+
+ /**
+ * Decode the "Content location" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "Content location" in this pdu
+ *
+ * @return false when error(not a Content location) occur
+ * return value can be retrieved by getValueString() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeContentLocation(int startIndex) {
+ return decodeTextString(startIndex);
+ }
+
+ /**
+ * Decode the "X-Wap-Application-Id" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "X-Wap-Application-Id" in this pdu
+ *
+ * @return false when error(not a X-Wap-Application-Id) occur
+ * return value can be retrieved first by getValueString() and second by getValue32()
+ * method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeXWapApplicationId(int startIndex) {
+ if (decodeIntegerValue(startIndex) == true) {
+ stringValue = null;
+ return true;
+ }
+ return decodeTextString(startIndex);
+ }
+
+ /**
+ * Seek for the "X-Wap-Application-Id" field for WSP pdu
+ *
+ * @param startIndex The starting position of seek pointer
+ * @param endIndex Valid seek area end point
+ *
+ * @return false when error(not a X-Wap-Application-Id) occur
+ * return value can be retrieved by getValue32()
+ */
+ public boolean seekXWapApplicationId(int startIndex, int endIndex) {
+ int index = startIndex;
+
+ try {
+ for (index = startIndex; index <= endIndex; ) {
+ /**
+ * 8.4.1.1 Field name
+ * Field name is integer or text.
+ */
+ if (decodeIntegerValue(index)) {
+ int fieldValue = (int) getValue32();
+
+ if (fieldValue == PARAMETER_ID_X_WAP_APPLICATION_ID) {
+ unsigned32bit = index + 1;
+ return true;
+ }
+ } else {
+ if (!decodeTextString(index)) return false;
+ }
+ index += getDecodedDataLength();
+ if (index > endIndex) return false;
+
+ /**
+ * 8.4.1.2 Field values
+ * Value Interpretation of First Octet
+ * 0 - 30 This octet is followed by the indicated number (0 - 30)
+ of data octets
+ * 31 This octet is followed by a uintvar, which indicates the number
+ * of data octets after it
+ * 32 - 127 The value is a text string, terminated by a zero octet
+ (NUL character)
+ * 128 - 255 It is an encoded 7-bit value; this header has no more data
+ */
+ byte val = wspData[index];
+ if (0 <= val && val <= WAP_PDU_SHORT_LENGTH_MAX) {
+ index += wspData[index] + 1;
+ } else if (val == WAP_PDU_LENGTH_QUOTE) {
+ if (index + 1 >= endIndex) return false;
+ index++;
+ if (!decodeUintvarInteger(index)) return false;
+ index += getDecodedDataLength();
+ } else if (WAP_PDU_LENGTH_QUOTE < val && val <= 127) {
+ if (!decodeTextString(index)) return false;
+ index += getDecodedDataLength();
+ } else {
+ index++;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ //seek application ID failed. WSP header might be corrupted
+ return false;
+ }
+ return false;
+ }
+
+ /**
+ * Decode the "X-Wap-Content-URI" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "X-Wap-Content-URI" in this pdu
+ *
+ * @return false when error(not a X-Wap-Content-URI) occur
+ * return value can be retrieved by getValueString() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeXWapContentURI(int startIndex) {
+ return decodeTextString(startIndex);
+ }
+
+ /**
+ * Decode the "X-Wap-Initiator-URI" type for WSP pdu
+ *
+ * @param startIndex The starting position of the "X-Wap-Initiator-URI" in this pdu
+ *
+ * @return false when error(not a X-Wap-Initiator-URI) occur
+ * return value can be retrieved by getValueString() method
+ * length of data in pdu can be retrieved by getDecodedDataLength() method
+ */
+ public boolean decodeXWapInitiatorURI(int startIndex) {
+ return decodeTextString(startIndex);
+ }
+
+ /**
+ * The data length of latest operation.
+ */
+ public int getDecodedDataLength() {
+ return dataLength;
+ }
+
+ /**
+ * The 32-bits result of latest operation.
+ */
+ public long getValue32() {
+ return unsigned32bit;
+ }
+
+ /**
+ * The String result of latest operation.
+ */
+ public String getValueString() {
+ return stringValue;
+ }
+
+ /**
+ * Any parameters encountered as part of a decodeContentType() invocation.
+ *
+ * @return a map of content parameters keyed by their names, or null if
+ * decodeContentType() has not been called If any unassigned
+ * well-known parameters are encountered, the key of the map will be
+ * 'unassigned/0x...', where '...' is the hex value of the
+ * unassigned parameter. If a parameter has No-Value the value will be null.
+ *
+ */
+ public HashMap<String, String> getContentParameters() {
+ return contentParameters;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/AppInterface.java b/src/java/com/android/internal/telephony/cat/AppInterface.java
new file mode 100644
index 0000000..299e140
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/AppInterface.java
@@ -0,0 +1,96 @@
+/*
+ * 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.cat;
+
+/**
+ * Interface for communication between STK App and CAT Telephony
+ *
+ * {@hide}
+ */
+public interface AppInterface {
+
+ /*
+ * Intent's actions which are broadcasted by the Telephony once a new CAT
+ * proactive command, session end arrive.
+ */
+ public static final String CAT_CMD_ACTION =
+ "android.intent.action.stk.command";
+ public static final String CAT_SESSION_END_ACTION =
+ "android.intent.action.stk.session_end";
+
+ /*
+ * Callback function from app to telephony to pass a result code and user's
+ * input back to the ICC.
+ */
+ void onCmdResponse(CatResponseMessage resMsg);
+
+ /*
+ * Enumeration for representing "Type of Command" of proactive commands.
+ * Those are the only commands which are supported by the Telephony. Any app
+ * implementation should support those.
+ * Refer to ETSI TS 102.223 section 9.4
+ */
+ public static 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),
+ PROVIDE_LOCAL_INFORMATION(0x26),
+ OPEN_CHANNEL(0x40),
+ CLOSE_CHANNEL(0x41),
+ RECEIVE_DATA(0x42),
+ SEND_DATA(0x43);
+
+ private int mValue;
+
+ CommandType(int value) {
+ mValue = value;
+ }
+
+ public int value() {
+ return mValue;
+ }
+
+ /**
+ * 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;
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/BerTlv.java b/src/java/com/android/internal/telephony/cat/BerTlv.java
new file mode 100644
index 0000000..095e65b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/BerTlv.java
@@ -0,0 +1,123 @@
+/*
+ * 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.cat;
+
+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) {
+ /* 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 < 0x80 length=" + Integer.toHexString(length) +
+ " curIndex=" + curIndex + " endIndex=" + endIndex);
+
+ }
+ length = temp;
+ } else {
+ throw new ResultException(
+ ResultCode.CMD_DATA_NOT_UNDERSTOOD,
+ "Expected first byte to be length or a length tag and < 0x81" +
+ " byte= " + Integer.toHexString(temp) + " curIndex=" + curIndex +
+ " endIndex=" + endIndex);
+ }
+ } else {
+ if (ComprehensionTlvTag.COMMAND_DETAILS.value() == (tag & ~0x80)) {
+ tag = BER_UNKNOWN_TAG;
+ curIndex = 0;
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING,
+ "IndexOutOfBoundsException " +
+ " curIndex=" + curIndex + " endIndex=" + endIndex);
+ } catch (ResultException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD, e.explanation());
+ }
+
+ /* COMPREHENSION-TLVs */
+ if (endIndex - curIndex < length) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD,
+ "Command had extra data endIndex=" + endIndex + " curIndex=" + curIndex +
+ " length=" + length);
+ }
+
+ List<ComprehensionTlv> ctlvs = ComprehensionTlv.decodeMany(data,
+ curIndex);
+
+ return new BerTlv(tag, ctlvs);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/CatCmdMessage.java b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
new file mode 100644
index 0000000..48c2e2b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
@@ -0,0 +1,182 @@
+/*
+ * 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.cat;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class used to pass CAT messages from telephony to application. Application
+ * should call getXXX() to get commands's specific values.
+ *
+ */
+public class CatCmdMessage implements Parcelable {
+ // members
+ CommandDetails mCmdDet;
+ private TextMessage mTextMsg;
+ private Menu mMenu;
+ private Input mInput;
+ private BrowserSettings mBrowserSettings = null;
+ private ToneSettings mToneSettings = null;
+ private CallSettings mCallSettings = null;
+
+ /*
+ * Container for Launch Browser command settings.
+ */
+ public class BrowserSettings {
+ public String url;
+ public LaunchBrowserMode mode;
+ }
+
+ /*
+ * Container for Call Setup command settings.
+ */
+ public class CallSettings {
+ public TextMessage confirmMsg;
+ public TextMessage callMsg;
+ }
+
+ CatCmdMessage(CommandParams cmdParams) {
+ mCmdDet = cmdParams.cmdDet;
+ switch(getCmdType()) {
+ case SET_UP_MENU:
+ case SELECT_ITEM:
+ mMenu = ((SelectItemParams) cmdParams).menu;
+ break;
+ case DISPLAY_TEXT:
+ case SET_UP_IDLE_MODE_TEXT:
+ case SEND_DTMF:
+ case SEND_SMS:
+ case SEND_SS:
+ case SEND_USSD:
+ mTextMsg = ((DisplayTextParams) cmdParams).textMsg;
+ break;
+ case GET_INPUT:
+ case GET_INKEY:
+ mInput = ((GetInputParams) cmdParams).input;
+ break;
+ case LAUNCH_BROWSER:
+ mTextMsg = ((LaunchBrowserParams) cmdParams).confirmMsg;
+ mBrowserSettings = new BrowserSettings();
+ mBrowserSettings.url = ((LaunchBrowserParams) cmdParams).url;
+ mBrowserSettings.mode = ((LaunchBrowserParams) cmdParams).mode;
+ break;
+ case PLAY_TONE:
+ PlayToneParams params = (PlayToneParams) cmdParams;
+ mToneSettings = params.settings;
+ mTextMsg = params.textMsg;
+ break;
+ case SET_UP_CALL:
+ mCallSettings = new CallSettings();
+ mCallSettings.confirmMsg = ((CallSetupParams) cmdParams).confirmMsg;
+ mCallSettings.callMsg = ((CallSetupParams) cmdParams).callMsg;
+ break;
+ case OPEN_CHANNEL:
+ case CLOSE_CHANNEL:
+ case RECEIVE_DATA:
+ case SEND_DATA:
+ BIPClientParams param = (BIPClientParams) cmdParams;
+ mTextMsg = param.textMsg;
+ break;
+ }
+ }
+
+ public CatCmdMessage(Parcel in) {
+ mCmdDet = in.readParcelable(null);
+ mTextMsg = in.readParcelable(null);
+ mMenu = in.readParcelable(null);
+ mInput = in.readParcelable(null);
+ switch (getCmdType()) {
+ case LAUNCH_BROWSER:
+ mBrowserSettings = new BrowserSettings();
+ mBrowserSettings.url = in.readString();
+ mBrowserSettings.mode = LaunchBrowserMode.values()[in.readInt()];
+ break;
+ case PLAY_TONE:
+ mToneSettings = in.readParcelable(null);
+ break;
+ case SET_UP_CALL:
+ mCallSettings = new CallSettings();
+ mCallSettings.confirmMsg = in.readParcelable(null);
+ mCallSettings.callMsg = in.readParcelable(null);
+ break;
+ }
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mCmdDet, 0);
+ dest.writeParcelable(mTextMsg, 0);
+ dest.writeParcelable(mMenu, 0);
+ dest.writeParcelable(mInput, 0);
+ switch(getCmdType()) {
+ case LAUNCH_BROWSER:
+ dest.writeString(mBrowserSettings.url);
+ dest.writeInt(mBrowserSettings.mode.ordinal());
+ break;
+ case PLAY_TONE:
+ dest.writeParcelable(mToneSettings, 0);
+ break;
+ case SET_UP_CALL:
+ dest.writeParcelable(mCallSettings.confirmMsg, 0);
+ dest.writeParcelable(mCallSettings.callMsg, 0);
+ break;
+ }
+ }
+
+ public static final Parcelable.Creator<CatCmdMessage> CREATOR = new Parcelable.Creator<CatCmdMessage>() {
+ public CatCmdMessage createFromParcel(Parcel in) {
+ return new CatCmdMessage(in);
+ }
+
+ public CatCmdMessage[] newArray(int size) {
+ return new CatCmdMessage[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /* external API to be used by application */
+ public AppInterface.CommandType getCmdType() {
+ return AppInterface.CommandType.fromInt(mCmdDet.typeOfCommand);
+ }
+
+ public Menu getMenu() {
+ return mMenu;
+ }
+
+ public Input geInput() {
+ return mInput;
+ }
+
+ public TextMessage geTextMessage() {
+ return mTextMsg;
+ }
+
+ public BrowserSettings getBrowserSettings() {
+ return mBrowserSettings;
+ }
+
+ public ToneSettings getToneSettings() {
+ return mToneSettings;
+ }
+
+ public CallSettings getCallSettings() {
+ return mCallSettings;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/CatException.java b/src/java/com/android/internal/telephony/cat/CatException.java
new file mode 100644
index 0000000..1bf1369
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/CatException.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.cat;
+
+import android.util.AndroidException;
+
+
+/**
+ * Base class for all the exceptions in CAT service.
+ *
+ * {@hide}
+ */
+class CatException extends AndroidException {
+ public CatException() {
+ super();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/CatLog.java b/src/java/com/android/internal/telephony/cat/CatLog.java
new file mode 100644
index 0000000..e19ff43
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/CatLog.java
@@ -0,0 +1,41 @@
+/*
+ * 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.cat;
+
+import android.util.Log;
+
+public abstract class CatLog {
+ static final boolean DEBUG = true;
+
+ public static void d(Object caller, String msg) {
+ if (!DEBUG) {
+ return;
+ }
+
+ String className = caller.getClass().getName();
+ Log.d("CAT", className.substring(className.lastIndexOf('.') + 1) + ": "
+ + msg);
+ }
+
+ public static void d(String caller, String msg) {
+ if (!DEBUG) {
+ return;
+ }
+
+ Log.d("CAT", caller + ": " + msg);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/CatResponseMessage.java b/src/java/com/android/internal/telephony/cat/CatResponseMessage.java
new file mode 100644
index 0000000..cfcac36
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/CatResponseMessage.java
@@ -0,0 +1,54 @@
+/*
+ * 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.cat;
+
+public class CatResponseMessage {
+ CommandDetails cmdDet = null;
+ ResultCode resCode = ResultCode.OK;
+ int usersMenuSelection = 0;
+ String usersInput = null;
+ boolean usersYesNoSelection = false;
+ boolean usersConfirm = false;
+
+ public CatResponseMessage(CatCmdMessage cmdMsg) {
+ this.cmdDet = cmdMsg.mCmdDet;
+ }
+
+ public void setResultCode(ResultCode resCode) {
+ this.resCode = resCode;
+ }
+
+ public void setMenuSelection(int selection) {
+ this.usersMenuSelection = selection;
+ }
+
+ public void setInput(String input) {
+ this.usersInput = input;
+ }
+
+ public void setYesNo(boolean yesNo) {
+ usersYesNoSelection = yesNo;
+ }
+
+ public void setConfirmation(boolean confirm) {
+ usersConfirm = confirm;
+ }
+
+ CommandDetails getCmdDetails() {
+ return cmdDet;
+ }
+ } \ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
new file mode 100644
index 0000000..2b37072
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -0,0 +1,750 @@
+/*
+ * 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.cat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.SystemProperties;
+
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccRecords;
+
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import java.util.Locale;
+
+class RilMessage {
+ int mId;
+ Object mData;
+ ResultCode mResCode;
+
+ RilMessage(int msgId, String rawData) {
+ mId = msgId;
+ mData = rawData;
+ }
+
+ RilMessage(RilMessage other) {
+ this.mId = other.mId;
+ this.mData = other.mData;
+ this.mResCode = other.mResCode;
+ }
+}
+
+/**
+ * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL
+ * and application.
+ *
+ * {@hide}
+ */
+public class CatService extends Handler implements AppInterface {
+
+ // Class members
+ private static IccRecords mIccRecords;
+
+ // Service members.
+ // Protects singleton instance lazy initialization.
+ private static final Object sInstanceLock = new Object();
+ private static CatService sInstance;
+ private CommandsInterface mCmdIf;
+ private Context mContext;
+ private CatCmdMessage mCurrntCmd = null;
+ private CatCmdMessage mMenuCmd = null;
+
+ private RilMessageDecoder mMsgDecoder = null;
+ private boolean mStkAppInstalled = false;
+
+ // Service constants.
+ static final int MSG_ID_SESSION_END = 1;
+ static final int MSG_ID_PROACTIVE_COMMAND = 2;
+ static final int MSG_ID_EVENT_NOTIFY = 3;
+ static final int MSG_ID_CALL_SETUP = 4;
+ static final int MSG_ID_REFRESH = 5;
+ static final int MSG_ID_RESPONSE = 6;
+ static final int MSG_ID_SIM_READY = 7;
+
+ static final int MSG_ID_RIL_MSG_DECODED = 10;
+
+ // Events to signal SIM presence or absent in the device.
+ private static final int MSG_ID_ICC_RECORDS_LOADED = 20;
+
+ 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;
+
+ static final String STK_DEFAULT = "Defualt Message";
+
+ /* Intentionally private for singleton */
+ private CatService(CommandsInterface ci, IccRecords ir, Context context,
+ IccFileHandler fh, IccCard ic) {
+ if (ci == null || ir == null || context == null || fh == null
+ || ic == null) {
+ throw new NullPointerException(
+ "Service: Input parameters must not be null");
+ }
+ mCmdIf = ci;
+ mContext = context;
+
+ // Get the RilMessagesDecoder for decoding the messages.
+ mMsgDecoder = RilMessageDecoder.getInstance(this, fh);
+
+ // Register ril events handling.
+ mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
+ mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
+ mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);
+ mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);
+ //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null);
+
+ mIccRecords = ir;
+
+ // Register for SIM ready event.
+ ic.registerForReady(this, MSG_ID_SIM_READY, null);
+ mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
+
+ // Check if STK application is availalbe
+ mStkAppInstalled = isStkAppInstalled();
+
+ CatLog.d(this, "Running CAT service. STK app installed:" + mStkAppInstalled);
+ }
+
+ public void dispose() {
+ mIccRecords.unregisterForRecordsLoaded(this);
+ mCmdIf.unSetOnCatSessionEnd(this);
+ mCmdIf.unSetOnCatProactiveCmd(this);
+ mCmdIf.unSetOnCatEvent(this);
+ mCmdIf.unSetOnCatCallSetUp(this);
+
+ this.removeCallbacksAndMessages(null);
+ }
+
+ protected void finalize() {
+ CatLog.d(this, "Service finalized");
+ }
+
+ private void handleRilMsg(RilMessage rilMsg) {
+ if (rilMsg == null) {
+ return;
+ }
+
+ // dispatch messages
+ CommandParams cmdParams = null;
+ switch (rilMsg.mId) {
+ case MSG_ID_EVENT_NOTIFY:
+ if (rilMsg.mResCode == ResultCode.OK) {
+ cmdParams = (CommandParams) rilMsg.mData;
+ if (cmdParams != null) {
+ handleCommand(cmdParams, false);
+ }
+ }
+ break;
+ case MSG_ID_PROACTIVE_COMMAND:
+ try {
+ cmdParams = (CommandParams) rilMsg.mData;
+ } catch (ClassCastException e) {
+ // for error handling : cast exception
+ CatLog.d(this, "Fail to parse proactive command");
+ sendTerminalResponse(mCurrntCmd.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD,
+ false, 0x00, null);
+ break;
+ }
+ if (cmdParams != null) {
+ if (rilMsg.mResCode == ResultCode.OK) {
+ handleCommand(cmdParams, true);
+ } else {
+ // for proactive commands that couldn't be decoded
+ // successfully respond with the code generated by the
+ // message decoder.
+ sendTerminalResponse(cmdParams.cmdDet, rilMsg.mResCode,
+ false, 0, null);
+ }
+ }
+ break;
+ case MSG_ID_REFRESH:
+ cmdParams = (CommandParams) rilMsg.mData;
+ if (cmdParams != null) {
+ handleCommand(cmdParams, false);
+ }
+ break;
+ case MSG_ID_SESSION_END:
+ handleSessionEnd();
+ break;
+ case MSG_ID_CALL_SETUP:
+ // prior event notify command supplied all the information
+ // needed for set up call processing.
+ break;
+ }
+ }
+
+ /**
+ * Handles RIL_UNSOL_STK_EVENT_NOTIFY or RIL_UNSOL_STK_PROACTIVE_COMMAND command
+ * from RIL.
+ * Sends valid proactive command data to the application using intents.
+ * RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE will be send back if the command is
+ * from RIL_UNSOL_STK_PROACTIVE_COMMAND.
+ */
+ private void handleCommand(CommandParams cmdParams, boolean isProactiveCmd) {
+ CatLog.d(this, cmdParams.getCommandType().name());
+
+ CharSequence message;
+ CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams);
+ switch (cmdParams.getCommandType()) {
+ case SET_UP_MENU:
+ if (removeMenu(cmdMsg.getMenu())) {
+ mMenuCmd = null;
+ } else {
+ mMenuCmd = cmdMsg;
+ }
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
+ break;
+ case DISPLAY_TEXT:
+ // when application is not required to respond, send an immediate response.
+ if (!cmdMsg.geTextMessage().responseNeeded) {
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
+ }
+ break;
+ case REFRESH:
+ // ME side only handles refresh commands which meant to remove IDLE
+ // MODE TEXT.
+ cmdParams.cmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT.value();
+ break;
+ case SET_UP_IDLE_MODE_TEXT:
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
+ break;
+ case PROVIDE_LOCAL_INFORMATION:
+ ResponseData resp;
+ switch (cmdParams.cmdDet.commandQualifier) {
+ case CommandParamsFactory.DTTZ_SETTING:
+ resp = new DTTZResponseData(null);
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, resp);
+ break;
+ case CommandParamsFactory.LANGUAGE_SETTING:
+ resp = new LanguageResponseData(Locale.getDefault().getLanguage());
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, resp);
+ break;
+ default:
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
+ }
+ // No need to start STK app here.
+ return;
+ case LAUNCH_BROWSER:
+ if ((((LaunchBrowserParams) cmdParams).confirmMsg.text != null)
+ && (((LaunchBrowserParams) cmdParams).confirmMsg.text.equals(STK_DEFAULT))) {
+ message = mContext.getText(com.android.internal.R.string.launchBrowserDefault);
+ ((LaunchBrowserParams) cmdParams).confirmMsg.text = message.toString();
+ }
+ break;
+ case SELECT_ITEM:
+ case GET_INPUT:
+ case GET_INKEY:
+ break;
+ case SEND_DTMF:
+ case SEND_SMS:
+ case SEND_SS:
+ case SEND_USSD:
+ if ((((DisplayTextParams)cmdParams).textMsg.text != null)
+ && (((DisplayTextParams)cmdParams).textMsg.text.equals(STK_DEFAULT))) {
+ message = mContext.getText(com.android.internal.R.string.sending);
+ ((DisplayTextParams)cmdParams).textMsg.text = message.toString();
+ }
+ break;
+ case PLAY_TONE:
+ break;
+ case SET_UP_CALL:
+ if ((((CallSetupParams) cmdParams).confirmMsg.text != null)
+ && (((CallSetupParams) cmdParams).confirmMsg.text.equals(STK_DEFAULT))) {
+ message = mContext.getText(com.android.internal.R.string.SetupCallDefault);
+ ((CallSetupParams) cmdParams).confirmMsg.text = message.toString();
+ }
+ break;
+ case OPEN_CHANNEL:
+ case CLOSE_CHANNEL:
+ case RECEIVE_DATA:
+ case SEND_DATA:
+ BIPClientParams cmd = (BIPClientParams) cmdParams;
+ if (cmd.bHasAlphaId && (cmd.textMsg.text == null)) {
+ CatLog.d(this, "cmd " + cmdParams.getCommandType() + " with null alpha id");
+ // If alpha length is zero, we just respond with OK.
+ if (isProactiveCmd) {
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
+ }
+ return;
+ }
+ // Respond with permanent failure to avoid retry if STK app is not present.
+ if (!mStkAppInstalled) {
+ CatLog.d(this, "No STK application found.");
+ if (isProactiveCmd) {
+ sendTerminalResponse(cmdParams.cmdDet,
+ ResultCode.BEYOND_TERMINAL_CAPABILITY,
+ false, 0, null);
+ return;
+ }
+ }
+ /*
+ * CLOSE_CHANNEL, RECEIVE_DATA and SEND_DATA can be delivered by
+ * either PROACTIVE_COMMAND or EVENT_NOTIFY.
+ * If PROACTIVE_COMMAND is used for those commands, send terminal
+ * response here.
+ */
+ if (isProactiveCmd &&
+ ((cmdParams.getCommandType() == CommandType.CLOSE_CHANNEL) ||
+ (cmdParams.getCommandType() == CommandType.RECEIVE_DATA) ||
+ (cmdParams.getCommandType() == CommandType.SEND_DATA))) {
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
+ }
+ break;
+ default:
+ CatLog.d(this, "Unsupported command");
+ return;
+ }
+ mCurrntCmd = cmdMsg;
+ Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
+ intent.putExtra("STK CMD", cmdMsg);
+ mContext.sendBroadcast(intent);
+ }
+
+ /**
+ * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL.
+ *
+ */
+ private void handleSessionEnd() {
+ CatLog.d(this, "SESSION END");
+
+ mCurrntCmd = mMenuCmd;
+ Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION);
+ mContext.sendBroadcast(intent);
+ }
+
+ private void sendTerminalResponse(CommandDetails cmdDet,
+ ResultCode resultCode, boolean includeAdditionalInfo,
+ int additionalInfo, ResponseData resp) {
+
+ if (cmdDet == null) {
+ return;
+ }
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ Input cmdInput = null;
+ if (mCurrntCmd != null) {
+ cmdInput = mCurrntCmd.geInput();
+ }
+
+ // 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
+ // According to TS102.223/TS31.111 section 6.8 Structure of
+ // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
+ // the ME should set the CR(comprehension required) flag to
+ // comprehension not required.(CR=0)"
+ // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
+ // the CR flag is not set.
+ tag = 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.value());
+
+ // additional info
+ if (includeAdditionalInfo) {
+ buf.write(additionalInfo);
+ }
+
+ // Fill optional data for each corresponding command
+ if (resp != null) {
+ resp.format(buf);
+ } else {
+ encodeOptionalTags(cmdDet, resultCode, cmdInput, buf);
+ }
+
+ byte[] rawData = buf.toByteArray();
+ String hexString = IccUtils.bytesToHexString(rawData);
+ if (false) {
+ CatLog.d(this, "TERMINAL RESPONSE: " + hexString);
+ }
+
+ mCmdIf.sendTerminalResponse(hexString, null);
+ }
+
+ private void encodeOptionalTags(CommandDetails cmdDet,
+ ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf) {
+ CommandType cmdType = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
+ if (cmdType != null) {
+ switch (cmdType) {
+ case GET_INKEY:
+ // ETSI TS 102 384,27.22.4.2.8.4.2.
+ // If it is a response for GET_INKEY command and the response timeout
+ // occured, then add DURATION TLV for variable timeout case.
+ if ((resultCode.value() == ResultCode.NO_RESPONSE_FROM_USER.value()) &&
+ (cmdInput != null) && (cmdInput.duration != null)) {
+ getInKeyResponse(buf, cmdInput);
+ }
+ break;
+ case PROVIDE_LOCAL_INFORMATION:
+ if ((cmdDet.commandQualifier == CommandParamsFactory.LANGUAGE_SETTING) &&
+ (resultCode.value() == ResultCode.OK.value())) {
+ getPliResponse(buf);
+ }
+ break;
+ default:
+ CatLog.d(this, "encodeOptionalTags() Unsupported Cmd details=" + cmdDet);
+ break;
+ }
+ } else {
+ CatLog.d(this, "encodeOptionalTags() bad Cmd details=" + cmdDet);
+ }
+ }
+
+ private void getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput) {
+ int tag = ComprehensionTlvTag.DURATION.value();
+
+ buf.write(tag);
+ buf.write(0x02); // length
+ buf.write(cmdInput.duration.timeUnit.SECOND.value()); // Time (Unit,Seconds)
+ buf.write(cmdInput.duration.timeInterval); // Time Duration
+ }
+
+ private void getPliResponse(ByteArrayOutputStream buf) {
+
+ // Locale Language Setting
+ String lang = SystemProperties.get("persist.sys.language");
+
+ if (lang != null) {
+ // tag
+ int tag = ComprehensionTlvTag.LANGUAGE.value();
+ buf.write(tag);
+ ResponseData.writeLength(buf, lang.length());
+ buf.write(lang.getBytes(), 0, lang.length());
+ }
+ }
+
+ private void sendMenuSelection(int menuId, boolean helpRequired) {
+
+ 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 = IccUtils.bytesToHexString(rawData);
+
+ mCmdIf.sendEnvelope(hexString, 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 = IccUtils.bytesToHexString(rawData);
+
+ mCmdIf.sendEnvelope(hexString, null);
+ }
+
+ /**
+ * Used for instantiating/updating the Service from the GsmPhone or CdmaPhone constructor.
+ *
+ * @param ci CommandsInterface object
+ * @param ir IccRecords object
+ * @param context phone app context
+ * @param fh Icc file handler
+ * @param ic Icc card
+ * @return The only Service object in the system
+ */
+ public static CatService getInstance(CommandsInterface ci, IccRecords ir,
+ Context context, IccFileHandler fh, IccCard ic) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ if (ci == null || ir == null || context == null || fh == null
+ || ic == null) {
+ return null;
+ }
+ HandlerThread thread = new HandlerThread("Cat Telephony service");
+ thread.start();
+ sInstance = new CatService(ci, ir, context, fh, ic);
+ CatLog.d(sInstance, "NEW sInstance");
+ } else if ((ir != null) && (mIccRecords != ir)) {
+ CatLog.d(sInstance, "Reinitialize the Service with SIMRecords");
+ mIccRecords = ir;
+
+ // re-Register for SIM ready event.
+ mIccRecords.registerForRecordsLoaded(sInstance, MSG_ID_ICC_RECORDS_LOADED, null);
+ CatLog.d(sInstance, "sr changed reinitialize and return current sInstance");
+ } else {
+ CatLog.d(sInstance, "Return current sInstance");
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Used by application to get an AppInterface object.
+ *
+ * @return The only Service object in the system
+ */
+ public static AppInterface getInstance() {
+ return getInstance(null, null, null, null, null);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+
+ switch (msg.what) {
+ case MSG_ID_SESSION_END:
+ case MSG_ID_PROACTIVE_COMMAND:
+ case MSG_ID_EVENT_NOTIFY:
+ case MSG_ID_REFRESH:
+ CatLog.d(this, "ril message arrived");
+ String data = null;
+ if (msg.obj != null) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+ if (ar != null && ar.result != null) {
+ try {
+ data = (String) ar.result;
+ } catch (ClassCastException e) {
+ break;
+ }
+ }
+ }
+ mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
+ break;
+ case MSG_ID_CALL_SETUP:
+ mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null));
+ break;
+ case MSG_ID_ICC_RECORDS_LOADED:
+ break;
+ case MSG_ID_RIL_MSG_DECODED:
+ handleRilMsg((RilMessage) msg.obj);
+ break;
+ case MSG_ID_RESPONSE:
+ handleCmdResponse((CatResponseMessage) msg.obj);
+ break;
+ case MSG_ID_SIM_READY:
+ CatLog.d(this, "SIM ready. Reporting STK service running now...");
+ mCmdIf.reportStkServiceIsRunning(null);
+ break;
+ default:
+ throw new AssertionError("Unrecognized CAT command: " + msg.what);
+ }
+ }
+
+ public synchronized void onCmdResponse(CatResponseMessage resMsg) {
+ if (resMsg == null) {
+ return;
+ }
+ // queue a response message.
+ Message msg = this.obtainMessage(MSG_ID_RESPONSE, resMsg);
+ msg.sendToTarget();
+ }
+
+ private boolean validateResponse(CatResponseMessage resMsg) {
+ if (mCurrntCmd != null) {
+ return (resMsg.cmdDet.compareTo(mCurrntCmd.mCmdDet));
+ }
+ return false;
+ }
+
+ private boolean removeMenu(Menu menu) {
+ try {
+ if (menu.items.size() == 1 && menu.items.get(0) == null) {
+ return true;
+ }
+ } catch (NullPointerException e) {
+ CatLog.d(this, "Unable to get Menu's items size");
+ return true;
+ }
+ return false;
+ }
+
+ private void handleCmdResponse(CatResponseMessage resMsg) {
+ // Make sure the response details match the last valid command. An invalid
+ // response is a one that doesn't have a corresponding proactive command
+ // and sending it can "confuse" the baseband/ril.
+ // One reason for out of order responses can be UI glitches. For example,
+ // if the application launch an activity, and that activity is stored
+ // by the framework inside the history stack. That activity will be
+ // available for relaunch using the latest application dialog
+ // (long press on the home button). Relaunching that activity can send
+ // the same command's result again to the CatService and can cause it to
+ // get out of sync with the SIM.
+ if (!validateResponse(resMsg)) {
+ return;
+ }
+ ResponseData resp = null;
+ boolean helpRequired = false;
+ CommandDetails cmdDet = resMsg.getCmdDetails();
+
+ switch (resMsg.resCode) {
+ case HELP_INFO_REQUIRED:
+ helpRequired = true;
+ // fall through
+ case OK:
+ case PRFRMD_WITH_PARTIAL_COMPREHENSION:
+ case PRFRMD_WITH_MISSING_INFO:
+ case PRFRMD_WITH_ADDITIONAL_EFS_READ:
+ case PRFRMD_ICON_NOT_DISPLAYED:
+ case PRFRMD_MODIFIED_BY_NAA:
+ case PRFRMD_LIMITED_SERVICE:
+ case PRFRMD_WITH_MODIFICATION:
+ case PRFRMD_NAA_NOT_ACTIVE:
+ case PRFRMD_TONE_NOT_PLAYED:
+ switch (AppInterface.CommandType.fromInt(cmdDet.typeOfCommand)) {
+ case SET_UP_MENU:
+ helpRequired = resMsg.resCode == ResultCode.HELP_INFO_REQUIRED;
+ sendMenuSelection(resMsg.usersMenuSelection, helpRequired);
+ return;
+ case SELECT_ITEM:
+ resp = new SelectItemResponseData(resMsg.usersMenuSelection);
+ break;
+ case GET_INPUT:
+ case GET_INKEY:
+ Input input = mCurrntCmd.geInput();
+ if (!input.yesNo) {
+ // when help is requested there is no need to send the text
+ // string object.
+ if (!helpRequired) {
+ resp = new GetInkeyInputResponseData(resMsg.usersInput,
+ input.ucs2, input.packed);
+ }
+ } else {
+ resp = new GetInkeyInputResponseData(
+ resMsg.usersYesNoSelection);
+ }
+ break;
+ case DISPLAY_TEXT:
+ case LAUNCH_BROWSER:
+ break;
+ case SET_UP_CALL:
+ mCmdIf.handleCallSetupRequestFromSim(resMsg.usersConfirm, null);
+ // No need to send terminal response for SET UP CALL. The user's
+ // confirmation result is send back using a dedicated ril message
+ // invoked by the CommandInterface call above.
+ mCurrntCmd = null;
+ return;
+ }
+ break;
+ case NO_RESPONSE_FROM_USER:
+ case UICC_SESSION_TERM_BY_USER:
+ case BACKWARD_MOVE_BY_USER:
+ case USER_NOT_ACCEPT:
+ resp = null;
+ break;
+ default:
+ return;
+ }
+ sendTerminalResponse(cmdDet, resMsg.resCode, false, 0, resp);
+ mCurrntCmd = null;
+ }
+
+ private boolean isStkAppInstalled() {
+ Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> broadcastReceivers =
+ pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA);
+ int numReceiver = broadcastReceivers == null ? 0 : broadcastReceivers.size();
+
+ return (numReceiver > 0);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/CommandDetails.java b/src/java/com/android/internal/telephony/cat/CommandDetails.java
new file mode 100644
index 0000000..3e7f722
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/CommandDetails.java
@@ -0,0 +1,115 @@
+/*
+ * 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.cat;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+abstract class ValueObject {
+ abstract ComprehensionTlvTag getTag();
+}
+
+/**
+ * Class for Command Detailes object of proactive commands from SIM.
+ * {@hide}
+ */
+class CommandDetails extends ValueObject implements Parcelable {
+ public boolean compRequired;
+ public int commandNumber;
+ public int typeOfCommand;
+ public int commandQualifier;
+
+ public ComprehensionTlvTag getTag() {
+ return ComprehensionTlvTag.COMMAND_DETAILS;
+ }
+
+ CommandDetails() {
+ }
+
+ public boolean compareTo(CommandDetails other) {
+ return (this.compRequired == other.compRequired &&
+ this.commandNumber == other.commandNumber &&
+ this.commandQualifier == other.commandQualifier &&
+ this.typeOfCommand == other.typeOfCommand);
+ }
+
+ public CommandDetails(Parcel in) {
+ compRequired = in.readInt() != 0;
+ commandNumber = in.readInt();
+ typeOfCommand = in.readInt();
+ commandQualifier = in.readInt();
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(compRequired ? 1 : 0);
+ dest.writeInt(commandNumber);
+ dest.writeInt(typeOfCommand);
+ dest.writeInt(commandQualifier);
+ }
+
+ public static final Parcelable.Creator<CommandDetails> CREATOR =
+ new Parcelable.Creator<CommandDetails>() {
+ public CommandDetails createFromParcel(Parcel in) {
+ return new CommandDetails(in);
+ }
+
+ public CommandDetails[] newArray(int size) {
+ return new CommandDetails[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "CmdDetails: compRequired=" + compRequired +
+ " commandNumber=" + commandNumber +
+ " typeOfCommand=" + typeOfCommand +
+ " commandQualifier=" + commandQualifier;
+ }
+}
+
+class DeviceIdentities extends ValueObject {
+ public int sourceId;
+ public int destinationId;
+
+ ComprehensionTlvTag getTag() {
+ return ComprehensionTlvTag.DEVICE_IDENTITIES;
+ }
+}
+
+// Container class to hold icon identifier value.
+class IconId extends ValueObject {
+ int recordNumber;
+ boolean selfExplanatory;
+
+ ComprehensionTlvTag getTag() {
+ return ComprehensionTlvTag.ICON_ID;
+ }
+}
+
+// Container class to hold item icon identifier list value.
+class ItemsIconId extends ValueObject {
+ int [] recordNumbers;
+ boolean selfExplanatory;
+
+ ComprehensionTlvTag getTag() {
+ return ComprehensionTlvTag.ITEM_ICON_ID_LIST;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/CommandParams.java b/src/java/com/android/internal/telephony/cat/CommandParams.java
new file mode 100644
index 0000000..79f6ad2
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/CommandParams.java
@@ -0,0 +1,199 @@
+/*
+ * 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.cat;
+
+import android.graphics.Bitmap;
+
+/**
+ * Container class for proactive command parameters.
+ *
+ */
+class CommandParams {
+ CommandDetails cmdDet;
+
+ CommandParams(CommandDetails cmdDet) {
+ this.cmdDet = cmdDet;
+ }
+
+ AppInterface.CommandType getCommandType() {
+ return AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
+ }
+
+ boolean setIcon(Bitmap icon) { return true; }
+
+ @Override
+ public String toString() {
+ return cmdDet.toString();
+ }
+}
+
+class DisplayTextParams extends CommandParams {
+ TextMessage textMsg;
+
+ DisplayTextParams(CommandDetails cmdDet, TextMessage textMsg) {
+ super(cmdDet);
+ this.textMsg = textMsg;
+ }
+
+ boolean setIcon(Bitmap icon) {
+ if (icon != null && textMsg != null) {
+ textMsg.icon = icon;
+ return true;
+ }
+ return false;
+ }
+}
+
+class LaunchBrowserParams extends CommandParams {
+ TextMessage confirmMsg;
+ LaunchBrowserMode mode;
+ String url;
+
+ LaunchBrowserParams(CommandDetails cmdDet, TextMessage confirmMsg,
+ String url, LaunchBrowserMode mode) {
+ super(cmdDet);
+ this.confirmMsg = confirmMsg;
+ this.mode = mode;
+ this.url = url;
+ }
+
+ boolean setIcon(Bitmap icon) {
+ if (icon != null && confirmMsg != null) {
+ confirmMsg.icon = icon;
+ return true;
+ }
+ return false;
+ }
+}
+
+class PlayToneParams extends CommandParams {
+ TextMessage textMsg;
+ ToneSettings settings;
+
+ PlayToneParams(CommandDetails cmdDet, TextMessage textMsg,
+ Tone tone, Duration duration, boolean vibrate) {
+ super(cmdDet);
+ this.textMsg = textMsg;
+ this.settings = new ToneSettings(duration, tone, vibrate);
+ }
+
+ boolean setIcon(Bitmap icon) {
+ if (icon != null && textMsg != null) {
+ textMsg.icon = icon;
+ return true;
+ }
+ return false;
+ }
+}
+
+class CallSetupParams extends CommandParams {
+ TextMessage confirmMsg;
+ TextMessage callMsg;
+
+ CallSetupParams(CommandDetails cmdDet, TextMessage confirmMsg,
+ TextMessage callMsg) {
+ super(cmdDet);
+ this.confirmMsg = confirmMsg;
+ this.callMsg = callMsg;
+ }
+
+ boolean setIcon(Bitmap icon) {
+ if (icon == null) {
+ return false;
+ }
+ if (confirmMsg != null && confirmMsg.icon == null) {
+ confirmMsg.icon = icon;
+ return true;
+ } else if (callMsg != null && callMsg.icon == null) {
+ callMsg.icon = icon;
+ return true;
+ }
+ return false;
+ }
+}
+
+class SelectItemParams extends CommandParams {
+ Menu menu = null;
+ boolean loadTitleIcon = false;
+
+ SelectItemParams(CommandDetails cmdDet, Menu menu, boolean loadTitleIcon) {
+ super(cmdDet);
+ this.menu = menu;
+ this.loadTitleIcon = loadTitleIcon;
+ }
+
+ boolean setIcon(Bitmap icon) {
+ if (icon != null && menu != null) {
+ if (loadTitleIcon && menu.titleIcon == null) {
+ menu.titleIcon = icon;
+ } else {
+ for (Item item : menu.items) {
+ if (item.icon != null) {
+ continue;
+ }
+ item.icon = icon;
+ break;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+}
+
+class GetInputParams extends CommandParams {
+ Input input = null;
+
+ GetInputParams(CommandDetails cmdDet, Input input) {
+ super(cmdDet);
+ this.input = input;
+ }
+
+ boolean setIcon(Bitmap icon) {
+ if (icon != null && input != null) {
+ input.icon = icon;
+ }
+ return true;
+ }
+}
+
+/*
+ * BIP (Bearer Independent Protocol) is the mechanism for SIM card applications
+ * to access data connection through the mobile device.
+ *
+ * SIM utilizes proactive commands (OPEN CHANNEL, CLOSE CHANNEL, SEND DATA and
+ * RECEIVE DATA to control/read/write data for BIP. Refer to ETSI TS 102 223 for
+ * the details of proactive commands procedures and their structures.
+ */
+class BIPClientParams extends CommandParams {
+ TextMessage textMsg;
+ boolean bHasAlphaId;
+
+ BIPClientParams(CommandDetails cmdDet, TextMessage textMsg, boolean has_alpha_id) {
+ super(cmdDet);
+ this.textMsg = textMsg;
+ this.bHasAlphaId = has_alpha_id;
+ }
+
+ boolean setIcon(Bitmap icon) {
+ if (icon != null && textMsg != null) {
+ textMsg.icon = icon;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
new file mode 100644
index 0000000..a554012
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -0,0 +1,943 @@
+/*
+ * 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.cat;
+
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccFileHandler;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Factory class, used for decoding raw byte arrays, received from baseband,
+ * into a CommandParams object.
+ *
+ */
+class CommandParamsFactory extends Handler {
+ private static CommandParamsFactory sInstance = null;
+ private IconLoader mIconLoader;
+ private CommandParams mCmdParams = null;
+ private int mIconLoadState = LOAD_NO_ICON;
+ private RilMessageDecoder mCaller = null;
+
+ // constants
+ static final int MSG_ID_LOAD_ICON_DONE = 1;
+
+ // loading icons state parameters.
+ static final int LOAD_NO_ICON = 0;
+ static final int LOAD_SINGLE_ICON = 1;
+ static final int LOAD_MULTI_ICONS = 2;
+
+ // Command Qualifier values for refresh command
+ 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;
+
+ // Command Qualifier values for PLI command
+ static final int DTTZ_SETTING = 0x03;
+ static final int LANGUAGE_SETTING = 0x04;
+
+ static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller,
+ IccFileHandler fh) {
+ if (sInstance != null) {
+ return sInstance;
+ }
+ if (fh != null) {
+ return new CommandParamsFactory(caller, fh);
+ }
+ return null;
+ }
+
+ private CommandParamsFactory(RilMessageDecoder caller, IccFileHandler fh) {
+ mCaller = caller;
+ mIconLoader = IconLoader.getInstance(this, fh);
+ }
+
+ private CommandDetails processCommandDetails(List<ComprehensionTlv> ctlvs) {
+ CommandDetails cmdDet = null;
+
+ if (ctlvs != null) {
+ // Search for the Command Details object.
+ ComprehensionTlv ctlvCmdDet = searchForTag(
+ ComprehensionTlvTag.COMMAND_DETAILS, ctlvs);
+ if (ctlvCmdDet != null) {
+ try {
+ cmdDet = ValueParser.retrieveCommandDetails(ctlvCmdDet);
+ } catch (ResultException e) {
+ CatLog.d(this,
+ "processCommandDetails: Failed to procees command details e=" + e);
+ }
+ }
+ }
+ return cmdDet;
+ }
+
+ void make(BerTlv berTlv) {
+ if (berTlv == null) {
+ return;
+ }
+ // reset global state parameters.
+ mCmdParams = null;
+ mIconLoadState = LOAD_NO_ICON;
+ // only proactive command messages are processed.
+ if (berTlv.getTag() != BerTlv.BER_PROACTIVE_COMMAND_TAG) {
+ sendCmdParams(ResultCode.CMD_TYPE_NOT_UNDERSTOOD);
+ return;
+ }
+ boolean cmdPending = false;
+ List<ComprehensionTlv> ctlvs = berTlv.getComprehensionTlvs();
+ // process command dtails from the tlv list.
+ CommandDetails cmdDet = processCommandDetails(ctlvs);
+ if (cmdDet == null) {
+ sendCmdParams(ResultCode.CMD_TYPE_NOT_UNDERSTOOD);
+ return;
+ }
+
+ // extract command type enumeration from the raw value stored inside
+ // the Command Details object.
+ AppInterface.CommandType cmdType = AppInterface.CommandType
+ .fromInt(cmdDet.typeOfCommand);
+ if (cmdType == null) {
+ // This PROACTIVE COMMAND is presently not handled. Hence set
+ // result code as BEYOND_TERMINAL_CAPABILITY in TR.
+ mCmdParams = new CommandParams(cmdDet);
+ sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
+ return;
+ }
+
+ try {
+ switch (cmdType) {
+ case SET_UP_MENU:
+ cmdPending = processSelectItem(cmdDet, ctlvs);
+ break;
+ case SELECT_ITEM:
+ cmdPending = processSelectItem(cmdDet, ctlvs);
+ break;
+ case DISPLAY_TEXT:
+ cmdPending = processDisplayText(cmdDet, ctlvs);
+ break;
+ case SET_UP_IDLE_MODE_TEXT:
+ cmdPending = processSetUpIdleModeText(cmdDet, ctlvs);
+ break;
+ case GET_INKEY:
+ cmdPending = processGetInkey(cmdDet, ctlvs);
+ break;
+ case GET_INPUT:
+ cmdPending = processGetInput(cmdDet, ctlvs);
+ break;
+ case SEND_DTMF:
+ case SEND_SMS:
+ case SEND_SS:
+ case SEND_USSD:
+ cmdPending = processEventNotify(cmdDet, ctlvs);
+ break;
+ case SET_UP_CALL:
+ cmdPending = processSetupCall(cmdDet, ctlvs);
+ break;
+ case REFRESH:
+ processRefresh(cmdDet, ctlvs);
+ cmdPending = false;
+ break;
+ case LAUNCH_BROWSER:
+ cmdPending = processLaunchBrowser(cmdDet, ctlvs);
+ break;
+ case PLAY_TONE:
+ cmdPending = processPlayTone(cmdDet, ctlvs);
+ break;
+ case PROVIDE_LOCAL_INFORMATION:
+ cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
+ break;
+ case OPEN_CHANNEL:
+ case CLOSE_CHANNEL:
+ case RECEIVE_DATA:
+ case SEND_DATA:
+ cmdPending = processBIPClient(cmdDet, ctlvs);
+ break;
+ default:
+ // unsupported proactive commands
+ mCmdParams = new CommandParams(cmdDet);
+ sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
+ return;
+ }
+ } catch (ResultException e) {
+ CatLog.d(this, "make: caught ResultException e=" + e);
+ mCmdParams = new CommandParams(cmdDet);
+ sendCmdParams(e.result());
+ return;
+ }
+ if (!cmdPending) {
+ sendCmdParams(ResultCode.OK);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ID_LOAD_ICON_DONE:
+ sendCmdParams(setIcons(msg.obj));
+ break;
+ }
+ }
+
+ private ResultCode setIcons(Object data) {
+ Bitmap[] icons = null;
+ int iconIndex = 0;
+
+ if (data == null) {
+ return ResultCode.PRFRMD_ICON_NOT_DISPLAYED;
+ }
+ switch(mIconLoadState) {
+ case LOAD_SINGLE_ICON:
+ mCmdParams.setIcon((Bitmap) data);
+ break;
+ case LOAD_MULTI_ICONS:
+ icons = (Bitmap[]) data;
+ // set each item icon.
+ for (Bitmap icon : icons) {
+ mCmdParams.setIcon(icon);
+ }
+ break;
+ }
+ return ResultCode.OK;
+ }
+
+ private void sendCmdParams(ResultCode resCode) {
+ mCaller.sendMsgParamsDecoded(resCode, mCmdParams);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Processes DISPLAY_TEXT proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details container 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(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+
+ CatLog.d(this, "process DisplayText");
+
+ TextMessage textMsg = new TextMessage();
+ IconId iconId = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
+ ctlvs);
+ if (ctlv != null) {
+ textMsg.text = ValueParser.retrieveTextString(ctlv);
+ }
+ // If the tlv object doesn't exist or the it is a null object reply
+ // with command not understood.
+ if (textMsg.text == null) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.IMMEDIATE_RESPONSE, ctlvs);
+ if (ctlv != null) {
+ textMsg.responseNeeded = false;
+ }
+ // parse icon identifier
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = ValueParser.retrieveIconId(ctlv);
+ textMsg.iconSelfExplanatory = iconId.selfExplanatory;
+ }
+ // parse tone duration
+ ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
+ if (ctlv != null) {
+ textMsg.duration = ValueParser.retrieveDuration(ctlv);
+ }
+
+ // Parse command qualifier parameters.
+ textMsg.isHighPriority = (cmdDet.commandQualifier & 0x01) != 0;
+ textMsg.userClear = (cmdDet.commandQualifier & 0x80) != 0;
+
+ mCmdParams = new DisplayTextParams(cmdDet, textMsg);
+
+ if (iconId != null) {
+ mIconLoadState = LOAD_SINGLE_ICON;
+ mIconLoader.loadIcon(iconId.recordNumber, this
+ .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Processes SET_UP_IDLE_MODE_TEXT proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details container 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(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+
+ CatLog.d(this, "process SetUpIdleModeText");
+
+ TextMessage textMsg = new TextMessage();
+ IconId iconId = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
+ ctlvs);
+ if (ctlv != null) {
+ textMsg.text = ValueParser.retrieveTextString(ctlv);
+ }
+ // load icons only when text exist.
+ if (textMsg.text != null) {
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = ValueParser.retrieveIconId(ctlv);
+ textMsg.iconSelfExplanatory = iconId.selfExplanatory;
+ }
+ }
+
+ mCmdParams = new DisplayTextParams(cmdDet, textMsg);
+
+ if (iconId != null) {
+ mIconLoadState = LOAD_SINGLE_ICON;
+ mIconLoader.loadIcon(iconId.recordNumber, this
+ .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Processes GET_INKEY proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details container 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(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+
+ CatLog.d(this, "process GetInkey");
+
+ Input input = new Input();
+ IconId iconId = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
+ ctlvs);
+ if (ctlv != null) {
+ input.text = ValueParser.retrieveTextString(ctlv);
+ } else {
+ throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
+ }
+ // parse icon identifier
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = ValueParser.retrieveIconId(ctlv);
+ }
+
+ // parse duration
+ ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
+ if (ctlv != null) {
+ input.duration = ValueParser.retrieveDuration(ctlv);
+ }
+
+ input.minLen = 1;
+ input.maxLen = 1;
+
+ input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
+ input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
+ input.yesNo = (cmdDet.commandQualifier & 0x04) != 0;
+ input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
+ input.echo = true;
+
+ mCmdParams = new GetInputParams(cmdDet, input);
+
+ if (iconId != null) {
+ mIconLoadState = LOAD_SINGLE_ICON;
+ mIconLoader.loadIcon(iconId.recordNumber, this
+ .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Processes GET_INPUT proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details container 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(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+
+ CatLog.d(this, "process GetInput");
+
+ Input input = new Input();
+ IconId iconId = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
+ ctlvs);
+ if (ctlv != null) {
+ input.text = ValueParser.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();
+ input.minLen = rawValue[valueIndex] & 0xff;
+ input.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.DEFAULT_TEXT, ctlvs);
+ if (ctlv != null) {
+ input.defaultText = ValueParser.retrieveTextString(ctlv);
+ }
+ // parse icon identifier
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = ValueParser.retrieveIconId(ctlv);
+ }
+
+ input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
+ input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
+ input.echo = (cmdDet.commandQualifier & 0x04) == 0;
+ input.packed = (cmdDet.commandQualifier & 0x08) != 0;
+ input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
+
+ mCmdParams = new GetInputParams(cmdDet, input);
+
+ if (iconId != null) {
+ mIconLoadState = LOAD_SINGLE_ICON;
+ mIconLoader.loadIcon(iconId.recordNumber, this
+ .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Processes REFRESH proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details container object.
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ */
+ private boolean processRefresh(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) {
+
+ CatLog.d(this, "process Refresh");
+
+ // 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"
+ 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:
+ mCmdParams = new DisplayTextParams(cmdDet, null);
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Processes SELECT_ITEM proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details container 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(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+
+ CatLog.d(this, "process SelectItem");
+
+ Menu menu = new Menu();
+ IconId titleIconId = null;
+ ItemsIconId itemsIconId = null;
+ Iterator<ComprehensionTlv> iter = ctlvs.iterator();
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
+ ctlvs);
+ if (ctlv != null) {
+ menu.title = ValueParser.retrieveAlphaId(ctlv);
+ }
+
+ while (true) {
+ ctlv = searchForNextTag(ComprehensionTlvTag.ITEM, iter);
+ if (ctlv != null) {
+ menu.items.add(ValueParser.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) {
+ // CAT items are listed 1...n while list start at 0, need to
+ // subtract one.
+ menu.defaultItem = ValueParser.retrieveItemId(ctlv) - 1;
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ mIconLoadState = LOAD_SINGLE_ICON;
+ titleIconId = ValueParser.retrieveIconId(ctlv);
+ menu.titleIconSelfExplanatory = titleIconId.selfExplanatory;
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.ITEM_ICON_ID_LIST, ctlvs);
+ if (ctlv != null) {
+ mIconLoadState = LOAD_MULTI_ICONS;
+ itemsIconId = ValueParser.retrieveItemsIconId(ctlv);
+ menu.itemsIconSelfExplanatory = itemsIconId.selfExplanatory;
+ }
+
+ boolean presentTypeSpecified = (cmdDet.commandQualifier & 0x01) != 0;
+ if (presentTypeSpecified) {
+ if ((cmdDet.commandQualifier & 0x02) == 0) {
+ menu.presentationType = PresentationType.DATA_VALUES;
+ } else {
+ menu.presentationType = PresentationType.NAVIGATION_OPTIONS;
+ }
+ }
+ menu.softKeyPreferred = (cmdDet.commandQualifier & 0x04) != 0;
+ menu.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
+
+ mCmdParams = new SelectItemParams(cmdDet, menu, titleIconId != null);
+
+ // Load icons data if needed.
+ switch(mIconLoadState) {
+ case LOAD_NO_ICON:
+ return false;
+ case LOAD_SINGLE_ICON:
+ mIconLoader.loadIcon(titleIconId.recordNumber, this
+ .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ break;
+ case LOAD_MULTI_ICONS:
+ int[] recordNumbers = itemsIconId.recordNumbers;
+ if (titleIconId != null) {
+ // Create a new array for all the icons (title and items).
+ 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(MSG_ID_LOAD_ICON_DONE));
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Processes EVENT_NOTIFY message from baseband.
+ *
+ * @param cmdDet Command Details container 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 processEventNotify(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+
+ CatLog.d(this, "process EventNotify");
+
+ TextMessage textMsg = new TextMessage();
+ IconId iconId = null;
+
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
+ ctlvs);
+ textMsg.text = ValueParser.retrieveAlphaId(ctlv);
+
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = ValueParser.retrieveIconId(ctlv);
+ textMsg.iconSelfExplanatory = iconId.selfExplanatory;
+ }
+
+ textMsg.responseNeeded = false;
+ mCmdParams = new DisplayTextParams(cmdDet, textMsg);
+
+ if (iconId != null) {
+ mIconLoadState = LOAD_SINGLE_ICON;
+ mIconLoader.loadIcon(iconId.recordNumber, this
+ .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Processes SET_UP_EVENT_LIST proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details object retrieved.
+ * @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(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) {
+
+ CatLog.d(this, "process SetUpEventList");
+ //
+ // 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 container 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(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+
+ CatLog.d(this, "process LaunchBrowser");
+
+ TextMessage confirmMsg = new TextMessage();
+ IconId iconId = null;
+ String url = 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);
+ }
+ }
+
+ // parse alpha identifier.
+ ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
+ confirmMsg.text = ValueParser.retrieveAlphaId(ctlv);
+
+ // parse icon identifier
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = ValueParser.retrieveIconId(ctlv);
+ confirmMsg.iconSelfExplanatory = iconId.selfExplanatory;
+ }
+
+ // parse command qualifier value.
+ 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;
+ }
+
+ mCmdParams = new LaunchBrowserParams(cmdDet, confirmMsg, url, mode);
+
+ if (iconId != null) {
+ mIconLoadState = LOAD_SINGLE_ICON;
+ mIconLoader.loadIcon(iconId.recordNumber, this
+ .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Processes PLAY_TONE proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details container 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(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+
+ CatLog.d(this, "process PlayTone");
+
+ Tone tone = null;
+ TextMessage textMsg = new TextMessage();
+ Duration duration = null;
+ IconId iconId = 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);
+ }
+ }
+ }
+ // parse alpha identifier
+ ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
+ if (ctlv != null) {
+ textMsg.text = ValueParser.retrieveAlphaId(ctlv);
+ }
+ // parse tone duration
+ ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
+ if (ctlv != null) {
+ duration = ValueParser.retrieveDuration(ctlv);
+ }
+ // parse icon identifier
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = ValueParser.retrieveIconId(ctlv);
+ textMsg.iconSelfExplanatory = iconId.selfExplanatory;
+ }
+
+ boolean vibrate = (cmdDet.commandQualifier & 0x01) != 0x00;
+
+ textMsg.responseNeeded = false;
+ mCmdParams = new PlayToneParams(cmdDet, textMsg, tone, duration, vibrate);
+
+ if (iconId != null) {
+ mIconLoadState = LOAD_SINGLE_ICON;
+ mIconLoader.loadIcon(iconId.recordNumber, this
+ .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Processes SETUP_CALL proactive command from the SIM card.
+ *
+ * @param cmdDet Command Details 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 processSetupCall(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+ CatLog.d(this, "process SetupCall");
+
+ Iterator<ComprehensionTlv> iter = ctlvs.iterator();
+ ComprehensionTlv ctlv = null;
+ // User confirmation phase message.
+ TextMessage confirmMsg = new TextMessage();
+ // Call set up phase message.
+ TextMessage callMsg = new TextMessage();
+ IconId confirmIconId = null;
+ IconId callIconId = null;
+
+ // get confirmation message string.
+ ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
+ confirmMsg.text = ValueParser.retrieveAlphaId(ctlv);
+
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ confirmIconId = ValueParser.retrieveIconId(ctlv);
+ confirmMsg.iconSelfExplanatory = confirmIconId.selfExplanatory;
+ }
+
+ // get call set up message string.
+ ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
+ if (ctlv != null) {
+ callMsg.text = ValueParser.retrieveAlphaId(ctlv);
+ }
+
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ callIconId = ValueParser.retrieveIconId(ctlv);
+ callMsg.iconSelfExplanatory = callIconId.selfExplanatory;
+ }
+
+ mCmdParams = new CallSetupParams(cmdDet, confirmMsg, callMsg);
+
+ if (confirmIconId != null || callIconId != null) {
+ mIconLoadState = LOAD_MULTI_ICONS;
+ int[] recordNumbers = new int[2];
+ recordNumbers[0] = confirmIconId != null
+ ? confirmIconId.recordNumber : -1;
+ recordNumbers[1] = callIconId != null ? callIconId.recordNumber
+ : -1;
+
+ mIconLoader.loadIcons(recordNumbers, this
+ .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+
+ private boolean processProvideLocalInfo(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+ CatLog.d(this, "process ProvideLocalInfo");
+ switch (cmdDet.commandQualifier) {
+ case DTTZ_SETTING:
+ CatLog.d(this, "PLI [DTTZ_SETTING]");
+ mCmdParams = new CommandParams(cmdDet);
+ break;
+ case LANGUAGE_SETTING:
+ CatLog.d(this, "PLI [LANGUAGE_SETTING]");
+ mCmdParams = new CommandParams(cmdDet);
+ break;
+ default:
+ CatLog.d(this, "PLI[" + cmdDet.commandQualifier + "] Command Not Supported");
+ mCmdParams = new CommandParams(cmdDet);
+ throw new ResultException(ResultCode.BEYOND_TERMINAL_CAPABILITY);
+ }
+ return false;
+ }
+
+ private boolean processBIPClient(CommandDetails cmdDet,
+ List<ComprehensionTlv> ctlvs) throws ResultException {
+ AppInterface.CommandType commandType =
+ AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
+ if (commandType != null) {
+ CatLog.d(this, "process "+ commandType.name());
+ }
+
+ TextMessage textMsg = new TextMessage();
+ IconId iconId = null;
+ ComprehensionTlv ctlv = null;
+ boolean has_alpha_id = false;
+
+ // parse alpha identifier
+ ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
+ if (ctlv != null) {
+ textMsg.text = ValueParser.retrieveAlphaId(ctlv);
+ CatLog.d(this, "alpha TLV text=" + textMsg.text);
+ has_alpha_id = true;
+ }
+
+ // parse icon identifier
+ ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+ if (ctlv != null) {
+ iconId = ValueParser.retrieveIconId(ctlv);
+ textMsg.iconSelfExplanatory = iconId.selfExplanatory;
+ }
+
+ textMsg.responseNeeded = false;
+ mCmdParams = new BIPClientParams(cmdDet, textMsg, has_alpha_id);
+
+ if (iconId != null) {
+ mIconLoadState = LOAD_SINGLE_ICON;
+ mIconLoader.loadIcon(iconId.recordNumber, this.obtainMessage(MSG_ID_LOAD_ICON_DONE));
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java b/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java
new file mode 100644
index 0000000..22cd5a4
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2011 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.cat;
+
+import android.util.Log;
+
+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 static final String LOG_TAG = "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
+ */
+ protected 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);
+ if (ctlv != null) {
+ items.add(ctlv);
+ startIndex = ctlv.mValueIndex + ctlv.mLength;
+ } else {
+ CatLog.d(LOG_TAG, "decodeMany: ctlv is null, stop decoding");
+ break;
+ }
+ }
+
+ 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 {
+ int curIndex = startIndex;
+ int endIndex = data.length;
+
+ try {
+ /* tag */
+ int tag;
+ boolean cr; // Comprehension required flag
+ int temp = data[curIndex++] & 0xff;
+ switch (temp) {
+ case 0:
+ case 0xff:
+ case 0x80:
+ Log.d("CAT ", "decode: unexpected first tag byte=" + Integer.toHexString(temp) +
+ ", startIndex=" + startIndex + " curIndex=" + curIndex +
+ " endIndex=" + endIndex);
+ // Return null which will stop decoding, this has occurred
+ // with Ghana MTN simcard and JDI simcard.
+ return null;
+
+ 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,
+ "length < 0x80 length=" + Integer.toHexString(length) +
+ " startIndex=" + startIndex + " curIndex=" + curIndex +
+ " endIndex=" + endIndex);
+ }
+ } 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,
+ "two byte length < 0x100 length=" + Integer.toHexString(length) +
+ " startIndex=" + startIndex + " curIndex=" + curIndex +
+ " endIndex=" + endIndex);
+ }
+ } 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,
+ "three byte length < 0x10000 length=0x" + Integer.toHexString(length) +
+ " startIndex=" + startIndex + " curIndex=" + curIndex +
+ " endIndex=" + endIndex);
+ }
+ } else {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD,
+ "Bad length modifer=" + temp +
+ " startIndex=" + startIndex + " curIndex=" + curIndex +
+ " endIndex=" + endIndex);
+
+ }
+
+ return new ComprehensionTlv(tag, cr, length, data, curIndex);
+
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD,
+ "IndexOutOfBoundsException" + " startIndex=" + startIndex +
+ " curIndex=" + curIndex + " endIndex=" + endIndex);
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java b/src/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java
new file mode 100644
index 0000000..973dbc8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 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.cat;
+
+/**
+ * Enumeration for representing the tag value of COMPREHENSION-TLV objects. If
+ * you want to get the actual value, call {@link #value() value} method.
+ *
+ * {@hide}
+ */
+public enum ComprehensionTlvTag {
+ COMMAND_DETAILS(0x01),
+ DEVICE_IDENTITIES(0x02),
+ RESULT(0x03),
+ DURATION(0x04),
+ ALPHA_ID(0x05),
+ ADDRESS(0x06),
+ USSD_STRING(0x0a),
+ SMS_TPDU(0x0b),
+ 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;
+ }
+
+ public static ComprehensionTlvTag fromInt(int value) {
+ for (ComprehensionTlvTag e : ComprehensionTlvTag.values()) {
+ if (e.mValue == value) {
+ return e;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/Duration.java b/src/java/com/android/internal/telephony/cat/Duration.java
new file mode 100644
index 0000000..e8cd404
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/Duration.java
@@ -0,0 +1,79 @@
+/*
+ * 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.cat;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+/**
+ * Class for representing "Duration" object for CAT.
+ *
+ * {@hide}
+ */
+public class Duration implements Parcelable {
+ 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;
+ }
+
+ private Duration(Parcel in) {
+ timeInterval = in.readInt();
+ timeUnit = TimeUnit.values()[in.readInt()];
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(timeInterval);
+ dest.writeInt(timeUnit.ordinal());
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Duration> CREATOR = new Parcelable.Creator<Duration>() {
+ public Duration createFromParcel(Parcel in) {
+ return new Duration(in);
+ }
+
+ public Duration[] newArray(int size) {
+ return new Duration[size];
+ }
+ };
+}
diff --git a/src/java/com/android/internal/telephony/cat/FontSize.java b/src/java/com/android/internal/telephony/cat/FontSize.java
new file mode 100644
index 0000000..02c7ea0
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/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.cat;
+
+
+/**
+ * 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/src/java/com/android/internal/telephony/cat/IconLoader.java b/src/java/com/android/internal/telephony/cat/IconLoader.java
new file mode 100644
index 0000000..2fa1811
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/IconLoader.java
@@ -0,0 +1,362 @@
+/*
+ * 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.cat;
+
+import com.android.internal.telephony.IccFileHandler;
+
+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;
+
+import java.util.HashMap;
+
+/**
+ * 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
+ private int mState = STATE_SINGLE_ICON;
+ private ImageDescriptor mId = null;
+ private Bitmap mCurrentIcon = null;
+ private int mRecordNumber;
+ private IccFileHandler mSimFH = null;
+ private Message mEndMsg = null;
+ private byte[] mIconData = null;
+ // multi icons state members
+ private int[] mRecordNumbers = null;
+ private int mCurrentRecordIndex = 0;
+ private Bitmap[] mIcons = null;
+ private HashMap<Integer, Bitmap> mIconsCache = 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;
+
+
+ private IconLoader(Looper looper , IccFileHandler fh) {
+ super(looper);
+ mSimFH = fh;
+
+ mIconsCache = new HashMap<Integer, Bitmap>(50);
+ }
+
+ static IconLoader getInstance(Handler caller, IccFileHandler fh) {
+ if (sLoader != null) {
+ return sLoader;
+ }
+ if (fh != null) {
+ HandlerThread thread = new HandlerThread("Cat Icon Loader");
+ 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;
+
+ // make sure the icon was not already loaded and saved in the local cache.
+ if (mIconsCache.containsKey(recordNumber)) {
+ mCurrentIcon = mIconsCache.get(recordNumber);
+ postIcon();
+ return;
+ }
+
+ // start the first phase ==> loading Image Descriptor.
+ readId();
+ }
+
+ @Override
+ 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);
+ mIconsCache.put(mRecordNumber, mCurrentIcon);
+ 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);
+ mIconsCache.put(mRecordNumber, mCurrentIcon);
+ postIcon();
+ break;
+ }
+ } catch (Exception e) {
+ CatLog.d(this, "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() {
+ if (mRecordNumber < 0) {
+ mCurrentIcon = null;
+ postIcon();
+ return;
+ }
+ 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) {
+ CatLog.d("IconLoader", "parseToBnW; 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/src/java/com/android/internal/telephony/cat/ImageDescriptor.java b/src/java/com/android/internal/telephony/cat/ImageDescriptor.java
new file mode 100644
index 0000000..711d977
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/ImageDescriptor.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.cat;
+
+/**
+ * {@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;
+ // ID_LENGTH substituted by IccFileHandlerBase.GET_RESPONSE_EF_IMG_SIZE_BYTES
+
+ 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) {
+ CatLog.d("ImageDescripter", "parse; failed parsing image descriptor");
+ d = null;
+ }
+ return d;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/Input.java b/src/java/com/android/internal/telephony/cat/Input.java
new file mode 100644
index 0000000..13a5ad4
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/Input.java
@@ -0,0 +1,101 @@
+/*
+ * 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.cat;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Container class for CAT GET INPUT, GET IN KEY commands parameters.
+ *
+ */
+public class Input implements Parcelable {
+ public String text;
+ public String defaultText;
+ public Bitmap icon;
+ public int minLen;
+ public int maxLen;
+ public boolean ucs2;
+ public boolean packed;
+ public boolean digitOnly;
+ public boolean echo;
+ public boolean yesNo;
+ public boolean helpAvailable;
+ public Duration duration;
+
+ Input() {
+ text = "";
+ defaultText = null;
+ icon = null;
+ minLen = 0;
+ maxLen = 1;
+ ucs2 = false;
+ packed = false;
+ digitOnly = false;
+ echo = false;
+ yesNo = false;
+ helpAvailable = false;
+ duration = null;
+ }
+
+ private Input(Parcel in) {
+ text = in.readString();
+ defaultText = in.readString();
+ icon = in.readParcelable(null);
+ minLen = in.readInt();
+ maxLen = in.readInt();
+ ucs2 = in.readInt() == 1 ? true : false;
+ packed = in.readInt() == 1 ? true : false;
+ digitOnly = in.readInt() == 1 ? true : false;
+ echo = in.readInt() == 1 ? true : false;
+ yesNo = in.readInt() == 1 ? true : false;
+ helpAvailable = in.readInt() == 1 ? true : false;
+ duration = in.readParcelable(null);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(text);
+ dest.writeString(defaultText);
+ dest.writeParcelable(icon, 0);
+ dest.writeInt(minLen);
+ dest.writeInt(maxLen);
+ dest.writeInt(ucs2 ? 1 : 0);
+ dest.writeInt(packed ? 1 : 0);
+ dest.writeInt(digitOnly ? 1 : 0);
+ dest.writeInt(echo ? 1 : 0);
+ dest.writeInt(yesNo ? 1 : 0);
+ dest.writeInt(helpAvailable ? 1 : 0);
+ dest.writeParcelable(duration, 0);
+ }
+
+ public static final Parcelable.Creator<Input> CREATOR = new Parcelable.Creator<Input>() {
+ public Input createFromParcel(Parcel in) {
+ return new Input(in);
+ }
+
+ public Input[] newArray(int size) {
+ return new Input[size];
+ }
+ };
+
+ boolean setIcon(Bitmap Icon) { return true; }
+} \ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/cat/Item.java b/src/java/com/android/internal/telephony/cat/Item.java
new file mode 100644
index 0000000..d4702bb
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/Item.java
@@ -0,0 +1,71 @@
+/*
+ * 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.cat;
+
+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();
+ icon = in.readParcelable(null);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(id);
+ dest.writeString(text);
+ dest.writeParcelable(icon, flags);
+ }
+
+ 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/src/java/com/android/internal/telephony/cat/LaunchBrowserMode.java b/src/java/com/android/internal/telephony/cat/LaunchBrowserMode.java
new file mode 100644
index 0000000..af043d1
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/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.cat;
+
+
+/**
+ * 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/src/java/com/android/internal/telephony/cat/Menu.java b/src/java/com/android/internal/telephony/cat/Menu.java
new file mode 100644
index 0000000..7bbae01
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/Menu.java
@@ -0,0 +1,105 @@
+/*
+ * 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.cat;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Container class for CAT menu (SET UP MENU, SELECT ITEM) parameters.
+ *
+ */
+public class Menu implements Parcelable {
+ public List<Item> items;
+ public List<TextAttribute> titleAttrs;
+ public PresentationType presentationType;
+ public String title;
+ public Bitmap titleIcon;
+ public int defaultItem;
+ public boolean softKeyPreferred;
+ public boolean helpAvailable;
+ public boolean titleIconSelfExplanatory;
+ public boolean itemsIconSelfExplanatory;
+
+ public Menu() {
+ // Create an empty list.
+ items = new ArrayList<Item>();
+ title = null;
+ titleAttrs = null;
+ defaultItem = 0;
+ softKeyPreferred = false;
+ helpAvailable = false;
+ titleIconSelfExplanatory = false;
+ itemsIconSelfExplanatory = false;
+ titleIcon = null;
+ // set default style to be navigation menu.
+ presentationType = PresentationType.NAVIGATION_OPTIONS;
+ }
+
+ private Menu(Parcel in) {
+ title = in.readString();
+ titleIcon = in.readParcelable(null);
+ // 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;
+ titleIconSelfExplanatory = in.readInt() == 1 ? true : false;
+ itemsIconSelfExplanatory = in.readInt() == 1 ? true : false;
+ presentationType = PresentationType.values()[in.readInt()];
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(title);
+ dest.writeParcelable(titleIcon, flags);
+ // 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);
+ dest.writeInt(titleIconSelfExplanatory ? 1 : 0);
+ dest.writeInt(itemsIconSelfExplanatory ? 1 : 0);
+ dest.writeInt(presentationType.ordinal());
+ }
+
+ 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/src/java/com/android/internal/telephony/cat/PresentationType.java b/src/java/com/android/internal/telephony/cat/PresentationType.java
new file mode 100644
index 0000000..7c8cd8c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/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.cat;
+
+
+/**
+ * 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/src/java/com/android/internal/telephony/cat/ResponseData.java b/src/java/com/android/internal/telephony/cat/ResponseData.java
new file mode 100644
index 0000000..1157c1a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/ResponseData.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2006-2007 Google Inc.
+ *
+ * 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.cat;
+
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
+import java.util.Calendar;
+import java.util.TimeZone;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.cat.AppInterface.CommandType;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+
+abstract class ResponseData {
+ /**
+ * Format the data appropriate for TERMINAL RESPONSE and write it into
+ * the ByteArrayOutputStream object.
+ */
+ public abstract void format(ByteArrayOutputStream buf);
+
+ public static void writeLength(ByteArrayOutputStream buf, int length) {
+ // As per ETSI 102.220 Sec7.1.2, if the total length is greater
+ // than 0x7F, it should be coded in two bytes and the first byte
+ // should be 0x81.
+ if (length > 0x7F) {
+ buf.write(0x81);
+ }
+ buf.write(length);
+ }
+}
+
+class SelectItemResponseData extends ResponseData {
+ // members
+ 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
+ }
+}
+
+class GetInkeyInputResponseData extends ResponseData {
+ // members
+ private boolean mIsUcs2;
+ private boolean mIsPacked;
+ private boolean mIsYesNo;
+ private boolean mYesNoResponse;
+ public String mInData;
+
+ // GetInKey Yes/No response characters constants.
+ protected static final byte GET_INKEY_YES = 0x01;
+ protected static final byte GET_INKEY_NO = 0x00;
+
+ 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, 0, 0);
+ 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.
+ writeLength(buf, 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);
+ }
+ }
+}
+
+// For "PROVIDE LOCAL INFORMATION" command.
+// See TS 31.111 section 6.4.15/ETSI TS 102 223
+// TS 31.124 section 27.22.4.15 for test spec
+class LanguageResponseData extends ResponseData {
+ private String lang;
+
+ public LanguageResponseData(String lang) {
+ super();
+ this.lang = lang;
+ }
+
+ @Override
+ public void format(ByteArrayOutputStream buf) {
+ if (buf == null) {
+ return;
+ }
+
+ // Text string object
+ int tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value();
+ buf.write(tag); // tag
+
+ byte[] data;
+
+ if (lang != null && lang.length() > 0) {
+ data = GsmAlphabet.stringToGsm8BitPacked(lang);
+ }
+ else {
+ data = new byte[0];
+ }
+
+ buf.write(data.length);
+
+ for (byte b : data) {
+ buf.write(b);
+ }
+ }
+}
+
+// For "PROVIDE LOCAL INFORMATION" command.
+// See TS 31.111 section 6.4.15/ETSI TS 102 223
+// TS 31.124 section 27.22.4.15 for test spec
+class DTTZResponseData extends ResponseData {
+ private Calendar calendar;
+
+ public DTTZResponseData(Calendar cal) {
+ super();
+ calendar = cal;
+ }
+
+ @Override
+ public void format(ByteArrayOutputStream buf) {
+ if (buf == null) {
+ return;
+ }
+
+ // DTTZ object
+ int tag = 0x80 | CommandType.PROVIDE_LOCAL_INFORMATION.value();
+ buf.write(tag); // tag
+
+ byte[] data = new byte[8];
+
+ data[0] = 0x07; // Write length of DTTZ data
+
+ if (calendar == null) {
+ calendar = Calendar.getInstance();
+ }
+ // Fill year byte
+ data[1] = byteToBCD(calendar.get(java.util.Calendar.YEAR) % 100);
+
+ // Fill month byte
+ data[2] = byteToBCD(calendar.get(java.util.Calendar.MONTH) + 1);
+
+ // Fill day byte
+ data[3] = byteToBCD(calendar.get(java.util.Calendar.DATE));
+
+ // Fill hour byte
+ data[4] = byteToBCD(calendar.get(java.util.Calendar.HOUR_OF_DAY));
+
+ // Fill minute byte
+ data[5] = byteToBCD(calendar.get(java.util.Calendar.MINUTE));
+
+ // Fill second byte
+ data[6] = byteToBCD(calendar.get(java.util.Calendar.SECOND));
+
+ String tz = SystemProperties.get("persist.sys.timezone", "");
+ if (TextUtils.isEmpty(tz)) {
+ data[7] = (byte) 0xFF; // set FF in terminal response
+ } else {
+ TimeZone zone = TimeZone.getTimeZone(tz);
+ int zoneOffset = zone.getRawOffset() + zone.getDSTSavings();
+ data[7] = getTZOffSetByte(zoneOffset);
+ }
+
+ for (byte b : data) {
+ buf.write(b);
+ }
+ }
+
+ private byte byteToBCD(int value) {
+ if (value < 0 && value > 99) {
+ CatLog.d(this, "Err: byteToBCD conversion Value is " + value +
+ " Value has to be between 0 and 99");
+ return 0;
+ }
+
+ return (byte) ((value / 10) | ((value % 10) << 4));
+ }
+
+ private byte getTZOffSetByte(long offSetVal) {
+ boolean isNegative = (offSetVal < 0);
+
+ /*
+ * The 'offSetVal' is in milliseconds. Convert it to hours and compute
+ * offset While sending T.R to UICC, offset is expressed is 'quarters of
+ * hours'
+ */
+
+ long tzOffset = offSetVal / (15 * 60 * 1000);
+ tzOffset = (isNegative ? -1 : 1) * tzOffset;
+ byte bcdVal = byteToBCD((int) tzOffset);
+ // For negative offsets, put '1' in the msb
+ return isNegative ? (bcdVal |= 0x08) : bcdVal;
+ }
+
+}
+
diff --git a/src/java/com/android/internal/telephony/cat/ResultCode.java b/src/java/com/android/internal/telephony/cat/ResultCode.java
new file mode 100644
index 0000000..8544175
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/ResultCode.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.cat;
+
+
+/**
+ * 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 value() {
+ return mCode;
+ }
+
+ public static ResultCode fromInt(int value) {
+ for (ResultCode r : ResultCode.values()) {
+ if (r.mCode == value) {
+ return r;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/ResultException.java b/src/java/com/android/internal/telephony/cat/ResultException.java
new file mode 100644
index 0000000..84879c2
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/ResultException.java
@@ -0,0 +1,97 @@
+/*
+ * 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.cat;
+
+
+/**
+ * Class for errors in the Result object.
+ *
+ * {@hide}
+ */
+public class ResultException extends CatException {
+ private ResultCode mResult;
+ private int mAdditionalInfo;
+ private String mExplanation;
+
+ 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;
+ mExplanation = "";
+ }
+
+ public ResultException(ResultCode result, String explanation) {
+ this(result);
+ mExplanation = explanation;
+ }
+
+ public ResultException(ResultCode result, int additionalInfo) {
+ this(result);
+
+ if (additionalInfo < 0) {
+ throw new AssertionError(
+ "Additional info must be greater than zero!");
+ }
+
+ mAdditionalInfo = additionalInfo;
+ }
+
+ public ResultException(ResultCode result, int additionalInfo, String explanation) {
+ this(result, additionalInfo);
+ mExplanation = explanation;
+ }
+
+ public ResultCode result() {
+ return mResult;
+ }
+
+ public boolean hasAdditionalInfo() {
+ return mAdditionalInfo >= 0;
+ }
+
+ public int additionalInfo() {
+ return mAdditionalInfo;
+ }
+
+ public String explanation() {
+ return mExplanation;
+ }
+
+ @Override
+ public String toString() {
+ return "result=" + mResult + " additionalInfo=" + mAdditionalInfo +
+ " explantion=" + mExplanation;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
new file mode 100644
index 0000000..fb33a8e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
@@ -0,0 +1,177 @@
+/*
+ * 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.cat;
+
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccUtils;
+
+import android.os.Handler;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import android.os.Message;
+
+/**
+ * Class used for queuing raw ril messages, decoding them into CommanParams
+ * objects and sending the result back to the CAT Service.
+ */
+class RilMessageDecoder extends StateMachine {
+
+ // constants
+ private static final int CMD_START = 1;
+ private static final int CMD_PARAMS_READY = 2;
+
+ // members
+ private static RilMessageDecoder sInstance = null;
+ private CommandParamsFactory mCmdParamsFactory = null;
+ private RilMessage mCurrentRilMessage = null;
+ private Handler mCaller = null;
+
+ // States
+ private StateStart mStateStart = new StateStart();
+ private StateCmdParamsReady mStateCmdParamsReady = new StateCmdParamsReady();
+
+ /**
+ * Get the singleton instance, constructing if necessary.
+ *
+ * @param caller
+ * @param fh
+ * @return RilMesssageDecoder
+ */
+ public static synchronized RilMessageDecoder getInstance(Handler caller, IccFileHandler fh) {
+ if (sInstance == null) {
+ sInstance = new RilMessageDecoder(caller, fh);
+ sInstance.start();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Start decoding the message parameters,
+ * when complete MSG_ID_RIL_MSG_DECODED will be returned to caller.
+ *
+ * @param rilMsg
+ */
+ public void sendStartDecodingMessageParams(RilMessage rilMsg) {
+ Message msg = obtainMessage(CMD_START);
+ msg.obj = rilMsg;
+ sendMessage(msg);
+ }
+
+ /**
+ * The command parameters have been decoded.
+ *
+ * @param resCode
+ * @param cmdParams
+ */
+ public void sendMsgParamsDecoded(ResultCode resCode, CommandParams cmdParams) {
+ Message msg = obtainMessage(RilMessageDecoder.CMD_PARAMS_READY);
+ msg.arg1 = resCode.value();
+ msg.obj = cmdParams;
+ sendMessage(msg);
+ }
+
+ private void sendCmdForExecution(RilMessage rilMsg) {
+ Message msg = mCaller.obtainMessage(CatService.MSG_ID_RIL_MSG_DECODED,
+ new RilMessage(rilMsg));
+ msg.sendToTarget();
+ }
+
+ private RilMessageDecoder(Handler caller, IccFileHandler fh) {
+ super("RilMessageDecoder");
+
+ addState(mStateStart);
+ addState(mStateCmdParamsReady);
+ setInitialState(mStateStart);
+
+ mCaller = caller;
+ mCmdParamsFactory = CommandParamsFactory.getInstance(this, fh);
+ }
+
+ private class StateStart extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what == CMD_START) {
+ if (decodeMessageParams((RilMessage)msg.obj)) {
+ transitionTo(mStateCmdParamsReady);
+ }
+ } else {
+ CatLog.d(this, "StateStart unexpected expecting START=" +
+ CMD_START + " got " + msg.what);
+ }
+ return true;
+ }
+ }
+
+ private class StateCmdParamsReady extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what == CMD_PARAMS_READY) {
+ mCurrentRilMessage.mResCode = ResultCode.fromInt(msg.arg1);
+ mCurrentRilMessage.mData = msg.obj;
+ sendCmdForExecution(mCurrentRilMessage);
+ transitionTo(mStateStart);
+ } else {
+ CatLog.d(this, "StateCmdParamsReady expecting CMD_PARAMS_READY="
+ + CMD_PARAMS_READY + " got " + msg.what);
+ deferMessage(msg);
+ }
+ return true;
+ }
+ }
+
+ private boolean decodeMessageParams(RilMessage rilMsg) {
+ boolean decodingStarted;
+
+ mCurrentRilMessage = rilMsg;
+ switch(rilMsg.mId) {
+ case CatService.MSG_ID_SESSION_END:
+ case CatService.MSG_ID_CALL_SETUP:
+ mCurrentRilMessage.mResCode = ResultCode.OK;
+ sendCmdForExecution(mCurrentRilMessage);
+ decodingStarted = false;
+ break;
+ case CatService.MSG_ID_PROACTIVE_COMMAND:
+ case CatService.MSG_ID_EVENT_NOTIFY:
+ case CatService.MSG_ID_REFRESH:
+ byte[] rawData = null;
+ try {
+ rawData = IccUtils.hexStringToBytes((String) rilMsg.mData);
+ } catch (Exception e) {
+ // zombie messages are dropped
+ CatLog.d(this, "decodeMessageParams dropping zombie messages");
+ decodingStarted = false;
+ break;
+ }
+ try {
+ // Start asynch parsing of the command parameters.
+ mCmdParamsFactory.make(BerTlv.decode(rawData));
+ decodingStarted = true;
+ } catch (ResultException e) {
+ // send to Service for proper RIL communication.
+ CatLog.d(this, "decodeMessageParams: caught ResultException e=" + e);
+ mCurrentRilMessage.mResCode = e.result();
+ sendCmdForExecution(mCurrentRilMessage);
+ decodingStarted = false;
+ }
+ break;
+ default:
+ decodingStarted = false;
+ break;
+ }
+ return decodingStarted;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/TextAlignment.java b/src/java/com/android/internal/telephony/cat/TextAlignment.java
new file mode 100644
index 0000000..7fb58a5
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/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.cat;
+
+
+/**
+ * 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/src/java/com/android/internal/telephony/cat/TextAttribute.java b/src/java/com/android/internal/telephony/cat/TextAttribute.java
new file mode 100644
index 0000000..0dea640
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/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.cat;
+
+
+/**
+ * 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/src/java/com/android/internal/telephony/cat/TextColor.java b/src/java/com/android/internal/telephony/cat/TextColor.java
new file mode 100644
index 0000000..6447e74
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/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.cat;
+
+
+/**
+ * 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/src/java/com/android/internal/telephony/cat/TextMessage.java b/src/java/com/android/internal/telephony/cat/TextMessage.java
new file mode 100644
index 0000000..5ffd076
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/TextMessage.java
@@ -0,0 +1,71 @@
+/*
+ * 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.cat;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class TextMessage implements Parcelable {
+ public String title = "";
+ public String text = null;
+ public Bitmap icon = null;
+ public boolean iconSelfExplanatory = false;
+ public boolean isHighPriority = false;
+ public boolean responseNeeded = true;
+ public boolean userClear = false;
+ public Duration duration = null;
+
+ TextMessage() {
+ }
+
+ private TextMessage(Parcel in) {
+ title = in.readString();
+ text = in.readString();
+ icon = in.readParcelable(null);
+ iconSelfExplanatory = in.readInt() == 1 ? true : false;
+ isHighPriority = in.readInt() == 1 ? true : false;
+ responseNeeded = in.readInt() == 1 ? true : false;
+ userClear = in.readInt() == 1 ? true : false;
+ duration = in.readParcelable(null);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(title);
+ dest.writeString(text);
+ dest.writeParcelable(icon, 0);
+ dest.writeInt(iconSelfExplanatory ? 1 : 0);
+ dest.writeInt(isHighPriority ? 1 : 0);
+ dest.writeInt(responseNeeded ? 1 : 0);
+ dest.writeInt(userClear ? 1 : 0);
+ dest.writeParcelable(duration, 0);
+ }
+
+ public static final Parcelable.Creator<TextMessage> CREATOR = new Parcelable.Creator<TextMessage>() {
+ public TextMessage createFromParcel(Parcel in) {
+ return new TextMessage(in);
+ }
+
+ public TextMessage[] newArray(int size) {
+ return new TextMessage[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/cat/Tone.java b/src/java/com/android/internal/telephony/cat/Tone.java
new file mode 100644
index 0000000..27b4489
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/Tone.java
@@ -0,0 +1,190 @@
+/*
+ * 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.cat;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Enumeration for representing the tone values for use with PLAY TONE
+ * proactive commands.
+ *
+ * {@hide}
+ */
+public enum Tone implements Parcelable {
+ // 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;
+ }
+
+ Tone(Parcel in) {
+ mValue = in.readInt();
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(ordinal());
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Tone> CREATOR = new Parcelable.Creator<Tone>() {
+ public Tone createFromParcel(Parcel in) {
+ return Tone.values()[in.readInt()];
+ }
+
+ public Tone[] newArray(int size) {
+ return new Tone[size];
+ }
+ };
+}
diff --git a/src/java/com/android/internal/telephony/cat/ToneSettings.java b/src/java/com/android/internal/telephony/cat/ToneSettings.java
new file mode 100644
index 0000000..6375afb
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/ToneSettings.java
@@ -0,0 +1,62 @@
+/*
+ * 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.cat;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Container class for PlayTone commands parameters.
+ *
+ */
+public class ToneSettings implements Parcelable {
+ public Duration duration;
+ public Tone tone;
+ public boolean vibrate;
+
+ public ToneSettings(Duration duration, Tone tone, boolean vibrate) {
+ this.duration = duration;
+ this.tone = tone;
+ this.vibrate = vibrate;
+ }
+
+ private ToneSettings(Parcel in) {
+ duration = in.readParcelable(null);
+ tone = in.readParcelable(null);
+ vibrate = in.readInt() == 1;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(duration, 0);
+ dest.writeParcelable(tone, 0);
+ dest.writeInt(vibrate ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<ToneSettings> CREATOR = new Parcelable.Creator<ToneSettings>() {
+ public ToneSettings createFromParcel(Parcel in) {
+ return new ToneSettings(in);
+ }
+
+ public ToneSettings[] newArray(int size) {
+ return new ToneSettings[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/cat/ValueParser.java b/src/java/com/android/internal/telephony/cat/ValueParser.java
new file mode 100644
index 0000000..584d96c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/ValueParser.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2006-2007 Google Inc.
+ *
+ * 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.cat;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.cat.Duration.TimeUnit;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+abstract class ValueParser {
+
+ /**
+ * 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
+ */
+ static CommandDetails retrieveCommandDetails(ComprehensionTlv ctlv)
+ throws ResultException {
+
+ CommandDetails cmdDet = new CommandDetails();
+ 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);
+ }
+ }
+
+ /**
+ * 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
+ */
+ static DeviceIdentities retrieveDeviceIdentities(ComprehensionTlv ctlv)
+ throws ResultException {
+
+ DeviceIdentities devIds = new DeviceIdentities();
+ 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);
+ }
+ }
+
+ /**
+ * Retrieves Duration information from the Duration COMPREHENSION-TLV
+ * object.
+ *
+ * @param ctlv A Text Attribute COMPREHENSION-TLV object
+ * @return A Duration object
+ * @throws ResultException
+ */
+ static 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
+ */
+ static 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 = IccUtils.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
+ */
+ static 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
+ */
+ static 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
+ */
+ static ItemsIconId retrieveItemsIconId(ComprehensionTlv ctlv)
+ throws ResultException {
+ CatLog.d("ValueParser", "retrieveItemsIconId:");
+ 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
+ */
+ static 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
+ */
+ static String retrieveAlphaId(ComprehensionTlv ctlv) throws ResultException {
+
+ if (ctlv != null) {
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ int length = ctlv.getLength();
+ if (length != 0) {
+ try {
+ return IccUtils.adnStringFieldToString(rawValue, valueIndex,
+ length);
+ } catch (IndexOutOfBoundsException e) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+ } else {
+ return CatService.STK_DEFAULT;
+ }
+ } else {
+ return CatService.STK_DEFAULT;
+ }
+ }
+
+ /**
+ * Retrieves text from the Text COMPREHENSION-TLV object, and decodes it
+ * into a Java String.
+ *
+ * @param ctlv A Text COMPREHENSION-TLV object
+ * @return A Java String object decoded from the Text object
+ * @throws ResultException
+ */
+ static 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);
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cat/package.html b/src/java/com/android/internal/telephony/cat/package.html
new file mode 100644
index 0000000..5b6bfc6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Provides classes for ICC Toolkit Service (CAT).
+</BODY>
+</HTML>
diff --git a/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
new file mode 100644
index 0000000..df42515
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2011 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.cdma;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.OperatorInfo;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.SMSDispatcher;
+import com.android.internal.telephony.gsm.GsmSMSDispatcher;
+import com.android.internal.telephony.gsm.SmsMessage;
+import com.android.internal.telephony.ims.IsimRecords;
+import com.android.internal.telephony.uicc.UiccController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class CDMALTEPhone extends CDMAPhone {
+ static final String LOG_TAG = "CDMA";
+
+ private static final boolean DBG = true;
+
+ /** Secondary SMSDispatcher for 3GPP format messages. */
+ SMSDispatcher m3gppSMS;
+
+ /**
+ * Small container class used to hold information relevant to
+ * the carrier selection process. operatorNumeric can be ""
+ * if we are looking for automatic selection. operatorAlphaLong is the
+ * corresponding operator name.
+ */
+ private static class NetworkSelectMessage {
+ public Message message;
+ public String operatorNumeric;
+ public String operatorAlphaLong;
+ }
+
+ // Constructors
+ public CDMALTEPhone(Context context, CommandsInterface ci, PhoneNotifier notifier) {
+ super(context, ci, notifier, false);
+ m3gppSMS = new GsmSMSDispatcher(this, mSmsStorageMonitor, mSmsUsageMonitor);
+ mIccRecords.registerForNewSms(this, EVENT_NEW_ICC_SMS, null);
+ }
+
+ @Override
+ public void handleMessage (Message msg) {
+ AsyncResult ar;
+ switch (msg.what) {
+ // handle the select network completion callbacks.
+ case EVENT_SET_NETWORK_MANUAL_COMPLETE:
+ handleSetSelectNetwork((AsyncResult) msg.obj);
+ break;
+ case EVENT_NEW_ICC_SMS:
+ ar = (AsyncResult)msg.obj;
+ m3gppSMS.dispatchMessage((SmsMessage)ar.result);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ @Override
+ protected void initSstIcc() {
+ mIccCard.set(UiccController.getInstance(this).getIccCard());
+ mIccRecords = mIccCard.get().getIccRecords();
+ // CdmaLteServiceStateTracker registers with IccCard to know
+ // when the card is ready. So create mIccCard before the ServiceStateTracker
+ mSST = new CdmaLteServiceStateTracker(this);
+ }
+
+ @Override
+ public void dispose() {
+ synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+ super.dispose();
+ m3gppSMS.dispose();
+ mIccRecords.unregisterForNewSms(this);
+ }
+ }
+
+ @Override
+ public void removeReferences() {
+ super.removeReferences();
+ m3gppSMS = null;
+ }
+
+ @Override
+ public PhoneConstants.DataState getDataConnectionState(String apnType) {
+ PhoneConstants.DataState ret = PhoneConstants.DataState.DISCONNECTED;
+
+ if (mSST == null) {
+ // Radio Technology Change is ongoing, dispose() and
+ // removeReferences() have already been called
+
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ } else if (mDataConnectionTracker.isApnTypeEnabled(apnType) == false) {
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ } else {
+ switch (mDataConnectionTracker.getState(apnType)) {
+ case FAILED:
+ case IDLE:
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ break;
+
+ case CONNECTED:
+ case DISCONNECTING:
+ if (mCT.state != PhoneConstants.State.IDLE &&
+ !mSST.isConcurrentVoiceAndDataAllowed()) {
+ ret = PhoneConstants.DataState.SUSPENDED;
+ } else {
+ ret = PhoneConstants.DataState.CONNECTED;
+ }
+ break;
+
+ case INITING:
+ case CONNECTING:
+ case SCANNING:
+ ret = PhoneConstants.DataState.CONNECTING;
+ break;
+ }
+ }
+
+ log("getDataConnectionState apnType=" + apnType + " ret=" + ret);
+ return ret;
+ }
+
+ @Override
+ public void
+ selectNetworkManually(OperatorInfo 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.getOperatorNumeric();
+ nsm.operatorAlphaLong = network.getOperatorAlphaLong();
+
+ // get the message
+ Message msg = obtainMessage(EVENT_SET_NETWORK_MANUAL_COMPLETE, nsm);
+
+ mCM.setNetworkSelectionModeManual(network.getOperatorNumeric(), msg);
+ }
+
+ /**
+ * 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)) {
+ Log.e(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 (DBG) log("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);
+ editor.putString(NETWORK_SELECTION_NAME_KEY, nsm.operatorAlphaLong);
+
+ // commit and log the result.
+ if (! editor.commit()) {
+ Log.e(LOG_TAG, "failed to commit network selection preference");
+ }
+
+ }
+
+ @Override
+ public boolean updateCurrentCarrierInProvider() {
+ if (mIccRecords != null) {
+ try {
+ Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
+ ContentValues map = new ContentValues();
+ String operatorNumeric = mIccRecords.getOperatorNumeric();
+ map.put(Telephony.Carriers.NUMERIC, operatorNumeric);
+ if (DBG) log("updateCurrentCarrierInProvider from UICC: numeric=" +
+ operatorNumeric);
+ mContext.getContentResolver().insert(uri, map);
+ return true;
+ } catch (SQLException e) {
+ Log.e(LOG_TAG, "[CDMALTEPhone] Can't store current operator ret false", e);
+ }
+ } else {
+ if (DBG) log("updateCurrentCarrierInProvider mIccRecords == null ret false");
+ }
+ return false;
+ }
+
+ // return IMSI from USIM as subscriber ID.
+ @Override
+ public String getSubscriberId() {
+ return mIccRecords.getIMSI();
+ }
+
+ @Override
+ public String getImei() {
+ return mImei;
+ }
+
+ @Override
+ public String getDeviceSvn() {
+ return mImeiSv;
+ }
+
+ @Override
+ public IsimRecords getIsimRecords() {
+ return mIccRecords.getIsimRecords();
+ }
+
+ @Override
+ public String getMsisdn() {
+ return mIccRecords.getMsisdnNumber();
+ }
+
+ @Override
+ public void getAvailableNetworks(Message response) {
+ mCM.getAvailableNetworks(response);
+ }
+
+ @Override
+ public void requestIsimAuthentication(String nonce, Message result) {
+ mCM.requestIsimAuthentication(nonce, result);
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[CDMALTEPhone] " + s);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("CDMALTEPhone extends:");
+ super.dump(fd, pw, args);
+ pw.println(" m3gppSMS=" + m3gppSMS);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CDMAPhone.java b/src/java/com/android/internal/telephony/cdma/CDMAPhone.java
new file mode 100755
index 0000000..26ef9dc
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -0,0 +1,1508 @@
+/*
+ * 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.cdma;
+
+import android.app.ActivityManagerNative;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.telephony.CellLocation;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallTracker;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccException;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccPhoneBookInterfaceManager;
+import com.android.internal.telephony.IccSmsInterfaceManager;
+import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.OperatorInfo;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.PhoneSubInfo;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.cat.CatService;
+import com.android.internal.telephony.uicc.UiccController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
+
+/**
+ * {@hide}
+ */
+public class CDMAPhone extends PhoneBase {
+ static final String LOG_TAG = "CDMA";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false; /* STOP SHIP if true */
+
+ // Default Emergency Callback Mode exit timer
+ private static final int DEFAULT_ECM_EXIT_TIMER_VALUE = 300000;
+
+ static final String VM_COUNT_CDMA = "vm_count_key_cdma";
+ private static final String VM_NUMBER_CDMA = "vm_number_key_cdma";
+ private String mVmNumber = null;
+
+ static final int RESTART_ECM_TIMER = 0; // restart Ecm timer
+ static final int CANCEL_ECM_TIMER = 1; // cancel Ecm timer
+
+ // Instance Variables
+ CdmaCallTracker mCT;
+ CdmaServiceStateTracker mSST;
+ CdmaSubscriptionSourceManager mCdmaSSM;
+ ArrayList <CdmaMmiCode> mPendingMmis = new ArrayList<CdmaMmiCode>();
+ RuimPhoneBookInterfaceManager mRuimPhoneBookInterfaceManager;
+ RuimSmsInterfaceManager mRuimSmsInterfaceManager;
+ int mCdmaSubscriptionSource = CdmaSubscriptionSourceManager.SUBSCRIPTION_SOURCE_UNKNOWN;
+ PhoneSubInfo mSubInfo;
+ EriManager mEriManager;
+ WakeLock mWakeLock;
+
+ // mEriFileLoadedRegistrants are informed after the ERI text has been loaded
+ private final RegistrantList mEriFileLoadedRegistrants = new RegistrantList();
+
+ // mEcmTimerResetRegistrants are informed after Ecm timer is canceled or re-started
+ private final RegistrantList mEcmTimerResetRegistrants = new RegistrantList();
+
+ // mEcmExitRespRegistrant is informed after the phone has been exited
+ //the emergency callback mode
+ //keep track of if phone is in emergency callback mode
+ private boolean mIsPhoneInEcmState;
+ private Registrant mEcmExitRespRegistrant;
+ protected String mImei;
+ protected String mImeiSv;
+ private String mEsn;
+ private String mMeid;
+ // string to define how the carrier specifies its own ota sp number
+ private String mCarrierOtaSpNumSchema;
+
+ // A runnable which is used to automatically exit from Ecm after a period of time.
+ private Runnable mExitEcmRunnable = new Runnable() {
+ @Override
+ public void run() {
+ exitEmergencyCallbackMode();
+ }
+ };
+
+ Registrant mPostDialHandler;
+
+ static String PROPERTY_CDMA_HOME_OPERATOR_NUMERIC = "ro.cdma.home.operator.numeric";
+
+ // Constructors
+ public CDMAPhone(Context context, CommandsInterface ci, PhoneNotifier notifier) {
+ super(notifier, context, ci, false);
+ initSstIcc();
+ init(context, notifier);
+ }
+
+ public CDMAPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
+ boolean unitTestMode) {
+ super(notifier, context, ci, unitTestMode);
+ initSstIcc();
+ init(context, notifier);
+ }
+
+ protected void initSstIcc() {
+ mIccCard.set(UiccController.getInstance(this).getIccCard());
+ mIccRecords = mIccCard.get().getIccRecords();
+ // CdmaServiceStateTracker registers with IccCard to know
+ // when the Ruim card is ready. So create mIccCard before the ServiceStateTracker
+ mSST = new CdmaServiceStateTracker(this);
+ }
+
+ protected void init(Context context, PhoneNotifier notifier) {
+ mCM.setPhoneType(PhoneConstants.PHONE_TYPE_CDMA);
+ mCT = new CdmaCallTracker(this);
+ mCdmaSSM = CdmaSubscriptionSourceManager.getInstance(context, mCM, this,
+ EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
+ mSMS = new CdmaSMSDispatcher(this, mSmsStorageMonitor, mSmsUsageMonitor);
+ mDataConnectionTracker = new CdmaDataConnectionTracker (this);
+ mRuimPhoneBookInterfaceManager = new RuimPhoneBookInterfaceManager(this);
+ mRuimSmsInterfaceManager = new RuimSmsInterfaceManager(this, mSMS);
+ mSubInfo = new PhoneSubInfo(this);
+ mEriManager = new EriManager(this, context, EriManager.ERI_FROM_XML);
+
+ mCM.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+ registerForRuimRecordEvents();
+ mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+ mCM.registerForOn(this, EVENT_RADIO_ON, null);
+ mCM.setOnSuppServiceNotification(this, EVENT_SSN, null);
+ mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
+ mCM.setEmergencyCallbackMode(this, EVENT_EMERGENCY_CALLBACK_MODE_ENTER, null);
+
+ PowerManager pm
+ = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,LOG_TAG);
+
+ //Change the system setting
+ SystemProperties.set(TelephonyProperties.CURRENT_ACTIVE_PHONE,
+ Integer.toString(PhoneConstants.PHONE_TYPE_CDMA));
+
+ // This is needed to handle phone process crashes
+ String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
+ mIsPhoneInEcmState = inEcm.equals("true");
+ if (mIsPhoneInEcmState) {
+ // Send a message which will invoke handleExitEmergencyCallbackMode
+ mCM.exitEmergencyCallbackMode(obtainMessage(EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE));
+ }
+
+ // get the string that specifies the carrier OTA Sp number
+ mCarrierOtaSpNumSchema = SystemProperties.get(
+ TelephonyProperties.PROPERTY_OTASP_NUM_SCHEMA,"");
+
+ // Sets operator alpha property by retrieving from build-time system property
+ String operatorAlpha = SystemProperties.get("ro.cdma.home.operator.alpha");
+ setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, operatorAlpha);
+
+ // Sets operator numeric property by retrieving from build-time system property
+ String operatorNumeric = SystemProperties.get(PROPERTY_CDMA_HOME_OPERATOR_NUMERIC);
+ log("CDMAPhone: init set 'gsm.sim.operator.numeric' to operator='" +
+ operatorNumeric + "'");
+ setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operatorNumeric);
+
+ // Sets iso country property by retrieving from build-time system property
+ setIsoCountryProperty(operatorNumeric);
+
+ // Sets current entry in the telephony carrier table
+ updateCurrentCarrierInProvider(operatorNumeric);
+
+ // Notify voicemails.
+ notifier.notifyMessageWaitingChanged(this);
+ }
+
+ @Override
+ public void dispose() {
+ synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+ super.dispose();
+ log("dispose");
+
+ //Unregister from all former registered events
+ unregisterForRuimRecordEvents();
+ mCM.unregisterForAvailable(this); //EVENT_RADIO_AVAILABLE
+ mCM.unregisterForOffOrNotAvailable(this); //EVENT_RADIO_OFF_OR_NOT_AVAILABLE
+ mCM.unregisterForOn(this); //EVENT_RADIO_ON
+ mSST.unregisterForNetworkAttached(this); //EVENT_REGISTERED_TO_NETWORK
+ mCM.unSetOnSuppServiceNotification(this);
+ removeCallbacks(mExitEcmRunnable);
+
+ mPendingMmis.clear();
+
+ //Force all referenced classes to unregister their former registered events
+ mCT.dispose();
+ mDataConnectionTracker.dispose();
+ mSST.dispose();
+ mCdmaSSM.dispose(this);
+ mSMS.dispose();
+ mRuimPhoneBookInterfaceManager.dispose();
+ mRuimSmsInterfaceManager.dispose();
+ mSubInfo.dispose();
+ mEriManager.dispose();
+ }
+ }
+
+ @Override
+ public void removeReferences() {
+ log("removeReferences");
+ mRuimPhoneBookInterfaceManager = null;
+ mRuimSmsInterfaceManager = null;
+ mSubInfo = null;
+ mCT = null;
+ mSST = null;
+ mEriManager = null;
+ mExitEcmRunnable = null;
+ super.removeReferences();
+ }
+
+ @Override
+ protected void finalize() {
+ if(DBG) Log.d(LOG_TAG, "CDMAPhone finalized");
+ if (mWakeLock.isHeld()) {
+ Log.e(LOG_TAG, "UNEXPECTED; mWakeLock is held when finalizing.");
+ mWakeLock.release();
+ }
+ }
+
+ public ServiceState getServiceState() {
+ return mSST.ss;
+ }
+
+ public CallTracker getCallTracker() {
+ return mCT;
+ }
+
+ public PhoneConstants.State getState() {
+ return mCT.state;
+ }
+
+ public ServiceStateTracker getServiceStateTracker() {
+ return mSST;
+ }
+
+ public String getPhoneName() {
+ return "CDMA";
+ }
+
+ public int getPhoneType() {
+ return PhoneConstants.PHONE_TYPE_CDMA;
+ }
+
+ public boolean canTransfer() {
+ Log.e(LOG_TAG, "canTransfer: not possible in CDMA");
+ return false;
+ }
+
+ public CdmaCall getRingingCall() {
+ return mCT.ringingCall;
+ }
+
+ public void setMute(boolean muted) {
+ mCT.setMute(muted);
+ }
+
+ public boolean getMute() {
+ return mCT.getMute();
+ }
+
+ public void conference() throws CallStateException {
+ // three way calls in CDMA will be handled by feature codes
+ Log.e(LOG_TAG, "conference: not possible in CDMA");
+ }
+
+ public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
+ this.mCM.setPreferredVoicePrivacy(enable, onComplete);
+ }
+
+ public void getEnhancedVoicePrivacy(Message onComplete) {
+ this.mCM.getPreferredVoicePrivacy(onComplete);
+ }
+
+ public void clearDisconnected() {
+ mCT.clearDisconnected();
+ }
+
+ public DataActivityState getDataActivityState() {
+ DataActivityState ret = DataActivityState.NONE;
+
+ if (mSST.getCurrentDataConnectionState() == ServiceState.STATE_IN_SERVICE) {
+
+ switch (mDataConnectionTracker.getActivity()) {
+ case DATAIN:
+ ret = DataActivityState.DATAIN;
+ break;
+
+ case DATAOUT:
+ ret = DataActivityState.DATAOUT;
+ break;
+
+ case DATAINANDOUT:
+ ret = DataActivityState.DATAINANDOUT;
+ break;
+
+ case DORMANT:
+ ret = DataActivityState.DORMANT;
+ break;
+ }
+ }
+ return ret;
+ }
+
+ /*package*/ void
+ notifySignalStrength() {
+ mNotifier.notifySignalStrength(this);
+ }
+
+ public Connection
+ dial (String dialString) throws CallStateException {
+ // Need to make sure dialString gets parsed properly
+ String newDialString = PhoneNumberUtils.stripSeparators(dialString);
+ return mCT.dial(newDialString);
+ }
+
+ public Connection dial(String dialString, UUSInfo uusInfo) throws CallStateException {
+ throw new CallStateException("Sending UUS information NOT supported in CDMA!");
+ }
+
+ public SignalStrength getSignalStrength() {
+ return mSST.mSignalStrength;
+ }
+
+ public boolean
+ getMessageWaitingIndicator() {
+ return (getVoiceMessageCount() > 0);
+ }
+
+ public List<? extends MmiCode>
+ getPendingMmiCodes() {
+ return mPendingMmis;
+ }
+
+ public void registerForSuppServiceNotification(
+ Handler h, int what, Object obj) {
+ Log.e(LOG_TAG, "method registerForSuppServiceNotification is NOT supported in CDMA!");
+ }
+
+ public CdmaCall getBackgroundCall() {
+ return mCT.backgroundCall;
+ }
+
+ public boolean handleInCallMmiCommands(String dialString) {
+ Log.e(LOG_TAG, "method handleInCallMmiCommands is NOT supported in CDMA!");
+ return false;
+ }
+
+ boolean isInCall() {
+ CdmaCall.State foregroundCallState = getForegroundCall().getState();
+ CdmaCall.State backgroundCallState = getBackgroundCall().getState();
+ CdmaCall.State ringingCallState = getRingingCall().getState();
+
+ return (foregroundCallState.isAlive() || backgroundCallState.isAlive() || ringingCallState
+ .isAlive());
+ }
+
+ public void
+ setNetworkSelectionModeAutomatic(Message response) {
+ Log.e(LOG_TAG, "method setNetworkSelectionModeAutomatic is NOT supported in CDMA!");
+ }
+
+ public void unregisterForSuppServiceNotification(Handler h) {
+ Log.e(LOG_TAG, "method unregisterForSuppServiceNotification is NOT supported in CDMA!");
+ }
+
+ public void
+ acceptCall() throws CallStateException {
+ mCT.acceptCall();
+ }
+
+ public void
+ rejectCall() throws CallStateException {
+ mCT.rejectCall();
+ }
+
+ public void
+ switchHoldingAndActive() throws CallStateException {
+ mCT.switchWaitingOrHoldingAndActive();
+ }
+
+ public String getLine1Number() {
+ return mSST.getMdnNumber();
+ }
+
+ public String getCdmaPrlVersion(){
+ return mSST.getPrlVersion();
+ }
+
+ public String getCdmaMin() {
+ return mSST.getCdmaMin();
+ }
+
+ public boolean isMinInfoReady() {
+ return mSST.isMinInfoReady();
+ }
+
+ public void getCallWaiting(Message onComplete) {
+ mCM.queryCallWaiting(CommandsInterface.SERVICE_CLASS_VOICE, onComplete);
+ }
+
+ public void
+ setRadioPower(boolean power) {
+ mSST.setRadioPower(power);
+ }
+
+ public String getEsn() {
+ return mEsn;
+ }
+
+ public String getMeid() {
+ return mMeid;
+ }
+
+ //returns MEID or ESN in CDMA
+ public String getDeviceId() {
+ String id = getMeid();
+ if ((id == null) || id.matches("^0*$")) {
+ Log.d(LOG_TAG, "getDeviceId(): MEID is not initialized use ESN");
+ id = getEsn();
+ }
+ return id;
+ }
+
+ public String getDeviceSvn() {
+ Log.d(LOG_TAG, "getDeviceSvn(): return 0");
+ return "0";
+ }
+
+ public String getSubscriberId() {
+ return mSST.getImsi();
+ }
+
+ public String getImei() {
+ Log.e(LOG_TAG, "IMEI is not available in CDMA");
+ return null;
+ }
+
+ public boolean canConference() {
+ Log.e(LOG_TAG, "canConference: not possible in CDMA");
+ return false;
+ }
+
+ public CellLocation getCellLocation() {
+ return mSST.cellLoc;
+ }
+
+ public CdmaCall getForegroundCall() {
+ return mCT.foregroundCall;
+ }
+
+ public void
+ selectNetworkManually(OperatorInfo network,
+ Message response) {
+ Log.e(LOG_TAG, "selectNetworkManually: not possible in CDMA");
+ }
+
+ public void setOnPostDialCharacter(Handler h, int what, Object obj) {
+ mPostDialHandler = new Registrant(h, what, obj);
+ }
+
+ public boolean handlePinMmi(String dialString) {
+ CdmaMmiCode mmi = CdmaMmiCode.newFromDialString(dialString, this);
+
+ if (mmi == null) {
+ Log.e(LOG_TAG, "Mmi is NULL!");
+ return false;
+ } else if (mmi.isPukCommand()) {
+ mPendingMmis.add(mmi);
+ mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
+ mmi.processCode();
+ return true;
+ }
+ Log.e(LOG_TAG, "Unrecognized mmi!");
+ return false;
+ }
+
+ /**
+ * Removes the given MMI from the pending list and notifies registrants that
+ * it is complete.
+ *
+ * @param mmi MMI that is done
+ */
+ void onMMIDone(CdmaMmiCode mmi) {
+ /*
+ * Only notify complete if it's on the pending list. Otherwise, it's
+ * already been handled (eg, previously canceled).
+ */
+ if (mPendingMmis.remove(mmi)) {
+ mMmiCompleteRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
+ }
+ }
+
+ public void setLine1Number(String alphaTag, String number, Message onComplete) {
+ Log.e(LOG_TAG, "setLine1Number: not possible in CDMA");
+ }
+
+ public void setCallWaiting(boolean enable, Message onComplete) {
+ Log.e(LOG_TAG, "method setCallWaiting is NOT supported in CDMA!");
+ }
+
+ public void updateServiceLocation() {
+ mSST.enableSingleLocationUpdate();
+ }
+
+ public void setDataRoamingEnabled(boolean enable) {
+ mDataConnectionTracker.setDataOnRoamingEnabled(enable);
+ }
+
+ public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj) {
+ mCM.registerForCdmaOtaProvision(h, what, obj);
+ }
+
+ public void unregisterForCdmaOtaStatusChange(Handler h) {
+ mCM.unregisterForCdmaOtaProvision(h);
+ }
+
+ public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
+ mSST.registerForSubscriptionInfoReady(h, what, obj);
+ }
+
+ public void unregisterForSubscriptionInfoReady(Handler h) {
+ mSST.unregisterForSubscriptionInfoReady(h);
+ }
+
+ public void setOnEcbModeExitResponse(Handler h, int what, Object obj) {
+ mEcmExitRespRegistrant = new Registrant (h, what, obj);
+ }
+
+ public void unsetOnEcbModeExitResponse(Handler h) {
+ mEcmExitRespRegistrant.clear();
+ }
+
+ public void registerForCallWaiting(Handler h, int what, Object obj) {
+ mCT.registerForCallWaiting(h, what, obj);
+ }
+
+ public void unregisterForCallWaiting(Handler h) {
+ mCT.unregisterForCallWaiting(h);
+ }
+
+ public void
+ getNeighboringCids(Message response) {
+ /*
+ * This is currently not implemented. At least as of June
+ * 2009, there is no neighbor cell information available for
+ * CDMA because some party is resisting making this
+ * information readily available. Consequently, calling this
+ * function can have no useful effect. This situation may
+ * (and hopefully will) change in the future.
+ */
+ if (response != null) {
+ CommandException ce = new CommandException(
+ CommandException.Error.REQUEST_NOT_SUPPORTED);
+ AsyncResult.forMessage(response).exception = ce;
+ response.sendToTarget();
+ }
+ }
+
+ public PhoneConstants.DataState getDataConnectionState(String apnType) {
+ PhoneConstants.DataState ret = PhoneConstants.DataState.DISCONNECTED;
+
+ if (mSST == null) {
+ // Radio Technology Change is ongoning, dispose() and removeReferences() have
+ // already been called
+
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ } else if (mSST.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE) {
+ // If we're out of service, open TCP sockets may still work
+ // but no data will flow
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ } else if (mDataConnectionTracker.isApnTypeEnabled(apnType) == false ||
+ mDataConnectionTracker.isApnTypeActive(apnType) == false) {
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ } else {
+ switch (mDataConnectionTracker.getState(apnType)) {
+ case FAILED:
+ case IDLE:
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ break;
+
+ case CONNECTED:
+ case DISCONNECTING:
+ if ( mCT.state != PhoneConstants.State.IDLE
+ && !mSST.isConcurrentVoiceAndDataAllowed()) {
+ ret = PhoneConstants.DataState.SUSPENDED;
+ } else {
+ ret = PhoneConstants.DataState.CONNECTED;
+ }
+ break;
+
+ case INITING:
+ case CONNECTING:
+ case SCANNING:
+ ret = PhoneConstants.DataState.CONNECTING;
+ break;
+ }
+ }
+
+ log("getDataConnectionState apnType=" + apnType + " ret=" + ret);
+ return ret;
+ }
+
+ public void sendUssdResponse(String ussdMessge) {
+ Log.e(LOG_TAG, "sendUssdResponse: not possible in CDMA");
+ }
+
+ public void sendDtmf(char c) {
+ if (!PhoneNumberUtils.is12Key(c)) {
+ Log.e(LOG_TAG,
+ "sendDtmf called with invalid character '" + c + "'");
+ } else {
+ if (mCT.state == PhoneConstants.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 sendBurstDtmf(String dtmfString, int on, int off, Message onComplete) {
+ boolean check = true;
+ for (int itr = 0;itr < dtmfString.length(); itr++) {
+ if (!PhoneNumberUtils.is12Key(dtmfString.charAt(itr))) {
+ Log.e(LOG_TAG,
+ "sendDtmf called with invalid character '" + dtmfString.charAt(itr)+ "'");
+ check = false;
+ break;
+ }
+ }
+ if ((mCT.state == PhoneConstants.State.OFFHOOK)&&(check)) {
+ mCM.sendBurstDtmf(dtmfString, on, off, onComplete);
+ }
+ }
+
+ public void getAvailableNetworks(Message response) {
+ Log.e(LOG_TAG, "getAvailableNetworks: not possible in CDMA");
+ }
+
+ public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode, Message onComplete) {
+ Log.e(LOG_TAG, "setOutgoingCallerIdDisplay: not possible in CDMA");
+ }
+
+ public void enableLocationUpdates() {
+ mSST.enableLocationUpdates();
+ }
+
+ public void disableLocationUpdates() {
+ mSST.disableLocationUpdates();
+ }
+
+ public void getDataCallList(Message response) {
+ mCM.getDataCallList(response);
+ }
+
+ public boolean getDataRoamingEnabled() {
+ return mDataConnectionTracker.getDataOnRoamingEnabled();
+ }
+
+ public void setVoiceMailNumber(String alphaTag,
+ String voiceMailNumber,
+ Message onComplete) {
+ Message resp;
+ mVmNumber = voiceMailNumber;
+ resp = obtainMessage(EVENT_SET_VM_NUMBER_DONE, 0, 0, onComplete);
+ mIccRecords.setVoiceMailNumber(alphaTag, mVmNumber, resp);
+ }
+
+ public String getVoiceMailNumber() {
+ String number = null;
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ // TODO: The default value of voicemail number should be read from a system property
+
+ // Read platform settings for dynamic voicemail number
+ if (getContext().getResources().getBoolean(com.android.internal
+ .R.bool.config_telephony_use_own_number_for_voicemail)) {
+ number = sp.getString(VM_NUMBER_CDMA, getLine1Number());
+ } else {
+ number = sp.getString(VM_NUMBER_CDMA, "*86");
+ }
+ return number;
+ }
+
+ /* Returns Number of Voicemails
+ * @hide
+ */
+ public int getVoiceMessageCount() {
+ int voicemailCount = mIccRecords.getVoiceMessageCount();
+ // If mRuimRecords.getVoiceMessageCount returns zero, then there is possibility
+ // that phone was power cycled and would have lost the voicemail count.
+ // So get the count from preferences.
+ if (voicemailCount == 0) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ voicemailCount = sp.getInt(VM_COUNT_CDMA, 0);
+ }
+ return voicemailCount;
+ }
+
+ public String getVoiceMailAlphaTag() {
+ // TODO: Where can we get this value has to be clarified with QC.
+ String ret = "";//TODO: Remove = "", if we know where to get this value.
+
+ //ret = mSIMRecords.getVoiceMailAlphaTag();
+
+ if (ret == null || ret.length() == 0) {
+ return mContext.getText(
+ com.android.internal.R.string.defaultVoiceMailAlphaTag).toString();
+ }
+
+ return ret;
+ }
+
+ public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
+ Log.e(LOG_TAG, "getCallForwardingOption: not possible in CDMA");
+ }
+
+ public void setCallForwardingOption(int commandInterfaceCFAction,
+ int commandInterfaceCFReason,
+ String dialingNumber,
+ int timerSeconds,
+ Message onComplete) {
+ Log.e(LOG_TAG, "setCallForwardingOption: not possible in CDMA");
+ }
+
+ public void
+ getOutgoingCallerIdDisplay(Message onComplete) {
+ Log.e(LOG_TAG, "getOutgoingCallerIdDisplay: not possible in CDMA");
+ }
+
+ public boolean
+ getCallForwardingIndicator() {
+ Log.e(LOG_TAG, "getCallForwardingIndicator: not possible in CDMA");
+ return false;
+ }
+
+ public void explicitCallTransfer() {
+ Log.e(LOG_TAG, "explicitCallTransfer: not possible in CDMA");
+ }
+
+ public String getLine1AlphaTag() {
+ Log.e(LOG_TAG, "getLine1AlphaTag: not possible in CDMA");
+ return null;
+ }
+
+ /**
+ * Notify any interested party of a Phone state change {@link PhoneConstants.State}
+ */
+ /*package*/ void notifyPhoneStateChanged() {
+ mNotifier.notifyPhoneState(this);
+ }
+
+ /**
+ * Notify registrants of a change in the call state. This notifies changes in {@link Call.State}
+ * Use this when changes in the precise call state are needed, else use notifyPhoneStateChanged.
+ */
+ /*package*/ void notifyPreciseCallStateChanged() {
+ /* we'd love it if this was package-scoped*/
+ super.notifyPreciseCallStateChangedP();
+ }
+
+ void notifyServiceStateChanged(ServiceState ss) {
+ super.notifyServiceStateChangedP(ss);
+ }
+
+ void notifyLocationChanged() {
+ mNotifier.notifyCellLocation(this);
+ }
+
+ /*package*/ void notifyNewRingingConnection(Connection c) {
+ /* we'd love it if this was package-scoped*/
+ super.notifyNewRingingConnectionP(c);
+ }
+
+ /*package*/ void notifyDisconnect(Connection cn) {
+ mDisconnectRegistrants.notifyResult(cn);
+ }
+
+ void notifyUnknownConnection() {
+ mUnknownConnectionRegistrants.notifyResult(this);
+ }
+
+ public boolean isInEmergencyCall() {
+ return mCT.isInEmergencyCall();
+ }
+
+ public boolean isInEcm() {
+ return mIsPhoneInEcmState;
+ }
+
+ void sendEmergencyCallbackModeChange(){
+ //Send an Intent
+ Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ intent.putExtra(PhoneConstants.PHONE_IN_ECM_STATE, mIsPhoneInEcmState);
+ ActivityManagerNative.broadcastStickyIntent(intent,null);
+ if (DBG) Log.d(LOG_TAG, "sendEmergencyCallbackModeChange");
+ }
+
+ @Override
+ public void exitEmergencyCallbackMode() {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ // Send a message which will invoke handleExitEmergencyCallbackMode
+ mCM.exitEmergencyCallbackMode(obtainMessage(EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE));
+ }
+
+ private void handleEnterEmergencyCallbackMode(Message msg) {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleEnterEmergencyCallbackMode,mIsPhoneInEcmState= "
+ + mIsPhoneInEcmState);
+ }
+ // if phone is not in Ecm mode, and it's changed to Ecm mode
+ if (mIsPhoneInEcmState == false) {
+ mIsPhoneInEcmState = true;
+ // notify change
+ sendEmergencyCallbackModeChange();
+ setSystemProperty(TelephonyProperties.PROPERTY_INECM_MODE, "true");
+
+ // Post this runnable so we will automatically exit
+ // if no one invokes exitEmergencyCallbackMode() directly.
+ long delayInMillis = SystemProperties.getLong(
+ TelephonyProperties.PROPERTY_ECM_EXIT_TIMER, DEFAULT_ECM_EXIT_TIMER_VALUE);
+ postDelayed(mExitEcmRunnable, delayInMillis);
+ // We don't want to go to sleep while in Ecm
+ mWakeLock.acquire();
+ }
+ }
+
+ private void handleExitEmergencyCallbackMode(Message msg) {
+ AsyncResult ar = (AsyncResult)msg.obj;
+ if (DBG) {
+ Log.d(LOG_TAG, "handleExitEmergencyCallbackMode,ar.exception , mIsPhoneInEcmState "
+ + ar.exception + mIsPhoneInEcmState);
+ }
+ // Remove pending exit Ecm runnable, if any
+ removeCallbacks(mExitEcmRunnable);
+
+ if (mEcmExitRespRegistrant != null) {
+ mEcmExitRespRegistrant.notifyRegistrant(ar);
+ }
+ // if exiting ecm success
+ if (ar.exception == null) {
+ if (mIsPhoneInEcmState) {
+ mIsPhoneInEcmState = false;
+ setSystemProperty(TelephonyProperties.PROPERTY_INECM_MODE, "false");
+ }
+ // send an Intent
+ sendEmergencyCallbackModeChange();
+ // Re-initiate data connection
+ mDataConnectionTracker.setInternalDataEnabled(true);
+ }
+ }
+
+ /**
+ * Handle to cancel or restart Ecm timer in emergency call back mode
+ * if action is CANCEL_ECM_TIMER, cancel Ecm timer and notify apps the timer is canceled;
+ * otherwise, restart Ecm timer and notify apps the timer is restarted.
+ */
+ void handleTimerInEmergencyCallbackMode(int action) {
+ switch(action) {
+ case CANCEL_ECM_TIMER:
+ removeCallbacks(mExitEcmRunnable);
+ mEcmTimerResetRegistrants.notifyResult(Boolean.TRUE);
+ break;
+ case RESTART_ECM_TIMER:
+ long delayInMillis = SystemProperties.getLong(
+ TelephonyProperties.PROPERTY_ECM_EXIT_TIMER, DEFAULT_ECM_EXIT_TIMER_VALUE);
+ postDelayed(mExitEcmRunnable, delayInMillis);
+ mEcmTimerResetRegistrants.notifyResult(Boolean.FALSE);
+ break;
+ default:
+ Log.e(LOG_TAG, "handleTimerInEmergencyCallbackMode, unsupported action " + action);
+ }
+ }
+
+ /**
+ * Registration point for Ecm timer reset
+ * @param h handler to notify
+ * @param what User-defined message code
+ * @param obj placed in Message.obj
+ */
+ public void registerForEcmTimerReset(Handler h, int what, Object obj) {
+ mEcmTimerResetRegistrants.addUnique(h, what, obj);
+ }
+
+ public void unregisterForEcmTimerReset(Handler h) {
+ mEcmTimerResetRegistrants.remove(h);
+ }
+
+ @Override
+ 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.getDeviceIdentity(obtainMessage(EVENT_GET_DEVICE_IDENTITY_DONE));
+ }
+ break;
+
+ case EVENT_GET_BASEBAND_VERSION_DONE:{
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ if (DBG) Log.d(LOG_TAG, "Baseband version: " + ar.result);
+ setSystemProperty(TelephonyProperties.PROPERTY_BASEBAND_VERSION, (String)ar.result);
+ }
+ break;
+
+ case EVENT_GET_DEVICE_IDENTITY_DONE:{
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+ break;
+ }
+ String[] respId = (String[])ar.result;
+ mImei = respId[0];
+ mImeiSv = respId[1];
+ mEsn = respId[2];
+ mMeid = respId[3];
+ }
+ break;
+
+ case EVENT_EMERGENCY_CALLBACK_MODE_ENTER:{
+ handleEnterEmergencyCallbackMode(msg);
+ }
+ break;
+
+ case EVENT_ICC_RECORD_EVENTS:
+ ar = (AsyncResult)msg.obj;
+ processIccRecordEvents((Integer)ar.result);
+ break;
+
+ case EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE:{
+ handleExitEmergencyCallbackMode(msg);
+ }
+ break;
+
+ case EVENT_RUIM_RECORDS_LOADED:{
+ Log.d(LOG_TAG, "Event EVENT_RUIM_RECORDS_LOADED Received");
+ updateCurrentCarrierInProvider();
+ }
+ break;
+
+ case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:{
+ Log.d(LOG_TAG, "Event EVENT_RADIO_OFF_OR_NOT_AVAILABLE Received");
+ }
+ break;
+
+ case EVENT_RADIO_ON:{
+ Log.d(LOG_TAG, "Event EVENT_RADIO_ON Received");
+ handleCdmaSubscriptionSource(mCdmaSSM.getCdmaSubscriptionSource());
+ }
+ break;
+
+ case EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED:{
+ Log.d(LOG_TAG, "EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED");
+ handleCdmaSubscriptionSource(mCdmaSSM.getCdmaSubscriptionSource());
+ }
+ break;
+
+ case EVENT_SSN:{
+ Log.d(LOG_TAG, "Event EVENT_SSN Received");
+ }
+ break;
+
+ case EVENT_REGISTERED_TO_NETWORK:{
+ Log.d(LOG_TAG, "Event EVENT_REGISTERED_TO_NETWORK Received");
+ }
+ break;
+
+ case EVENT_NV_READY:{
+ Log.d(LOG_TAG, "Event EVENT_NV_READY Received");
+ prepareEri();
+ }
+ break;
+
+ case EVENT_SET_VM_NUMBER_DONE:{
+ ar = (AsyncResult)msg.obj;
+ if (IccException.class.isInstance(ar.exception)) {
+ storeVoiceMailNumber(mVmNumber);
+ ar.exception = null;
+ }
+ onComplete = (Message) ar.userObj;
+ if (onComplete != null) {
+ AsyncResult.forMessage(onComplete, ar.result, ar.exception);
+ onComplete.sendToTarget();
+ }
+ }
+ break;
+
+ default:{
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ private void processIccRecordEvents(int eventCode) {
+ switch (eventCode) {
+ case RuimRecords.EVENT_MWI:
+ notifyMessageWaitingIndicator();
+ break;
+
+ default:
+ Log.e(LOG_TAG,"Unknown icc records event code " + eventCode);
+ break;
+ }
+ }
+
+ /**
+ * Handles the call to get the subscription source
+ *
+ * @param newSubscriptionSource holds the new CDMA subscription source value
+ */
+ private void handleCdmaSubscriptionSource(int newSubscriptionSource) {
+ if (newSubscriptionSource != mCdmaSubscriptionSource) {
+ mCdmaSubscriptionSource = newSubscriptionSource;
+ if (newSubscriptionSource == CDMA_SUBSCRIPTION_NV) {
+ // NV is ready when subscription source is NV
+ sendMessage(obtainMessage(EVENT_NV_READY));
+ }
+ }
+ }
+
+ /**
+ * Retrieves the PhoneSubInfo of the CDMAPhone
+ */
+ public PhoneSubInfo getPhoneSubInfo() {
+ return mSubInfo;
+ }
+
+ /**
+ * Retrieves the IccSmsInterfaceManager of the CDMAPhone
+ */
+ public IccSmsInterfaceManager getIccSmsInterfaceManager() {
+ return mRuimSmsInterfaceManager;
+ }
+
+ /**
+ * Retrieves the IccPhoneBookInterfaceManager of the CDMAPhone
+ */
+ public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager() {
+ return mRuimPhoneBookInterfaceManager;
+ }
+
+ public void registerForEriFileLoaded(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mEriFileLoadedRegistrants.add(r);
+ }
+
+ public void unregisterForEriFileLoaded(Handler h) {
+ mEriFileLoadedRegistrants.remove(h);
+ }
+
+ // override for allowing access from other classes of this package
+ /**
+ * {@inheritDoc}
+ */
+ public final void setSystemProperty(String property, String value) {
+ super.setSystemProperty(property, value);
+ }
+
+ /**
+ * Activate or deactivate cell broadcast SMS.
+ *
+ * @param activate 0 = activate, 1 = deactivate
+ * @param response Callback message is empty on completion
+ */
+ public void activateCellBroadcastSms(int activate, Message response) {
+ Log.e(LOG_TAG, "[CDMAPhone] activateCellBroadcastSms() is obsolete; use SmsManager");
+ response.sendToTarget();
+ }
+
+ /**
+ * Query the current configuration of cdma cell broadcast SMS.
+ *
+ * @param response Callback message is empty on completion
+ */
+ public void getCellBroadcastSmsConfig(Message response) {
+ Log.e(LOG_TAG, "[CDMAPhone] getCellBroadcastSmsConfig() is obsolete; use SmsManager");
+ response.sendToTarget();
+ }
+
+ /**
+ * Configure cdma cell broadcast SMS.
+ *
+ * @param response Callback message is empty on completion
+ */
+ public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
+ Log.e(LOG_TAG, "[CDMAPhone] setCellBroadcastSmsConfig() is obsolete; use SmsManager");
+ response.sendToTarget();
+ }
+
+ /**
+ * Returns true if OTA Service Provisioning needs to be performed.
+ */
+ @Override
+ public boolean needsOtaServiceProvisioning() {
+ return mSST.getOtasp() != ServiceStateTracker.OTASP_NOT_NEEDED;
+ }
+
+ private static final String IS683A_FEATURE_CODE = "*228";
+ private static final int IS683A_FEATURE_CODE_NUM_DIGITS = 4;
+ private static final int IS683A_SYS_SEL_CODE_NUM_DIGITS = 2;
+ private static final int IS683A_SYS_SEL_CODE_OFFSET = 4;
+
+ private static final int IS683_CONST_800MHZ_A_BAND = 0;
+ private static final int IS683_CONST_800MHZ_B_BAND = 1;
+ private static final int IS683_CONST_1900MHZ_A_BLOCK = 2;
+ private static final int IS683_CONST_1900MHZ_B_BLOCK = 3;
+ private static final int IS683_CONST_1900MHZ_C_BLOCK = 4;
+ private static final int IS683_CONST_1900MHZ_D_BLOCK = 5;
+ private static final int IS683_CONST_1900MHZ_E_BLOCK = 6;
+ private static final int IS683_CONST_1900MHZ_F_BLOCK = 7;
+ private static final int INVALID_SYSTEM_SELECTION_CODE = -1;
+
+ private static boolean isIs683OtaSpDialStr(String dialStr) {
+ int sysSelCodeInt;
+ boolean isOtaspDialString = false;
+ int dialStrLen = dialStr.length();
+
+ if (dialStrLen == IS683A_FEATURE_CODE_NUM_DIGITS) {
+ if (dialStr.equals(IS683A_FEATURE_CODE)) {
+ isOtaspDialString = true;
+ }
+ } else {
+ sysSelCodeInt = extractSelCodeFromOtaSpNum(dialStr);
+ switch (sysSelCodeInt) {
+ case IS683_CONST_800MHZ_A_BAND:
+ case IS683_CONST_800MHZ_B_BAND:
+ case IS683_CONST_1900MHZ_A_BLOCK:
+ case IS683_CONST_1900MHZ_B_BLOCK:
+ case IS683_CONST_1900MHZ_C_BLOCK:
+ case IS683_CONST_1900MHZ_D_BLOCK:
+ case IS683_CONST_1900MHZ_E_BLOCK:
+ case IS683_CONST_1900MHZ_F_BLOCK:
+ isOtaspDialString = true;
+ break;
+ default:
+ break;
+ }
+ }
+ return isOtaspDialString;
+ }
+ /**
+ * This function extracts the system selection code from the dial string.
+ */
+ private static int extractSelCodeFromOtaSpNum(String dialStr) {
+ int dialStrLen = dialStr.length();
+ int sysSelCodeInt = INVALID_SYSTEM_SELECTION_CODE;
+
+ if ((dialStr.regionMatches(0, IS683A_FEATURE_CODE,
+ 0, IS683A_FEATURE_CODE_NUM_DIGITS)) &&
+ (dialStrLen >= (IS683A_FEATURE_CODE_NUM_DIGITS +
+ IS683A_SYS_SEL_CODE_NUM_DIGITS))) {
+ // Since we checked the condition above, the system selection code
+ // extracted from dialStr will not cause any exception
+ sysSelCodeInt = Integer.parseInt (
+ dialStr.substring (IS683A_FEATURE_CODE_NUM_DIGITS,
+ IS683A_FEATURE_CODE_NUM_DIGITS + IS683A_SYS_SEL_CODE_NUM_DIGITS));
+ }
+ if (DBG) Log.d(LOG_TAG, "extractSelCodeFromOtaSpNum " + sysSelCodeInt);
+ return sysSelCodeInt;
+ }
+
+ /**
+ * This function checks if the system selection code extracted from
+ * the dial string "sysSelCodeInt' is the system selection code specified
+ * in the carrier ota sp number schema "sch".
+ */
+ private static boolean
+ checkOtaSpNumBasedOnSysSelCode (int sysSelCodeInt, String sch[]) {
+ boolean isOtaSpNum = false;
+ try {
+ // Get how many number of system selection code ranges
+ int selRc = Integer.parseInt((String)sch[1]);
+ for (int i = 0; i < selRc; i++) {
+ if (!TextUtils.isEmpty(sch[i+2]) && !TextUtils.isEmpty(sch[i+3])) {
+ int selMin = Integer.parseInt((String)sch[i+2]);
+ int selMax = Integer.parseInt((String)sch[i+3]);
+ // Check if the selection code extracted from the dial string falls
+ // within any of the range pairs specified in the schema.
+ if ((sysSelCodeInt >= selMin) && (sysSelCodeInt <= selMax)) {
+ isOtaSpNum = true;
+ break;
+ }
+ }
+ }
+ } catch (NumberFormatException ex) {
+ // If the carrier ota sp number schema is not correct, we still allow dial
+ // and only log the error:
+ Log.e(LOG_TAG, "checkOtaSpNumBasedOnSysSelCode, error", ex);
+ }
+ return isOtaSpNum;
+ }
+
+ // Define the pattern/format for carrier specified OTASP number schema.
+ // It separates by comma and/or whitespace.
+ private static Pattern pOtaSpNumSchema = Pattern.compile("[,\\s]+");
+
+ /**
+ * The following function checks if a dial string is a carrier specified
+ * OTASP number or not by checking against the OTASP number schema stored
+ * in PROPERTY_OTASP_NUM_SCHEMA.
+ *
+ * Currently, there are 2 schemas for carriers to specify the OTASP number:
+ * 1) Use system selection code:
+ * The schema is:
+ * SELC,the # of code pairs,min1,max1,min2,max2,...
+ * e.g "SELC,3,10,20,30,40,60,70" indicates that there are 3 pairs of
+ * selection codes, and they are {10,20}, {30,40} and {60,70} respectively.
+ *
+ * 2) Use feature code:
+ * The schema is:
+ * "FC,length of feature code,feature code".
+ * e.g "FC,2,*2" indicates that the length of the feature code is 2,
+ * and the code itself is "*2".
+ */
+ private boolean isCarrierOtaSpNum(String dialStr) {
+ boolean isOtaSpNum = false;
+ int sysSelCodeInt = extractSelCodeFromOtaSpNum(dialStr);
+ if (sysSelCodeInt == INVALID_SYSTEM_SELECTION_CODE) {
+ return isOtaSpNum;
+ }
+ // mCarrierOtaSpNumSchema is retrieved from PROPERTY_OTASP_NUM_SCHEMA:
+ if (!TextUtils.isEmpty(mCarrierOtaSpNumSchema)) {
+ Matcher m = pOtaSpNumSchema.matcher(mCarrierOtaSpNumSchema);
+ if (DBG) {
+ Log.d(LOG_TAG, "isCarrierOtaSpNum,schema" + mCarrierOtaSpNumSchema);
+ }
+
+ if (m.find()) {
+ String sch[] = pOtaSpNumSchema.split(mCarrierOtaSpNumSchema);
+ // If carrier uses system selection code mechanism
+ if (!TextUtils.isEmpty(sch[0]) && sch[0].equals("SELC")) {
+ if (sysSelCodeInt!=INVALID_SYSTEM_SELECTION_CODE) {
+ isOtaSpNum=checkOtaSpNumBasedOnSysSelCode(sysSelCodeInt,sch);
+ } else {
+ if (DBG) {
+ Log.d(LOG_TAG, "isCarrierOtaSpNum,sysSelCodeInt is invalid");
+ }
+ }
+ } else if (!TextUtils.isEmpty(sch[0]) && sch[0].equals("FC")) {
+ int fcLen = Integer.parseInt((String)sch[1]);
+ String fc = (String)sch[2];
+ if (dialStr.regionMatches(0,fc,0,fcLen)) {
+ isOtaSpNum = true;
+ } else {
+ if (DBG) Log.d(LOG_TAG, "isCarrierOtaSpNum,not otasp number");
+ }
+ } else {
+ if (DBG) {
+ Log.d(LOG_TAG, "isCarrierOtaSpNum,ota schema not supported" + sch[0]);
+ }
+ }
+ } else {
+ if (DBG) {
+ Log.d(LOG_TAG, "isCarrierOtaSpNum,ota schema pattern not right" +
+ mCarrierOtaSpNumSchema);
+ }
+ }
+ } else {
+ if (DBG) Log.d(LOG_TAG, "isCarrierOtaSpNum,ota schema pattern empty");
+ }
+ return isOtaSpNum;
+ }
+
+ /**
+ * isOTASPNumber: checks a given number against the IS-683A OTASP dial string and carrier
+ * OTASP dial string.
+ *
+ * @param dialStr the number to look up.
+ * @return true if the number is in IS-683A OTASP dial string or carrier OTASP dial string
+ */
+ @Override
+ public boolean isOtaSpNumber(String dialStr){
+ boolean isOtaSpNum = false;
+ String dialableStr = PhoneNumberUtils.extractNetworkPortionAlt(dialStr);
+ if (dialableStr != null) {
+ isOtaSpNum = isIs683OtaSpDialStr(dialableStr);
+ if (isOtaSpNum == false) {
+ isOtaSpNum = isCarrierOtaSpNum(dialableStr);
+ }
+ }
+ if (DBG) Log.d(LOG_TAG, "isOtaSpNumber " + isOtaSpNum);
+ return isOtaSpNum;
+ }
+
+ @Override
+ public int getCdmaEriIconIndex() {
+ return getServiceState().getCdmaEriIconIndex();
+ }
+
+ /**
+ * Returns the CDMA ERI icon mode,
+ * 0 - ON
+ * 1 - FLASHING
+ */
+ @Override
+ public int getCdmaEriIconMode() {
+ return getServiceState().getCdmaEriIconMode();
+ }
+
+ /**
+ * Returns the CDMA ERI text,
+ */
+ @Override
+ public String getCdmaEriText() {
+ int roamInd = getServiceState().getCdmaRoamingIndicator();
+ int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator();
+ return mEriManager.getCdmaEriText(roamInd, defRoamInd);
+ }
+
+ /**
+ * Store the voicemail number in preferences
+ */
+ private void storeVoiceMailNumber(String number) {
+ // Update the preference value of voicemail number
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(VM_NUMBER_CDMA, number);
+ editor.apply();
+ }
+
+ /**
+ * Sets PROPERTY_ICC_OPERATOR_ISO_COUNTRY property
+ *
+ */
+ private void setIsoCountryProperty(String operatorNumeric) {
+ if (TextUtils.isEmpty(operatorNumeric)) {
+ setSystemProperty(PROPERTY_ICC_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);
+ }
+
+ setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, iso);
+ }
+ }
+
+ /**
+ * Sets the "current" field in the telephony provider according to the
+ * build-time operator numeric property
+ *
+ * @return true for success; false otherwise.
+ */
+ boolean updateCurrentCarrierInProvider(String operatorNumeric) {
+ if (!TextUtils.isEmpty(operatorNumeric)) {
+ try {
+ Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
+ ContentValues map = new ContentValues();
+ map.put(Telephony.Carriers.NUMERIC, operatorNumeric);
+ log("updateCurrentCarrierInProvider from system: numeric=" + operatorNumeric);
+ getContext().getContentResolver().insert(uri, map);
+
+ // Updates MCC MNC device configuration information
+ MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
+
+ return true;
+ } catch (SQLException e) {
+ Log.e(LOG_TAG, "Can't store current operator", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the "current" field in the telephony provider according to the SIM's operator.
+ * Implemented in {@link CDMALTEPhone} for CDMA/LTE devices.
+ *
+ * @return true for success; false otherwise.
+ */
+ boolean updateCurrentCarrierInProvider() {
+ return true;
+ }
+
+ public void prepareEri() {
+ mEriManager.loadEriFile();
+ if(mEriManager.isEriFileLoaded()) {
+ // when the ERI file is loaded
+ log("ERI read, notify registrants");
+ mEriFileLoadedRegistrants.notifyRegistrants();
+ }
+ }
+
+ public boolean isEriFileLoaded() {
+ return mEriManager.isEriFileLoaded();
+ }
+
+ private void registerForRuimRecordEvents() {
+ mIccRecords.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null);
+ mIccRecords.registerForRecordsLoaded(this, EVENT_RUIM_RECORDS_LOADED, null);
+ }
+
+ private void unregisterForRuimRecordEvents() {
+ mIccRecords.unregisterForRecordsEvents(this);
+ mIccRecords.unregisterForRecordsLoaded(this);
+ }
+
+ protected void log(String s) {
+ if (DBG)
+ Log.d(LOG_TAG, "[CDMAPhone] " + s);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("CDMAPhone extends:");
+ super.dump(fd, pw, args);
+ pw.println(" mVmNumber=" + mVmNumber);
+ pw.println(" mCT=" + mCT);
+ pw.println(" mSST=" + mSST);
+ pw.println(" mCdmaSSM=" + mCdmaSSM);
+ pw.println(" mPendingMmis=" + mPendingMmis);
+ pw.println(" mRuimPhoneBookInterfaceManager=" + mRuimPhoneBookInterfaceManager);
+ pw.println(" mRuimSmsInterfaceManager=" + mRuimSmsInterfaceManager);
+ pw.println(" mCdmaSubscriptionSource=" + mCdmaSubscriptionSource);
+ pw.println(" mSubInfo=" + mSubInfo);
+ pw.println(" mEriManager=" + mEriManager);
+ pw.println(" mWakeLock=" + mWakeLock);
+ pw.println(" mIsPhoneInEcmState=" + mIsPhoneInEcmState);
+ if (VDBG) pw.println(" mImei=" + mImei);
+ if (VDBG) pw.println(" mImeiSv=" + mImeiSv);
+ if (VDBG) pw.println(" mEsn=" + mEsn);
+ if (VDBG) pw.println(" mMeid=" + mMeid);
+ pw.println(" mCarrierOtaSpNumSchema=" + mCarrierOtaSpNumSchema);
+ pw.println(" getCdmaEriIconIndex()=" + getCdmaEriIconIndex());
+ pw.println(" getCdmaEriIconMode()=" + getCdmaEriIconMode());
+ pw.println(" getCdmaEriText()=" + getCdmaEriText());
+ pw.println(" isMinInfoReady()=" + isMinInfoReady());
+ pw.println(" isCspPlmnEnabled()=" + isCspPlmnEnabled());
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CallFailCause.java b/src/java/com/android/internal/telephony/cdma/CallFailCause.java
new file mode 100644
index 0000000..ad6c23c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CallFailCause.java
@@ -0,0 +1,59 @@
+/*
+ * 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.cdma;
+
+/**
+ * CDMA Call fail causes covering all the possible failures that are
+ * needed to be distinguished by the UI. CDMA call failure reasons
+ * are derived from the possible call failure scenarios described
+ * in "CDMA IS2000 - Release A (C.S0005-A v6.0)" standard.
+ *
+ * {@hide}
+ *
+ */
+public interface CallFailCause {
+ static final int NORMAL_CLEARING = 16;
+ // Busy Tone
+ static final int USER_BUSY = 17;
+
+ static final int NORMAL_UNSPECIFIED = 31;
+
+ // Congestion Tone
+ static final int NO_CIRCUIT_AVAIL = 34;
+
+ // others
+ static final int ACM_LIMIT_EXCEEDED = 68;
+ static final int CALL_BARRED = 240;
+ static final int FDN_BLOCKED = 241;
+
+ static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000;
+ static final int CDMA_DROP = 1001;
+ static final int CDMA_INTERCEPT = 1002;
+ static final int CDMA_REORDER = 1003;
+ static final int CDMA_SO_REJECT = 1004;
+ static final int CDMA_RETRY_ORDER = 1005;
+ static final int CDMA_ACCESS_FAILURE = 1006;
+ static final int CDMA_PREEMPTED = 1007;
+
+ // For non-emergency number dialed while in emergency callback mode.
+ static final int CDMA_NOT_EMERGENCY = 1008;
+
+ // Access Blocked by CDMA Network.
+ static final int CDMA_ACCESS_BLOCKED = 1009;
+
+ static final int ERROR_UNSPECIFIED = 0xffff;
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaCall.java b/src/java/com/android/internal/telephony/cdma/CdmaCall.java
new file mode 100644
index 0000000..4ad61bb
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaCall.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.cdma;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.DriverCall;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.Call.State;
+
+/**
+ * {@hide}
+ */
+public final class CdmaCall extends Call {
+ /*************************** Instance Variables **************************/
+
+ /*package*/ ArrayList<Connection> connections = new ArrayList<Connection>();
+ /*package*/ State state = State.IDLE;
+ /*package*/ CdmaCallTracker 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*/
+ CdmaCall (CdmaCallTracker owner) {
+ this.owner = owner;
+ }
+
+ public void dispose() {
+ }
+
+ /************************** Overridden from Call *************************/
+ public List<Connection>
+ getConnections() {
+ // FIXME should return Collections.unmodifiableList();
+ return connections;
+ }
+
+ public State
+ getState() {
+ return state;
+ }
+
+ public Phone
+ getPhone() {
+ return owner.phone;
+ }
+
+ 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 CdmaConnection
+
+ /*package*/ void
+ attach(Connection conn, DriverCall dc) {
+ connections.add(conn);
+
+ state = stateFromDCState (dc.state);
+ }
+
+ /*package*/ void
+ attachFake(Connection conn, State state) {
+ connections.add(conn);
+
+ this.state = state;
+ }
+
+ /**
+ * Called by CdmaConnection when it has disconnected
+ */
+ void
+ connectionDisconnected(CdmaConnection 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(CdmaConnection conn) {
+ connections.remove(conn);
+
+ if (connections.size() == 0) {
+ state = State.IDLE;
+ }
+ }
+
+ /*package*/ boolean
+ update (CdmaConnection 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() == CdmaCallTracker.MAX_CONNECTIONS_PER_CALL;
+ }
+
+ //***** Called from CdmaCallTracker
+
+
+ /**
+ * 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++) {
+ CdmaConnection cn = (CdmaConnection)connections.get(i);
+
+ cn.onHangupLocal();
+ }
+ state = State.DISCONNECTING;
+ }
+
+ /**
+ * Called when it's time to clean up disconnected Connection objects
+ */
+ void clearDisconnected() {
+ for (int i = connections.size() - 1 ; i >= 0 ; i--) {
+ CdmaConnection cn = (CdmaConnection)connections.get(i);
+
+ if (cn.getState() == State.DISCONNECTED) {
+ connections.remove(i);
+ }
+ }
+
+ if (connections.size() == 0) {
+ state = State.IDLE;
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaCallTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaCallTracker.java
new file mode 100644
index 0000000..a7d5d0a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaCallTracker.java
@@ -0,0 +1,1163 @@
+/*
+ * 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.cdma;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.util.Log;
+import android.os.SystemProperties;
+
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallTracker;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.DriverCall;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyProperties;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * {@hide}
+ */
+public final class CdmaCallTracker extends CallTracker {
+ static final String LOG_TAG = "CDMA";
+
+ private static final boolean REPEAT_POLLING = false;
+
+ private static final boolean DBG_POLL = false;
+
+ //***** Constants
+
+ static final int MAX_CONNECTIONS = 1; // only 1 connection allowed in CDMA
+ static final int MAX_CONNECTIONS_PER_CALL = 1; // only 1 connection allowed per call
+
+ //***** Instance Variables
+
+ CdmaConnection connections[] = new CdmaConnection[MAX_CONNECTIONS];
+ RegistrantList voiceCallEndedRegistrants = new RegistrantList();
+ RegistrantList voiceCallStartedRegistrants = new RegistrantList();
+ RegistrantList callWaitingRegistrants = new RegistrantList();
+
+
+ // connections dropped during last poll
+ ArrayList<CdmaConnection> droppedDuringPoll
+ = new ArrayList<CdmaConnection>(MAX_CONNECTIONS);
+
+ CdmaCall ringingCall = new CdmaCall(this);
+ // A call that is ringing or (call) waiting
+ CdmaCall foregroundCall = new CdmaCall(this);
+ CdmaCall backgroundCall = new CdmaCall(this);
+
+ CdmaConnection pendingMO;
+ boolean hangupPendingMO;
+ boolean pendingCallInEcm=false;
+ boolean mIsInEmergencyCall = false;
+ CDMAPhone phone;
+
+ boolean desiredMute = false; // false = mute off
+
+ int pendingCallClirMode;
+ PhoneConstants.State state = PhoneConstants.State.IDLE;
+
+ private boolean mIsEcmTimerCanceled = false;
+
+// boolean needsPoll;
+
+
+
+ //***** Events
+
+ //***** Constructors
+ CdmaCallTracker(CDMAPhone 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);
+ cm.registerForCallWaitingInfo(this, EVENT_CALL_WAITING_INFO_CDMA, null);
+ foregroundCall.setGeneric(false);
+ }
+
+ public void dispose() {
+ cm.unregisterForCallStateChanged(this);
+ cm.unregisterForOn(this);
+ cm.unregisterForNotAvailable(this);
+ cm.unregisterForCallWaitingInfo(this);
+ for(CdmaConnection c : connections) {
+ try {
+ if(c != null) hangup(c);
+ } catch (CallStateException ex) {
+ Log.e(LOG_TAG, "unexpected error on hangup during dispose");
+ }
+ }
+
+ try {
+ if(pendingMO != null) hangup(pendingMO);
+ } catch (CallStateException ex) {
+ Log.e(LOG_TAG, "unexpected error on hangup during dispose");
+ }
+
+ clearDisconnected();
+
+ }
+
+ @Override
+ protected void finalize() {
+ Log.d(LOG_TAG, "CdmaCallTracker finalized");
+ }
+
+ //***** Instance Methods
+
+ //***** Public Methods
+ public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ voiceCallStartedRegistrants.add(r);
+ // Notify if in call when registering
+ if (state != PhoneConstants.State.IDLE) {
+ r.notifyRegistrant(new AsyncResult(null, null, null));
+ }
+ }
+ public void unregisterForVoiceCallStarted(Handler h) {
+ voiceCallStartedRegistrants.remove(h);
+ }
+
+ public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ voiceCallEndedRegistrants.add(r);
+ }
+
+ public void unregisterForVoiceCallEnded(Handler h) {
+ voiceCallEndedRegistrants.remove(h);
+ }
+
+ public void registerForCallWaiting(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ callWaitingRegistrants.add(r);
+ }
+
+ public void unregisterForCallWaiting(Handler h) {
+ callWaitingRegistrants.remove(h);
+ }
+
+ 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++) {
+ CdmaConnection conn = (CdmaConnection)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");
+ }
+
+ String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
+ boolean isPhoneInEcmMode = inEcm.equals("true");
+ boolean isEmergencyCall =
+ PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext());
+
+ // Cancel Ecm timer if a second emergency call is originating in Ecm mode
+ if (isPhoneInEcmMode && isEmergencyCall) {
+ handleEcmTimer(phone.CANCEL_ECM_TIMER);
+ }
+
+ // We are initiating a call therefore even if we previously
+ // didn't know the state (i.e. Generic was true) we now know
+ // and therefore can set Generic to false.
+ foregroundCall.setGeneric(false);
+
+ // 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() == CdmaCall.State.ACTIVE) {
+ return dialThreeWay(dialString);
+ }
+
+ pendingMO = new CdmaConnection(phone.getContext(), checkForTestEmergencyNumber(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);
+
+ // Check data call
+ disableDataCallInEmergencyCall(dialString);
+
+ // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit.
+ if(!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) {
+ cm.dial(pendingMO.address, clirMode, obtainCompleteMessage());
+ } else {
+ phone.exitEmergencyCallbackMode();
+ phone.setOnEcbModeExitResponse(this,EVENT_EXIT_ECM_RESPONSE_CDMA, null);
+ pendingCallClirMode=clirMode;
+ pendingCallInEcm=true;
+ }
+ }
+
+ updatePhoneState();
+ phone.notifyPreciseCallStateChanged();
+
+ return pendingMO;
+ }
+
+
+ Connection
+ dial (String dialString) throws CallStateException {
+ return dial(dialString, CommandsInterface.CLIR_DEFAULT);
+ }
+
+ private Connection
+ dialThreeWay (String dialString) {
+ if (!foregroundCall.isIdle()) {
+ // Check data call
+ disableDataCallInEmergencyCall(dialString);
+
+ // Attach the new connection to foregroundCall
+ pendingMO = new CdmaConnection(phone.getContext(),
+ checkForTestEmergencyNumber(dialString), this, foregroundCall);
+ cm.sendCDMAFeatureCode(pendingMO.address,
+ obtainMessage(EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA));
+ return pendingMO;
+ }
+ return null;
+ }
+
+ void
+ acceptCall() throws CallStateException {
+ if (ringingCall.getState() == CdmaCall.State.INCOMING) {
+ Log.i("phone", "acceptCall: incoming...");
+ // Always unmute when answering a new call
+ setMute(false);
+ cm.acceptCall(obtainCompleteMessage());
+ } else if (ringingCall.getState() == CdmaCall.State.WAITING) {
+ CdmaConnection cwConn = (CdmaConnection)(ringingCall.getLatestConnection());
+
+ // Since there is no network response for supplimentary
+ // service for CDMA, we assume call waiting is answered.
+ // ringing Call state change to idle is in CdmaCall.detach
+ // triggered by updateParent.
+ cwConn.updateParent(ringingCall, foregroundCall);
+ cwConn.onConnectedInOrOut();
+ updatePhoneState();
+ 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() == CdmaCall.State.INCOMING) {
+ throw new CallStateException("cannot be in the incoming state");
+ } else if (foregroundCall.getConnections().size() > 1) {
+ flashAndSetGenericTrue();
+ } else {
+ // Send a flash command to CDMA network for putting the other party on hold.
+ // For CDMA networks which do not support this the user would just hear a beep
+ // from the network. For CDMA networks which do support it will put the other
+ // party on hold.
+ cm.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT));
+ }
+ }
+
+ void
+ conference() throws CallStateException {
+ // Should we be checking state?
+ flashAndSetGenericTrue();
+ }
+
+ void
+ explicitCallTransfer() throws CallStateException {
+ cm.explicitCallTransfer(obtainCompleteMessage(EVENT_ECT_RESULT));
+ }
+
+ void
+ clearDisconnected() {
+ internalClearDisconnected();
+
+ updatePhoneState();
+ phone.notifyPreciseCallStateChanged();
+ }
+
+ boolean
+ canConference() {
+ return foregroundCall.getState() == CdmaCall.State.ACTIVE
+ && backgroundCall.getState() == CdmaCall.State.HOLDING
+ && !backgroundCall.isFull()
+ && !foregroundCall.isFull();
+ }
+
+ boolean
+ canDial() {
+ boolean ret;
+ int serviceState = phone.getServiceState().getState();
+ String disableCall = SystemProperties.get(
+ TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
+
+ ret = (serviceState != ServiceState.STATE_POWER_OFF)
+ && pendingMO == null
+ && !ringingCall.isRinging()
+ && !disableCall.equals("true")
+ && (!foregroundCall.getState().isAlive()
+ || (foregroundCall.getState() == CdmaCall.State.ACTIVE)
+ || !backgroundCall.getState().isAlive());
+
+ if (!ret) {
+ log(String.format("canDial is false\n" +
+ "((serviceState=%d) != ServiceState.STATE_POWER_OFF)::=%s\n" +
+ "&& pendingMO == null::=%s\n" +
+ "&& !ringingCall.isRinging()::=%s\n" +
+ "&& !disableCall.equals(\"true\")::=%s\n" +
+ "&& (!foregroundCall.getState().isAlive()::=%s\n" +
+ " || foregroundCall.getState() == CdmaCall.State.ACTIVE::=%s\n" +
+ " ||!backgroundCall.getState().isAlive())::=%s)",
+ serviceState,
+ serviceState != ServiceState.STATE_POWER_OFF,
+ pendingMO == null,
+ !ringingCall.isRinging(),
+ !disableCall.equals("true"),
+ !foregroundCall.getState().isAlive(),
+ foregroundCall.getState() == CdmaCall.State.ACTIVE,
+ !backgroundCall.getState().isAlive()));
+ }
+ return ret;
+ }
+
+ boolean
+ canTransfer() {
+ Log.e(LOG_TAG, "canTransfer: not possible in CDMA");
+ return false;
+ }
+
+ //***** Private Instance Methods
+
+ private void
+ internalClearDisconnected() {
+ ringingCall.clearDisconnected();
+ foregroundCall.clearDisconnected();
+ backgroundCall.clearDisconnected();
+ }
+
+ /**
+ * 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);
+ }
+
+ 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,"CdmaCallTracker.pendingOperations < 0");
+ pendingOperations = 0;
+ }
+ }
+
+
+
+ private void
+ updatePhoneState() {
+ PhoneConstants.State oldState = state;
+
+ if (ringingCall.isRinging()) {
+ state = PhoneConstants.State.RINGING;
+ } else if (pendingMO != null ||
+ !(foregroundCall.isIdle() && backgroundCall.isIdle())) {
+ state = PhoneConstants.State.OFFHOOK;
+ } else {
+ state = PhoneConstants.State.IDLE;
+ }
+
+ if (state == PhoneConstants.State.IDLE && oldState != state) {
+ voiceCallEndedRegistrants.notifyRegistrants(
+ new AsyncResult(null, null, null));
+ } else if (oldState == PhoneConstants.State.IDLE && oldState != state) {
+ voiceCallStartedRegistrants.notifyRegistrants (
+ new AsyncResult(null, null, null));
+ }
+ if (Phone.DEBUG_PHONE) {
+ log("update phone state, old=" + oldState + " new="+ state);
+ }
+ if (state != oldState) {
+ phone.notifyPhoneStateChanged();
+ }
+ }
+
+ // ***** Overwritten from CallTracker
+
+ protected 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++) {
+ CdmaConnection 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;
+ // Re-start Ecm timer when an uncompleted emergency call ends
+ if (mIsEcmTimerCanceled) {
+ handleEcmTimer(phone.RESTART_ECM_TIMER);
+ }
+
+ 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 {
+ if (Phone.DEBUG_PHONE) {
+ log("pendingMo=" + pendingMO + ", dc=" + dc);
+ }
+ // find if the MT call is a new ring or unknown connection
+ newRinging = checkMtFindNewRinging(dc,i);
+ if (newRinging == null) {
+ unknownConnectionAppeared = true;
+ }
+ checkAndEnableDataCallAfterEmergencyCallDropped();
+ }
+ hasNonHangupStateChanged = true;
+ } else if (conn != null && dc == null) {
+ // This case means the RIL has no more active call anymore and
+ // we need to clean up the foregroundCall and ringingCall.
+ // Loop through foreground call connections as
+ // it contains the known logical connections.
+ int count = foregroundCall.connections.size();
+ for (int n = 0; n < count; n++) {
+ if (Phone.DEBUG_PHONE) log("adding fgCall cn " + n + " to droppedDuringPoll");
+ CdmaConnection cn = (CdmaConnection)foregroundCall.connections.get(n);
+ droppedDuringPoll.add(cn);
+ }
+ count = ringingCall.connections.size();
+ // Loop through ringing call connections as
+ // it may contain the known logical connections.
+ for (int n = 0; n < count; n++) {
+ if (Phone.DEBUG_PHONE) log("adding rgCall cn " + n + " to droppedDuringPoll");
+ CdmaConnection cn = (CdmaConnection)ringingCall.connections.get(n);
+ droppedDuringPoll.add(cn);
+ }
+ foregroundCall.setGeneric(false);
+ ringingCall.setGeneric(false);
+
+ // Re-start Ecm timer when the connected emergency call ends
+ if (mIsEcmTimerCanceled) {
+ handleEcmTimer(phone.RESTART_ECM_TIMER);
+ }
+ // If emergency call is not going through while dialing
+ checkAndEnableDataCallAfterEmergencyCallDropped();
+
+ // Dropped connections are removed from the CallTracker
+ // list but kept in the Call list
+ connections[i] = null;
+ } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */
+ // Call collision case
+ if (conn.isIncoming != dc.isMT) {
+ if (dc.isMT == true){
+ // Mt call takes precedence than Mo,drops Mo
+ droppedDuringPoll.add(conn);
+ // find if the MT call is a new ring or unknown connection
+ newRinging = checkMtFindNewRinging(dc,i);
+ if (newRinging == null) {
+ unknownConnectionAppeared = true;
+ }
+ checkAndEnableDataCallAfterEmergencyCallDropped();
+ } else {
+ // Call info stored in conn is not consistent with the call info from dc.
+ // We should follow the rule of MT calls taking precedence over MO calls
+ // when there is conflict, so here we drop the call info from dc and
+ // continue to use the call info from conn, and only take a log.
+ Log.e(LOG_TAG,"Error in RIL, Phantom call appeared " + dc);
+ }
+ } else {
+ 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( pendingCallInEcm) {
+ pendingCallInEcm = 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--) {
+ CdmaConnection 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.notifyPreciseCallStateChanged();
+ }
+
+ //dumpState();
+ }
+
+ //***** Called from CdmaConnection
+ /*package*/ void
+ hangup (CdmaConnection conn) throws CallStateException {
+ if (conn.owner != this) {
+ throw new CallStateException ("CdmaConnection " + conn
+ + "does not belong to CdmaCallTracker " + 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 if ((conn.getCall() == ringingCall)
+ && (ringingCall.getState() == CdmaCall.State.WAITING)) {
+ // Handle call waiting hang up case.
+ //
+ // The ringingCall state will change to IDLE in CdmaCall.detach
+ // if the ringing call connection size is 0. We don't specifically
+ // set the ringing call state to IDLE here to avoid a race condition
+ // where a new call waiting could get a hang up from an old call
+ // waiting ringingCall.
+ //
+ // PhoneApp does the call log itself since only PhoneApp knows
+ // the hangup reason is user ignoring or timing out. So conn.onDisconnect()
+ // is not called here. Instead, conn.onLocalDisconnect() is called.
+ conn.onLocalDisconnect();
+ updatePhoneState();
+ phone.notifyPreciseCallStateChanged();
+ return;
+ } else {
+ try {
+ cm.hangupConnection (conn.getCDMAIndex(), obtainCompleteMessage());
+ } catch (CallStateException ex) {
+ // Ignore "connection not found"
+ // Call may have hung up already
+ Log.w(LOG_TAG,"CdmaCallTracker WARN: hangup() on absent connection "
+ + conn);
+ }
+ }
+
+ conn.onHangupLocal();
+ }
+
+ /*package*/ void
+ separate (CdmaConnection conn) throws CallStateException {
+ if (conn.owner != this) {
+ throw new CallStateException ("CdmaConnection " + conn
+ + "does not belong to CdmaCallTracker " + this);
+ }
+ try {
+ cm.separateConnection (conn.getCDMAIndex(),
+ obtainCompleteMessage(EVENT_SEPARATE_RESULT));
+ } catch (CallStateException ex) {
+ // Ignore "connection not found"
+ // Call may have hung up already
+ Log.w(LOG_TAG,"CdmaCallTracker WARN: separate() on absent connection "
+ + conn);
+ }
+ }
+
+ //***** Called from CDMAPhone
+
+ /*package*/ void
+ setMute(boolean mute) {
+ desiredMute = mute;
+ cm.setMute(desiredMute, null);
+ }
+
+ /*package*/ boolean
+ getMute() {
+ return desiredMute;
+ }
+
+
+ //***** Called from CdmaCall
+
+ /* package */ void
+ hangup (CdmaCall 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((CdmaConnection)(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 ("CdmaCall " + call +
+ "does not belong to CdmaCallTracker " + this);
+ }
+
+ call.onHangupLocal();
+ phone.notifyPreciseCallStateChanged();
+ }
+
+ /* 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(CdmaCall call, int index)
+ throws CallStateException {
+ int count = call.connections.size();
+ for (int i = 0; i < count; i++) {
+ CdmaConnection cn = (CdmaConnection)call.connections.get(i);
+ if (cn.getCDMAIndex() == index) {
+ cm.hangupConnection(index, obtainCompleteMessage());
+ return;
+ }
+ }
+
+ throw new CallStateException("no gsm index found");
+ }
+
+ void hangupAllConnections(CdmaCall call) throws CallStateException{
+ try {
+ int count = call.connections.size();
+ for (int i = 0; i < count; i++) {
+ CdmaConnection cn = (CdmaConnection)call.connections.get(i);
+ cm.hangupConnection(cn.getCDMAIndex(), obtainCompleteMessage());
+ }
+ } catch (CallStateException ex) {
+ Log.e(LOG_TAG, "hangupConnectionByIndex caught " + ex);
+ }
+ }
+
+ /* package */
+ CdmaConnection getConnectionByIndex(CdmaCall call, int index)
+ throws CallStateException {
+ int count = call.connections.size();
+ for (int i = 0; i < count; i++) {
+ CdmaConnection cn = (CdmaConnection)call.connections.get(i);
+ if (cn.getCDMAIndex() == index) {
+ return cn;
+ }
+ }
+
+ return null;
+ }
+
+ private void flashAndSetGenericTrue() throws CallStateException {
+ cm.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT));
+
+ // Set generic to true because in CDMA it is not known what
+ // the status of the call is after a call waiting is answered,
+ // 3 way call merged or a switch between calls.
+ foregroundCall.setGeneric(true);
+ phone.notifyPreciseCallStateChanged();
+ }
+
+ 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;
+ }
+
+ private void handleRadioNotAvailable() {
+ // handlePollCalls will clear out its
+ // call list when it gets the CommandException
+ // error result from this
+ pollCallsWhenSafe();
+ }
+
+ private void notifyCallWaitingInfo(CdmaCallWaitingNotification obj) {
+ if (callWaitingRegistrants != null) {
+ callWaitingRegistrants.notifyRegistrants(new AsyncResult(null, obj, null));
+ }
+ }
+
+ private void handleCallWaitingInfo (CdmaCallWaitingNotification cw) {
+ // Check how many connections in foregroundCall.
+ // If the connection in foregroundCall is more
+ // than one, then the connection information is
+ // not reliable anymore since it means either
+ // call waiting is connected or 3 way call is
+ // dialed before, so set generic.
+ if (foregroundCall.connections.size() > 1 ) {
+ foregroundCall.setGeneric(true);
+ }
+
+ // Create a new CdmaConnection which attaches itself to ringingCall.
+ ringingCall.setGeneric(false);
+ new CdmaConnection(phone.getContext(), cw, this, ringingCall);
+ updatePhoneState();
+
+ // Finally notify application
+ notifyCallWaitingInfo(cw);
+ }
+ //****** Overridden from Handler
+
+ public void
+ handleMessage (Message msg) {
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case EVENT_POLL_CALLS_RESULT:{
+ Log.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");
+ 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:
+ operationComplete();
+ break;
+
+ case EVENT_SWITCH_RESULT:
+ // In GSM call operationComplete() here which gets the
+ // current call list. But in CDMA there is no list so
+ // there is nothing to do.
+ 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++
+ ) {
+ CdmaConnection conn = droppedDuringPoll.get(i);
+
+ conn.onRemoteDisconnect(causeCode);
+ }
+
+ updatePhoneState();
+
+ phone.notifyPreciseCallStateChanged();
+ 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;
+
+ case EVENT_EXIT_ECM_RESPONSE_CDMA:
+ //no matter the result, we still do the same here
+ if (pendingCallInEcm) {
+ cm.dial(pendingMO.address, pendingCallClirMode, obtainCompleteMessage());
+ pendingCallInEcm = false;
+ }
+ phone.unsetOnEcbModeExitResponse(this);
+ break;
+
+ case EVENT_CALL_WAITING_INFO_CDMA:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ handleCallWaitingInfo((CdmaCallWaitingNotification)ar.result);
+ Log.d(LOG_TAG, "Event EVENT_CALL_WAITING_INFO_CDMA Received");
+ }
+ break;
+
+ case EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ // Assume 3 way call is connected
+ pendingMO.onConnectedInOrOut();
+ pendingMO = null;
+ }
+ break;
+
+ default:{
+ throw new RuntimeException("unexpected event not handled");
+ }
+ }
+ }
+
+ /**
+ * Handle Ecm timer to be canceled or re-started
+ */
+ private void handleEcmTimer(int action) {
+ phone.handleTimerInEmergencyCallbackMode(action);
+ switch(action) {
+ case CDMAPhone.CANCEL_ECM_TIMER: mIsEcmTimerCanceled = true; break;
+ case CDMAPhone.RESTART_ECM_TIMER: mIsEcmTimerCanceled = false; break;
+ default:
+ Log.e(LOG_TAG, "handleEcmTimer, unsupported action " + action);
+ }
+ }
+
+ /**
+ * Disable data call when emergency call is connected
+ */
+ private void disableDataCallInEmergencyCall(String dialString) {
+ if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext())) {
+ if (Phone.DEBUG_PHONE) log("disableDataCallInEmergencyCall");
+ mIsInEmergencyCall = true;
+ phone.mDataConnectionTracker.setInternalDataEnabled(false);
+ }
+ }
+
+ /**
+ * Check and enable data call after an emergency call is dropped if it's
+ * not in ECM
+ */
+ private void checkAndEnableDataCallAfterEmergencyCallDropped() {
+ if (mIsInEmergencyCall) {
+ mIsInEmergencyCall = false;
+ String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
+ if (Phone.DEBUG_PHONE) {
+ log("checkAndEnableDataCallAfterEmergencyCallDropped,inEcm=" + inEcm);
+ }
+ if (inEcm.compareTo("false") == 0) {
+ // Re-initiate data connection
+ phone.mDataConnectionTracker.setInternalDataEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Check the MT call to see if it's a new ring or
+ * a unknown connection.
+ */
+ private Connection checkMtFindNewRinging(DriverCall dc, int i) {
+
+ Connection newRinging = null;
+
+ connections[i] = new CdmaConnection(phone.getContext(), dc, this, i);
+ // it's a ringing call
+ if (connections[i].getCall() == ringingCall) {
+ newRinging = connections[i];
+ if (Phone.DEBUG_PHONE) log("Notify new ring " + dc);
+ } else {
+ // Something strange happened: a call which is neither
+ // a ringing call nor the one we created. It could be the
+ // call collision result from RIL
+ Log.e(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();
+ }
+ }
+ return newRinging;
+ }
+
+ /**
+ * Check if current call is in emergency call
+ *
+ * @return true if it is in emergency call
+ * false if it is not in emergency call
+ */
+ boolean isInEmergencyCall() {
+ return mIsInEmergencyCall;
+ }
+
+ protected void log(String msg) {
+ Log.d(LOG_TAG, "[CdmaCallTracker] " + msg);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("GsmCallTracker extends:");
+ super.dump(fd, pw, args);
+ pw.println("droppedDuringPoll: length=" + connections.length);
+ for(int i=0; i < connections.length; i++) {
+ pw.printf(" connections[%d]=%s\n", i, connections[i]);
+ }
+ pw.println(" voiceCallEndedRegistrants=" + voiceCallEndedRegistrants);
+ pw.println(" voiceCallStartedRegistrants=" + voiceCallStartedRegistrants);
+ pw.println(" callWaitingRegistrants=" + callWaitingRegistrants);
+ pw.println("droppedDuringPoll: size=" + droppedDuringPoll.size());
+ for(int i = 0; i < droppedDuringPoll.size(); i++) {
+ pw.printf( " droppedDuringPoll[%d]=%s\n", i, droppedDuringPoll.get(i));
+ }
+ pw.println(" ringingCall=" + ringingCall);
+ pw.println(" foregroundCall=" + foregroundCall);
+ pw.println(" backgroundCall=" + backgroundCall);
+ pw.println(" pendingMO=" + pendingMO);
+ pw.println(" hangupPendingMO=" + hangupPendingMO);
+ pw.println(" pendingCallInEcm=" + pendingCallInEcm);
+ pw.println(" mIsInEmergencyCall=" + mIsInEmergencyCall);
+ pw.println(" phone=" + phone);
+ pw.println(" desiredMute=" + desiredMute);
+ pw.println(" pendingCallClirMode=" + pendingCallClirMode);
+ pw.println(" state=" + state);
+ pw.println(" mIsEcmTimerCanceled=" + mIsEcmTimerCanceled);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaCallWaitingNotification.java b/src/java/com/android/internal/telephony/cdma/CdmaCallWaitingNotification.java
new file mode 100644
index 0000000..0a9bdb7
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaCallWaitingNotification.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009 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.cdma;
+
+import android.util.Log;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.PhoneConstants;
+
+/**
+ * Represents a Supplementary Service Notification received from the network.
+ *
+ * {@hide}
+ */
+public class CdmaCallWaitingNotification {
+ static final String LOG_TAG = "CDMA";
+ public String number = null;
+ public int numberPresentation = 0;
+ public String name = null;
+ public int namePresentation = 0;
+ public int numberType = 0;
+ public int numberPlan = 0;
+ public int isPresent = 0;
+ public int signalType = 0;
+ public int alertPitch = 0;
+ public int signal = 0;
+
+ public String toString()
+ {
+ return super.toString() + "Call Waiting Notification "
+ + " number: " + number
+ + " numberPresentation: " + numberPresentation
+ + " name: " + name
+ + " namePresentation: " + namePresentation
+ + " numberType: " + numberType
+ + " numberPlan: " + numberPlan
+ + " isPresent: " + isPresent
+ + " signalType: " + signalType
+ + " alertPitch: " + alertPitch
+ + " signal: " + signal ;
+ }
+
+ public static int
+ presentationFromCLIP(int cli)
+ {
+ switch(cli) {
+ case 0: return PhoneConstants.PRESENTATION_ALLOWED;
+ case 1: return PhoneConstants.PRESENTATION_RESTRICTED;
+ case 2: return PhoneConstants.PRESENTATION_UNKNOWN;
+ default:
+ // This shouldn't happen, just log an error and treat as Unknown
+ Log.d(LOG_TAG, "Unexpected presentation " + cli);
+ return PhoneConstants.PRESENTATION_UNKNOWN;
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaConnection.java b/src/java/com/android/internal/telephony/cdma/CdmaConnection.java
new file mode 100755
index 0000000..c75290e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaConnection.java
@@ -0,0 +1,945 @@
+/*
+ * 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.cdma;
+
+import com.android.internal.telephony.*;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Registrant;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.text.TextUtils;
+
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.RILConstants;
+
+/**
+ * {@hide}
+ */
+public class CdmaConnection extends Connection {
+ static final String LOG_TAG = "CDMA";
+
+ //***** Instance Variables
+
+ CdmaCallTracker owner;
+ CdmaCall parent;
+
+
+ String address; // MAY BE NULL!!!
+ String dialString; // outgoing calls only
+ String postDialString; // outgoing calls only
+ boolean isIncoming;
+ boolean disconnected;
+ int index; // index in CdmaCallTracker.connections[], -1 if unassigned
+
+ /*
+ * 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;
+ int numberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
+
+
+ Handler h;
+
+ private PowerManager.WakeLock mPartialWakeLock;
+
+ //***** Event Constants
+ static final int EVENT_DTMF_DONE = 1;
+ static final int EVENT_PAUSE_DONE = 2;
+ static final int EVENT_NEXT_POST_DIAL = 3;
+ static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
+
+ //***** Constants
+ static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
+ static final int PAUSE_DELAY_MILLIS = 2 * 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;
+ case EVENT_WAKE_LOCK_TIMEOUT:
+ releaseWakeLock();
+ break;
+ }
+ }
+ }
+
+ //***** Constructors
+
+ /** This is probably an MT call that we first saw in a CLCC response */
+ /*package*/
+ CdmaConnection (Context context, DriverCall dc, CdmaCallTracker ct, int index) {
+ createWakeLock(context);
+ acquireWakeLock();
+
+ owner = ct;
+ h = new MyHandler(owner.getLooper());
+
+ address = dc.number;
+
+ isIncoming = dc.isMT;
+ createTime = System.currentTimeMillis();
+ cnapName = dc.name;
+ cnapNamePresentation = dc.namePresentation;
+ numberPresentation = dc.numberPresentation;
+
+ this.index = index;
+
+ parent = parentFromDCState (dc.state);
+ parent.attach(this, dc);
+ }
+
+ /** This is an MO call/three way call, created when dialing */
+ /*package*/
+ CdmaConnection(Context context, String dialString, CdmaCallTracker ct, CdmaCall parent) {
+ createWakeLock(context);
+ acquireWakeLock();
+
+ owner = ct;
+ h = new MyHandler(owner.getLooper());
+
+ this.dialString = dialString;
+ Log.d(LOG_TAG, "[CDMAConn] CdmaConnection: dialString=" + dialString);
+ dialString = formatDialString(dialString);
+ Log.d(LOG_TAG, "[CDMAConn] CdmaConnection:formated dialString=" + dialString);
+
+ this.address = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
+ this.postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
+
+ index = -1;
+
+ isIncoming = false;
+ cnapName = null;
+ cnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
+ numberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
+ createTime = System.currentTimeMillis();
+
+ if (parent != null) {
+ this.parent = parent;
+
+ //for the three way call case, not change parent state
+ if (parent.state == CdmaCall.State.ACTIVE) {
+ parent.attachFake(this, CdmaCall.State.ACTIVE);
+ } else {
+ parent.attachFake(this, CdmaCall.State.DIALING);
+ }
+ }
+ }
+
+ /** This is a Call waiting call*/
+ CdmaConnection(Context context, CdmaCallWaitingNotification cw, CdmaCallTracker ct,
+ CdmaCall parent) {
+ createWakeLock(context);
+ acquireWakeLock();
+
+ owner = ct;
+ h = new MyHandler(owner.getLooper());
+ address = cw.number;
+ numberPresentation = cw.numberPresentation;
+ cnapName = cw.name;
+ cnapNamePresentation = cw.namePresentation;
+ index = -1;
+ isIncoming = true;
+ createTime = System.currentTimeMillis();
+ connectTime = 0;
+ this.parent = parent;
+ parent.attachFake(this, CdmaCall.State.WAITING);
+ }
+
+ public void dispose() {
+ }
+
+ 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 getOrigDialString(){
+ return dialString;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public CdmaCall 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() != CdmaCall.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 CdmaCall.State getState() {
+ if (disconnected) {
+ return CdmaCall.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, "CdmaConnection.proceedAfterWaitChar(): Expected "
+ + "getPostDialState() to be WAIT but was " + postDialState);
+ return;
+ }
+
+ setPostDialState(PostDialState.STARTED);
+
+ processNextPostDialChar();
+ }
+
+ public void proceedAfterWildChar(String str) {
+ if (postDialState != PostDialState.WILD) {
+ Log.w(LOG_TAG, "CdmaConnection.proceedAfterWaitChar(): Expected "
+ + "getPostDialState() to be WILD but was " + postDialState);
+ return;
+ }
+
+ setPostDialState(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() {
+ setPostDialState(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:
+ 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.CDMA_LOCKED_UNTIL_POWER_CYCLE:
+ return DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE;
+ case CallFailCause.CDMA_DROP:
+ return DisconnectCause.CDMA_DROP;
+ case CallFailCause.CDMA_INTERCEPT:
+ return DisconnectCause.CDMA_INTERCEPT;
+ case CallFailCause.CDMA_REORDER:
+ return DisconnectCause.CDMA_REORDER;
+ case CallFailCause.CDMA_SO_REJECT:
+ return DisconnectCause.CDMA_SO_REJECT;
+ case CallFailCause.CDMA_RETRY_ORDER:
+ return DisconnectCause.CDMA_RETRY_ORDER;
+ case CallFailCause.CDMA_ACCESS_FAILURE:
+ return DisconnectCause.CDMA_ACCESS_FAILURE;
+ case CallFailCause.CDMA_PREEMPTED:
+ return DisconnectCause.CDMA_PREEMPTED;
+ case CallFailCause.CDMA_NOT_EMERGENCY:
+ return DisconnectCause.CDMA_NOT_EMERGENCY;
+ case CallFailCause.CDMA_ACCESS_BLOCKED:
+ return DisconnectCause.CDMA_ACCESS_BLOCKED;
+ case CallFailCause.ERROR_UNSPECIFIED:
+ case CallFailCause.NORMAL_CLEARING:
+ default:
+ CDMAPhone 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.mCdmaSubscriptionSource ==
+ CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM
+ && phone.getIccCard().getState() != IccCardConstants.State.READY) {
+ return DisconnectCause.ICC_ERROR;
+ } else if (causeCode==CallFailCause.NORMAL_CLEARING) {
+ return DisconnectCause.NORMAL;
+ } else {
+ return DisconnectCause.ERROR_UNSPECIFIED;
+ }
+ }
+ }
+
+ /*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) {
+ doDisconnect();
+ if (false) Log.d(LOG_TAG,
+ "[CDMAConn] onDisconnect: cause=" + cause);
+
+ owner.phone.notifyDisconnect(this);
+
+ if (parent != null) {
+ parent.connectionDisconnected(this);
+ }
+ }
+ releaseWakeLock();
+ }
+
+ /** Called when the call waiting connection has been hung up */
+ /*package*/ void
+ onLocalDisconnect() {
+ if (!disconnected) {
+ doDisconnect();
+ if (false) Log.d(LOG_TAG,
+ "[CDMAConn] onLoalDisconnect" );
+
+ if (parent != null) {
+ parent.detach(this);
+ }
+ }
+ releaseWakeLock();
+ }
+
+ // Returns true if state has changed, false if nothing changed
+ /*package*/ boolean
+ update (DriverCall dc) {
+ CdmaCall newParent;
+ boolean changed = false;
+ boolean wasConnectingInOrOut = isConnectingInOrOut();
+ boolean wasHolding = (getState() == CdmaCall.State.HOLDING);
+
+ newParent = parentFromDCState(dc.state);
+
+ if (Phone.DEBUG_PHONE) log("parent= " +parent +", newParent= " + newParent);
+
+ if (!equalsHandlesNulls(address, dc.number)) {
+ if (Phone.DEBUG_PHONE) log("update: phone # changed!");
+ address = dc.number;
+ changed = true;
+ }
+
+ // A null cnapName should be the same as ""
+ if (TextUtils.isEmpty(dc.name)) {
+ if (!TextUtils.isEmpty(cnapName)) {
+ changed = true;
+ cnapName = "";
+ }
+ } else if (!dc.name.equals(cnapName)) {
+ changed = true;
+ cnapName = dc.name;
+ }
+
+ if (Phone.DEBUG_PHONE) log("--dssds----"+cnapName);
+ cnapNamePresentation = dc.namePresentation;
+ numberPresentation = dc.numberPresentation;
+
+ 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, wasConnectingInOrOut=" + wasConnectingInOrOut +
+ ", wasHolding=" + wasHolding +
+ ", isConnectingInOrOut=" + isConnectingInOrOut() +
+ ", changed=" + changed);
+
+
+ if (wasConnectingInOrOut && !isConnectingInOrOut()) {
+ onConnectedInOrOut();
+ }
+
+ if (changed && !wasHolding && (getState() == CdmaCall.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, CdmaCall.State.HOLDING);
+
+ onStartedHolding();
+ }
+
+ /*package*/ int
+ getCDMAIndex() throws CallStateException {
+ if (index >= 0) {
+ return index + 1;
+ } else {
+ throw new CallStateException ("CDMA connection index not 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();
+ } else {
+ // Only release wake lock for incoming calls, for outgoing calls the wake lock
+ // will be released after any pause-dial is completed
+ releaseWakeLock();
+ }
+ }
+
+ private void
+ doDisconnect() {
+ index = -1;
+ disconnectTime = System.currentTimeMillis();
+ duration = SystemClock.elapsedRealtime() - connectTimeReal;
+ disconnected = true;
+ }
+
+ 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) {
+ setPostDialState(PostDialState.PAUSE);
+
+ // Upon occurrences of the separator, the UE shall
+ // pause again for 2 seconds before sending any
+ // further DTMF digits.
+ h.sendMessageDelayed(h.obtainMessage(EVENT_PAUSE_DONE),
+ PAUSE_DELAY_MILLIS);
+ } else if (c == PhoneNumberUtils.WAIT) {
+ setPostDialState(PostDialState.WAIT);
+ } else if (c == PhoneNumberUtils.WILD) {
+ setPostDialState(PostDialState.WILD);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ public String getRemainingPostDialString() {
+ if (postDialState == PostDialState.CANCELLED
+ || postDialState == PostDialState.COMPLETE
+ || postDialString == null
+ || postDialString.length() <= nextPostDialChar) {
+ return "";
+ }
+
+ String subStr = postDialString.substring(nextPostDialChar);
+ if (subStr != null) {
+ int wIndex = subStr.indexOf(PhoneNumberUtils.WAIT);
+ int pIndex = subStr.indexOf(PhoneNumberUtils.PAUSE);
+
+ if (wIndex > 0 && (wIndex < pIndex || pIndex <= 0)) {
+ subStr = subStr.substring(0, wIndex);
+ } else if (pIndex > 0) {
+ subStr = subStr.substring(0, pIndex);
+ }
+ }
+ return subStr;
+ }
+
+ public void updateParent(CdmaCall oldParent, CdmaCall newParent){
+ if (newParent != oldParent) {
+ if (oldParent != null) {
+ oldParent.detach(this);
+ }
+ newParent.attachFake(this, CdmaCall.State.ACTIVE);
+ parent = newParent;
+ }
+ }
+
+ @Override
+ protected void finalize()
+ {
+ /**
+ * It is understood that This finializer is not guaranteed
+ * to be called and the release lock call is here just in
+ * case there is some path that doesn't call onDisconnect
+ * and or onConnectedInOrOut.
+ */
+ if (mPartialWakeLock.isHeld()) {
+ Log.e(LOG_TAG, "[CdmaConn] UNEXPECTED; mPartialWakeLock is held when finalizing.");
+ }
+ releaseWakeLock();
+ }
+
+ void processNextPostDialChar() {
+ char c = 0;
+ Registrant postDialHandler;
+
+ if (postDialState == PostDialState.CANCELLED) {
+ releaseWakeLock();
+ //Log.v("CDMA", "##### processNextPostDialChar: postDialState == CANCELLED, bail");
+ return;
+ }
+
+ if (postDialString == null ||
+ postDialString.length() <= nextPostDialChar) {
+ setPostDialState(PostDialState.COMPLETE);
+
+ // We were holding a wake lock until pause-dial was complete, so give it up now
+ releaseWakeLock();
+
+ // notifyMessage.arg1 is 0 on complete
+ c = 0;
+ } else {
+ boolean isValid;
+
+ setPostDialState(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("CDMA", "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;
+
+ notifyMessage.sendToTarget();
+ }
+ }
+
+
+ /** "connecting" means "has never been ACTIVE" for both incoming
+ * and outgoing calls
+ */
+ private boolean
+ isConnectingInOrOut() {
+ return parent == null || parent == owner.ringingCall
+ || parent.state == CdmaCall.State.DIALING
+ || parent.state == CdmaCall.State.ALERTING;
+ }
+
+ private CdmaCall
+ 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);
+ }
+ }
+
+ /**
+ * Set post dial state and acquire wake lock while switching to "started" or "wait"
+ * state, the wake lock will be released if state switches out of "started" or "wait"
+ * state or after WAKE_LOCK_TIMEOUT_MILLIS.
+ * @param s new PostDialState
+ */
+ private void setPostDialState(PostDialState s) {
+ if (s == PostDialState.STARTED ||
+ s == PostDialState.PAUSE) {
+ synchronized (mPartialWakeLock) {
+ if (mPartialWakeLock.isHeld()) {
+ h.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
+ } else {
+ acquireWakeLock();
+ }
+ Message msg = h.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
+ h.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
+ }
+ } else {
+ h.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
+ releaseWakeLock();
+ }
+ postDialState = s;
+ }
+
+ private void createWakeLock(Context context) {
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+ }
+
+ private void acquireWakeLock() {
+ log("acquireWakeLock");
+ mPartialWakeLock.acquire();
+ }
+
+ private void releaseWakeLock() {
+ synchronized (mPartialWakeLock) {
+ if (mPartialWakeLock.isHeld()) {
+ log("releaseWakeLock");
+ mPartialWakeLock.release();
+ }
+ }
+ }
+
+ private static boolean isPause(char c) {
+ return c == PhoneNumberUtils.PAUSE;
+ }
+
+ private static boolean isWait(char c) {
+ return c == PhoneNumberUtils.WAIT;
+ }
+
+ // This function is to find the next PAUSE character index if
+ // multiple pauses in a row. Otherwise it finds the next non PAUSE or
+ // non WAIT character index.
+ private static int
+ findNextPCharOrNonPOrNonWCharIndex(String phoneNumber, int currIndex) {
+ boolean wMatched = isWait(phoneNumber.charAt(currIndex));
+ int index = currIndex + 1;
+ int length = phoneNumber.length();
+ while (index < length) {
+ char cNext = phoneNumber.charAt(index);
+ // if there is any W inside P/W sequence,mark it
+ if (isWait(cNext)) {
+ wMatched = true;
+ }
+ // if any characters other than P/W chars after P/W sequence
+ // we break out the loop and append the correct
+ if (!isWait(cNext) && !isPause(cNext)) {
+ break;
+ }
+ index++;
+ }
+
+ // It means the PAUSE character(s) is in the middle of dial string
+ // and it needs to be handled one by one.
+ if ((index < length) && (index > (currIndex + 1)) &&
+ ((wMatched == false) && isPause(phoneNumber.charAt(currIndex)))) {
+ return (currIndex + 1);
+ }
+ return index;
+ }
+
+ // This function returns either PAUSE or WAIT character to append.
+ // It is based on the next non PAUSE/WAIT character in the phoneNumber and the
+ // index for the current PAUSE/WAIT character
+ private static char
+ findPOrWCharToAppend(String phoneNumber, int currPwIndex, int nextNonPwCharIndex) {
+ char c = phoneNumber.charAt(currPwIndex);
+ char ret;
+
+ // Append the PW char
+ ret = (isPause(c)) ? PhoneNumberUtils.PAUSE : PhoneNumberUtils.WAIT;
+
+ // If the nextNonPwCharIndex is greater than currPwIndex + 1,
+ // it means the PW sequence contains not only P characters.
+ // Since for the sequence that only contains P character,
+ // the P character is handled one by one, the nextNonPwCharIndex
+ // equals to currPwIndex + 1.
+ // In this case, skip P, append W.
+ if (nextNonPwCharIndex > (currPwIndex + 1)) {
+ ret = PhoneNumberUtils.WAIT;
+ }
+ return ret;
+ }
+
+ /**
+ * format original dial string
+ * 1) convert international dialing prefix "+" to
+ * string specified per region
+ *
+ * 2) handle corner cases for PAUSE/WAIT dialing:
+ *
+ * If PAUSE/WAIT sequence at the end, ignore them.
+ *
+ * If consecutive PAUSE/WAIT sequence in the middle of the string,
+ * and if there is any WAIT in PAUSE/WAIT sequence, treat them like WAIT.
+ */
+ public static String formatDialString(String phoneNumber) {
+ /**
+ * TODO(cleanup): This function should move to PhoneNumberUtils, and
+ * tests should be added.
+ */
+
+ if (phoneNumber == null) {
+ return null;
+ }
+ int length = phoneNumber.length();
+ StringBuilder ret = new StringBuilder();
+ char c;
+ int currIndex = 0;
+
+ while (currIndex < length) {
+ c = phoneNumber.charAt(currIndex);
+ if (isPause(c) || isWait(c)) {
+ if (currIndex < length - 1) {
+ // if PW not at the end
+ int nextIndex = findNextPCharOrNonPOrNonWCharIndex(phoneNumber, currIndex);
+ // If there is non PW char following PW sequence
+ if (nextIndex < length) {
+ char pC = findPOrWCharToAppend(phoneNumber, currIndex, nextIndex);
+ ret.append(pC);
+ // If PW char sequence has more than 2 PW characters,
+ // skip to the last PW character since the sequence already be
+ // converted to WAIT character
+ if (nextIndex > (currIndex + 1)) {
+ currIndex = nextIndex - 1;
+ }
+ } else if (nextIndex == length) {
+ // It means PW characters at the end, ignore
+ currIndex = length - 1;
+ }
+ }
+ } else {
+ ret.append(c);
+ }
+ currIndex++;
+ }
+ return PhoneNumberUtils.cdmaCheckAndProcessPlusCode(ret.toString());
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[CDMAConn] " + msg);
+ }
+
+ @Override
+ public int getNumberPresentation() {
+ return numberPresentation;
+ }
+
+ @Override
+ public UUSInfo getUUSInfo() {
+ // UUS information not supported in CDMA
+ return null;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaDataConnection.java b/src/java/com/android/internal/telephony/cdma/CdmaDataConnection.java
new file mode 100644
index 0000000..8761828
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaDataConnection.java
@@ -0,0 +1,124 @@
+/*
+ * 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.cdma;
+
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.DataConnectionTracker;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.RetryManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * {@hide}
+ */
+public class CdmaDataConnection extends DataConnection {
+
+ private static final String LOG_TAG = "CDMA";
+
+ // ***** Constructor
+ private CdmaDataConnection(CDMAPhone phone, String name, int id, RetryManager rm,
+ DataConnectionTracker dct) {
+ super(phone, name, id, rm, dct);
+ }
+
+ /**
+ * Create the connection object
+ *
+ * @param phone the Phone
+ * @param id the connection id
+ * @param rm the RetryManager
+ * @return CdmaDataConnection that was created.
+ */
+ static CdmaDataConnection makeDataConnection(CDMAPhone phone, int id, RetryManager rm,
+ DataConnectionTracker dct) {
+ CdmaDataConnection cdmaDc = new CdmaDataConnection(phone,
+ "CdmaDC-" + mCount.incrementAndGet(), id, rm, dct);
+ cdmaDc.start();
+ if (DBG) cdmaDc.log("Made " + cdmaDc.getName());
+ return cdmaDc;
+ }
+
+ /**
+ * Begin setting up a data connection, calls setupDataCall
+ * and the ConnectionParams will be returned with the
+ * EVENT_SETUP_DATA_CONNECTION_DONE AsyncResul.userObj.
+ *
+ * @param cp is the connection parameters
+ */
+ @Override
+ protected void onConnect(ConnectionParams cp) {
+ if (DBG) log("CdmaDataConnection Connecting...");
+
+ mApn = cp.apn;
+ createTime = -1;
+ lastFailTime = -1;
+ lastFailCause = FailCause.NONE;
+ int dataProfile;
+ if ((cp.apn != null) && (cp.apn.types.length > 0) && (cp.apn.types[0] != null) &&
+ (cp.apn.types[0].equals(PhoneConstants.APN_TYPE_DUN))) {
+ if (DBG) log("CdmaDataConnection using DUN");
+ dataProfile = RILConstants.DATA_PROFILE_TETHERED;
+ } else {
+ dataProfile = RILConstants.DATA_PROFILE_DEFAULT;
+ }
+
+ // msg.obj will be returned in AsyncResult.userObj;
+ Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
+ msg.obj = cp;
+ phone.mCM.setupDataCall(
+ Integer.toString(getRilRadioTechnology(RILConstants.SETUP_DATA_TECH_CDMA)),
+ Integer.toString(dataProfile),
+ null, null, null,
+ Integer.toString(RILConstants.SETUP_DATA_AUTH_PAP_CHAP),
+ RILConstants.SETUP_DATA_PROTOCOL_IP, msg);
+ }
+
+ @Override
+ public String toString() {
+ return "State=" + getCurrentState().getName() + " create=" + createTime + " lastFail="
+ + lastFailTime + " lastFasilCause=" + lastFailCause;
+ }
+
+ @Override
+ protected boolean isDnsOk(String[] domainNameServers) {
+ if (NULL_IP.equals(domainNameServers[0])
+ && NULL_IP.equals(domainNameServers[1])
+ && !phone.isDnsCheckDisabled()) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[" + getName() + "] " + s);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("CdmaDataConnection extends:");
+ super.dump(fd, pw, args);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
new file mode 100644
index 0000000..1088131
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -0,0 +1,1045 @@
+/*
+ * 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.cdma;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.TrafficStats;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaCellLocation;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+
+import com.android.internal.telephony.ApnSetting;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.DataCallState;
+import com.android.internal.telephony.DataConnection.FailCause;
+import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.DataConnectionAc;
+import com.android.internal.telephony.DataConnectionTracker;
+import com.android.internal.telephony.DctConstants;
+import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.RetryManager;
+import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.telephony.RILConstants;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public final class CdmaDataConnectionTracker extends DataConnectionTracker {
+ protected final String LOG_TAG = "CDMA";
+
+ private CDMAPhone mCdmaPhone;
+ private CdmaSubscriptionSourceManager mCdmaSSM;
+
+ /** The DataConnection being setup */
+ private CdmaDataConnection mPendingDataConnection;
+
+ private boolean mPendingRestartRadio = false;
+ private static final int TIME_DELAYED_TO_RESTART_RADIO =
+ SystemProperties.getInt("ro.cdma.timetoradiorestart", 60000);
+
+ /**
+ * Pool size of CdmaDataConnection objects.
+ */
+ private static final int DATA_CONNECTION_POOL_SIZE = 1;
+
+ private static final String INTENT_RECONNECT_ALARM =
+ "com.android.internal.telephony.cdma-reconnect";
+
+ private static final String INTENT_DATA_STALL_ALARM =
+ "com.android.internal.telephony.cdma-data-stall";
+
+
+ /**
+ * Constants for the data connection activity:
+ * physical link down/up
+ */
+ private static final int DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE = 0;
+ private static final int DATA_CONNECTION_ACTIVE_PH_LINK_DOWN = 1;
+ private static final int DATA_CONNECTION_ACTIVE_PH_LINK_UP = 2;
+
+ private static final String[] mSupportedApnTypes = {
+ PhoneConstants.APN_TYPE_DEFAULT,
+ PhoneConstants.APN_TYPE_MMS,
+ PhoneConstants.APN_TYPE_DUN,
+ PhoneConstants.APN_TYPE_HIPRI };
+
+ private static final String[] mDefaultApnTypes = {
+ PhoneConstants.APN_TYPE_DEFAULT,
+ PhoneConstants.APN_TYPE_MMS,
+ PhoneConstants.APN_TYPE_HIPRI };
+
+ private String[] mDunApnTypes = {
+ PhoneConstants.APN_TYPE_DUN };
+
+ private static final int mDefaultApnId = DctConstants.APN_DEFAULT_ID;
+
+ /* Constructor */
+
+ CdmaDataConnectionTracker(CDMAPhone p) {
+ super(p);
+ mCdmaPhone = p;
+
+ p.mCM.registerForAvailable (this, DctConstants.EVENT_RADIO_AVAILABLE, null);
+ p.mCM.registerForOffOrNotAvailable(this, DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+ p.mIccRecords.registerForRecordsLoaded(this, DctConstants.EVENT_RECORDS_LOADED, null);
+ p.mCM.registerForDataNetworkStateChanged (this, DctConstants.EVENT_DATA_STATE_CHANGED, null);
+ p.mCT.registerForVoiceCallEnded (this, DctConstants.EVENT_VOICE_CALL_ENDED, null);
+ p.mCT.registerForVoiceCallStarted (this, DctConstants.EVENT_VOICE_CALL_STARTED, null);
+ p.mSST.registerForDataConnectionAttached(this, DctConstants.EVENT_TRY_SETUP_DATA, null);
+ p.mSST.registerForDataConnectionDetached(this, DctConstants.EVENT_CDMA_DATA_DETACHED, null);
+ p.mSST.registerForRoamingOn(this, DctConstants.EVENT_ROAMING_ON, null);
+ p.mSST.registerForRoamingOff(this, DctConstants.EVENT_ROAMING_OFF, null);
+ p.mCM.registerForCdmaOtaProvision(this, DctConstants.EVENT_CDMA_OTA_PROVISION, null);
+ mCdmaSSM = CdmaSubscriptionSourceManager.getInstance (p.getContext(), p.mCM, this,
+ DctConstants.EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
+
+ mDataConnectionTracker = this;
+
+ createAllDataConnectionList();
+ broadcastMessenger();
+
+ Context c = mCdmaPhone.getContext();
+ String[] t = c.getResources().getStringArray(
+ com.android.internal.R.array.config_cdma_dun_supported_types);
+ if (t != null && t.length > 0) {
+ ArrayList<String> temp = new ArrayList<String>();
+ for(int i=0; i< t.length; i++) {
+ if (!PhoneConstants.APN_TYPE_DUN.equalsIgnoreCase(t[i])) {
+ temp.add(t[i]);
+ }
+ }
+ temp.add(0,PhoneConstants.APN_TYPE_DUN);
+ mDunApnTypes = temp.toArray(t);
+ }
+
+ }
+
+ @Override
+ public void dispose() {
+ cleanUpConnection(false, null, false);
+
+ super.dispose();
+
+ // Unregister from all events
+ mPhone.mCM.unregisterForAvailable(this);
+ mPhone.mCM.unregisterForOffOrNotAvailable(this);
+ mCdmaPhone.mIccRecords.unregisterForRecordsLoaded(this);
+ mPhone.mCM.unregisterForDataNetworkStateChanged(this);
+ mCdmaPhone.mCT.unregisterForVoiceCallEnded(this);
+ mCdmaPhone.mCT.unregisterForVoiceCallStarted(this);
+ mCdmaPhone.mSST.unregisterForDataConnectionAttached(this);
+ mCdmaPhone.mSST.unregisterForDataConnectionDetached(this);
+ mCdmaPhone.mSST.unregisterForRoamingOn(this);
+ mCdmaPhone.mSST.unregisterForRoamingOff(this);
+ mCdmaSSM.dispose(this);
+ mPhone.mCM.unregisterForCdmaOtaProvision(this);
+
+ destroyAllDataConnectionList();
+ }
+
+ @Override
+ protected void finalize() {
+ if(DBG) log("CdmaDataConnectionTracker finalized");
+ }
+
+ @Override
+ protected String getActionIntentReconnectAlarm() {
+ return INTENT_RECONNECT_ALARM;
+ }
+
+ @Override
+ protected String getActionIntentDataStallAlarm() {
+ return INTENT_DATA_STALL_ALARM;
+ }
+
+ @Override
+ protected void restartDataStallAlarm() {}
+
+ @Override
+ protected void setState(DctConstants.State s) {
+ if (DBG) log ("setState: " + s);
+ if (mState != s) {
+ EventLog.writeEvent(EventLogTags.CDMA_DATA_STATE_CHANGE,
+ mState.toString(), s.toString());
+ mState = s;
+ }
+ }
+
+ @Override
+ public synchronized DctConstants.State getState(String apnType) {
+ return mState;
+ }
+
+ @Override
+ protected boolean isApnTypeAvailable(String type) {
+ for (String s : mSupportedApnTypes) {
+ if (TextUtils.equals(type, s)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean isDataAllowed() {
+ final boolean internalDataEnabled;
+ synchronized (mDataEnabledLock) {
+ internalDataEnabled = mInternalDataEnabled;
+ }
+
+ int psState = mCdmaPhone.mSST.getCurrentDataConnectionState();
+ boolean roaming = (mPhone.getServiceState().getRoaming() && !getDataOnRoamingEnabled());
+ boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState();
+ boolean subscriptionFromNv = (mCdmaSSM.getCdmaSubscriptionSource()
+ == CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_NV);
+
+ boolean allowed =
+ (psState == ServiceState.STATE_IN_SERVICE ||
+ mAutoAttachOnCreation) &&
+ (subscriptionFromNv ||
+ mCdmaPhone.mIccRecords.getRecordsLoaded()) &&
+ (mCdmaPhone.mSST.isConcurrentVoiceAndDataAllowed() ||
+ mPhone.getState() ==PhoneConstants.State.IDLE) &&
+ !roaming &&
+ internalDataEnabled &&
+ desiredPowerState &&
+ !mPendingRestartRadio &&
+ ((mPhone.getLteOnCdmaMode() ==PhoneConstants.LTE_ON_CDMA_TRUE) ||
+ !mCdmaPhone.needsOtaServiceProvisioning());
+ if (!allowed && DBG) {
+ String reason = "";
+ if (!((psState == ServiceState.STATE_IN_SERVICE) || mAutoAttachOnCreation)) {
+ reason += " - psState= " + psState;
+ }
+ if (!subscriptionFromNv &&
+ !mCdmaPhone.mIccRecords.getRecordsLoaded()) {
+ reason += " - RUIM not loaded";
+ }
+ if (!(mCdmaPhone.mSST.isConcurrentVoiceAndDataAllowed() ||
+ mPhone.getState() ==PhoneConstants.State.IDLE)) {
+ reason += " - concurrentVoiceAndData not allowed and state= " + mPhone.getState();
+ }
+ if (roaming) reason += " - Roaming";
+ if (!internalDataEnabled) reason += " - mInternalDataEnabled= false";
+ if (!desiredPowerState) reason += " - desiredPowerState= false";
+ if (mPendingRestartRadio) reason += " - mPendingRestartRadio= true";
+ if (mCdmaPhone.needsOtaServiceProvisioning()) reason += " - needs Provisioning";
+ log("Data not allowed due to" + reason);
+ }
+ return allowed;
+ }
+
+ @Override
+ protected boolean isDataPossible(String apnType) {
+ boolean possible = isDataAllowed() && !(getAnyDataEnabled() &&
+ (mState == DctConstants.State.FAILED || mState == DctConstants.State.IDLE));
+ if (!possible && DBG && isDataAllowed()) {
+ log("Data not possible. No coverage: dataState = " + mState);
+ }
+ return possible;
+ }
+
+ private boolean trySetupData(String reason) {
+ if (DBG) log("***trySetupData due to " + (reason == null ? "(unspecified)" : reason));
+
+ if (mPhone.getSimulatedRadioControl() != null) {
+ // Assume data is connected on the simulator
+ // FIXME this can be improved
+ setState(DctConstants.State.CONNECTED);
+ notifyDataConnection(reason);
+ notifyOffApnsOfAvailability(reason);
+
+ log("(fix?) We're on the simulator; assuming data is connected");
+ return true;
+ }
+
+ int psState = mCdmaPhone.mSST.getCurrentDataConnectionState();
+ boolean roaming = mPhone.getServiceState().getRoaming();
+ boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState();
+
+ if ((mState == DctConstants.State.IDLE || mState == DctConstants.State.SCANNING) &&
+ isDataAllowed() && getAnyDataEnabled() && !isEmergency()) {
+ boolean retValue = setupData(reason);
+ notifyOffApnsOfAvailability(reason);
+ return retValue;
+ } else {
+ notifyOffApnsOfAvailability(reason);
+ return false;
+ }
+ }
+
+ /**
+ * Cleanup the CDMA data connection (only one is supported)
+ *
+ * @param tearDown true if the underlying DataConnection should be disconnected.
+ * @param reason for the clean up.
+ */
+ private void cleanUpConnection(boolean tearDown, String reason, boolean doAll) {
+ if (DBG) log("cleanUpConnection: reason: " + reason);
+
+ // Clear the reconnect alarm, if set.
+ if (mReconnectIntent != null) {
+ AlarmManager am =
+ (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+ am.cancel(mReconnectIntent);
+ mReconnectIntent = null;
+ }
+
+ setState(DctConstants.State.DISCONNECTING);
+ notifyOffApnsOfAvailability(reason);
+
+ boolean notificationDeferred = false;
+ for (DataConnection conn : mDataConnections.values()) {
+ if(conn != null) {
+ DataConnectionAc dcac =
+ mDataConnectionAsyncChannels.get(conn.getDataConnectionId());
+ if (tearDown) {
+ if (doAll) {
+ if (DBG) log("cleanUpConnection: teardown, conn.tearDownAll");
+ conn.tearDownAll(reason, obtainMessage(DctConstants.EVENT_DISCONNECT_DONE,
+ conn.getDataConnectionId(), 0, reason));
+ } else {
+ if (DBG) log("cleanUpConnection: teardown, conn.tearDown");
+ conn.tearDown(reason, obtainMessage(DctConstants.EVENT_DISCONNECT_DONE,
+ conn.getDataConnectionId(), 0, reason));
+ }
+ notificationDeferred = true;
+ } else {
+ if (DBG) log("cleanUpConnection: !tearDown, call conn.resetSynchronously");
+ if (dcac != null) {
+ dcac.resetSync();
+ }
+ notificationDeferred = false;
+ }
+ }
+ }
+
+ stopNetStatPoll();
+
+ if (!notificationDeferred) {
+ if (DBG) log("cleanupConnection: !notificationDeferred");
+ gotoIdleAndNotifyDataConnection(reason);
+ }
+ }
+
+ private CdmaDataConnection findFreeDataConnection() {
+ for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+ if (dcac.isInactiveSync()) {
+ log("found free GsmDataConnection");
+ return (CdmaDataConnection) dcac.dataConnection;
+ }
+ }
+ log("NO free CdmaDataConnection");
+ return null;
+ }
+
+ private boolean setupData(String reason) {
+ CdmaDataConnection conn = findFreeDataConnection();
+
+ if (conn == null) {
+ if (DBG) log("setupData: No free CdmaDataConnection found!");
+ return false;
+ }
+
+ /** TODO: We probably want the connection being setup to a parameter passed around */
+ mPendingDataConnection = conn;
+ String[] types;
+ int apnId;
+ if (mRequestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) {
+ types = mDunApnTypes;
+ apnId = DctConstants.APN_DUN_ID;
+ } else {
+ types = mDefaultApnTypes;
+ apnId = mDefaultApnId;
+ }
+ mActiveApn = new ApnSetting(apnId, "", "", "", "", "", "", "", "", "",
+ "", 0, types, "IP", "IP", true, 0);
+ if (DBG) log("call conn.bringUp mActiveApn=" + mActiveApn);
+
+ Message msg = obtainMessage();
+ msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
+ msg.obj = reason;
+ conn.bringUp(msg, mActiveApn);
+
+ setState(DctConstants.State.INITING);
+ notifyDataConnection(reason);
+ return true;
+ }
+
+ private void notifyDefaultData(String reason) {
+ setState(DctConstants.State.CONNECTED);
+ notifyDataConnection(reason);
+ startNetStatPoll();
+ mDataConnections.get(0).resetRetryCount();
+ }
+
+ private void resetPollStats() {
+ mTxPkts = -1;
+ mRxPkts = -1;
+ mSentSinceLastRecv = 0;
+ mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
+ mNoRecvPollCount = 0;
+ }
+
+ @Override
+ protected void startNetStatPoll() {
+ if (mState == DctConstants.State.CONNECTED && mNetStatPollEnabled == false) {
+ log("[DataConnection] Start poll NetStat");
+ resetPollStats();
+ mNetStatPollEnabled = true;
+ mPollNetStat.run();
+ }
+ }
+
+ @Override
+ protected void stopNetStatPoll() {
+ mNetStatPollEnabled = false;
+ removeCallbacks(mPollNetStat);
+ log("[DataConnection] Stop poll NetStat");
+ }
+
+ @Override
+ protected void restartRadio() {
+ if (DBG) log("Cleanup connection and wait " +
+ (TIME_DELAYED_TO_RESTART_RADIO / 1000) + "s to restart radio");
+ cleanUpAllConnections(null);
+ sendEmptyMessageDelayed(DctConstants.EVENT_RESTART_RADIO, TIME_DELAYED_TO_RESTART_RADIO);
+ mPendingRestartRadio = true;
+ }
+
+ private Runnable mPollNetStat = new Runnable() {
+
+ public void run() {
+ long sent, received;
+ long preTxPkts = -1, preRxPkts = -1;
+
+ DctConstants.Activity newActivity;
+
+ preTxPkts = mTxPkts;
+ preRxPkts = mRxPkts;
+
+ mTxPkts = TrafficStats.getMobileTxPackets();
+ mRxPkts = TrafficStats.getMobileRxPackets();
+
+ //log("rx " + String.valueOf(rxPkts) + " tx " + String.valueOf(txPkts));
+
+ if (mNetStatPollEnabled && (preTxPkts > 0 || preRxPkts > 0)) {
+ sent = mTxPkts - preTxPkts;
+ received = mRxPkts - preRxPkts;
+
+ if ( sent > 0 && received > 0 ) {
+ mSentSinceLastRecv = 0;
+ newActivity = DctConstants.Activity.DATAINANDOUT;
+ } else if (sent > 0 && received == 0) {
+ if (mPhone.getState() ==PhoneConstants.State.IDLE) {
+ mSentSinceLastRecv += sent;
+ } else {
+ mSentSinceLastRecv = 0;
+ }
+ newActivity = DctConstants.Activity.DATAOUT;
+ } else if (sent == 0 && received > 0) {
+ mSentSinceLastRecv = 0;
+ newActivity = DctConstants.Activity.DATAIN;
+ } else if (sent == 0 && received == 0) {
+ newActivity = (mActivity == DctConstants.Activity.DORMANT) ?
+ mActivity : DctConstants.Activity.NONE;
+ } else {
+ mSentSinceLastRecv = 0;
+ newActivity = (mActivity == DctConstants.Activity.DORMANT) ?
+ mActivity : DctConstants.Activity.NONE;
+ }
+
+ if (mActivity != newActivity && mIsScreenOn) {
+ mActivity = newActivity;
+ mPhone.notifyDataActivity();
+ }
+ }
+
+ if (mSentSinceLastRecv >= NUMBER_SENT_PACKETS_OF_HANG) {
+ // Packets sent without ack exceeded threshold.
+
+ if (mNoRecvPollCount == 0) {
+ EventLog.writeEvent(
+ EventLogTags.PDP_RADIO_RESET_COUNTDOWN_TRIGGERED,
+ mSentSinceLastRecv);
+ }
+
+ if (mNoRecvPollCount < NO_RECV_POLL_LIMIT) {
+ mNoRecvPollCount++;
+ // Slow down the poll interval to let things happen
+ mNetStatPollPeriod = POLL_NETSTAT_SLOW_MILLIS;
+ } else {
+ if (DBG) log("Sent " + String.valueOf(mSentSinceLastRecv) +
+ " pkts since last received");
+ // We've exceeded the threshold. Restart the radio.
+ mNetStatPollEnabled = false;
+ stopNetStatPoll();
+ restartRadio();
+ EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET, NO_RECV_POLL_LIMIT);
+ }
+ } else {
+ mNoRecvPollCount = 0;
+ mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
+ }
+
+ if (mNetStatPollEnabled) {
+ mDataConnectionTracker.postDelayed(this, mNetStatPollPeriod);
+ }
+ }
+ };
+
+ /**
+ * Returns true if the last fail cause is something that
+ * seems like it deserves an error notification.
+ * Transient errors are ignored
+ */
+ private boolean
+ shouldPostNotification(FailCause cause) {
+ return (cause != FailCause.UNKNOWN);
+ }
+
+ /**
+ * Return true if data connection need to be setup after disconnected due to
+ * reason.
+ *
+ * @param reason the reason why data is disconnected
+ * @return true if try setup data connection is need for this reason
+ */
+ private boolean retryAfterDisconnected(String reason) {
+ boolean retry = true;
+
+ if ( Phone.REASON_RADIO_TURNED_OFF.equals(reason) ) {
+ retry = false;
+ }
+ return retry;
+ }
+
+ private void reconnectAfterFail(FailCause lastFailCauseCode, String reason, int retryOverride) {
+ if (mState == DctConstants.State.FAILED) {
+ /**
+ * For now With CDMA we never try to reconnect on
+ * error and instead just continue to retry
+ * at the last time until the state is changed.
+ * TODO: Make this configurable?
+ */
+ int nextReconnectDelay = retryOverride;
+ if (nextReconnectDelay < 0) {
+ nextReconnectDelay = mDataConnections.get(0).getRetryTimer();
+ mDataConnections.get(0).increaseRetryCount();
+ }
+ startAlarmForReconnect(nextReconnectDelay, reason);
+
+ if (!shouldPostNotification(lastFailCauseCode)) {
+ log("NOT Posting Data Connection Unavailable notification "
+ + "-- likely transient error");
+ } else {
+ notifyNoData(lastFailCauseCode);
+ }
+ }
+ }
+
+ private void startAlarmForReconnect(int delay, String reason) {
+
+ log("Data Connection activate failed. Scheduling next attempt for "
+ + (delay / 1000) + "s");
+
+ AlarmManager am =
+ (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(INTENT_RECONNECT_ALARM);
+ intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, reason);
+ mReconnectIntent = PendingIntent.getBroadcast(
+ mPhone.getContext(), 0, intent, 0);
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + delay, mReconnectIntent);
+
+ }
+
+ private void notifyNoData(FailCause lastFailCauseCode) {
+ setState(DctConstants.State.FAILED);
+ notifyOffApnsOfAvailability(null);
+ }
+
+ protected void gotoIdleAndNotifyDataConnection(String reason) {
+ if (DBG) log("gotoIdleAndNotifyDataConnection: reason=" + reason);
+ setState(DctConstants.State.IDLE);
+ notifyDataConnection(reason);
+ mActiveApn = null;
+ }
+
+ protected void onRecordsLoaded() {
+ if (mState == DctConstants.State.FAILED) {
+ cleanUpAllConnections(null);
+ }
+ sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, Phone.REASON_SIM_LOADED));
+ }
+
+ protected void onNVReady() {
+ if (mState == DctConstants.State.FAILED) {
+ cleanUpAllConnections(null);
+ }
+ sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA));
+ }
+
+ /**
+ * @override com.android.internal.telephony.DataConnectionTracker
+ */
+ @Override
+ protected void onEnableNewApn() {
+ // No mRequestedApnType check; only one connection is supported
+ cleanUpConnection(true, Phone.REASON_APN_SWITCHED, false);
+ }
+
+ /**
+ * @override com.android.internal.telephony.DataConnectionTracker
+ */
+ @Override
+ protected boolean onTrySetupData(String reason) {
+ return trySetupData(reason);
+ }
+
+ /**
+ * @override com.android.internal.telephony.DataConnectionTracker
+ */
+ @Override
+ protected void onRoamingOff() {
+ if (mUserDataEnabled == false) return;
+
+ if (getDataOnRoamingEnabled() == false) {
+ notifyOffApnsOfAvailability(Phone.REASON_ROAMING_OFF);
+ trySetupData(Phone.REASON_ROAMING_OFF);
+ } else {
+ notifyDataConnection(Phone.REASON_ROAMING_OFF);
+ }
+ }
+
+ /**
+ * @override com.android.internal.telephony.DataConnectionTracker
+ */
+ @Override
+ protected void onRoamingOn() {
+ if (mUserDataEnabled == false) return;
+
+ if (getDataOnRoamingEnabled()) {
+ trySetupData(Phone.REASON_ROAMING_ON);
+ notifyDataConnection(Phone.REASON_ROAMING_ON);
+ } else {
+ if (DBG) log("Tear down data connection on roaming.");
+ cleanUpAllConnections(null);
+ notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON);
+ }
+ }
+
+ /**
+ * @override com.android.internal.telephony.DataConnectionTracker
+ */
+ @Override
+ protected void onRadioAvailable() {
+ if (mPhone.getSimulatedRadioControl() != null) {
+ // Assume data is connected on the simulator
+ // FIXME this can be improved
+ setState(DctConstants.State.CONNECTED);
+ notifyDataConnection(null);
+
+ log("We're on the simulator; assuming data is connected");
+ }
+
+ notifyOffApnsOfAvailability(null);
+
+ if (mState != DctConstants.State.IDLE) {
+ cleanUpAllConnections(null);
+ }
+ }
+
+ /**
+ * @override com.android.internal.telephony.DataConnectionTracker
+ */
+ @Override
+ protected void onRadioOffOrNotAvailable() {
+ mDataConnections.get(0).resetRetryCount();
+
+ if (mPhone.getSimulatedRadioControl() != null) {
+ // Assume data is connected on the simulator
+ // FIXME this can be improved
+ log("We're on the simulator; assuming radio off is meaningless");
+ } else {
+ if (DBG) log("Radio is off and clean up all connection");
+ cleanUpAllConnections(null);
+ }
+ }
+
+ /**
+ * @override com.android.internal.telephony.DataConnectionTracker
+ */
+ @Override
+ protected void onDataSetupComplete(AsyncResult ar) {
+ String reason = null;
+ if (ar.userObj instanceof String) {
+ reason = (String) ar.userObj;
+ }
+
+ if (isDataSetupCompleteOk(ar)) {
+ // Everything is setup
+ notifyDefaultData(reason);
+ } else {
+ FailCause cause = (FailCause) (ar.result);
+ if(DBG) log("Data Connection setup failed " + cause);
+
+ // No try for permanent failure
+ if (cause.isPermanentFail()) {
+ notifyNoData(cause);
+ return;
+ }
+
+ int retryOverride = -1;
+ if (ar.exception instanceof DataConnection.CallSetupException) {
+ retryOverride =
+ ((DataConnection.CallSetupException)ar.exception).getRetryOverride();
+ }
+ if (retryOverride == RILConstants.MAX_INT) {
+ if (DBG) log("No retry is suggested.");
+ } else {
+ startDelayedRetry(cause, reason, retryOverride);
+ }
+ }
+ }
+
+ /**
+ * Called when DctConstants.EVENT_DISCONNECT_DONE is received.
+ */
+ @Override
+ protected void onDisconnectDone(int connId, AsyncResult ar) {
+ if(DBG) log("EVENT_DISCONNECT_DONE connId=" + connId);
+ String reason = null;
+ if (ar.userObj instanceof String) {
+ reason = (String) ar.userObj;
+ }
+ setState(DctConstants.State.IDLE);
+
+ // Since the pending request to turn off or restart radio will be processed here,
+ // remove the pending event to restart radio from the message queue.
+ if (mPendingRestartRadio) removeMessages(DctConstants.EVENT_RESTART_RADIO);
+
+ // Process the pending request to turn off radio in ServiceStateTracker first.
+ // If radio is turned off in ServiceStateTracker, ignore the pending event to restart radio.
+ CdmaServiceStateTracker ssTracker = mCdmaPhone.mSST;
+ if (ssTracker.processPendingRadioPowerOffAfterDataOff()) {
+ mPendingRestartRadio = false;
+ } else {
+ onRestartRadio();
+ }
+
+ notifyDataConnection(reason);
+ mActiveApn = null;
+ if (retryAfterDisconnected(reason)) {
+ // Wait a bit before trying, so we're not tying up RIL command channel.
+ startAlarmForReconnect(APN_DELAY_MILLIS, reason);
+ }
+ }
+
+ /**
+ * @override com.android.internal.telephony.DataConnectionTracker
+ */
+ @Override
+ protected void onVoiceCallStarted() {
+ if (mState == DctConstants.State.CONNECTED &&
+ !mCdmaPhone.mSST.isConcurrentVoiceAndDataAllowed()) {
+ stopNetStatPoll();
+ notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
+ notifyOffApnsOfAvailability(Phone.REASON_VOICE_CALL_STARTED);
+ }
+ }
+
+ /**
+ * @override com.android.internal.telephony.DataConnectionTracker
+ */
+ @Override
+ protected void onVoiceCallEnded() {
+ if (mState == DctConstants.State.CONNECTED) {
+ if (!mCdmaPhone.mSST.isConcurrentVoiceAndDataAllowed()) {
+ startNetStatPoll();
+ notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
+ } else {
+ // clean slate after call end.
+ resetPollStats();
+ }
+ notifyOffApnsOfAvailability(Phone.REASON_VOICE_CALL_ENDED);
+ } else {
+ mDataConnections.get(0).resetRetryCount();
+ // in case data setup was attempted when we were on a voice call
+ trySetupData(Phone.REASON_VOICE_CALL_ENDED);
+ }
+ }
+
+ @Override
+ protected void onCleanUpConnection(boolean tearDown, int apnId, String reason) {
+ // No apnId check; only one connection is supported
+ cleanUpConnection(tearDown, reason, (apnId == DctConstants.APN_DUN_ID));
+ }
+
+ @Override
+ protected void onCleanUpAllConnections(String cause) {
+ // Only one CDMA connection is supported
+ cleanUpConnection(true, cause, false);
+ }
+
+ private void createAllDataConnectionList() {
+ CdmaDataConnection dataConn;
+
+ String retryConfig = SystemProperties.get("ro.cdma.data_retry_config");
+ for (int i = 0; i < DATA_CONNECTION_POOL_SIZE; i++) {
+ RetryManager rm = new RetryManager();
+ if (!rm.configure(retryConfig)) {
+ if (!rm.configure(DEFAULT_DATA_RETRY_CONFIG)) {
+ // Should never happen, log an error and default to a simple linear sequence.
+ log("Could not configure using DEFAULT_DATA_RETRY_CONFIG="
+ + DEFAULT_DATA_RETRY_CONFIG);
+ rm.configure(20, 2000, 1000);
+ }
+ }
+
+ int id = mUniqueIdGenerator.getAndIncrement();
+ dataConn = CdmaDataConnection.makeDataConnection(mCdmaPhone, id, rm, this);
+ mDataConnections.put(id, dataConn);
+ DataConnectionAc dcac = new DataConnectionAc(dataConn, LOG_TAG);
+ int status = dcac.fullyConnectSync(mPhone.getContext(), this, dataConn.getHandler());
+ if (status == AsyncChannel.STATUS_SUCCESSFUL) {
+ log("Fully connected");
+ mDataConnectionAsyncChannels.put(dcac.dataConnection.getDataConnectionId(), dcac);
+ } else {
+ log("Could not connect to dcac.dataConnection=" + dcac.dataConnection +
+ " status=" + status);
+ }
+
+ }
+ }
+
+ private void destroyAllDataConnectionList() {
+ if(mDataConnections != null) {
+ mDataConnections.clear();
+ }
+ }
+
+ private void onCdmaDataDetached() {
+ if (mState == DctConstants.State.CONNECTED) {
+ startNetStatPoll();
+ notifyDataConnection(Phone.REASON_CDMA_DATA_DETACHED);
+ } else {
+ if (mState == DctConstants.State.FAILED) {
+ cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED, false);
+ mDataConnections.get(0).resetRetryCount();
+
+ CdmaCellLocation loc = (CdmaCellLocation)(mPhone.getCellLocation());
+ EventLog.writeEvent(EventLogTags.CDMA_DATA_SETUP_FAILED,
+ loc != null ? loc.getBaseStationId() : -1,
+ TelephonyManager.getDefault().getNetworkType());
+ }
+ trySetupData(Phone.REASON_CDMA_DATA_DETACHED);
+ }
+ }
+
+ private void onCdmaOtaProvision(AsyncResult ar) {
+ if (ar.exception != null) {
+ int [] otaPrivision = (int [])ar.result;
+ if ((otaPrivision != null) && (otaPrivision.length > 1)) {
+ switch (otaPrivision[0]) {
+ case Phone.CDMA_OTA_PROVISION_STATUS_COMMITTED:
+ case Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_STOPPED:
+ mDataConnections.get(0).resetRetryCount();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ private void onRestartRadio() {
+ if (mPendingRestartRadio) {
+ log("************TURN OFF RADIO**************");
+ mPhone.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.
+ */
+ mPendingRestartRadio = false;
+ }
+ }
+
+ private void writeEventLogCdmaDataDrop() {
+ CdmaCellLocation loc = (CdmaCellLocation)(mPhone.getCellLocation());
+ EventLog.writeEvent(EventLogTags.CDMA_DATA_DROP,
+ loc != null ? loc.getBaseStationId() : -1,
+ TelephonyManager.getDefault().getNetworkType());
+ }
+
+ protected void onDataStateChanged(AsyncResult ar) {
+ ArrayList<DataCallState> dataCallStates = (ArrayList<DataCallState>)(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;
+ }
+
+ if (mState == DctConstants.State.CONNECTED) {
+ boolean isActiveOrDormantConnectionPresent = false;
+ int connectionState = DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE;
+
+ // Check for an active or dormant connection element in
+ // the DATA_CALL_LIST array
+ for (int index = 0; index < dataCallStates.size(); index++) {
+ connectionState = dataCallStates.get(index).active;
+ if (connectionState != DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
+ isActiveOrDormantConnectionPresent = true;
+ break;
+ }
+ }
+
+ if (!isActiveOrDormantConnectionPresent) {
+ // No active or dormant connection
+ log("onDataStateChanged: No active connection"
+ + "state is CONNECTED, disconnecting/cleanup");
+ writeEventLogCdmaDataDrop();
+ cleanUpConnection(true, null, false);
+ return;
+ }
+
+ switch (connectionState) {
+ case DATA_CONNECTION_ACTIVE_PH_LINK_UP:
+ log("onDataStateChanged: active=LINK_ACTIVE && CONNECTED, ignore");
+ mActivity = DctConstants.Activity.NONE;
+ mPhone.notifyDataActivity();
+ startNetStatPoll();
+ break;
+
+ case DATA_CONNECTION_ACTIVE_PH_LINK_DOWN:
+ log("onDataStateChanged active=LINK_DOWN && CONNECTED, dormant");
+ mActivity = DctConstants.Activity.DORMANT;
+ mPhone.notifyDataActivity();
+ stopNetStatPoll();
+ break;
+
+ default:
+ log("onDataStateChanged: IGNORE unexpected DataCallState.active="
+ + connectionState);
+ }
+ } else {
+ // TODO: Do we need to do anything?
+ log("onDataStateChanged: not connected, state=" + mState + " ignoring");
+ }
+ }
+
+ private void startDelayedRetry(FailCause cause, String reason, int retryOverride) {
+ notifyNoData(cause);
+ reconnectAfterFail(cause, reason, retryOverride);
+ }
+
+ @Override
+ public void handleMessage (Message msg) {
+ if (DBG) log("CdmaDCT handleMessage msg=" + msg);
+
+ if (!mPhone.mIsTheCurrentActivePhone || mIsDisposed) {
+ log("Ignore CDMA msgs since CDMA phone is inactive");
+ return;
+ }
+
+ switch (msg.what) {
+ case DctConstants.EVENT_RECORDS_LOADED:
+ onRecordsLoaded();
+ break;
+
+ case DctConstants.EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
+ if(mCdmaSSM.getCdmaSubscriptionSource() ==
+ CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_NV) {
+ onNVReady();
+ }
+ break;
+
+ case DctConstants.EVENT_CDMA_DATA_DETACHED:
+ onCdmaDataDetached();
+ break;
+
+ case DctConstants.EVENT_DATA_STATE_CHANGED:
+ onDataStateChanged((AsyncResult) msg.obj);
+ break;
+
+ case DctConstants.EVENT_CDMA_OTA_PROVISION:
+ onCdmaOtaProvision((AsyncResult) msg.obj);
+ break;
+
+ case DctConstants.EVENT_RESTART_RADIO:
+ if (DBG) log("EVENT_RESTART_RADIO");
+ onRestartRadio();
+ break;
+
+ default:
+ // handle the message in the super class DataConnectionTracker
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ @Override
+ public boolean isDisconnected() {
+ return ((mState == DctConstants.State.IDLE) || (mState == DctConstants.State.FAILED));
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[CdmaDCT] " + s);
+ }
+
+ @Override
+ protected void loge(String s) {
+ Log.e(LOG_TAG, "[CdmaDCT] " + s);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("CdmaDataConnectionTracker extends:");
+ super.dump(fd, pw, args);
+ pw.println(" mCdmaPhone=" + mCdmaPhone);
+ pw.println(" mCdmaSSM=" + mCdmaSSM);
+ pw.println(" mPendingDataConnection=" + mPendingDataConnection);
+ pw.println(" mPendingRestartRadio=" + mPendingRestartRadio);
+ pw.println(" mSupportedApnTypes=" + mSupportedApnTypes);
+ pw.println(" mDefaultApnTypes=" + mDefaultApnTypes);
+ pw.println(" mDunApnTypes=" + mDunApnTypes);
+ pw.println(" mDefaultApnId=" + mDefaultApnId);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInformationRecords.java b/src/java/com/android/internal/telephony/cdma/CdmaInformationRecords.java
new file mode 100644
index 0000000..ce6530a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInformationRecords.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2009 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.cdma;
+import static com.android.internal.telephony.RILConstants.*;
+import android.os.Parcel;
+
+public final class CdmaInformationRecords {
+ public Object record;
+
+ /**
+ * Record type identifier
+ */
+ public static final int RIL_CDMA_DISPLAY_INFO_REC = 0;
+ public static final int RIL_CDMA_CALLED_PARTY_NUMBER_INFO_REC = 1;
+ public static final int RIL_CDMA_CALLING_PARTY_NUMBER_INFO_REC = 2;
+ public static final int RIL_CDMA_CONNECTED_NUMBER_INFO_REC = 3;
+ public static final int RIL_CDMA_SIGNAL_INFO_REC = 4;
+ public static final int RIL_CDMA_REDIRECTING_NUMBER_INFO_REC = 5;
+ public static final int RIL_CDMA_LINE_CONTROL_INFO_REC = 6;
+ public static final int RIL_CDMA_EXTENDED_DISPLAY_INFO_REC = 7;
+ public static final int RIL_CDMA_T53_CLIR_INFO_REC = 8;
+ public static final int RIL_CDMA_T53_RELEASE_INFO_REC = 9;
+ public static final int RIL_CDMA_T53_AUDIO_CONTROL_INFO_REC = 10;
+
+ public CdmaInformationRecords(Parcel p) {
+ int id = p.readInt();
+ switch (id) {
+ case RIL_CDMA_DISPLAY_INFO_REC:
+ case RIL_CDMA_EXTENDED_DISPLAY_INFO_REC:
+ record = new CdmaDisplayInfoRec(id, p.readString());
+ break;
+
+ case RIL_CDMA_CALLED_PARTY_NUMBER_INFO_REC:
+ case RIL_CDMA_CALLING_PARTY_NUMBER_INFO_REC:
+ case RIL_CDMA_CONNECTED_NUMBER_INFO_REC:
+ record = new CdmaNumberInfoRec(id, p.readString(), p.readInt(), p.readInt(),
+ p.readInt(), p.readInt());
+ break;
+
+ case RIL_CDMA_SIGNAL_INFO_REC:
+ record = new CdmaSignalInfoRec(p.readInt(), p.readInt(), p.readInt(), p.readInt());
+ break;
+
+ case RIL_CDMA_REDIRECTING_NUMBER_INFO_REC:
+ record = new CdmaRedirectingNumberInfoRec(p.readString(), p.readInt(), p.readInt(),
+ p.readInt(), p.readInt(), p.readInt());
+ break;
+
+ case RIL_CDMA_LINE_CONTROL_INFO_REC:
+ record = new CdmaLineControlInfoRec(p.readInt(), p.readInt(), p.readInt(),
+ p.readInt());
+ break;
+
+ case RIL_CDMA_T53_CLIR_INFO_REC:
+ record = new CdmaT53ClirInfoRec(p.readInt());
+ break;
+
+ case RIL_CDMA_T53_AUDIO_CONTROL_INFO_REC:
+ record = new CdmaT53AudioControlInfoRec(p.readInt(), p.readInt());
+ break;
+
+ case RIL_CDMA_T53_RELEASE_INFO_REC:
+ // TODO: WHAT to do, for now fall through and throw exception
+ default:
+ throw new RuntimeException("RIL_UNSOL_CDMA_INFO_REC: unsupported record. Got "
+ + CdmaInformationRecords.idToString(id) + " ");
+
+ }
+ }
+
+ public static String idToString(int id) {
+ switch(id) {
+ case RIL_CDMA_DISPLAY_INFO_REC: return "RIL_CDMA_DISPLAY_INFO_REC";
+ case RIL_CDMA_CALLED_PARTY_NUMBER_INFO_REC: return "RIL_CDMA_CALLED_PARTY_NUMBER_INFO_REC";
+ case RIL_CDMA_CALLING_PARTY_NUMBER_INFO_REC: return "RIL_CDMA_CALLING_PARTY_NUMBER_INFO_REC";
+ case RIL_CDMA_CONNECTED_NUMBER_INFO_REC: return "RIL_CDMA_CONNECTED_NUMBER_INFO_REC";
+ case RIL_CDMA_SIGNAL_INFO_REC: return "RIL_CDMA_SIGNAL_INFO_REC";
+ case RIL_CDMA_REDIRECTING_NUMBER_INFO_REC: return "RIL_CDMA_REDIRECTING_NUMBER_INFO_REC";
+ case RIL_CDMA_LINE_CONTROL_INFO_REC: return "RIL_CDMA_LINE_CONTROL_INFO_REC";
+ case RIL_CDMA_EXTENDED_DISPLAY_INFO_REC: return "RIL_CDMA_EXTENDED_DISPLAY_INFO_REC";
+ case RIL_CDMA_T53_CLIR_INFO_REC: return "RIL_CDMA_T53_CLIR_INFO_REC";
+ case RIL_CDMA_T53_RELEASE_INFO_REC: return "RIL_CDMA_T53_RELEASE_INFO_REC";
+ case RIL_CDMA_T53_AUDIO_CONTROL_INFO_REC: return "RIL_CDMA_T53_AUDIO_CONTROL_INFO_REC";
+ default: return "<unknown record>";
+ }
+ }
+
+ /**
+ * Signal Information record from 3GPP2 C.S005 3.7.5.5
+ */
+ public static class CdmaSignalInfoRec {
+ public boolean isPresent; /* non-zero if signal information record is present */
+ public int signalType;
+ public int alertPitch;
+ public int signal;
+
+ public CdmaSignalInfoRec() {}
+
+ public CdmaSignalInfoRec(int isPresent, int signalType, int alertPitch, int signal) {
+ this.isPresent = isPresent != 0;
+ this.signalType = signalType;
+ this.alertPitch = alertPitch;
+ this.signal = signal;
+ }
+
+ @Override
+ public String toString() {
+ return "CdmaSignalInfo: {" +
+ " isPresent: " + isPresent +
+ ", signalType: " + signalType +
+ ", alertPitch: " + alertPitch +
+ ", signal: " + signal +
+ " }";
+ }
+ }
+
+ public static class CdmaDisplayInfoRec {
+ public int id;
+ public String alpha;
+
+ public CdmaDisplayInfoRec(int id, String alpha) {
+ this.id = id;
+ this.alpha = alpha;
+ }
+
+ @Override
+ public String toString() {
+ return "CdmaDisplayInfoRec: {" +
+ " id: " + CdmaInformationRecords.idToString(id) +
+ ", alpha: " + alpha +
+ " }";
+ }
+ }
+
+ public static class CdmaNumberInfoRec {
+ public int id;
+ public String number;
+ public byte numberType;
+ public byte numberPlan;
+ public byte pi;
+ public byte si;
+
+ public CdmaNumberInfoRec(int id, String number, int numberType, int numberPlan, int pi,
+ int si) {
+ this.number = number;
+ this.numberType = (byte)numberType;
+ this.numberPlan = (byte)numberPlan;
+ this.pi = (byte)pi;
+ this.si = (byte)si;
+ }
+
+ @Override
+ public String toString() {
+ return "CdmaNumberInfoRec: {" +
+ " id: " + CdmaInformationRecords.idToString(id) +
+ ", number: " + number +
+ ", numberType: " + numberType +
+ ", numberPlan: " + numberPlan +
+ ", pi: " + pi +
+ ", si: " + si +
+ " }";
+ }
+ }
+
+ public static class CdmaRedirectingNumberInfoRec {
+ public static final int REASON_UNKNOWN = 0;
+ public static final int REASON_CALL_FORWARDING_BUSY = 1;
+ public static final int REASON_CALL_FORWARDING_NO_REPLY = 2;
+ public static final int REASON_CALLED_DTE_OUT_OF_ORDER = 9;
+ public static final int REASON_CALL_FORWARDING_BY_THE_CALLED_DTE = 10;
+ public static final int REASON_CALL_FORWARDING_UNCONDITIONAL = 15;
+
+ public CdmaNumberInfoRec numberInfoRec;
+ public int redirectingReason;
+
+ public CdmaRedirectingNumberInfoRec(String number, int numberType, int numberPlan,
+ int pi, int si, int reason) {
+ numberInfoRec = new CdmaNumberInfoRec(RIL_CDMA_REDIRECTING_NUMBER_INFO_REC,
+ number, numberType, numberPlan, pi, si);
+ redirectingReason = reason;
+ }
+
+ @Override
+ public String toString() {
+ return "CdmaNumberInfoRec: {" +
+ " numberInfoRec: " + numberInfoRec +
+ ", redirectingReason: " + redirectingReason +
+ " }";
+ }
+ }
+
+ public static class CdmaLineControlInfoRec {
+ public byte lineCtrlPolarityIncluded;
+ public byte lineCtrlToggle;
+ public byte lineCtrlReverse;
+ public byte lineCtrlPowerDenial;
+
+ public CdmaLineControlInfoRec(int lineCtrlPolarityIncluded, int lineCtrlToggle,
+ int lineCtrlReverse, int lineCtrlPowerDenial) {
+ this.lineCtrlPolarityIncluded = (byte)lineCtrlPolarityIncluded;
+ this.lineCtrlToggle = (byte)lineCtrlToggle;
+ this.lineCtrlReverse = (byte)lineCtrlReverse;
+ this.lineCtrlPowerDenial = (byte)lineCtrlPowerDenial;
+ }
+
+ @Override
+ public String toString() {
+ return "CdmaLineControlInfoRec: {" +
+ " lineCtrlPolarityIncluded: " + lineCtrlPolarityIncluded +
+ " lineCtrlToggle: " + lineCtrlToggle +
+ " lineCtrlReverse: " + lineCtrlReverse +
+ " lineCtrlPowerDenial: " + lineCtrlPowerDenial +
+ " }";
+ }
+ }
+
+ public static class CdmaT53ClirInfoRec {
+ public byte cause;
+
+ public CdmaT53ClirInfoRec(int cause) {
+ this.cause = (byte)cause;
+ }
+
+ @Override
+ public String toString() {
+ return "CdmaT53ClirInfoRec: {" +
+ " cause: " + cause +
+ " }";
+ }
+ }
+
+ public static class CdmaT53AudioControlInfoRec {
+ public byte uplink;
+ public byte downlink;
+
+ public CdmaT53AudioControlInfoRec(int uplink, int downlink) {
+ this.uplink = (byte) uplink;
+ this.downlink = (byte) downlink;
+ }
+
+ @Override
+ public String toString() {
+ return "CdmaT53AudioControlInfoRec: {" +
+ " uplink: " + uplink +
+ " downlink: " + downlink +
+ " }";
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
new file mode 100644
index 0000000..0c5c342
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
@@ -0,0 +1,546 @@
+/*
+ * 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.cdma;
+
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.IccCard;
+
+import android.content.Intent;
+import android.telephony.SignalStrength;
+import android.telephony.ServiceState;
+import android.telephony.cdma.CdmaCellLocation;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.os.SystemProperties;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.EventLog;
+
+import com.android.internal.telephony.gsm.GsmDataConnectionTracker;
+import com.android.internal.telephony.IccCardConstants;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker {
+ CDMALTEPhone mCdmaLtePhone;
+
+ private ServiceState mLteSS; // The last LTE state from Voice Registration
+
+ public CdmaLteServiceStateTracker(CDMALTEPhone phone) {
+ super(phone);
+ mCdmaLtePhone = phone;
+
+ mLteSS = new ServiceState();
+ if (DBG) log("CdmaLteServiceStateTracker Constructors");
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+ int[] ints;
+ String[] strings;
+ switch (msg.what) {
+ case EVENT_POLL_STATE_GPRS:
+ if (DBG) log("handleMessage EVENT_POLL_STATE_GPRS");
+ ar = (AsyncResult)msg.obj;
+ handlePollStateResult(msg.what, ar);
+ break;
+ case EVENT_RUIM_RECORDS_LOADED:
+ CdmaLteUiccRecords sim = (CdmaLteUiccRecords)phone.mIccRecords;
+ if ((sim != null) && sim.isProvisioned()) {
+ mMdn = sim.getMdn();
+ mMin = sim.getMin();
+ parseSidNid(sim.getSid(), sim.getNid());
+ mPrlVersion = sim.getPrlVersion();;
+ mIsMinInfoReady = true;
+ updateOtaspState();
+ }
+ // SID/NID/PRL is loaded. Poll service state
+ // again to update to the roaming state with
+ // the latest variables.
+ pollState();
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ /**
+ * Set the cdmaSS for EVENT_POLL_STATE_REGISTRATION_CDMA
+ */
+ @Override
+ protected void setCdmaTechnology(int radioTechnology) {
+ // Called on voice registration state response.
+ // Just record new CDMA radio technology
+ newSS.setRadioTechnology(radioTechnology);
+ }
+
+ /**
+ * Handle the result of one of the pollState()-related requests
+ */
+ @Override
+ protected void handlePollStateResultMessage(int what, AsyncResult ar) {
+ if (what == EVENT_POLL_STATE_GPRS) {
+ if (DBG) log("handlePollStateResultMessage: EVENT_POLL_STATE_GPRS");
+ String states[] = (String[])ar.result;
+
+ int type = 0;
+ int 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) {
+ loge("handlePollStateResultMessage: error parsing GprsRegistrationState: "
+ + ex);
+ }
+ }
+
+ mLteSS.setRadioTechnology(type);
+ mLteSS.setState(regCodeToServiceState(regState));
+ } else {
+ super.handlePollStateResultMessage(what, ar);
+ }
+ }
+
+ @Override
+ protected void setSignalStrengthDefaultValues() {
+ // TODO Make a constructor only has boolean gsm as parameter
+ mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, SignalStrength.INVALID_SNR, -1, false);
+ }
+
+ @Override
+ protected void pollState() {
+ pollingContext = new int[1];
+ pollingContext[0] = 0;
+
+ switch (cm.getRadioState()) {
+ case RADIO_UNAVAILABLE:
+ newSS.setStateOutOfService();
+ newCellLoc.setStateInvalid();
+ setSignalStrengthDefaultValues();
+ mGotCountryCode = false;
+
+ pollStateDone();
+ break;
+
+ case RADIO_OFF:
+ newSS.setStateOff();
+ newCellLoc.setStateInvalid();
+ setSignalStrengthDefaultValues();
+ 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]++;
+ // RIL_REQUEST_OPERATOR is necessary for CDMA
+ cm.getOperator(obtainMessage(EVENT_POLL_STATE_OPERATOR_CDMA, pollingContext));
+
+ pollingContext[0]++;
+ // RIL_REQUEST_VOICE_REGISTRATION_STATE is necessary for CDMA
+ cm.getVoiceRegistrationState(obtainMessage(EVENT_POLL_STATE_REGISTRATION_CDMA,
+ pollingContext));
+
+ int networkMode = android.provider.Settings.Secure.getInt(phone.getContext()
+ .getContentResolver(),
+ android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ RILConstants.PREFERRED_NETWORK_MODE);
+ if (DBG) log("pollState: network mode here is = " + networkMode);
+ if ((networkMode == RILConstants.NETWORK_MODE_GLOBAL)
+ || (networkMode == RILConstants.NETWORK_MODE_LTE_ONLY)) {
+ pollingContext[0]++;
+ // RIL_REQUEST_DATA_REGISTRATION_STATE
+ cm.getDataRegistrationState(obtainMessage(EVENT_POLL_STATE_GPRS,
+ pollingContext));
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void pollStateDone() {
+ // determine data RadioTechnology from both LET and CDMA SS
+ if (mLteSS.getState() == ServiceState.STATE_IN_SERVICE) {
+ //in LTE service
+ mNewRilRadioTechnology = mLteSS.getRilRadioTechnology();
+ mNewDataConnectionState = mLteSS.getState();
+ newSS.setRadioTechnology(mNewRilRadioTechnology);
+ log("pollStateDone LTE/eHRPD STATE_IN_SERVICE mNewRilRadioTechnology = " +
+ mNewRilRadioTechnology);
+ } else {
+ // LTE out of service, get CDMA Service State
+ mNewRilRadioTechnology = newSS.getRilRadioTechnology();
+ mNewDataConnectionState = radioTechnologyToDataServiceState(mNewRilRadioTechnology);
+ log("pollStateDone CDMA STATE_IN_SERVICE mNewRilRadioTechnology = " +
+ mNewRilRadioTechnology + " mNewDataConnectionState = " +
+ mNewDataConnectionState);
+ }
+
+ // TODO: Add proper support for LTE Only, we should be looking at
+ // the preferred network mode, to know when newSS state should
+ // be coming from mLteSs state. This was needed to pass a VZW
+ // LTE Only test.
+ //
+ // If CDMA service is OOS, double check if the device is running with LTE only
+ // mode. If that is the case, derive the service state from LTE side.
+ // To set in LTE only mode, sqlite3 /data/data/com.android.providers.settings/
+ // databases/settings.db "update secure set value='11' where name='preferred_network_mode'"
+ if (newSS.getState() == ServiceState.STATE_OUT_OF_SERVICE) {
+ int networkMode = android.provider.Settings.Secure.getInt(phone.getContext()
+ .getContentResolver(),
+ android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ RILConstants.PREFERRED_NETWORK_MODE);
+ if (networkMode == RILConstants.NETWORK_MODE_LTE_ONLY) {
+ if (DBG) log("pollState: LTE Only mode");
+ newSS.setState(mLteSS.getState());
+ }
+ }
+
+ if (DBG) log("pollStateDone: oldSS=[" + ss + "] newSS=[" + newSS + "]");
+
+ 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 hasCdmaDataConnectionAttached =
+ mDataConnectionState != ServiceState.STATE_IN_SERVICE
+ && mNewDataConnectionState == ServiceState.STATE_IN_SERVICE;
+
+ boolean hasCdmaDataConnectionDetached =
+ mDataConnectionState == ServiceState.STATE_IN_SERVICE
+ && mNewDataConnectionState != ServiceState.STATE_IN_SERVICE;
+
+ boolean hasCdmaDataConnectionChanged =
+ mDataConnectionState != mNewDataConnectionState;
+
+ boolean hasRadioTechnologyChanged = mRilRadioTechnology != mNewRilRadioTechnology;
+
+ boolean hasChanged = !newSS.equals(ss);
+
+ boolean hasRoamingOn = !ss.getRoaming() && newSS.getRoaming();
+
+ boolean hasRoamingOff = ss.getRoaming() && !newSS.getRoaming();
+
+ boolean hasLocationChanged = !newCellLoc.equals(cellLoc);
+
+ boolean has4gHandoff =
+ mNewDataConnectionState == ServiceState.STATE_IN_SERVICE &&
+ (((mRilRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_LTE) &&
+ (mNewRilRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)) ||
+ ((mRilRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD) &&
+ (mNewRilRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_LTE)));
+
+ boolean hasMultiApnSupport =
+ (((mNewRilRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_LTE) ||
+ (mNewRilRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)) &&
+ ((mRilRadioTechnology != ServiceState.RIL_RADIO_TECHNOLOGY_LTE) &&
+ (mRilRadioTechnology != ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)));
+
+ boolean hasLostMultiApnSupport =
+ ((mNewRilRadioTechnology >= ServiceState.RIL_RADIO_TECHNOLOGY_IS95A) &&
+ (mNewRilRadioTechnology <= ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A));
+
+ if (DBG) {
+ log("pollStateDone:"
+ + " hasRegistered=" + hasRegistered
+ + " hasDeegistered=" + hasDeregistered
+ + " hasCdmaDataConnectionAttached=" + hasCdmaDataConnectionAttached
+ + " hasCdmaDataConnectionDetached=" + hasCdmaDataConnectionDetached
+ + " hasCdmaDataConnectionChanged=" + hasCdmaDataConnectionChanged
+ + " hasRadioTechnologyChanged = " + hasRadioTechnologyChanged
+ + " hasChanged=" + hasChanged
+ + " hasRoamingOn=" + hasRoamingOn
+ + " hasRoamingOff=" + hasRoamingOff
+ + " hasLocationChanged=" + hasLocationChanged
+ + " has4gHandoff = " + has4gHandoff
+ + " hasMultiApnSupport=" + hasMultiApnSupport
+ + " hasLostMultiApnSupport=" + hasLostMultiApnSupport);
+ }
+ // Add an event log when connection state changes
+ if (ss.getState() != newSS.getState()
+ || mDataConnectionState != mNewDataConnectionState) {
+ EventLog.writeEvent(EventLogTags.CDMA_SERVICE_STATE_CHANGE, ss.getState(),
+ mDataConnectionState, newSS.getState(), mNewDataConnectionState);
+ }
+
+ ServiceState tss;
+ tss = ss;
+ ss = newSS;
+ newSS = tss;
+ // clean slate for next time
+ newSS.setStateOutOfService();
+ mLteSS.setStateOutOfService();
+
+ if ((hasMultiApnSupport)
+ && (phone.mDataConnectionTracker instanceof CdmaDataConnectionTracker)) {
+ if (DBG) log("GsmDataConnectionTracker Created");
+ phone.mDataConnectionTracker.dispose();
+ phone.mDataConnectionTracker = new GsmDataConnectionTracker(mCdmaLtePhone);
+ }
+
+ if ((hasLostMultiApnSupport)
+ && (phone.mDataConnectionTracker instanceof GsmDataConnectionTracker)) {
+ if (DBG)log("GsmDataConnectionTracker disposed");
+ phone.mDataConnectionTracker.dispose();
+ phone.mDataConnectionTracker = new CdmaDataConnectionTracker(phone);
+ }
+
+ CdmaCellLocation tcl = cellLoc;
+ cellLoc = newCellLoc;
+ newCellLoc = tcl;
+
+ mDataConnectionState = mNewDataConnectionState;
+ mRilRadioTechnology = mNewRilRadioTechnology;
+ mNewRilRadioTechnology = 0;
+
+ newSS.setStateOutOfService(); // clean slate for next time
+
+ if (hasRadioTechnologyChanged) {
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE,
+ ServiceState.rilRadioTechnologyToString(mRilRadioTechnology));
+ }
+
+ if (hasRegistered) {
+ mNetworkAttachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasChanged) {
+ if (phone.isEriFileLoaded()) {
+ String eriText;
+ // Now the CDMAPhone sees the new ServiceState so it can get the
+ // new ERI text
+ if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
+ eriText = phone.getCdmaEriText();
+ } else if (ss.getState() == ServiceState.STATE_POWER_OFF) {
+ eriText = phone.mIccRecords.getServiceProviderName();
+ if (TextUtils.isEmpty(eriText)) {
+ // Sets operator alpha property by retrieving from
+ // build-time system property
+ eriText = SystemProperties.get("ro.cdma.home.operator.alpha");
+ }
+ } else {
+ // Note that ServiceState.STATE_OUT_OF_SERVICE is valid used
+ // for mRegistrationState 0,2,3 and 4
+ eriText = phone.getContext()
+ .getText(com.android.internal.R.string.roamingTextSearching).toString();
+ }
+ ss.setOperatorAlphaLong(eriText);
+ }
+
+ if (phone.getIccCard().getState() == IccCardConstants.State.READY) {
+ // SIM is found on the device. If ERI roaming is OFF, and SID/NID matches
+ // one configfured in SIM, use operator name from CSIM record.
+ boolean showSpn =
+ ((CdmaLteUiccRecords)phone.mIccRecords).getCsimSpnDisplayCondition();
+ int iconIndex = ss.getCdmaEriIconIndex();
+
+ if (showSpn && (iconIndex == EriInfo.ROAMING_INDICATOR_OFF) &&
+ isInHomeSidNid(ss.getSystemId(), ss.getNetworkId())) {
+ ss.setOperatorAlphaLong(phone.mIccRecords.getServiceProviderName());
+ }
+ }
+
+ String operatorNumeric;
+
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
+ ss.getOperatorAlphaLong());
+
+ String prevOperatorNumeric =
+ SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
+ operatorNumeric = ss.getOperatorNumeric();
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
+
+ if (operatorNumeric == null) {
+ if (DBG) log("operatorNumeric is null");
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
+ mGotCountryCode = false;
+ } else {
+ String isoCountryCode = "";
+ String mcc = operatorNumeric.substring(0, 3);
+ try {
+ isoCountryCode = MccTable.countryCodeForMcc(Integer.parseInt(operatorNumeric
+ .substring(0, 3)));
+ } catch (NumberFormatException ex) {
+ loge("countryCodeForMcc error" + ex);
+ } catch (StringIndexOutOfBoundsException ex) {
+ loge("countryCodeForMcc error" + ex);
+ }
+
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY,
+ isoCountryCode);
+ mGotCountryCode = true;
+
+ if (shouldFixTimeZoneNow(phone, operatorNumeric, prevOperatorNumeric,
+ mNeedFixZone)) {
+ fixTimeZone(isoCountryCode);
+ }
+ }
+
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING,
+ ss.getRoaming() ? "true" : "false");
+
+ updateSpnDisplay();
+ phone.notifyServiceStateChanged(ss);
+ }
+
+ if (hasCdmaDataConnectionAttached || has4gHandoff) {
+ mAttachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasCdmaDataConnectionDetached) {
+ mDetachedRegistrants.notifyRegistrants();
+ }
+
+ if ((hasCdmaDataConnectionChanged || hasRadioTechnologyChanged)) {
+ phone.notifyDataConnection(null);
+ }
+
+ if (hasRoamingOn) {
+ mRoamingOnRegistrants.notifyRegistrants();
+ }
+
+ if (hasRoamingOff) {
+ mRoamingOffRegistrants.notifyRegistrants();
+ }
+
+ if (hasLocationChanged) {
+ phone.notifyLocationChanged();
+ }
+ }
+
+ @Override
+ protected void onSignalStrengthResult(AsyncResult ar) {
+ SignalStrength oldSignalStrength = mSignalStrength;
+
+ if (ar.exception != null) {
+ // Most likely radio is resetting/disconnected change to default
+ // values.
+ setSignalStrengthDefaultValues();
+ } else {
+ int[] ints = (int[])ar.result;
+
+ int lteRssi = -1;
+ int lteRsrp = -1;
+ int lteRsrq = -1;
+ int lteRssnr = SignalStrength.INVALID_SNR;
+ int lteCqi = -1;
+
+ int offset = 2;
+ int cdmaDbm = (ints[offset] > 0) ? -ints[offset] : -120;
+ int cdmaEcio = (ints[offset + 1] > 0) ? -ints[offset + 1] : -160;
+ int evdoRssi = (ints[offset + 2] > 0) ? -ints[offset + 2] : -120;
+ int evdoEcio = (ints[offset + 3] > 0) ? -ints[offset + 3] : -1;
+ int evdoSnr = ((ints[offset + 4] > 0) && (ints[offset + 4] <= 8)) ? ints[offset + 4]
+ : -1;
+
+ if (mRilRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_LTE) {
+ lteRssi = ints[offset+5];
+ lteRsrp = ints[offset+6];
+ lteRsrq = ints[offset+7];
+ lteRssnr = ints[offset+8];
+ lteCqi = ints[offset+9];
+ }
+
+ if (mRilRadioTechnology != ServiceState.RIL_RADIO_TECHNOLOGY_LTE) {
+ mSignalStrength = new SignalStrength(99, -1, cdmaDbm, cdmaEcio, evdoRssi, evdoEcio,
+ evdoSnr, false);
+ } else {
+ mSignalStrength = new SignalStrength(99, -1, cdmaDbm, cdmaEcio, evdoRssi, evdoEcio,
+ evdoSnr, lteRssi, lteRsrp, lteRsrq, lteRssnr, lteCqi, true);
+ }
+ }
+
+ try {
+ phone.notifySignalStrength();
+ } catch (NullPointerException ex) {
+ loge("onSignalStrengthResult() Phone already destroyed: " + ex
+ + "SignalStrength not notified");
+ }
+ }
+
+ @Override
+ public boolean isConcurrentVoiceAndDataAllowed() {
+ // Note: it needs to be confirmed which CDMA network types
+ // can support voice and data calls concurrently.
+ // For the time-being, the return value will be false.
+ return (mRilRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+ }
+
+ /**
+ * Check whether the specified SID and NID pair appears in the HOME SID/NID list
+ * read from NV or SIM.
+ *
+ * @return true if provided sid/nid pair belongs to operator's home network.
+ */
+ private boolean isInHomeSidNid(int sid, int nid) {
+ // if SID/NID is not available, assume this is home network.
+ if (isSidsAllZeros()) return true;
+
+ // length of SID/NID shold be same
+ if (mHomeSystemId.length != mHomeNetworkId.length) return true;
+
+ if (sid == 0) return true;
+
+ for (int i = 0; i < mHomeSystemId.length; i++) {
+ // Use SID only if NID is a reserved value.
+ // SID 0 and NID 0 and 65535 are reserved. (C.0005 2.6.5.2)
+ if ((mHomeSystemId[i] == sid) &&
+ ((mHomeNetworkId[i] == 0) || (mHomeNetworkId[i] == 65535) ||
+ (nid == 0) || (nid == 65535) || (mHomeNetworkId[i] == nid))) {
+ return true;
+ }
+ }
+ // SID/NID are not in the list. So device is not in home network
+ return false;
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[CdmaLteSST] " + s);
+ }
+
+ @Override
+ protected void loge(String s) {
+ Log.e(LOG_TAG, "[CdmaLteSST] " + s);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("CdmaLteServiceStateTracker extends:");
+ super.dump(fd, pw, args);
+ pw.println(" mCdmaLtePhone=" + mCdmaLtePhone);
+ pw.println(" mLteSS=" + mLteSS);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java
new file mode 100644
index 0000000..93a6290
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 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.cdma;
+
+import android.util.Log;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccConstants;
+import com.android.internal.telephony.IccFileHandler;
+import android.os.Message;
+
+/**
+ * {@hide}
+ */
+public final class CdmaLteUiccFileHandler extends IccFileHandler {
+ static final String LOG_TAG = "CDMA";
+
+ public CdmaLteUiccFileHandler(IccCard card, String aid, CommandsInterface ci) {
+ super(card, aid, ci);
+ }
+
+ protected String getEFPath(int efid) {
+ switch(efid) {
+ case EF_CSIM_SPN:
+ case EF_CSIM_LI:
+ case EF_CSIM_MDN:
+ case EF_CSIM_IMSIM:
+ case EF_CSIM_CDMAHOME:
+ case EF_CSIM_EPRL:
+ return MF_SIM + DF_CDMA;
+ case EF_AD:
+ return MF_SIM + DF_GSM;
+ case EF_IMPI:
+ case EF_DOMAIN:
+ case EF_IMPU:
+ return MF_SIM + DF_ADFISIM;
+ }
+ return getCommonIccEFPath(efid);
+ }
+
+ @Override
+ public void loadEFTransparent(int fileid, Message onLoaded) {
+ if (fileid == EF_CSIM_EPRL) {
+ // Entire PRL could be huge. We are only interested in
+ // the first 4 bytes of the record.
+ mCi.iccIOForApp(COMMAND_READ_BINARY, fileid, getEFPath(fileid),
+ 0, 0, 4, null, null, mAid,
+ obtainMessage(EVENT_READ_BINARY_DONE,
+ fileid, 0, onLoaded));
+ } else {
+ super.loadEFTransparent(fileid, onLoaded);
+ }
+ }
+
+
+ protected void logd(String msg) {
+ Log.d(LOG_TAG, "[CdmaLteUiccFileHandler] " + msg);
+ }
+
+ protected void loge(String msg) {
+ Log.e(LOG_TAG, "[CdmaLteUiccFileHandler] " + msg);
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java b/src/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
new file mode 100755
index 0000000..eaa2ede
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2011 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.cdma;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.telephony.AdnRecordLoader;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccCardApplication.AppType;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.telephony.gsm.SIMRecords;
+import com.android.internal.telephony.ims.IsimRecords;
+import com.android.internal.telephony.ims.IsimUiccRecords;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_TEST_CSIM;
+
+/**
+ * {@hide}
+ */
+public final class CdmaLteUiccRecords extends SIMRecords {
+ // From CSIM application
+ private byte[] mEFpl = null;
+ private byte[] mEFli = null;
+ boolean mCsimSpnDisplayCondition = false;
+ private String mMdn;
+ private String mMin;
+ private String mPrlVersion;
+ private String mHomeSystemId;
+ private String mHomeNetworkId;
+
+ private final IsimUiccRecords mIsimUiccRecords = new IsimUiccRecords();
+
+ public CdmaLteUiccRecords(IccCard card, Context c, CommandsInterface ci) {
+ super(card, c, ci);
+ }
+
+ // Refer to ETSI TS 102.221
+ private class EfPlLoaded implements IccRecordLoaded {
+ public String getEfName() {
+ return "EF_PL";
+ }
+
+ public void onRecordLoaded(AsyncResult ar) {
+ mEFpl = (byte[]) ar.result;
+ if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEFpl));
+ }
+ }
+
+ // Refer to C.S0065 5.2.26
+ private class EfCsimLiLoaded implements IccRecordLoaded {
+ public String getEfName() {
+ return "EF_CSIM_LI";
+ }
+
+ public void onRecordLoaded(AsyncResult ar) {
+ mEFli = (byte[]) ar.result;
+ // convert csim efli data to iso 639 format
+ for (int i = 0; i < mEFli.length; i+=2) {
+ switch(mEFli[i+1]) {
+ case 0x01: mEFli[i] = 'e'; mEFli[i+1] = 'n';break;
+ case 0x02: mEFli[i] = 'f'; mEFli[i+1] = 'r';break;
+ case 0x03: mEFli[i] = 'e'; mEFli[i+1] = 's';break;
+ case 0x04: mEFli[i] = 'j'; mEFli[i+1] = 'a';break;
+ case 0x05: mEFli[i] = 'k'; mEFli[i+1] = 'o';break;
+ case 0x06: mEFli[i] = 'z'; mEFli[i+1] = 'h';break;
+ case 0x07: mEFli[i] = 'h'; mEFli[i+1] = 'e';break;
+ default: mEFli[i] = ' '; mEFli[i+1] = ' ';
+ }
+ }
+
+ if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEFli));
+ }
+ }
+
+ // Refer to C.S0065 5.2.32
+ private class EfCsimSpnLoaded implements IccRecordLoaded {
+ public String getEfName() {
+ return "EF_CSIM_SPN";
+ }
+
+ public void onRecordLoaded(AsyncResult ar) {
+ byte[] data = (byte[]) ar.result;
+ if (DBG) log("CSIM_SPN=" +
+ IccUtils.bytesToHexString(data));
+
+ // C.S0065 for EF_SPN decoding
+ mCsimSpnDisplayCondition = ((0x01 & data[0]) != 0);
+
+ int encoding = data[1];
+ int language = data[2];
+ byte[] spnData = new byte[32];
+ System.arraycopy(data, 3, spnData, 0, (data.length < 32) ? data.length : 32);
+
+ int numBytes;
+ for (numBytes = 0; numBytes < spnData.length; numBytes++) {
+ if ((spnData[numBytes] & 0xFF) == 0xFF) break;
+ }
+
+ if (numBytes == 0) {
+ spn = "";
+ return;
+ }
+ try {
+ switch (encoding) {
+ case UserData.ENCODING_OCTET:
+ case UserData.ENCODING_LATIN:
+ spn = new String(spnData, 0, numBytes, "ISO-8859-1");
+ break;
+ case UserData.ENCODING_IA5:
+ case UserData.ENCODING_GSM_7BIT_ALPHABET:
+ case UserData.ENCODING_7BIT_ASCII:
+ spn = GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7);
+ break;
+ case UserData.ENCODING_UNICODE_16:
+ spn = new String(spnData, 0, numBytes, "utf-16");
+ break;
+ default:
+ log("SPN encoding not supported");
+ }
+ } catch(Exception e) {
+ log("spn decode error: " + e);
+ }
+ if (DBG) log("spn=" + spn);
+ if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition);
+ SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, spn);
+ }
+ }
+
+ private class EfCsimMdnLoaded implements IccRecordLoaded {
+ public String getEfName() {
+ return "EF_CSIM_MDN";
+ }
+
+ public void onRecordLoaded(AsyncResult ar) {
+ byte[] data = (byte[]) ar.result;
+ if (DBG) log("CSIM_MDN=" + IccUtils.bytesToHexString(data));
+ int mdnDigitsNum = 0x0F & data[0];
+ mMdn = IccUtils.cdmaBcdToString(data, 1, mdnDigitsNum);
+ if (DBG) log("CSIM MDN=" + mMdn);
+ }
+ }
+
+ private class EfCsimImsimLoaded implements IccRecordLoaded {
+ public String getEfName() {
+ return "EF_CSIM_IMSIM";
+ }
+
+ public void onRecordLoaded(AsyncResult ar) {
+ byte[] data = (byte[]) ar.result;
+ if (DBG) log("CSIM_IMSIM=" + IccUtils.bytesToHexString(data));
+ // C.S0065 section 5.2.2 for IMSI_M encoding
+ // C.S0005 section 2.3.1 for MIN encoding in IMSI_M.
+ boolean provisioned = ((data[7] & 0x80) == 0x80);
+
+ if (provisioned) {
+ int first3digits = ((0x03 & data[2]) << 8) + (0xFF & data[1]);
+ int second3digits = (((0xFF & data[5]) << 8) | (0xFF & data[4])) >> 6;
+ int digit7 = 0x0F & (data[4] >> 2);
+ if (digit7 > 0x09) digit7 = 0;
+ int last3digits = ((0x03 & data[4]) << 8) | (0xFF & data[3]);
+ first3digits = adjstMinDigits(first3digits);
+ second3digits = adjstMinDigits(second3digits);
+ last3digits = adjstMinDigits(last3digits);
+
+ StringBuilder builder = new StringBuilder();
+ builder.append(String.format(Locale.US, "%03d", first3digits));
+ builder.append(String.format(Locale.US, "%03d", second3digits));
+ builder.append(String.format(Locale.US, "%d", digit7));
+ builder.append(String.format(Locale.US, "%03d", last3digits));
+ mMin = builder.toString();
+ if (DBG) log("min present=" + mMin);
+ } else {
+ if (DBG) log("min not present");
+ }
+ }
+ }
+
+ private class EfCsimCdmaHomeLoaded implements IccRecordLoaded {
+ public String getEfName() {
+ return "EF_CSIM_CDMAHOME";
+ }
+
+ public void onRecordLoaded(AsyncResult ar) {
+ // Per C.S0065 section 5.2.8
+ ArrayList<byte[]> dataList = (ArrayList<byte[]>) ar.result;
+ if (DBG) log("CSIM_CDMAHOME data size=" + dataList.size());
+ if (dataList.isEmpty()) {
+ return;
+ }
+ StringBuilder sidBuf = new StringBuilder();
+ StringBuilder nidBuf = new StringBuilder();
+
+ for (byte[] data : dataList) {
+ if (data.length == 5) {
+ int sid = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
+ int nid = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
+ sidBuf.append(sid).append(',');
+ nidBuf.append(nid).append(',');
+ }
+ }
+ // remove trailing ","
+ sidBuf.setLength(sidBuf.length()-1);
+ nidBuf.setLength(nidBuf.length()-1);
+
+ mHomeSystemId = sidBuf.toString();
+ mHomeNetworkId = nidBuf.toString();
+ }
+ }
+
+ private class EfCsimEprlLoaded implements IccRecordLoaded {
+ public String getEfName() {
+ return "EF_CSIM_EPRL";
+ }
+ public void onRecordLoaded(AsyncResult ar) {
+ onGetCSimEprlDone(ar);
+ }
+ }
+
+ @Override
+ protected 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;
+ }
+ }
+
+ @Override
+ protected void onAllRecordsLoaded() {
+ setLocaleFromCsim();
+ super.onAllRecordsLoaded(); // broadcasts ICC state change to "LOADED"
+ }
+
+ @Override
+ protected void fetchSimRecords() {
+ recordsRequested = true;
+
+ mCi.getIMSIForApp(mParentCard.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_PL,
+ obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded()));
+ recordsToLoad++;
+
+ new AdnRecordLoader(mFh).loadFromEF(EF_MSISDN, EF_EXT1, 1,
+ obtainMessage(EVENT_GET_MSISDN_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_CSIM_LI,
+ obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimLiLoaded()));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_CSIM_SPN,
+ obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimSpnLoaded()));
+ recordsToLoad++;
+
+ mFh.loadEFLinearFixed(EF_CSIM_MDN, 1,
+ obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMdnLoaded()));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_CSIM_IMSIM,
+ obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimImsimLoaded()));
+ recordsToLoad++;
+
+ mFh.loadEFLinearFixedAll(EF_CSIM_CDMAHOME,
+ obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimCdmaHomeLoaded()));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_CSIM_EPRL,
+ obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimEprlLoaded()));
+ recordsToLoad++;
+
+ // load ISIM records
+ recordsToLoad += mIsimUiccRecords.fetchIsimRecords(mFh, this);
+ }
+
+ private int adjstMinDigits (int digits) {
+ // Per C.S0005 section 2.3.1.
+ digits += 111;
+ digits = (digits % 10 == 0)?(digits - 10):digits;
+ digits = ((digits / 10) % 10 == 0)?(digits - 100):digits;
+ digits = ((digits / 100) % 10 == 0)?(digits - 1000):digits;
+ return digits;
+ }
+
+ private void onGetCSimEprlDone(AsyncResult ar) {
+ // C.S0065 section 5.2.57 for EFeprl encoding
+ // C.S0016 section 3.5.5 for PRL format.
+ byte[] data = (byte[]) ar.result;
+ if (DBG) log("CSIM_EPRL=" + IccUtils.bytesToHexString(data));
+
+ // Only need the first 4 bytes of record
+ if (data.length > 3) {
+ int prlId = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
+ mPrlVersion = Integer.toString(prlId);
+ }
+ if (DBG) log("CSIM PRL version=" + mPrlVersion);
+ }
+
+ private void setLocaleFromCsim() {
+ String prefLang = null;
+ // check EFli then EFpl
+ prefLang = findBestLanguage(mEFli);
+
+ if (prefLang == null) {
+ prefLang = findBestLanguage(mEFpl);
+ }
+
+ if (prefLang != null) {
+ // check country code from SIM
+ String imsi = getIMSI();
+ String country = null;
+ if (imsi != null) {
+ country = MccTable.countryCodeForMcc(
+ Integer.parseInt(imsi.substring(0,3)));
+ }
+ log("Setting locale to " + prefLang + "_" + country);
+ MccTable.setSystemLocale(mContext, prefLang, country);
+ } else {
+ log ("No suitable CSIM selected locale");
+ }
+ }
+
+ private String findBestLanguage(byte[] languages) {
+ String bestMatch = null;
+ String[] locales = mContext.getAssets().getLocales();
+
+ if ((languages == null) || (locales == null)) return null;
+
+ // Each 2-bytes consists of one language
+ for (int i = 0; (i + 1) < languages.length; i += 2) {
+ try {
+ String lang = new String(languages, i, 2, "ISO-8859-1");
+ for (int j = 0; j < locales.length; j++) {
+ if (locales[j] != null && locales[j].length() >= 2 &&
+ locales[j].substring(0, 2).equals(lang)) {
+ return lang;
+ }
+ }
+ if (bestMatch != null) break;
+ } catch(java.io.UnsupportedEncodingException e) {
+ log ("Failed to parse SIM language records");
+ }
+ }
+ // no match found. return null
+ return null;
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[CSIM] " + s);
+ }
+
+ @Override
+ protected void loge(String s) {
+ Log.e(LOG_TAG, "[CSIM] " + s);
+ }
+
+ public String getMdn() {
+ return mMdn;
+ }
+
+ public String getMin() {
+ return mMin;
+ }
+
+ public String getSid() {
+ return mHomeSystemId;
+ }
+
+ public String getNid() {
+ return mHomeNetworkId;
+ }
+
+ public String getPrlVersion() {
+ return mPrlVersion;
+ }
+
+ public boolean getCsimSpnDisplayCondition() {
+ return mCsimSpnDisplayCondition;
+ }
+
+ @Override
+ public IsimRecords getIsimRecords() {
+ return mIsimUiccRecords;
+ }
+
+ @Override
+ public boolean isProvisioned() {
+ // If UICC card has CSIM app, look for MDN and MIN field
+ // to determine if the SIM is provisioned. Otherwise,
+ // consider the SIM is provisioned. (for case of ordinal
+ // USIM only UICC.)
+ // If PROPERTY_TEST_CSIM is defined, bypess provision check
+ // and consider the SIM is provisioned.
+ if (SystemProperties.getBoolean(PROPERTY_TEST_CSIM, false)) {
+ return true;
+ }
+
+ if (mParentCard == null) {
+ return false;
+ }
+
+ if (mParentCard.isApplicationOnIcc(AppType.APPTYPE_CSIM) &&
+ ((mMdn == null) || (mMin == null))) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java b/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
new file mode 100644
index 0000000..8dd8c2e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
@@ -0,0 +1,296 @@
+/*
+ * 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.cdma;
+
+import android.content.Context;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.MmiCode;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * This class can handle Puk code Mmi
+ *
+ * {@hide}
+ *
+ */
+public final class CdmaMmiCode extends Handler implements MmiCode {
+ static final String LOG_TAG = "CDMA_MMI";
+
+ // Constants
+
+ // From TS 22.030 6.5.2
+ static final String ACTION_REGISTER = "**";
+
+ // Supp Service codes from TS 22.030 Annex B
+ static final String SC_PUK = "05";
+
+ // Event Constant
+
+ static final int EVENT_SET_COMPLETE = 1;
+
+ // Instance Variables
+
+ CDMAPhone phone;
+ Context context;
+
+ String action; // ACTION_REGISTER
+ 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
+
+ State state = State.PENDING;
+ CharSequence message;
+
+ // Class Variables
+
+ 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
+ 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;
+ 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
+
+ /**
+ * Check if provided string contains Mmi code in it and create corresponding
+ * Mmi if it does
+ */
+
+ public static CdmaMmiCode
+ newFromDialString(String dialString, CDMAPhone phone) {
+ Matcher m;
+ CdmaMmiCode ret = null;
+
+ m = sPatternSuppService.matcher(dialString);
+
+ // Is this formatted like a standard supplementary service code?
+ if (m.matches()) {
+ ret = new CdmaMmiCode(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));
+
+ }
+
+ 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;
+ }
+
+ // Constructor
+
+ CdmaMmiCode (CDMAPhone phone) {
+ super(phone.getHandler().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;
+ phone.onMMIDone (this);
+ }
+
+ public boolean isCancelable() {
+ return false;
+ }
+
+ // Instance Methods
+
+ /**
+ * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
+ */
+ boolean isPukCommand() {
+ return sc != null && sc.equals(SC_PUK);
+ }
+
+ boolean isRegister() {
+ return action != null && action.equals(ACTION_REGISTER);
+ }
+
+ public boolean isUssdRequest() {
+ Log.w(LOG_TAG, "isUssdRequest is not implemented in CdmaMmiCode");
+ return false;
+ }
+
+ /** Process a MMI PUK code */
+ void
+ processCode () {
+ try {
+ if (isPukCommand()) {
+ // sia = old 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 {
+ phone.mCM.supplyIccPuk(oldPinOrPuk, newPin,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ }
+ } else {
+ throw new RuntimeException ("Invalid or Unsupported MMI Code");
+ }
+ } 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);
+ }
+
+ public void
+ handleMessage (Message msg) {
+ AsyncResult ar;
+
+ if (msg.what == EVENT_SET_COMPLETE) {
+ ar = (AsyncResult) (msg.obj);
+ onSetComplete(ar);
+ } else {
+ Log.e(LOG_TAG, "Unexpected reply");
+ }
+ }
+ // Private instance methods
+
+ private CharSequence getScString() {
+ if (sc != null) {
+ if (isPukCommand()) {
+ 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 (isPukCommand()) {
+ sb.append(context.getText(
+ com.android.internal.R.string.badPuk));
+ } else {
+ sb.append(context.getText(
+ com.android.internal.R.string.passwordIncorrect));
+ }
+ } else {
+ sb.append(context.getText(
+ com.android.internal.R.string.mmiError));
+ }
+ } else {
+ sb.append(context.getText(
+ com.android.internal.R.string.mmiError));
+ }
+ } else if (isRegister()) {
+ state = State.COMPLETE;
+ sb.append(context.getText(
+ com.android.internal.R.string.serviceRegistered));
+ } else {
+ state = State.FAILED;
+ sb.append(context.getText(
+ com.android.internal.R.string.mmiError));
+ }
+
+ message = sb;
+ phone.onMMIDone(this);
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
new file mode 100755
index 0000000..0f64e1d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -0,0 +1,428 @@
+/*
+ * 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.cdma;
+
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.SmsCbMessage;
+import android.telephony.SmsManager;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SMSDispatcher;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsStorageMonitor;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.WspTypeDecoder;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.util.HexDump;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import android.content.res.Resources;
+
+
+final class CdmaSMSDispatcher extends SMSDispatcher {
+ private static final String TAG = "CDMA";
+
+ private byte[] mLastDispatchedSmsFingerprint;
+ private byte[] mLastAcknowledgedSmsFingerprint;
+
+ private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
+
+ CdmaSMSDispatcher(CDMAPhone phone, SmsStorageMonitor storageMonitor,
+ SmsUsageMonitor usageMonitor) {
+ super(phone, storageMonitor, usageMonitor);
+ mCm.setOnNewCdmaSms(this, EVENT_NEW_SMS, null);
+ }
+
+ @Override
+ public void dispose() {
+ mCm.unSetOnNewCdmaSms(this);
+ }
+
+ @Override
+ protected String getFormat() {
+ return android.telephony.SmsMessage.FORMAT_3GPP2;
+ }
+
+ private void handleCdmaStatusReport(SmsMessage sms) {
+ for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
+ SmsTracker tracker = deliveryPendingList.get(i);
+ if (tracker.mMessageRef == sms.messageRef) {
+ // Found it. Remove from list and broadcast.
+ deliveryPendingList.remove(i);
+ PendingIntent intent = tracker.mDeliveryIntent;
+ Intent fillIn = new Intent();
+ fillIn.putExtra("pdu", sms.getPdu());
+ fillIn.putExtra("format", android.telephony.SmsMessage.FORMAT_3GPP2);
+ try {
+ intent.send(mContext, Activity.RESULT_OK, fillIn);
+ } catch (CanceledException ex) {}
+ break; // Only expect to see one tracker matching this message.
+ }
+ }
+ }
+
+ /**
+ * Dispatch service category program data to the CellBroadcastReceiver app, which filters
+ * the broadcast alerts to display.
+ * @param sms the SMS message containing one or more
+ * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects.
+ */
+ private void handleServiceCategoryProgramData(SmsMessage sms) {
+ List<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData();
+ if (programDataList == null) {
+ Log.e(TAG, "handleServiceCategoryProgramData: program data list is null!");
+ return;
+ }
+
+ Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION);
+ intent.putExtra("program_data_list", (CdmaSmsCbProgramData[]) programDataList.toArray());
+ dispatch(intent, RECEIVE_SMS_PERMISSION);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int dispatchMessage(SmsMessageBase smsb) {
+
+ // If sms is null, means there was a parsing error.
+ if (smsb == null) {
+ Log.e(TAG, "dispatchMessage: message is null");
+ return Intents.RESULT_SMS_GENERIC_ERROR;
+ }
+
+ String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
+ if (inEcm.equals("true")) {
+ return Activity.RESULT_OK;
+ }
+
+ if (mSmsReceiveDisabled) {
+ // Device doesn't support receiving SMS,
+ Log.d(TAG, "Received short message on device which doesn't support "
+ + "receiving SMS. Ignored.");
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ SmsMessage sms = (SmsMessage) smsb;
+
+ // Handle CMAS emergency broadcast messages.
+ if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) {
+ Log.d(TAG, "Broadcast type message");
+ SmsCbMessage message = sms.parseBroadcastSms();
+ if (message != null) {
+ dispatchBroadcastMessage(message);
+ }
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ // See if we have a network duplicate SMS.
+ mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
+ if (mLastAcknowledgedSmsFingerprint != null &&
+ Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
+ return Intents.RESULT_SMS_HANDLED;
+ }
+ // Decode BD stream and set sms variables.
+ sms.parseSms();
+ int teleService = sms.getTeleService();
+ boolean handled = false;
+
+ if ((SmsEnvelope.TELESERVICE_VMN == teleService) ||
+ (SmsEnvelope.TELESERVICE_MWI == teleService)) {
+ // handling Voicemail
+ int voicemailCount = sms.getNumOfVoicemails();
+ Log.d(TAG, "Voicemail count=" + voicemailCount);
+ // Store the voicemail count in preferences.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
+ mContext);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
+ editor.apply();
+ mPhone.setVoiceMessageWaiting(1, voicemailCount);
+ handled = true;
+ } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
+ (SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
+ sms.isStatusReportMessage()) {
+ handleCdmaStatusReport(sms);
+ handled = true;
+ } else if (SmsEnvelope.TELESERVICE_SCPT == teleService) {
+ handleServiceCategoryProgramData(sms);
+ handled = true;
+ } else if ((sms.getUserData() == null)) {
+ if (false) {
+ Log.d(TAG, "Received SMS without user data");
+ }
+ handled = true;
+ }
+
+ if (handled) {
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ if (!mStorageMonitor.isStorageAvailable() &&
+ sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
+ // It's a storable message and there's no storage available. Bail.
+ // (See C.S0015-B v2.0 for a description of "Immediate Display"
+ // messages, which we represent as CLASS_0.)
+ return Intents.RESULT_SMS_OUT_OF_MEMORY;
+ }
+
+ if (SmsEnvelope.TELESERVICE_WAP == teleService) {
+ return processCdmaWapPdu(sms.getUserData(), sms.messageRef,
+ sms.getOriginatingAddress());
+ }
+
+ // Reject (NAK) any messages with teleservice ids that have
+ // not yet been handled and also do not correspond to the two
+ // kinds that are processed below.
+ if ((SmsEnvelope.TELESERVICE_WMT != teleService) &&
+ (SmsEnvelope.TELESERVICE_WEMT != teleService) &&
+ (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) {
+ return Intents.RESULT_SMS_UNSUPPORTED;
+ }
+
+ return dispatchNormalMessage(smsb);
+ }
+
+ /**
+ * Processes inbound messages that are in the WAP-WDP PDU format. See
+ * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
+ * WDP segments are gathered until a datagram completes and gets dispatched.
+ *
+ * @param pdu The WAP-WDP PDU segment
+ * @return a result code from {@link Telephony.Sms.Intents}, or
+ * {@link Activity#RESULT_OK} if the message has been broadcast
+ * to applications
+ */
+ protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
+ int index = 0;
+
+ int msgType = (0xFF & pdu[index++]);
+ if (msgType != 0) {
+ Log.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
+ return Intents.RESULT_SMS_HANDLED;
+ }
+ int totalSegments = (0xFF & pdu[index++]); // >= 1
+ int segment = (0xFF & pdu[index++]); // >= 0
+
+ if (segment >= totalSegments) {
+ Log.e(TAG, "WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1));
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ // Only the first segment contains sourcePort and destination Port
+ int sourcePort = 0;
+ int destinationPort = 0;
+ if (segment == 0) {
+ //process WDP segment
+ sourcePort = (0xFF & pdu[index++]) << 8;
+ sourcePort |= 0xFF & pdu[index++];
+ destinationPort = (0xFF & pdu[index++]) << 8;
+ destinationPort |= 0xFF & pdu[index++];
+ // Some carriers incorrectly send duplicate port fields in omadm wap pushes.
+ // If configured, check for that here
+ if (mCheckForDuplicatePortsInOmadmWapPush) {
+ if (checkDuplicatePortOmadmWappush(pdu,index)) {
+ index = index + 4; // skip duplicate port fields
+ }
+ }
+ }
+
+ // Lookup all other related parts
+ Log.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
+ + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
+ + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
+
+ // pass the user data portion of the PDU to the shared handler in SMSDispatcher
+ byte[] userData = new byte[pdu.length - index];
+ System.arraycopy(pdu, index, userData, 0, pdu.length - index);
+
+ return processMessagePart(userData, address, referenceNumber, segment, totalSegments,
+ 0L, destinationPort, true);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void sendData(String destAddr, String scAddr, int destPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
+ scAddr, destAddr, destPort, data, (deliveryIntent != null));
+ sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void sendText(String destAddr, String scAddr, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
+ scAddr, destAddr, text, (deliveryIntent != null), null);
+ sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
+ boolean use7bitOnly) {
+ return SmsMessage.calculateLength(messageBody, use7bitOnly);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
+ String message, SmsHeader smsHeader, int encoding,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
+ UserData uData = new UserData();
+ uData.payloadStr = message;
+ uData.userDataHeader = smsHeader;
+ if (encoding == SmsConstants.ENCODING_7BIT) {
+ uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+ } else { // assume UTF-16
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ }
+ uData.msgEncodingSet = true;
+
+ /* By setting the statusReportRequested bit only for the
+ * last message fragment, this will result in only one
+ * callback to the sender when that last fragment delivery
+ * has been acknowledged. */
+ SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
+ uData, (deliveryIntent != null) && lastPart);
+
+ sendSubmitPdu(submitPdu, sentIntent, deliveryIntent, destinationAddress);
+ }
+
+ protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, String destAddr) {
+ if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
+ if (sentIntent != null) {
+ try {
+ sentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
+ } catch (CanceledException ex) {}
+ }
+ if (false) {
+ Log.d(TAG, "Block SMS in Emergency Callback mode");
+ }
+ return;
+ }
+ sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, destAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void sendSms(SmsTracker tracker) {
+ HashMap<String, Object> map = tracker.mData;
+
+ // byte smsc[] = (byte[]) map.get("smsc"); // unused for CDMA
+ byte pdu[] = (byte[]) map.get("pdu");
+
+ Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
+ mCm.sendCdmaSms(pdu, reply);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
+ String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
+ if (inEcm.equals("true")) {
+ return;
+ }
+
+ int causeCode = resultToCause(result);
+ mCm.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
+
+ if (causeCode == 0) {
+ mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
+ }
+ mLastDispatchedSmsFingerprint = null;
+ }
+
+ private static int resultToCause(int rc) {
+ switch (rc) {
+ case Activity.RESULT_OK:
+ case Intents.RESULT_SMS_HANDLED:
+ // Cause code is ignored on success.
+ return 0;
+ case Intents.RESULT_SMS_OUT_OF_MEMORY:
+ return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
+ case Intents.RESULT_SMS_UNSUPPORTED:
+ return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
+ case Intents.RESULT_SMS_GENERIC_ERROR:
+ default:
+ return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
+ }
+ }
+
+ /**
+ * Optional check to see if the received WapPush is an OMADM notification with erroneous
+ * extra port fields.
+ * - Some carriers make this mistake.
+ * ex: MSGTYPE-TotalSegments-CurrentSegment
+ * -SourcePortDestPort-SourcePortDestPort-OMADM PDU
+ * @param origPdu The WAP-WDP PDU segment
+ * @param index Current Index while parsing the PDU.
+ * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
+ * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
+ */
+ private static boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) {
+ index += 4;
+ byte[] omaPdu = new byte[origPdu.length - index];
+ System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
+
+ WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
+ int wspIndex = 2;
+
+ // Process header length field
+ if (pduDecoder.decodeUintvarInteger(wspIndex) == false) {
+ return false;
+ }
+
+ wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field
+
+ // Process content type field
+ if (pduDecoder.decodeContentType(wspIndex) == false) {
+ return false;
+ }
+
+ String mimeType = pduDecoder.getValueString();
+ if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI)) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
new file mode 100755
index 0000000..5a4af7a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -0,0 +1,1752 @@
+/*
+ * 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.cdma;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.DataConnectionTracker;
+import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.CommandsInterface.RadioState;
+
+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.PowerManager;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.provider.Settings.SettingNotFoundException;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.cdma.CdmaCellLocation;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * {@hide}
+ */
+public class CdmaServiceStateTracker extends ServiceStateTracker {
+ static final String LOG_TAG = "CDMA";
+
+ CDMAPhone phone;
+ CdmaCellLocation cellLoc;
+ CdmaCellLocation newCellLoc;
+
+ // Min values used to by getOtasp()
+ private static final String UNACTIVATED_MIN2_VALUE = "000000";
+ private static final String UNACTIVATED_MIN_VALUE = "1111110111";
+
+ // Current Otasp value
+ int mCurrentOtaspMode = OTASP_UNINITIALIZED;
+
+ /** if time between NITZ updates is less than mNitzUpdateSpacing the update may be ignored. */
+ private static final int NITZ_UPDATE_SPACING_DEFAULT = 1000 * 60 * 10;
+ private int mNitzUpdateSpacing = SystemProperties.getInt("ro.nitz_update_spacing",
+ NITZ_UPDATE_SPACING_DEFAULT);
+
+ /** If mNitzUpdateSpacing hasn't been exceeded but update is > mNitzUpdate do the update */
+ private static final int NITZ_UPDATE_DIFF_DEFAULT = 2000;
+ private int mNitzUpdateDiff = SystemProperties.getInt("ro.nitz_update_diff",
+ NITZ_UPDATE_DIFF_DEFAULT);
+
+ private boolean mCdmaRoaming = false;
+ private int mRoamingIndicator;
+ private boolean mIsInPrl;
+ private int mDefaultRoamingIndicator;
+
+ /**
+ * Initially assume no data connection.
+ */
+ protected int mDataConnectionState = ServiceState.STATE_OUT_OF_SERVICE;
+ protected int mNewDataConnectionState = ServiceState.STATE_OUT_OF_SERVICE;
+ protected int mRegistrationState = -1;
+ protected RegistrantList cdmaForSubscriptionInfoReadyRegistrants = 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.
+ */
+ protected boolean mNeedFixZone = false;
+ private int mZoneOffset;
+ private boolean mZoneDst;
+ private long mZoneTime;
+ protected 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 mNeedToRegForRuimLoaded = false;
+
+ /** Wake lock used while setting time of day. */
+ private PowerManager.WakeLock mWakeLock;
+ private static final String WAKELOCK_TAG = "ServiceStateTracker";
+
+ /** Contains the name of the registered network in CDMA (either ONS or ERI text). */
+ protected String mCurPlmn = null;
+
+ protected String mMdn;
+ protected int mHomeSystemId[] = null;
+ protected int mHomeNetworkId[] = null;
+ protected String mMin;
+ protected String mPrlVersion;
+ protected boolean mIsMinInfoReady = false;
+
+ private boolean isEriTextLoaded = false;
+ protected boolean isSubscriptionFromRuim = false;
+ private CdmaSubscriptionSourceManager mCdmaSSM;
+
+ /* Used only for debugging purposes. */
+ private String mRegistrationDeniedReason;
+
+ private ContentResolver cr;
+ private String currentCarrier = null;
+
+ private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (DBG) log("Auto time state changed");
+ revertToNitzTime();
+ }
+ };
+
+ private ContentObserver mAutoTimeZoneObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (DBG) log("Auto time zone state changed");
+ revertToNitzTimeZone();
+ }
+ };
+
+ public CdmaServiceStateTracker(CDMAPhone phone) {
+ super();
+
+ this.phone = phone;
+ cr = phone.getContext().getContentResolver();
+ cm = phone.mCM;
+ ss = new ServiceState();
+ newSS = new ServiceState();
+ cellLoc = new CdmaCellLocation();
+ newCellLoc = new CdmaCellLocation();
+ mSignalStrength = new SignalStrength();
+
+ mCdmaSSM = CdmaSubscriptionSourceManager.getInstance(phone.getContext(), cm, this,
+ EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
+ isSubscriptionFromRuim = (mCdmaSSM.getCdmaSubscriptionSource() ==
+ CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM);
+
+ PowerManager powerManager =
+ (PowerManager)phone.getContext().getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+
+ cm.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
+
+ cm.registerForVoiceNetworkStateChanged(this, EVENT_NETWORK_STATE_CHANGED_CDMA, null);
+ cm.setOnNITZTime(this, EVENT_NITZ_TIME, null);
+ cm.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
+
+ cm.registerForCdmaPrlChanged(this, EVENT_CDMA_PRL_VERSION_CHANGED, null);
+ phone.registerForEriFileLoaded(this, EVENT_ERI_FILE_LOADED, null);
+ cm.registerForCdmaOtaProvision(this,EVENT_OTA_PROVISION_STATUS_CHANGE, null);
+
+ // System setting property AIRPLANE_MODE_ON is set in Settings.
+ int airplaneMode = Settings.System.getInt(cr, Settings.System.AIRPLANE_MODE_ON, 0);
+ mDesiredPowerState = ! (airplaneMode > 0);
+
+ cr.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.AUTO_TIME), true,
+ mAutoTimeObserver);
+ cr.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.AUTO_TIME_ZONE), true,
+ mAutoTimeZoneObserver);
+ setSignalStrengthDefaultValues();
+
+ mNeedToRegForRuimLoaded = true;
+ }
+
+ public void dispose() {
+ // Unregister for all events.
+ cm.unregisterForRadioStateChanged(this);
+ cm.unregisterForVoiceNetworkStateChanged(this);
+ phone.getIccCard().unregisterForReady(this);
+ cm.unregisterForCdmaOtaProvision(this);
+ phone.unregisterForEriFileLoaded(this);
+ phone.mIccRecords.unregisterForRecordsLoaded(this);
+ cm.unSetOnSignalStrengthUpdate(this);
+ cm.unSetOnNITZTime(this);
+ cr.unregisterContentObserver(mAutoTimeObserver);
+ cr.unregisterContentObserver(mAutoTimeZoneObserver);
+ mCdmaSSM.dispose(this);
+ cm.unregisterForCdmaPrlChanged(this);
+ }
+
+ @Override
+ protected void finalize() {
+ if (DBG) log("CdmaServiceStateTracker finalized");
+ }
+
+ /**
+ * Registration point for subscription info ready
+ * @param h handler to notify
+ * @param what what code of message when delivered
+ * @param obj placed in Message.obj
+ */
+ public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ cdmaForSubscriptionInfoReadyRegistrants.add(r);
+
+ if (isMinInfoReady()) {
+ r.notifyRegistrant();
+ }
+ }
+
+ public void unregisterForSubscriptionInfoReady(Handler h) {
+ cdmaForSubscriptionInfoReadyRegistrants.remove(h);
+ }
+
+ /**
+ * Save current source of cdma subscription
+ * @param source - 1 for NV, 0 for RUIM
+ */
+ private void saveCdmaSubscriptionSource(int source) {
+ log("Storing cdma subscription source: " + source);
+ Secure.putInt(phone.getContext().getContentResolver(),
+ Secure.CDMA_SUBSCRIPTION_MODE,
+ source );
+ }
+
+ private void getSubscriptionInfoAndStartPollingThreads() {
+ cm.getCDMASubscription(obtainMessage(EVENT_POLL_STATE_CDMA_SUBSCRIPTION));
+
+ // Get Registration Information
+ pollState();
+ }
+
+ @Override
+ public void handleMessage (Message msg) {
+ AsyncResult ar;
+ int[] ints;
+ String[] strings;
+
+ if (!phone.mIsTheCurrentActivePhone) {
+ loge("Received message " + msg + "[" + msg.what + "]" +
+ " while being destroyed. Ignoring.");
+ return;
+ }
+
+ switch (msg.what) {
+ case EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
+ handleCdmaSubscriptionSource(mCdmaSSM.getCdmaSubscriptionSource());
+ break;
+
+ case EVENT_RUIM_READY:
+ // TODO: Consider calling setCurrentPreferredNetworkType as we do in GsmSST.
+ // cm.setCurrentPreferredNetworkType();
+
+ // The RUIM is now ready i.e if it was locked it has been
+ // unlocked. At this stage, the radio is already powered on.
+ if (mNeedToRegForRuimLoaded) {
+ phone.mIccRecords.registerForRecordsLoaded(this,
+ EVENT_RUIM_RECORDS_LOADED, null);
+ mNeedToRegForRuimLoaded = false;
+ }
+
+ if (phone.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE) {
+ // Subscription will be read from SIM I/O
+ if (DBG) log("Receive EVENT_RUIM_READY");
+ pollState();
+ } else {
+ if (DBG) log("Receive EVENT_RUIM_READY and Send Request getCDMASubscription.");
+ getSubscriptionInfoAndStartPollingThreads();
+ }
+ phone.prepareEri();
+ break;
+
+ case EVENT_NV_READY:
+ // For Non-RUIM phones, the subscription information is stored in
+ // Non Volatile. Here when Non-Volatile is ready, we can poll the CDMA
+ // subscription info.
+ getSubscriptionInfoAndStartPollingThreads();
+ break;
+
+ case EVENT_RADIO_STATE_CHANGED:
+ if(cm.getRadioState() == RadioState.RADIO_ON) {
+ handleCdmaSubscriptionSource(mCdmaSSM.getCdmaSubscriptionSource());
+
+ // Signal strength polling stops when radio is off.
+ queueNextSignalStrengthPoll();
+ }
+ // This will do nothing in the 'radio not available' case.
+ setPowerStateToDesired();
+ pollState();
+ break;
+
+ case EVENT_NETWORK_STATE_CHANGED_CDMA:
+ 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_CDMA:
+ ar = (AsyncResult) msg.obj;
+
+ if (ar.exception == null) {
+ String states[] = (String[])ar.result;
+ int baseStationId = -1;
+ int baseStationLatitude = CdmaCellLocation.INVALID_LAT_LONG;
+ int baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
+ int systemId = -1;
+ int networkId = -1;
+
+ if (states.length > 9) {
+ try {
+ if (states[4] != null) {
+ baseStationId = Integer.parseInt(states[4]);
+ }
+ if (states[5] != null) {
+ baseStationLatitude = Integer.parseInt(states[5]);
+ }
+ if (states[6] != null) {
+ baseStationLongitude = Integer.parseInt(states[6]);
+ }
+ // Some carriers only return lat-lngs of 0,0
+ if (baseStationLatitude == 0 && baseStationLongitude == 0) {
+ baseStationLatitude = CdmaCellLocation.INVALID_LAT_LONG;
+ baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
+ }
+ if (states[8] != null) {
+ systemId = Integer.parseInt(states[8]);
+ }
+ if (states[9] != null) {
+ networkId = Integer.parseInt(states[9]);
+ }
+ } catch (NumberFormatException ex) {
+ loge("error parsing cell location data: " + ex);
+ }
+ }
+
+ cellLoc.setCellLocationData(baseStationId, baseStationLatitude,
+ baseStationLongitude, systemId, networkId);
+ phone.notifyLocationChanged();
+ }
+
+ // Release any temporary cell lock, which could have been
+ // acquired to allow a single-shot location update.
+ disableSingleLocationUpdate();
+ break;
+
+ case EVENT_POLL_STATE_REGISTRATION_CDMA:
+ case EVENT_POLL_STATE_OPERATOR_CDMA:
+ ar = (AsyncResult) msg.obj;
+ handlePollStateResult(msg.what, ar);
+ break;
+
+ case EVENT_POLL_STATE_CDMA_SUBSCRIPTION: // Handle RIL_CDMA_SUBSCRIPTION
+ ar = (AsyncResult) msg.obj;
+
+ if (ar.exception == null) {
+ String cdmaSubscription[] = (String[])ar.result;
+ if (cdmaSubscription != null && cdmaSubscription.length >= 5) {
+ mMdn = cdmaSubscription[0];
+ parseSidNid(cdmaSubscription[1], cdmaSubscription[2]);
+
+ mMin = cdmaSubscription[3];
+ mPrlVersion = cdmaSubscription[4];
+ if (DBG) log("GET_CDMA_SUBSCRIPTION: MDN=" + mMdn);
+
+ mIsMinInfoReady = true;
+
+ updateOtaspState();
+ phone.getIccCard().broadcastIccStateChangedIntent(
+ IccCardConstants.INTENT_VALUE_ICC_IMSI, null);
+ } else {
+ if (DBG) {
+ log("GET_CDMA_SUBSCRIPTION: error parsing cdmaSubscription params num="
+ + cdmaSubscription.length);
+ }
+ }
+ }
+ 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];
+ long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
+
+ 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,
+ // so we don't have to ask it.
+ dontPollSignalStrength = true;
+
+ onSignalStrengthResult(ar);
+ break;
+
+ case EVENT_RUIM_RECORDS_LOADED:
+ updateSpnDisplay();
+ break;
+
+ case EVENT_LOCATION_UPDATES_ENABLED:
+ ar = (AsyncResult) msg.obj;
+
+ if (ar.exception == null) {
+ cm.getVoiceRegistrationState(obtainMessage(EVENT_GET_LOC_DONE_CDMA, null));
+ }
+ break;
+
+ case EVENT_ERI_FILE_LOADED:
+ // Repoll the state once the ERI file has been loaded.
+ if (DBG) log("[CdmaServiceStateTracker] ERI file has been loaded, repolling.");
+ pollState();
+ break;
+
+ case EVENT_OTA_PROVISION_STATUS_CHANGE:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ ints = (int[]) ar.result;
+ int otaStatus = ints[0];
+ if (otaStatus == Phone.CDMA_OTA_PROVISION_STATUS_COMMITTED
+ || otaStatus == Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_STOPPED) {
+ if (DBG) log("EVENT_OTA_PROVISION_STATUS_CHANGE: Complete, Reload MDN");
+ cm.getCDMASubscription( obtainMessage(EVENT_POLL_STATE_CDMA_SUBSCRIPTION));
+ }
+ }
+ break;
+
+ case EVENT_CDMA_PRL_VERSION_CHANGED:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ ints = (int[]) ar.result;
+ mPrlVersion = Integer.toString(ints[0]);
+ }
+ break;
+
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ //***** Private Instance Methods
+
+ private void handleCdmaSubscriptionSource(int newSubscriptionSource) {
+ log("Subscription Source : " + newSubscriptionSource);
+ isSubscriptionFromRuim =
+ (newSubscriptionSource == CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM);
+ saveCdmaSubscriptionSource(newSubscriptionSource);
+ if (!isSubscriptionFromRuim) {
+ // NV is ready when subscription source is NV
+ sendMessage(obtainMessage(EVENT_NV_READY));
+ } else {
+ phone.getIccCard().registerForReady(this, EVENT_RUIM_READY, null);
+ }
+ }
+
+ @Override
+ protected 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()) {
+ DataConnectionTracker dcTracker = phone.mDataConnectionTracker;
+
+ // If it's on and available and we want it off gracefully
+ powerOffRadioSafely(dcTracker);
+ } // Otherwise, we're in the desired state
+ }
+
+ @Override
+ protected void updateSpnDisplay() {
+ // mOperatorAlphaLong contains the ERI text
+ String plmn = ss.getOperatorAlphaLong();
+ if (!TextUtils.equals(plmn, mCurPlmn)) {
+ // Allow A blank plmn, "" to set showPlmn to true. Previously, we
+ // would set showPlmn to true only if plmn was not empty, i.e. was not
+ // null and not blank. But this would cause us to incorrectly display
+ // "No Service". Now showPlmn is set to true for any non null string.
+ boolean showPlmn = plmn != null;
+ if (DBG) {
+ log(String.format("updateSpnDisplay: changed sending intent" +
+ " showPlmn='%b' plmn='%s'", showPlmn, plmn));
+ }
+ Intent intent = new Intent(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(TelephonyIntents.EXTRA_SHOW_SPN, false);
+ intent.putExtra(TelephonyIntents.EXTRA_SPN, "");
+ intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn);
+ intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn);
+ phone.getContext().sendStickyBroadcast(intent);
+ }
+
+ mCurPlmn = plmn;
+ }
+
+ @Override
+ protected Phone getPhone() {
+ return phone;
+ }
+
+ /**
+ * Determine data network type based on radio technology.
+ */
+ protected void setCdmaTechnology(int radioTech){
+ mNewDataConnectionState = radioTechnologyToDataServiceState(radioTech);
+ newSS.setRadioTechnology(radioTech);
+ mNewRilRadioTechnology = radioTech;
+ }
+
+ /**
+ * Hanlde the PollStateResult message
+ */
+ protected void handlePollStateResultMessage(int what, AsyncResult ar){
+ int ints[];
+ String states[];
+ switch (what) {
+ case EVENT_POLL_STATE_REGISTRATION_CDMA: // Handle RIL_REQUEST_REGISTRATION_STATE.
+ states = (String[])ar.result;
+
+ int registrationState = 4; //[0] registrationState
+ int radioTechnology = -1; //[3] radioTechnology
+ int baseStationId = -1; //[4] baseStationId
+ //[5] baseStationLatitude
+ int baseStationLatitude = CdmaCellLocation.INVALID_LAT_LONG;
+ //[6] baseStationLongitude
+ int baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
+ int cssIndicator = 0; //[7] init with 0, because it is treated as a boolean
+ int systemId = 0; //[8] systemId
+ int networkId = 0; //[9] networkId
+ int roamingIndicator = -1; //[10] Roaming indicator
+ int systemIsInPrl = 0; //[11] Indicates if current system is in PRL
+ int defaultRoamingIndicator = 0; //[12] Is default roaming indicator from PRL
+ int reasonForDenial = 0; //[13] Denial reason if registrationState = 3
+
+ if (states.length >= 14) {
+ try {
+ if (states[0] != null) {
+ registrationState = Integer.parseInt(states[0]);
+ }
+ if (states[3] != null) {
+ radioTechnology = Integer.parseInt(states[3]);
+ }
+ if (states[4] != null) {
+ baseStationId = Integer.parseInt(states[4]);
+ }
+ if (states[5] != null) {
+ baseStationLatitude = Integer.parseInt(states[5]);
+ }
+ if (states[6] != null) {
+ baseStationLongitude = Integer.parseInt(states[6]);
+ }
+ // Some carriers only return lat-lngs of 0,0
+ if (baseStationLatitude == 0 && baseStationLongitude == 0) {
+ baseStationLatitude = CdmaCellLocation.INVALID_LAT_LONG;
+ baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
+ }
+ if (states[7] != null) {
+ cssIndicator = Integer.parseInt(states[7]);
+ }
+ if (states[8] != null) {
+ systemId = Integer.parseInt(states[8]);
+ }
+ if (states[9] != null) {
+ networkId = Integer.parseInt(states[9]);
+ }
+ if (states[10] != null) {
+ roamingIndicator = Integer.parseInt(states[10]);
+ }
+ if (states[11] != null) {
+ systemIsInPrl = Integer.parseInt(states[11]);
+ }
+ if (states[12] != null) {
+ defaultRoamingIndicator = Integer.parseInt(states[12]);
+ }
+ if (states[13] != null) {
+ reasonForDenial = Integer.parseInt(states[13]);
+ }
+ } catch (NumberFormatException ex) {
+ loge("EVENT_POLL_STATE_REGISTRATION_CDMA: error parsing: " + ex);
+ }
+ } else {
+ throw new RuntimeException("Warning! Wrong number of parameters returned from "
+ + "RIL_REQUEST_REGISTRATION_STATE: expected 14 or more "
+ + "strings and got " + states.length + " strings");
+ }
+
+ mRegistrationState = registrationState;
+ // When registration state is roaming and TSB58
+ // roaming indicator is not in the carrier-specified
+ // list of ERIs for home system, mCdmaRoaming is true.
+ mCdmaRoaming =
+ regCodeIsRoaming(registrationState) && !isRoamIndForHomeSystem(states[10]);
+ newSS.setState (regCodeToServiceState(registrationState));
+
+ setCdmaTechnology(radioTechnology);
+
+ newSS.setCssIndicator(cssIndicator);
+ newSS.setSystemAndNetworkId(systemId, networkId);
+ mRoamingIndicator = roamingIndicator;
+ mIsInPrl = (systemIsInPrl == 0) ? false : true;
+ mDefaultRoamingIndicator = defaultRoamingIndicator;
+
+
+ // Values are -1 if not available.
+ newCellLoc.setCellLocationData(baseStationId, baseStationLatitude,
+ baseStationLongitude, systemId, networkId);
+
+ if (reasonForDenial == 0) {
+ mRegistrationDeniedReason = ServiceStateTracker.REGISTRATION_DENIED_GEN;
+ } else if (reasonForDenial == 1) {
+ mRegistrationDeniedReason = ServiceStateTracker.REGISTRATION_DENIED_AUTH;
+ } else {
+ mRegistrationDeniedReason = "";
+ }
+
+ if (mRegistrationState == 3) {
+ if (DBG) log("Registration denied, " + mRegistrationDeniedReason);
+ }
+ break;
+
+ case EVENT_POLL_STATE_OPERATOR_CDMA: // Handle RIL_REQUEST_OPERATOR
+ String opNames[] = (String[])ar.result;
+
+ if (opNames != null && opNames.length >= 3) {
+ // If the NUMERIC field isn't valid use PROPERTY_CDMA_HOME_OPERATOR_NUMERIC
+ if ((opNames[2] == null) || (opNames[2].length() < 5)
+ || ("00000".equals(opNames[2]))) {
+ opNames[2] = SystemProperties.get(
+ CDMAPhone.PROPERTY_CDMA_HOME_OPERATOR_NUMERIC, "00000");
+ if (DBG) {
+ log("RIL_REQUEST_OPERATOR.response[2], the numeric, " +
+ " is bad. Using SystemProperties '" +
+ CDMAPhone.PROPERTY_CDMA_HOME_OPERATOR_NUMERIC +
+ "'= " + opNames[2]);
+ }
+ }
+
+ if (!isSubscriptionFromRuim) {
+ // In CDMA in case on NV, the ss.mOperatorAlphaLong is set later with the
+ // ERI text, so here it is ignored what is coming from the modem.
+ newSS.setOperatorName(null, opNames[1], opNames[2]);
+ } else {
+ newSS.setOperatorName(opNames[0], opNames[1], opNames[2]);
+ }
+ } else {
+ if (DBG) log("EVENT_POLL_STATE_OPERATOR_CDMA: error parsing opNames");
+ }
+ break;
+ default:
+ loge("handlePollStateResultMessage: RIL response handle in wrong phone!"
+ + " Expected CDMA RIL request and get GSM RIL request.");
+ break;
+ }
+ }
+
+ /**
+ * Handle the result of one of the pollState() - related requests
+ */
+ @Override
+ protected void handlePollStateResult(int what, AsyncResult ar) {
+ // 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) {
+ loge("handlePollStateResult: RIL returned an error where it must succeed"
+ + ar.exception);
+ }
+ } else try {
+ handlePollStateResultMessage(what, ar);
+ } catch (RuntimeException ex) {
+ loge("handlePollStateResult: Exception while polling service state. "
+ + "Probably malformed RIL response." + ex);
+ }
+
+ pollingContext[0]--;
+
+ if (pollingContext[0] == 0) {
+ boolean namMatch = false;
+ if (!isSidsAllZeros() && isHomeSid(newSS.getSystemId())) {
+ namMatch = true;
+ }
+
+ // Setting SS Roaming (general)
+ if (isSubscriptionFromRuim) {
+ newSS.setRoaming(isRoamingBetweenOperators(mCdmaRoaming, newSS));
+ } else {
+ newSS.setRoaming(mCdmaRoaming);
+ }
+
+ // Setting SS CdmaRoamingIndicator and CdmaDefaultRoamingIndicator
+ newSS.setCdmaDefaultRoamingIndicator(mDefaultRoamingIndicator);
+ newSS.setCdmaRoamingIndicator(mRoamingIndicator);
+ boolean isPrlLoaded = true;
+ if (TextUtils.isEmpty(mPrlVersion)) {
+ isPrlLoaded = false;
+ }
+ if (!isPrlLoaded) {
+ newSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_OFF);
+ } else if (!isSidsAllZeros()) {
+ if (!namMatch && !mIsInPrl) {
+ // Use default
+ newSS.setCdmaRoamingIndicator(mDefaultRoamingIndicator);
+ } else if (namMatch && !mIsInPrl) {
+ newSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_FLASH);
+ } else if (!namMatch && mIsInPrl) {
+ // Use the one from PRL/ERI
+ newSS.setCdmaRoamingIndicator(mRoamingIndicator);
+ } else {
+ // It means namMatch && mIsInPrl
+ if ((mRoamingIndicator <= 2)) {
+ newSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_OFF);
+ } else {
+ // Use the one from PRL/ERI
+ newSS.setCdmaRoamingIndicator(mRoamingIndicator);
+ }
+ }
+ }
+
+ int roamingIndicator = newSS.getCdmaRoamingIndicator();
+ newSS.setCdmaEriIconIndex(phone.mEriManager.getCdmaEriIconIndex(roamingIndicator,
+ mDefaultRoamingIndicator));
+ newSS.setCdmaEriIconMode(phone.mEriManager.getCdmaEriIconMode(roamingIndicator,
+ mDefaultRoamingIndicator));
+
+ // NOTE: Some operator may require overriding mCdmaRoaming
+ // (set by the modem), depending on the mRoamingIndicator.
+
+ if (DBG) {
+ log("Set CDMA Roaming Indicator to: " + newSS.getCdmaRoamingIndicator()
+ + ". mCdmaRoaming = " + mCdmaRoaming + ", isPrlLoaded = " + isPrlLoaded
+ + ". namMatch = " + namMatch + " , mIsInPrl = " + mIsInPrl
+ + ", mRoamingIndicator = " + mRoamingIndicator
+ + ", mDefaultRoamingIndicator= " + mDefaultRoamingIndicator);
+ }
+ pollStateDone();
+ }
+
+ }
+
+ protected void setSignalStrengthDefaultValues() {
+ mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1, false);
+ }
+
+ /**
+ * 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
+ */
+ protected void
+ pollState() {
+ pollingContext = new int[1];
+ pollingContext[0] = 0;
+
+ switch (cm.getRadioState()) {
+ case RADIO_UNAVAILABLE:
+ newSS.setStateOutOfService();
+ newCellLoc.setStateInvalid();
+ setSignalStrengthDefaultValues();
+ mGotCountryCode = false;
+
+ pollStateDone();
+ break;
+
+ case RADIO_OFF:
+ newSS.setStateOff();
+ newCellLoc.setStateInvalid();
+ setSignalStrengthDefaultValues();
+ 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]++;
+ // RIL_REQUEST_OPERATOR is necessary for CDMA
+ cm.getOperator(
+ obtainMessage(EVENT_POLL_STATE_OPERATOR_CDMA, pollingContext));
+
+ pollingContext[0]++;
+ // RIL_REQUEST_VOICE_REGISTRATION_STATE is necessary for CDMA
+ cm.getVoiceRegistrationState(
+ obtainMessage(EVENT_POLL_STATE_REGISTRATION_CDMA, pollingContext));
+
+ break;
+ }
+ }
+
+ protected void fixTimeZone(String isoCountryCode) {
+ TimeZone zone = null;
+ // If the offset is (0, false) and the time zone property
+ // is set, use the time zone property rather than GMT.
+ String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
+ if (DBG) {
+ log("fixTimeZone zoneName='" + zoneName +
+ "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
+ " iso-cc='" + isoCountryCode +
+ "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode));
+ }
+ if ((mZoneOffset == 0) && (mZoneDst == false) && (zoneName != null)
+ && (zoneName.length() > 0)
+ && (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) {
+ // For NITZ string without time zone,
+ // need adjust time to reflect default time zone setting
+ zone = TimeZone.getDefault();
+ if (mNeedFixZone) {
+ long ctm = System.currentTimeMillis();
+ long tzOffset = zone.getOffset(ctm);
+ if (DBG) {
+ log("fixTimeZone: tzOffset=" + tzOffset +
+ " ltod=" + TimeUtils.logTimeOfDay(ctm));
+ }
+ if (getAutoTime()) {
+ long adj = ctm - tzOffset;
+ if (DBG) log("fixTimeZone: adj ltod=" + TimeUtils.logTimeOfDay(adj));
+ setAndBroadcastNetworkSetTime(adj);
+ } else {
+ // Adjust the saved NITZ time to account for tzOffset.
+ mSavedTime = mSavedTime - tzOffset;
+ if (DBG) log("fixTimeZone: adj mSavedTime=" + mSavedTime);
+ }
+ }
+ if (DBG) log("fixTimeZone: using default TimeZone");
+ } else if (isoCountryCode.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);
+ if (DBG) log("fixTimeZone: using NITZ TimeZone");
+ } else {
+ zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, isoCountryCode);
+ if (DBG) log("fixTimeZone: using getTimeZone(off, dst, time, iso)");
+ }
+
+ mNeedFixZone = false;
+
+ if (zone != null) {
+ log("fixTimeZone: zone != null zone.getID=" + zone.getID());
+ if (getAutoTimeZone()) {
+ setAndBroadcastNetworkSetTimeZone(zone.getID());
+ } else {
+ log("fixTimeZone: skip changing zone as getAutoTimeZone was false");
+ }
+ saveNitzTimeZone(zone.getID());
+ } else {
+ log("fixTimeZone: zone == null, do nothing for zone");
+ }
+ }
+
+ protected void pollStateDone() {
+ if (DBG) log("pollStateDone: oldSS=[" + ss + "] newSS=[" + newSS + "]");
+
+ 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 hasCdmaDataConnectionAttached =
+ mDataConnectionState != ServiceState.STATE_IN_SERVICE
+ && mNewDataConnectionState == ServiceState.STATE_IN_SERVICE;
+
+ boolean hasCdmaDataConnectionDetached =
+ mDataConnectionState == ServiceState.STATE_IN_SERVICE
+ && mNewDataConnectionState != ServiceState.STATE_IN_SERVICE;
+
+ boolean hasCdmaDataConnectionChanged =
+ mDataConnectionState != mNewDataConnectionState;
+
+ boolean hasRadioTechnologyChanged = mRilRadioTechnology != mNewRilRadioTechnology;
+
+ boolean hasChanged = !newSS.equals(ss);
+
+ boolean hasRoamingOn = !ss.getRoaming() && newSS.getRoaming();
+
+ boolean hasRoamingOff = ss.getRoaming() && !newSS.getRoaming();
+
+ boolean hasLocationChanged = !newCellLoc.equals(cellLoc);
+
+ // Add an event log when connection state changes
+ if (ss.getState() != newSS.getState() ||
+ mDataConnectionState != mNewDataConnectionState) {
+ EventLog.writeEvent(EventLogTags.CDMA_SERVICE_STATE_CHANGE,
+ ss.getState(), mDataConnectionState,
+ newSS.getState(), mNewDataConnectionState);
+ }
+
+ ServiceState tss;
+ tss = ss;
+ ss = newSS;
+ newSS = tss;
+ // clean slate for next time
+ newSS.setStateOutOfService();
+
+ CdmaCellLocation tcl = cellLoc;
+ cellLoc = newCellLoc;
+ newCellLoc = tcl;
+
+ mDataConnectionState = mNewDataConnectionState;
+ mRilRadioTechnology = mNewRilRadioTechnology;
+ // this new state has been applied - forget it until we get a new new state
+ mNewRilRadioTechnology = 0;
+
+ newSS.setStateOutOfService(); // clean slate for next time
+
+ if (hasRadioTechnologyChanged) {
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE,
+ ServiceState.rilRadioTechnologyToString(mRilRadioTechnology));
+ }
+
+ if (hasRegistered) {
+ mNetworkAttachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasChanged) {
+ if ((cm.getRadioState().isOn()) && (!isSubscriptionFromRuim)) {
+ String eriText;
+ // Now the CDMAPhone sees the new ServiceState so it can get the new ERI text
+ if (ss.getState() == ServiceState.STATE_IN_SERVICE) {
+ eriText = phone.getCdmaEriText();
+ } else {
+ // Note that ServiceState.STATE_OUT_OF_SERVICE is valid used for
+ // mRegistrationState 0,2,3 and 4
+ eriText = phone.getContext().getText(
+ com.android.internal.R.string.roamingTextSearching).toString();
+ }
+ ss.setOperatorAlphaLong(eriText);
+ }
+
+ String operatorNumeric;
+
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
+ ss.getOperatorAlphaLong());
+
+ String prevOperatorNumeric =
+ SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
+ operatorNumeric = ss.getOperatorNumeric();
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
+
+ if (operatorNumeric == null) {
+ if (DBG) log("operatorNumeric is null");
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
+ mGotCountryCode = false;
+ } else {
+ String isoCountryCode = "";
+ String mcc = operatorNumeric.substring(0, 3);
+ try{
+ isoCountryCode = MccTable.countryCodeForMcc(Integer.parseInt(
+ operatorNumeric.substring(0,3)));
+ } catch ( NumberFormatException ex){
+ loge("pollStateDone: countryCodeForMcc error" + ex);
+ } catch ( StringIndexOutOfBoundsException ex) {
+ loge("pollStateDone: countryCodeForMcc error" + ex);
+ }
+
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY,
+ isoCountryCode);
+ mGotCountryCode = true;
+
+ if (shouldFixTimeZoneNow(phone, operatorNumeric, prevOperatorNumeric,
+ mNeedFixZone)) {
+ fixTimeZone(isoCountryCode);
+ }
+ }
+
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING,
+ ss.getRoaming() ? "true" : "false");
+
+ updateSpnDisplay();
+ phone.notifyServiceStateChanged(ss);
+ }
+
+ if (hasCdmaDataConnectionAttached) {
+ mAttachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasCdmaDataConnectionDetached) {
+ mDetachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasCdmaDataConnectionChanged || hasRadioTechnologyChanged) {
+ phone.notifyDataConnection(null);
+ }
+
+ if (hasRoamingOn) {
+ mRoamingOnRegistrants.notifyRegistrants();
+ }
+
+ if (hasRoamingOff) {
+ mRoamingOffRegistrants.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("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;
+ }
+
+ /**
+ * TODO: This code is exactly the same as in GsmServiceStateTracker
+ * and has a TODO to not poll signal strength if screen is off.
+ * This code should probably be hoisted to the base class so
+ * the fix, when added, works for both.
+ */
+ 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;
+
+ // TODO Don't poll signal strength if screen is off
+ sendMessageDelayed(msg, POLL_PERIOD_MILLIS);
+ }
+
+ /**
+ * send signal-strength-changed notification if changed
+ * Called both for solicited and unsolicited signal strength updates
+ */
+ protected void
+ onSignalStrengthResult(AsyncResult ar) {
+ SignalStrength oldSignalStrength = mSignalStrength;
+
+ if (ar.exception != null) {
+ // Most likely radio is resetting/disconnected change to default values.
+ setSignalStrengthDefaultValues();
+ } else {
+ int[] ints = (int[])ar.result;
+ int offset = 2;
+ int cdmaDbm = (ints[offset] > 0) ? -ints[offset] : -120;
+ int cdmaEcio = (ints[offset+1] > 0) ? -ints[offset+1] : -160;
+ int evdoRssi = (ints[offset+2] > 0) ? -ints[offset+2] : -120;
+ int evdoEcio = (ints[offset+3] > 0) ? -ints[offset+3] : -1;
+ int evdoSnr = ((ints[offset+4] > 0) && (ints[offset+4] <= 8)) ? ints[offset+4] : -1;
+
+ //log(String.format("onSignalStrengthResult cdmaDbm=%d cdmaEcio=%d evdoRssi=%d evdoEcio=%d evdoSnr=%d",
+ // cdmaDbm, cdmaEcio, evdoRssi, evdoEcio, evdoSnr));
+ mSignalStrength = new SignalStrength(99, -1, cdmaDbm, cdmaEcio,
+ evdoRssi, evdoEcio, evdoSnr, false);
+ }
+
+ try {
+ phone.notifySignalStrength();
+ } catch (NullPointerException ex) {
+ loge("onSignalStrengthResult() Phone already destroyed: " + ex
+ + "SignalStrength not notified");
+ }
+ }
+
+
+ protected int radioTechnologyToDataServiceState(int code) {
+ int retVal = ServiceState.STATE_OUT_OF_SERVICE;
+ switch(code) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ break;
+ case 6: // RADIO_TECHNOLOGY_1xRTT
+ case 7: // RADIO_TECHNOLOGY_EVDO_0
+ case 8: // RADIO_TECHNOLOGY_EVDO_A
+ case 12: // RADIO_TECHNOLOGY_EVDO_B
+ case 13: // RADIO_TECHNOLOGY_EHRPD
+ retVal = ServiceState.STATE_IN_SERVICE;
+ break;
+ default:
+ loge("radioTechnologyToDataServiceState: Wrong radioTechnology code.");
+ break;
+ }
+ return(retVal);
+ }
+
+ /** code is registration state 0-5 from TS 27.007 7.2 */
+ protected int
+ regCodeToServiceState(int code) {
+ switch (code) {
+ case 0: // Not searching and not registered
+ return ServiceState.STATE_OUT_OF_SERVICE;
+ case 1:
+ return ServiceState.STATE_IN_SERVICE;
+ case 2: // 2 is "searching", fall through
+ case 3: // 3 is "registration denied", fall through
+ case 4: // 4 is "unknown", not valid in current baseband
+ return ServiceState.STATE_OUT_OF_SERVICE;
+ case 5:// 5 is "Registered, roaming"
+ return ServiceState.STATE_IN_SERVICE;
+
+ default:
+ loge("regCodeToServiceState: unexpected service state " + code);
+ return ServiceState.STATE_OUT_OF_SERVICE;
+ }
+ }
+
+ public int getCurrentDataConnectionState() {
+ return mDataConnectionState;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Determine whether a roaming indicator is in the carrier-specified list of ERIs for
+ * home system
+ *
+ * @param roamInd roaming indicator in String
+ * @return true if the roamInd is in the carrier-specified list of ERIs for home network
+ */
+ private boolean isRoamIndForHomeSystem(String roamInd) {
+ // retrieve the carrier-specified list of ERIs for home system
+ String homeRoamIndicators = SystemProperties.get("ro.cdma.homesystem");
+
+ if (!TextUtils.isEmpty(homeRoamIndicators)) {
+ // searches through the comma-separated list for a match,
+ // return true if one is found.
+ for (String homeRoamInd : homeRoamIndicators.split(",")) {
+ if (homeRoamInd.equals(roamInd)) {
+ return true;
+ }
+ }
+ // no matches found against the list!
+ return false;
+ }
+
+ // no system property found for the roaming indicators for home system
+ return false;
+ }
+
+ /**
+ * Set roaming state when cdmaRoaming is true and ons is different from spn
+ * @param cdmaRoaming TS 27.007 7.2 CREG registered roaming
+ * @param s ServiceState hold current ons
+ * @return true for roaming state set
+ */
+ private
+ boolean isRoamingBetweenOperators(boolean cdmaRoaming, ServiceState s) {
+ String spn = SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, "empty");
+
+ // NOTE: in case of RUIM we should completely ignore the ERI data file and
+ // mOperatorAlphaLong is set from RIL_REQUEST_OPERATOR response 0 (alpha ONS)
+ String onsl = s.getOperatorAlphaLong();
+ String onss = s.getOperatorAlphaShort();
+
+ boolean equalsOnsl = onsl != null && spn.equals(onsl);
+ boolean equalsOnss = onss != null && spn.equals(onss);
+
+ return cdmaRoaming && !(equalsOnsl || equalsOnss);
+ }
+
+
+ /**
+ * nitzReceiveTime is time_t that the NITZ time was posted
+ */
+
+ private
+ void setTimeFromNITZString (String nitz, long nitzReceiveTime)
+ {
+ // "yy/mm/dd,hh:mm:ss(+/-)tz"
+ // tz is in number of quarter-hours
+
+ long start = SystemClock.elapsedRealtime();
+ if (DBG) {
+ log("NITZ: " + nitz + "," + nitzReceiveTime +
+ " start=" + start + " delay=" + (start - 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(TelephonyProperties.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) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
+ // We got the time before the country or the zone has changed
+ // 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 (DBG) {
+ log("NITZ: tzOffset=" + tzOffset + " dst=" + dst + " zone=" + zone.getID() +
+ " iso=" + iso + " mGotCountryCode=" + mGotCountryCode +
+ " mNeedFixZone=" + mNeedFixZone);
+ }
+
+ if (zone != null) {
+ if (getAutoTimeZone()) {
+ setAndBroadcastNetworkSetTimeZone(zone.getID());
+ }
+ saveNitzTimeZone(zone.getID());
+ }
+
+ String ignore = SystemProperties.get("gsm.ignore-nitz");
+ if (ignore != null && ignore.equals("yes")) {
+ if (DBG) log("NITZ: Not setting clock because gsm.ignore-nitz is set");
+ return;
+ }
+
+ try {
+ mWakeLock.acquire();
+
+ /**
+ * Correct the NITZ time by how long its taken to get here.
+ */
+ long millisSinceNitzReceived
+ = SystemClock.elapsedRealtime() - nitzReceiveTime;
+
+ if (millisSinceNitzReceived < 0) {
+ // Sanity check: something is wrong
+ if (DBG) {
+ log("NITZ: not setting time, clock has rolled "
+ + "backwards since NITZ time was received, "
+ + nitz);
+ }
+ return;
+ }
+
+ if (millisSinceNitzReceived > Integer.MAX_VALUE) {
+ // If the time is this far off, something is wrong > 24 days!
+ if (DBG) {
+ log("NITZ: not setting time, processing has taken "
+ + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
+ + " days");
+ }
+ return;
+ }
+
+ // Note: with range checks above, cast to int is safe
+ c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
+
+ if (getAutoTime()) {
+ /**
+ * Update system time automatically
+ */
+ long gained = c.getTimeInMillis() - System.currentTimeMillis();
+ long timeSinceLastUpdate = SystemClock.elapsedRealtime() - mSavedAtTime;
+ int nitzUpdateSpacing = Settings.Secure.getInt(cr,
+ Settings.Secure.NITZ_UPDATE_SPACING, mNitzUpdateSpacing);
+ int nitzUpdateDiff = Settings.Secure.getInt(cr,
+ Settings.Secure.NITZ_UPDATE_DIFF, mNitzUpdateDiff);
+
+ if ((mSavedAtTime == 0) || (timeSinceLastUpdate > nitzUpdateSpacing)
+ || (Math.abs(gained) > nitzUpdateDiff)) {
+ if (DBG) {
+ log("NITZ: Auto updating time of day to " + c.getTime()
+ + " NITZ receive delay=" + millisSinceNitzReceived
+ + "ms gained=" + gained + "ms from " + nitz);
+ }
+
+ setAndBroadcastNetworkSetTime(c.getTimeInMillis());
+ } else {
+ if (DBG) {
+ log("NITZ: ignore, a previous update was "
+ + timeSinceLastUpdate + "ms ago and gained=" + gained + "ms");
+ }
+ return;
+ }
+ }
+
+ /**
+ * Update properties and save the time we did the update
+ */
+ if (DBG) log("NITZ: update nitz time property");
+ SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
+ mSavedTime = c.getTimeInMillis();
+ mSavedAtTime = SystemClock.elapsedRealtime();
+ } finally {
+ long end = SystemClock.elapsedRealtime();
+ if (DBG) log("NITZ: end=" + end + " dur=" + (end - start));
+ mWakeLock.release();
+ }
+ } catch (RuntimeException ex) {
+ loge("NITZ: Parsing NITZ time " + nitz + " ex=" + ex);
+ }
+ }
+
+ private boolean getAutoTime() {
+ try {
+ return Settings.System.getInt(cr, Settings.System.AUTO_TIME) > 0;
+ } catch (SettingNotFoundException snfe) {
+ return true;
+ }
+ }
+
+ private boolean getAutoTimeZone() {
+ try {
+ return Settings.System.getInt(cr, Settings.System.AUTO_TIME_ZONE) > 0;
+ } catch (SettingNotFoundException snfe) {
+ return true;
+ }
+ }
+
+ private void saveNitzTimeZone(String zoneId) {
+ mSavedTimeZone = zoneId;
+ }
+
+ /**
+ * Set the timezone and send out a sticky broadcast so the system can
+ * determine if the timezone was set by the carrier.
+ *
+ * @param zoneId timezone set by carrier
+ */
+ private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
+ if (DBG) log("setAndBroadcastNetworkSetTimeZone: setTimeZone=" + zoneId);
+ AlarmManager alarm =
+ (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
+ alarm.setTimeZone(zoneId);
+ Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("time-zone", zoneId);
+ phone.getContext().sendStickyBroadcast(intent);
+ }
+
+ /**
+ * Set the time and Send out a sticky broadcast so the system can determine
+ * if the time was set by the carrier.
+ *
+ * @param time time set by network
+ */
+ private void setAndBroadcastNetworkSetTime(long time) {
+ if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms");
+ SystemClock.setCurrentTimeMillis(time);
+ Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("time", time);
+ phone.getContext().sendStickyBroadcast(intent);
+ }
+
+ private void revertToNitzTime() {
+ if (Settings.System.getInt(cr, Settings.System.AUTO_TIME, 0) == 0) {
+ return;
+ }
+ if (DBG) {
+ log("revertToNitzTime: mSavedTime=" + mSavedTime + " mSavedAtTime=" + mSavedAtTime);
+ }
+ if (mSavedTime != 0 && mSavedAtTime != 0) {
+ setAndBroadcastNetworkSetTime(mSavedTime
+ + (SystemClock.elapsedRealtime() - mSavedAtTime));
+ }
+ }
+
+ private void revertToNitzTimeZone() {
+ if (Settings.System.getInt(phone.getContext().getContentResolver(),
+ Settings.System.AUTO_TIME_ZONE, 0) == 0) {
+ return;
+ }
+ if (DBG) log("revertToNitzTimeZone: tz='" + mSavedTimeZone);
+ if (mSavedTimeZone != null) {
+ setAndBroadcastNetworkSetTimeZone(mSavedTimeZone);
+ }
+ }
+
+ protected boolean isSidsAllZeros() {
+ if (mHomeSystemId != null) {
+ for (int i=0; i < mHomeSystemId.length; i++) {
+ if (mHomeSystemId[i] != 0) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check whether a specified system ID that matches one of the home system IDs.
+ */
+ private boolean isHomeSid(int sid) {
+ if (mHomeSystemId != null) {
+ for (int i=0; i < mHomeSystemId.length; i++) {
+ if (sid == mHomeSystemId[i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return true if phone is camping on a technology
+ * that could support voice and data simultaneously.
+ */
+ public boolean isConcurrentVoiceAndDataAllowed() {
+ // Note: it needs to be confirmed which CDMA network types
+ // can support voice and data calls concurrently.
+ // For the time-being, the return value will be false.
+ return false;
+ }
+
+ public String getMdnNumber() {
+ return mMdn;
+ }
+
+ public String getCdmaMin() {
+ return mMin;
+ }
+
+ /** Returns null if NV is not yet ready */
+ public String getPrlVersion() {
+ return mPrlVersion;
+ }
+
+ /**
+ * Returns IMSI as MCC + MNC + MIN
+ */
+ String getImsi() {
+ // TODO: When RUIM is enabled, IMSI will come from RUIM not build-time props.
+ String operatorNumeric = SystemProperties.get(
+ TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, "");
+
+ if (!TextUtils.isEmpty(operatorNumeric) && getCdmaMin() != null) {
+ return (operatorNumeric + getCdmaMin());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Check if subscription data has been assigned to mMin
+ *
+ * return true if MIN info is ready; false otherwise.
+ */
+ public boolean isMinInfoReady() {
+ return mIsMinInfoReady;
+ }
+
+ /**
+ * Returns OTASP_UNKNOWN, OTASP_NEEDED or OTASP_NOT_NEEDED
+ */
+ int getOtasp() {
+ int provisioningState;
+ if (mMin == null || (mMin.length() < 6)) {
+ if (DBG) log("getOtasp: bad mMin='" + mMin + "'");
+ provisioningState = OTASP_UNKNOWN;
+ } else {
+ if ((mMin.equals(UNACTIVATED_MIN_VALUE)
+ || mMin.substring(0,6).equals(UNACTIVATED_MIN2_VALUE))
+ || SystemProperties.getBoolean("test_cdma_setup", false)) {
+ provisioningState = OTASP_NEEDED;
+ } else {
+ provisioningState = OTASP_NOT_NEEDED;
+ }
+ }
+ if (DBG) log("getOtasp: state=" + provisioningState);
+ return provisioningState;
+ }
+
+ @Override
+ protected void hangupAndPowerOff() {
+ // hang up all active voice calls
+ phone.mCT.ringingCall.hangupIfAlive();
+ phone.mCT.backgroundCall.hangupIfAlive();
+ phone.mCT.foregroundCall.hangupIfAlive();
+ cm.setRadioPower(false, null);
+ }
+
+ protected void parseSidNid (String sidStr, String nidStr) {
+ if (sidStr != null) {
+ String[] sid = sidStr.split(",");
+ mHomeSystemId = new int[sid.length];
+ for (int i = 0; i < sid.length; i++) {
+ try {
+ mHomeSystemId[i] = Integer.parseInt(sid[i]);
+ } catch (NumberFormatException ex) {
+ loge("error parsing system id: " + ex);
+ }
+ }
+ }
+ if (DBG) log("CDMA_SUBSCRIPTION: SID=" + sidStr);
+
+ if (nidStr != null) {
+ String[] nid = nidStr.split(",");
+ mHomeNetworkId = new int[nid.length];
+ for (int i = 0; i < nid.length; i++) {
+ try {
+ mHomeNetworkId[i] = Integer.parseInt(nid[i]);
+ } catch (NumberFormatException ex) {
+ loge("CDMA_SUBSCRIPTION: error parsing network id: " + ex);
+ }
+ }
+ }
+ if (DBG) log("CDMA_SUBSCRIPTION: NID=" + nidStr);
+ }
+
+ protected void updateOtaspState() {
+ int otaspMode = getOtasp();
+ int oldOtaspMode = mCurrentOtaspMode;
+ mCurrentOtaspMode = otaspMode;
+
+ // Notify apps subscription info is ready
+ if (cdmaForSubscriptionInfoReadyRegistrants != null) {
+ if (DBG) log("CDMA_SUBSCRIPTION: call notifyRegistrants()");
+ cdmaForSubscriptionInfoReadyRegistrants.notifyRegistrants();
+ }
+ if (oldOtaspMode != mCurrentOtaspMode) {
+ if (DBG) {
+ log("CDMA_SUBSCRIPTION: call notifyOtaspChanged old otaspMode=" +
+ oldOtaspMode + " new otaspMode=" + mCurrentOtaspMode);
+ }
+ phone.notifyOtaspChanged(mCurrentOtaspMode);
+ }
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[CdmaSST] " + s);
+ }
+
+ @Override
+ protected void loge(String s) {
+ Log.e(LOG_TAG, "[CdmaSST] " + s);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("CdmaServiceStateTracker extends:");
+ super.dump(fd, pw, args);
+ pw.println(" phone=" + phone);
+ pw.println(" cellLoc=" + cellLoc);
+ pw.println(" newCellLoc=" + newCellLoc);
+ pw.println(" mCurrentOtaspMode=" + mCurrentOtaspMode);
+ pw.println(" mCdmaRoaming=" + mCdmaRoaming);
+ pw.println(" mRoamingIndicator=" + mRoamingIndicator);
+ pw.println(" mIsInPrl=" + mIsInPrl);
+ pw.println(" mDefaultRoamingIndicator=" + mDefaultRoamingIndicator);
+ pw.println(" mDataConnectionState=" + mDataConnectionState);
+ pw.println(" mNewDataConnectionState=" + mNewDataConnectionState);
+ pw.println(" mRegistrationState=" + mRegistrationState);
+ pw.println(" mNeedFixZone=" + mNeedFixZone);
+ pw.println(" mZoneOffset=" + mZoneOffset);
+ pw.println(" mZoneDst=" + mZoneDst);
+ pw.println(" mZoneTime=" + mZoneTime);
+ pw.println(" mGotCountryCode=" + mGotCountryCode);
+ pw.println(" mSavedTimeZone=" + mSavedTimeZone);
+ pw.println(" mSavedTime=" + mSavedTime);
+ pw.println(" mSavedAtTime=" + mSavedAtTime);
+ pw.println(" mNeedToRegForRuimLoaded=" + mNeedToRegForRuimLoaded);
+ pw.println(" mWakeLock=" + mWakeLock);
+ pw.println(" mCurPlmn=" + mCurPlmn);
+ pw.println(" mMdn=" + mMdn);
+ pw.println(" mHomeSystemId=" + mHomeSystemId);
+ pw.println(" mHomeNetworkId=" + mHomeNetworkId);
+ pw.println(" mMin=" + mMin);
+ pw.println(" mPrlVersion=" + mPrlVersion);
+ pw.println(" mIsMinInfoReady=" + mIsMinInfoReady);
+ pw.println(" isEriTextLoaded=" + isEriTextLoaded);
+ pw.println(" isSubscriptionFromRuim=" + isSubscriptionFromRuim);
+ pw.println(" mCdmaSSM=" + mCdmaSSM);
+ pw.println(" mRegistrationDeniedReason=" + mRegistrationDeniedReason);
+ pw.println(" currentCarrier=" + currentCarrier);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java b/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
new file mode 100644
index 0000000..80af9d4
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2011 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.cdma;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.RILConstants;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.provider.Settings;
+import android.util.Log;
+
+/**
+ * Class that handles the CDMA subscription source changed events from RIL
+ */
+public class CdmaSubscriptionSourceManager extends Handler {
+ static final String LOG_TAG = "CDMA";
+ private static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 1;
+ private static final int EVENT_GET_CDMA_SUBSCRIPTION_SOURCE = 2;
+ private static final int EVENT_RADIO_ON = 3;
+
+ public static final int SUBSCRIPTION_SOURCE_UNKNOWN = -1;
+ public static final int SUBSCRIPTION_FROM_RUIM = 0; /* CDMA subscription from RUIM */
+ public static final int SUBSCRIPTION_FROM_NV = 1; /* CDMA subscription from NV */
+ public static final int PREFERRED_CDMA_SUBSCRIPTION = SUBSCRIPTION_FROM_NV;
+
+ private static CdmaSubscriptionSourceManager sInstance;
+ private static final Object sReferenceCountMonitor = new Object();
+ private static int sReferenceCount = 0;
+
+ // ***** Instance Variables
+ private CommandsInterface mCM;
+ private Context mContext;
+ private RegistrantList mCdmaSubscriptionSourceChangedRegistrants = new RegistrantList();
+
+ // Type of CDMA subscription source
+ private AtomicInteger mCdmaSubscriptionSource = new AtomicInteger(SUBSCRIPTION_FROM_NV);
+
+ // Constructor
+ private CdmaSubscriptionSourceManager(Context context, CommandsInterface ci) {
+ mContext = context;
+ mCM = ci;
+ mCM.registerForCdmaSubscriptionChanged(this, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
+ mCM.registerForOn(this, EVENT_RADIO_ON, null);
+ int subscriptionSource = getDefaultCdmaSubscriptionSource();
+ mCdmaSubscriptionSource.set(subscriptionSource);
+ }
+
+ /**
+ * This function creates a single instance of this class
+ *
+ * @return object of type CdmaSubscriptionSourceManager
+ */
+ public static CdmaSubscriptionSourceManager getInstance(Context context,
+ CommandsInterface ci, Handler h, int what, Object obj) {
+ synchronized (sReferenceCountMonitor) {
+ if (null == sInstance) {
+ sInstance = new CdmaSubscriptionSourceManager(context, ci);
+ }
+ sInstance.sReferenceCount++;
+ }
+ sInstance.registerForCdmaSubscriptionSourceChanged(h, what, obj);
+ return sInstance;
+ }
+
+ /**
+ * Unregisters for the registered event with RIL
+ */
+ public void dispose(Handler h) {
+ mCdmaSubscriptionSourceChangedRegistrants.remove(h);
+ synchronized (sReferenceCountMonitor) {
+ sReferenceCount--;
+ if (sReferenceCount <= 0) {
+ mCM.unregisterForCdmaSubscriptionChanged(this);
+ mCM.unregisterForOn(this);
+ sInstance = null;
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see android.os.Handler#handleMessage(android.os.Message)
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+ switch (msg.what) {
+ case EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
+ case EVENT_GET_CDMA_SUBSCRIPTION_SOURCE:
+ {
+ log("CDMA_SUBSCRIPTION_SOURCE event = " + msg.what);
+ ar = (AsyncResult) msg.obj;
+ handleGetCdmaSubscriptionSource(ar);
+ }
+ break;
+ case EVENT_RADIO_ON: {
+ mCM.getCdmaSubscriptionSource(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_SOURCE));
+ }
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ /**
+ * Returns the current CDMA subscription source value
+ * @return CDMA subscription source value
+ */
+ public int getCdmaSubscriptionSource() {
+ return mCdmaSubscriptionSource.get();
+ }
+
+ /**
+ * Gets the default CDMA subscription source
+ *
+ * @return Default CDMA subscription source from Settings DB if present.
+ */
+ private int getDefaultCdmaSubscriptionSource() {
+ // Get the default value from the Settings
+ int subscriptionSource = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.CDMA_SUBSCRIPTION_MODE, PREFERRED_CDMA_SUBSCRIPTION);
+ return subscriptionSource;
+ }
+
+ /**
+ * Clients automatically register for CDMA subscription source changed event
+ * when they get an instance of this object.
+ */
+ private void registerForCdmaSubscriptionSourceChanged(Handler h, int what, Object obj) {
+ Registrant r = new Registrant (h, what, obj);
+ mCdmaSubscriptionSourceChangedRegistrants.add(r);
+ }
+
+ /**
+ * Handles the call to get the subscription source
+ *
+ * @param ar AsyncResult object that contains the result of get CDMA
+ * subscription source call
+ */
+ private void handleGetCdmaSubscriptionSource(AsyncResult ar) {
+ if ((ar.exception == null) && (ar.result != null)) {
+ int newSubscriptionSource = ((int[]) ar.result)[0];
+
+ if (newSubscriptionSource != mCdmaSubscriptionSource.get()) {
+ log("Subscription Source Changed : " + mCdmaSubscriptionSource + " >> "
+ + newSubscriptionSource);
+ mCdmaSubscriptionSource.set(newSubscriptionSource);
+
+ // Notify registrants of the new CDMA subscription source
+ mCdmaSubscriptionSourceChangedRegistrants.notifyRegistrants(new AsyncResult(null,
+ null, null));
+ }
+ } else {
+ // GET_CDMA_SUBSCRIPTION is returning Failure. Probably
+ // because modem created GSM Phone. If modem created
+ // GSMPhone, then PhoneProxy will trigger a change in
+ // Phone objects and this object will be destroyed.
+ logw("Unable to get CDMA Subscription Source, Exception: " + ar.exception
+ + ", result: " + ar.result);
+ }
+ }
+
+ private void log(String s) {
+ Log.d(LOG_TAG, "[CdmaSSM] " + s);
+ }
+
+ private void loge(String s) {
+ Log.e(LOG_TAG, "[CdmaSSM] " + s);
+ }
+
+ private void logw(String s) {
+ Log.w(LOG_TAG, "[CdmaSSM] " + s);
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/cdma/EriInfo.java b/src/java/com/android/internal/telephony/cdma/EriInfo.java
new file mode 100644
index 0000000..3e5d37e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/EriInfo.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2009 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.cdma;
+
+public final class EriInfo {
+
+ public static final int ROAMING_INDICATOR_ON = 0;
+ public static final int ROAMING_INDICATOR_OFF = 1;
+ public static final int ROAMING_INDICATOR_FLASH = 2;
+
+ public static final int ROAMING_ICON_MODE_NORMAL = 0;
+ public static final int ROAMING_ICON_MODE_FLASH = 1;
+
+ public int mRoamingIndicator;
+ public int mIconIndex;
+ public int mIconMode;
+ public String mEriText;
+ public int mCallPromptId;
+ public int mAlertId;
+
+ public EriInfo (int roamingIndicator, int iconIndex, int iconMode, String eriText,
+ int callPromptId, int alertId) {
+
+ this.mRoamingIndicator = roamingIndicator;
+ this.mIconIndex = iconIndex;
+ this.mIconMode = iconMode;
+ this.mEriText = eriText;
+ this.mCallPromptId = callPromptId;
+ this.mAlertId = alertId;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/EriManager.java b/src/java/com/android/internal/telephony/cdma/EriManager.java
new file mode 100644
index 0000000..1bcc90a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/EriManager.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2009 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.cdma;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Message;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.util.XmlUtils;
+
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * EriManager loads the ERI file definitions and manages the CDMA roaming information.
+ *
+ */
+public final class EriManager {
+
+ class EriFile {
+
+ public int mVersionNumber; // File version number
+ public int mNumberOfEriEntries; // Number of entries
+ public int mEriFileType; // Eri Phase 0/1
+ //public int mNumberOfIconImages; // reserved for future use
+ //public int mIconImageType; // reserved for future use
+ public String[] mCallPromptId; // reserved for future use
+ public HashMap<Integer, EriInfo> mRoamIndTable; // Roaming Indicator Table
+
+ public EriFile() {
+ this.mVersionNumber = -1;
+ this.mNumberOfEriEntries = 0;
+ this.mEriFileType = -1;
+ this.mCallPromptId = new String[] { "", "", "" };
+ this.mRoamIndTable = new HashMap<Integer, EriInfo>();
+ }
+ }
+
+ class EriDisplayInformation {
+ public int mEriIconIndex;
+ public int mEriIconMode;
+ public String mEriIconText;
+
+ public EriDisplayInformation(int eriIconIndex, int eriIconMode, String eriIconText) {
+ mEriIconIndex = eriIconIndex;
+ mEriIconMode = eriIconMode;
+ mEriIconText = eriIconText;
+ }
+
+// public void setParameters(int eriIconIndex, int eriIconMode, String eriIconText){
+// this.mEriIconIndex = eriIconIndex;
+// this.mEriIconMode = eriIconMode;
+// this.mEriIconText = eriIconText;
+// }
+
+ @Override
+ public String toString() {
+ return "EriDisplayInformation: {" + " IconIndex: " + mEriIconIndex + " EriIconMode: "
+ + mEriIconMode + " EriIconText: " + mEriIconText + " }";
+ }
+ }
+
+ private static final String LOG_TAG = "CDMA";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ public static final int ERI_FROM_XML = 0;
+ public static final int ERI_FROM_FILE_SYSTEM = 1;
+ public static final int ERI_FROM_MODEM = 2;
+
+ private PhoneBase mPhone;
+ private Context mContext;
+ private int mEriFileSource = ERI_FROM_XML;
+ private boolean isEriFileLoaded;
+ private EriFile mEriFile;
+
+ public EriManager(PhoneBase phone, Context context, int eriFileSource) {
+ this.mPhone = phone;
+ this.mContext = context;
+ this.mEriFileSource = eriFileSource;
+ this.mEriFile = new EriFile();
+ }
+
+ public void dispose() {
+ mEriFile = new EriFile();
+ isEriFileLoaded = false;
+ }
+
+
+ public void loadEriFile() {
+ switch (mEriFileSource) {
+ case ERI_FROM_MODEM:
+ loadEriFileFromModem();
+ break;
+
+ case ERI_FROM_FILE_SYSTEM:
+ loadEriFileFromFileSystem();
+ break;
+
+ case ERI_FROM_XML:
+ default:
+ loadEriFileFromXml();
+ break;
+ }
+ }
+
+ /**
+ * Load the ERI file from the MODEM through chipset specific RIL_REQUEST_OEM_HOOK
+ *
+ * In this case the ERI file can be updated from the Phone Support Tool available
+ * from the Chipset vendor
+ */
+ private void loadEriFileFromModem() {
+ // NOT IMPLEMENTED, Chipset vendor/Operator specific
+ }
+
+ /**
+ * Load the ERI file from a File System file
+ *
+ * In this case the a Phone Support Tool to update the ERI file must be provided
+ * to the Operator
+ */
+ private void loadEriFileFromFileSystem() {
+ // NOT IMPLEMENTED, Chipset vendor/Operator specific
+ }
+
+ /**
+ * Load the ERI file from the application framework resources encoded in XML
+ *
+ */
+ private void loadEriFileFromXml() {
+ XmlPullParser parser = null;
+ FileInputStream stream = null;
+ Resources r = mContext.getResources();
+
+ try {
+ if (DBG) Log.d(LOG_TAG, "loadEriFileFromXml: check for alternate file");
+ stream = new FileInputStream(
+ r.getString(com.android.internal.R.string.alternate_eri_file));
+ parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+ if (DBG) Log.d(LOG_TAG, "loadEriFileFromXml: opened alternate file");
+ } catch (FileNotFoundException e) {
+ if (DBG) Log.d(LOG_TAG, "loadEriFileFromXml: no alternate file");
+ parser = null;
+ } catch (XmlPullParserException e) {
+ if (DBG) Log.d(LOG_TAG, "loadEriFileFromXml: no parser for alternate file");
+ parser = null;
+ }
+
+ if (parser == null) {
+ if (DBG) Log.d(LOG_TAG, "loadEriFileFromXml: open normal file");
+ parser = r.getXml(com.android.internal.R.xml.eri);
+ }
+
+ try {
+ XmlUtils.beginDocument(parser, "EriFile");
+ mEriFile.mVersionNumber = Integer.parseInt(
+ parser.getAttributeValue(null, "VersionNumber"));
+ mEriFile.mNumberOfEriEntries = Integer.parseInt(
+ parser.getAttributeValue(null, "NumberOfEriEntries"));
+ mEriFile.mEriFileType = Integer.parseInt(
+ parser.getAttributeValue(null, "EriFileType"));
+
+ int parsedEriEntries = 0;
+ while(true) {
+ XmlUtils.nextElement(parser);
+ String name = parser.getName();
+ if (name == null) {
+ if (parsedEriEntries != mEriFile.mNumberOfEriEntries)
+ Log.e(LOG_TAG, "Error Parsing ERI file: " + mEriFile.mNumberOfEriEntries
+ + " defined, " + parsedEriEntries + " parsed!");
+ break;
+ } else if (name.equals("CallPromptId")) {
+ int id = Integer.parseInt(parser.getAttributeValue(null, "Id"));
+ String text = parser.getAttributeValue(null, "CallPromptText");
+ if (id >= 0 && id <= 2) {
+ mEriFile.mCallPromptId[id] = text;
+ } else {
+ Log.e(LOG_TAG, "Error Parsing ERI file: found" + id + " CallPromptId");
+ }
+
+ } else if (name.equals("EriInfo")) {
+ int roamingIndicator = Integer.parseInt(
+ parser.getAttributeValue(null, "RoamingIndicator"));
+ int iconIndex = Integer.parseInt(parser.getAttributeValue(null, "IconIndex"));
+ int iconMode = Integer.parseInt(parser.getAttributeValue(null, "IconMode"));
+ String eriText = parser.getAttributeValue(null, "EriText");
+ int callPromptId = Integer.parseInt(
+ parser.getAttributeValue(null, "CallPromptId"));
+ int alertId = Integer.parseInt(parser.getAttributeValue(null, "AlertId"));
+ parsedEriEntries++;
+ mEriFile.mRoamIndTable.put(roamingIndicator, new EriInfo (roamingIndicator,
+ iconIndex, iconMode, eriText, callPromptId, alertId));
+ }
+ }
+
+ if (DBG) Log.d(LOG_TAG, "loadEriFileFromXml: eri parsing successful, file loaded");
+ isEriFileLoaded = true;
+
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Got exception while loading ERI file.", e);
+ } finally {
+ if (parser instanceof XmlResourceParser) {
+ ((XmlResourceParser)parser).close();
+ }
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+
+ /**
+ * Returns the version of the ERI file
+ *
+ */
+ public int getEriFileVersion() {
+ return mEriFile.mVersionNumber;
+ }
+
+ /**
+ * Returns the number of ERI entries parsed
+ *
+ */
+ public int getEriNumberOfEntries() {
+ return mEriFile.mNumberOfEriEntries;
+ }
+
+ /**
+ * Returns the ERI file type value ( 0 for Phase 0, 1 for Phase 1)
+ *
+ */
+ public int getEriFileType() {
+ return mEriFile.mEriFileType;
+ }
+
+ /**
+ * Returns if the ERI file has been loaded
+ *
+ */
+ public boolean isEriFileLoaded() {
+ return isEriFileLoaded;
+ }
+
+ /**
+ * Returns the EriInfo record associated with roamingIndicator
+ * or null if the entry is not found
+ */
+ private EriInfo getEriInfo(int roamingIndicator) {
+ if (mEriFile.mRoamIndTable.containsKey(roamingIndicator)) {
+ return mEriFile.mRoamIndTable.get(roamingIndicator);
+ } else {
+ return null;
+ }
+ }
+
+ private EriDisplayInformation getEriDisplayInformation(int roamInd, int defRoamInd){
+ EriDisplayInformation ret;
+
+ // Carrier can use eri.xml to customize any built-in roaming display indications
+ if (isEriFileLoaded) {
+ EriInfo eriInfo = getEriInfo(roamInd);
+ if (eriInfo != null) {
+ if (VDBG) Log.v(LOG_TAG, "ERI roamInd " + roamInd + " found in ERI file");
+ ret = new EriDisplayInformation(
+ eriInfo.mIconIndex,
+ eriInfo.mIconMode,
+ eriInfo.mEriText);
+ return ret;
+ }
+ }
+
+ switch (roamInd) {
+ // Handling the standard roaming indicator (non-ERI)
+ case EriInfo.ROAMING_INDICATOR_ON:
+ ret = new EriDisplayInformation(
+ EriInfo.ROAMING_INDICATOR_ON,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText0).toString());
+ break;
+
+ case EriInfo.ROAMING_INDICATOR_OFF:
+ ret = new EriDisplayInformation(
+ EriInfo.ROAMING_INDICATOR_OFF,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText1).toString());
+ break;
+
+ case EriInfo.ROAMING_INDICATOR_FLASH:
+ ret = new EriDisplayInformation(
+ EriInfo.ROAMING_INDICATOR_FLASH,
+ EriInfo.ROAMING_ICON_MODE_FLASH,
+ mContext.getText(com.android.internal.R.string.roamingText2).toString());
+ break;
+
+
+ // Handling the standard ERI
+ case 3:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText3).toString());
+ break;
+
+ case 4:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText4).toString());
+ break;
+
+ case 5:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText5).toString());
+ break;
+
+ case 6:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText6).toString());
+ break;
+
+ case 7:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText7).toString());
+ break;
+
+ case 8:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText8).toString());
+ break;
+
+ case 9:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText9).toString());
+ break;
+
+ case 10:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText10).toString());
+ break;
+
+ case 11:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText11).toString());
+ break;
+
+ case 12:
+ ret = new EriDisplayInformation(
+ roamInd,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal.R.string.roamingText12).toString());
+ break;
+
+ // Handling the non standard Enhanced Roaming Indicator (roamInd > 63)
+ default:
+ if (!isEriFileLoaded) {
+ // ERI file NOT loaded
+ if (DBG) Log.d(LOG_TAG, "ERI File not loaded");
+ if(defRoamInd > 2) {
+ if (VDBG) Log.v(LOG_TAG, "ERI defRoamInd > 2 ...flashing");
+ ret = new EriDisplayInformation(
+ EriInfo.ROAMING_INDICATOR_FLASH,
+ EriInfo.ROAMING_ICON_MODE_FLASH,
+ mContext.getText(com.android.internal
+ .R.string.roamingText2).toString());
+ } else {
+ if (VDBG) Log.v(LOG_TAG, "ERI defRoamInd <= 2");
+ switch (defRoamInd) {
+ case EriInfo.ROAMING_INDICATOR_ON:
+ ret = new EriDisplayInformation(
+ EriInfo.ROAMING_INDICATOR_ON,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal
+ .R.string.roamingText0).toString());
+ break;
+
+ case EriInfo.ROAMING_INDICATOR_OFF:
+ ret = new EriDisplayInformation(
+ EriInfo.ROAMING_INDICATOR_OFF,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal
+ .R.string.roamingText1).toString());
+ break;
+
+ case EriInfo.ROAMING_INDICATOR_FLASH:
+ ret = new EriDisplayInformation(
+ EriInfo.ROAMING_INDICATOR_FLASH,
+ EriInfo.ROAMING_ICON_MODE_FLASH,
+ mContext.getText(com.android.internal
+ .R.string.roamingText2).toString());
+ break;
+
+ default:
+ ret = new EriDisplayInformation(-1, -1, "ERI text");
+ }
+ }
+ } else {
+ // ERI file loaded
+ EriInfo eriInfo = getEriInfo(roamInd);
+ EriInfo defEriInfo = getEriInfo(defRoamInd);
+ if (eriInfo == null) {
+ if (VDBG) {
+ Log.v(LOG_TAG, "ERI roamInd " + roamInd
+ + " not found in ERI file ...using defRoamInd " + defRoamInd);
+ }
+ if(defEriInfo == null) {
+ Log.e(LOG_TAG, "ERI defRoamInd " + defRoamInd
+ + " not found in ERI file ...on");
+ ret = new EriDisplayInformation(
+ EriInfo.ROAMING_INDICATOR_ON,
+ EriInfo.ROAMING_ICON_MODE_NORMAL,
+ mContext.getText(com.android.internal
+ .R.string.roamingText0).toString());
+
+ } else {
+ if (VDBG) {
+ Log.v(LOG_TAG, "ERI defRoamInd " + defRoamInd + " found in ERI file");
+ }
+ ret = new EriDisplayInformation(
+ defEriInfo.mIconIndex,
+ defEriInfo.mIconMode,
+ defEriInfo.mEriText);
+ }
+ } else {
+ if (VDBG) Log.v(LOG_TAG, "ERI roamInd " + roamInd + " found in ERI file");
+ ret = new EriDisplayInformation(
+ eriInfo.mIconIndex,
+ eriInfo.mIconMode,
+ eriInfo.mEriText);
+ }
+ }
+ break;
+ }
+ if (VDBG) Log.v(LOG_TAG, "Displaying ERI " + ret.toString());
+ return ret;
+ }
+
+ public int getCdmaEriIconIndex(int roamInd, int defRoamInd){
+ return getEriDisplayInformation(roamInd, defRoamInd).mEriIconIndex;
+ }
+
+ public int getCdmaEriIconMode(int roamInd, int defRoamInd){
+ return getEriDisplayInformation(roamInd, defRoamInd).mEriIconMode;
+ }
+
+ public String getCdmaEriText(int roamInd, int defRoamInd){
+ return getEriDisplayInformation(roamInd, defRoamInd).mEriIconText;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/RuimFileHandler.java b/src/java/com/android/internal/telephony/cdma/RuimFileHandler.java
new file mode 100644
index 0000000..f440935
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/RuimFileHandler.java
@@ -0,0 +1,89 @@
+/*
+ * 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.cdma;
+
+import android.os.*;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccConstants;
+import com.android.internal.telephony.IccException;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccFileTypeMismatch;
+import com.android.internal.telephony.IccIoResult;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneProxy;
+
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public final class RuimFileHandler extends IccFileHandler {
+ static final String LOG_TAG = "CDMA";
+
+ //***** Instance Variables
+
+ //***** Constructor
+ public RuimFileHandler(IccCard card, String aid, CommandsInterface ci) {
+ super(card, aid, ci);
+ }
+
+ protected void finalize() {
+ Log.d(LOG_TAG, "RuimFileHandler finalized");
+ }
+
+ //***** Overridden from IccFileHandler
+
+ @Override
+ public void loadEFImgTransparent(int fileid, int highOffset, int lowOffset,
+ int length, Message onLoaded) {
+ Message response = obtainMessage(EVENT_READ_ICON_DONE, fileid, 0,
+ onLoaded);
+
+ mCi.iccIOForApp(COMMAND_GET_RESPONSE, fileid, "img", 0, 0,
+ GET_RESPONSE_EF_IMG_SIZE_BYTES, null, null,
+ mAid, response);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+
+ super.handleMessage(msg);
+ }
+
+ protected String getEFPath(int efid) {
+ switch(efid) {
+ case EF_SMS:
+ case EF_CST:
+ case EF_RUIM_SPN:
+ return MF_SIM + DF_CDMA;
+ }
+ return getCommonIccEFPath(efid);
+ }
+
+ protected void logd(String msg) {
+ Log.d(LOG_TAG, "[RuimFileHandler] " + msg);
+ }
+
+ protected void loge(String msg) {
+ Log.e(LOG_TAG, "[RuimFileHandler] " + msg);
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java
new file mode 100644
index 0000000..04ee2dd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java
@@ -0,0 +1,79 @@
+/*
+** 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.cdma;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.IccPhoneBookInterfaceManager;
+
+/**
+ * RuimPhoneBookInterfaceManager to provide an inter-process communication to
+ * access ADN-like SIM records.
+ */
+
+
+public class RuimPhoneBookInterfaceManager extends IccPhoneBookInterfaceManager {
+ static final String LOG_TAG = "CDMA";
+
+ public RuimPhoneBookInterfaceManager(CDMAPhone phone) {
+ super(phone);
+ adnCache = phone.mIccRecords.getAdnCache();
+ //NOTE service "simphonebook" added by IccSmsInterfaceManagerProxy
+ }
+
+ public void dispose() {
+ super.dispose();
+ }
+
+ protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
+ if(DBG) Log.d(LOG_TAG, "RuimPhoneBookInterfaceManager finalized");
+ }
+
+ public int[] getAdnRecordsSize(int efid) {
+ if (DBG) logd("getAdnRecordsSize: efid=" + efid);
+ synchronized(mLock) {
+ checkThread();
+ recordSize = new int[3];
+
+ //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling
+ AtomicBoolean status = new AtomicBoolean(false);
+ Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, status);
+
+ phone.getIccFileHandler().getEFLinearRecordSize(efid, response);
+ waitForResult(status);
+ }
+
+ return recordSize;
+ }
+
+ protected void logd(String msg) {
+ Log.d(LOG_TAG, "[RuimPbInterfaceManager] " + msg);
+ }
+
+ protected void loge(String msg) {
+ Log.e(LOG_TAG, "[RuimPbInterfaceManager] " + msg);
+ }
+}
+
diff --git a/src/java/com/android/internal/telephony/cdma/RuimRecords.java b/src/java/com/android/internal/telephony/cdma/RuimRecords.java
new file mode 100755
index 0000000..e257fb6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/RuimRecords.java
@@ -0,0 +1,462 @@
+/*
+ * 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.cdma;
+
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.telephony.AdnRecord;
+import com.android.internal.telephony.AdnRecordCache;
+import com.android.internal.telephony.AdnRecordLoader;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.IccRefreshResponse;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.MccTable;
+
+// can't be used since VoiceMailConstants is not public
+//import com.android.internal.telephony.gsm.VoiceMailConstants;
+import com.android.internal.telephony.IccException;
+import com.android.internal.telephony.IccRecords;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.PhoneProxy;
+
+
+/**
+ * {@hide}
+ */
+public final class RuimRecords extends IccRecords {
+ static final String LOG_TAG = "CDMA";
+
+ private static final boolean DBG = true;
+ private boolean m_ota_commited=false;
+
+ // ***** Instance Variables
+
+ private String mImsi;
+ private String mMyMobileNumber;
+ private String mMin2Min1;
+
+ private String mPrlVersion;
+
+ // ***** Event Constants
+
+ 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_DEVICE_IDENTITY_DONE = 4;
+ private static final int EVENT_GET_ICCID_DONE = 5;
+ private static final int EVENT_GET_CDMA_SUBSCRIPTION_DONE = 10;
+ private static final int EVENT_UPDATE_DONE = 14;
+ 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_SMS_ON_RUIM = 21;
+ private static final int EVENT_GET_SMS_DONE = 22;
+
+ private static final int EVENT_RUIM_REFRESH = 31;
+
+
+ public RuimRecords(IccCard card, Context c, CommandsInterface ci) {
+ super(card, c, ci);
+
+ adnCache = new AdnRecordCache(mFh);
+
+ recordsRequested = false; // No load request is made till SIM ready
+
+ // recordsToLoad is set to 0 because no requests are made yet
+ recordsToLoad = 0;
+
+ mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+ // NOTE the EVENT_SMS_ON_RUIM is not registered
+ mCi.registerForIccRefresh(this, EVENT_RUIM_REFRESH, null);
+
+ // Start off by setting empty state
+ onRadioOffOrNotAvailable();
+
+ }
+
+ @Override
+ public void dispose() {
+ if (DBG) log("Disposing RuimRecords " + this);
+ //Unregister for all events
+ mCi.unregisterForOffOrNotAvailable( this);
+ mCi.unregisterForIccRefresh(this);
+ super.dispose();
+ }
+
+ @Override
+ protected void finalize() {
+ if(DBG) log("RuimRecords finalized");
+ }
+
+ @Override
+ protected void onRadioOffOrNotAvailable() {
+ countVoiceMessages = 0;
+ mncLength = UNINITIALIZED;
+ iccid = null;
+
+ adnCache.reset();
+
+ // Don't clean up PROPERTY_ICC_OPERATOR_ISO_COUNTRY and
+ // PROPERTY_ICC_OPERATOR_NUMERIC here. Since not all CDMA
+ // devices have RUIM, these properties should keep the original
+ // values, e.g. build time settings, when there is no RUIM but
+ // set new values when RUIM is available and loaded.
+
+ // 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 String getMdnNumber() {
+ return mMyMobileNumber;
+ }
+
+ public String getCdmaMin() {
+ return mMin2Min1;
+ }
+
+ /** Returns null if RUIM is not yet ready */
+ public String getPrlVersion() {
+ return mPrlVersion;
+ }
+
+ @Override
+ public void setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete){
+ // In CDMA this is Operator/OEM dependent
+ AsyncResult.forMessage((onComplete)).exception =
+ new IccException("setVoiceMailNumber not implemented");
+ onComplete.sendToTarget();
+ loge("method setVoiceMailNumber is not implemented");
+ }
+
+ /**
+ * Called by CCAT Service when REFRESH is received.
+ * @param fileChanged indicates whether any files changed
+ * @param fileList if non-null, a list of EF files that changed
+ */
+ @Override
+ 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 RUIM records that we cache.
+ fetchRuimRecords();
+ }
+ }
+
+ /**
+ * Returns the 5 or 6 digit MCC/MNC of the operator that
+ * provided the RUIM card. Returns null of RUIM is not yet ready
+ */
+ public String getRUIMOperatorNumeric() {
+ if (mImsi == null) {
+ return null;
+ }
+
+ if (mncLength != UNINITIALIZED && mncLength != UNKNOWN) {
+ // Length = length of MCC + length of MNC
+ // length of mcc = 3 (3GPP2 C.S0005 - Section 2.3)
+ return mImsi.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 = Integer.parseInt(mImsi.substring(0,3));
+ return mImsi.substring(0, 3 + MccTable.smallestDigitsMccForMnc(mcc));
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+
+ byte data[];
+
+ boolean isRecordLoadResponse = false;
+
+ if (mDestroyed) {
+ loge("Received message " + msg +
+ "[" + msg.what + "] while being destroyed. Ignoring.");
+ return;
+ }
+
+ try { switch (msg.what) {
+ case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
+ onRadioOffOrNotAvailable();
+ break;
+
+ case EVENT_GET_DEVICE_IDENTITY_DONE:
+ log("Event EVENT_GET_DEVICE_IDENTITY_DONE Received");
+ break;
+
+ /* IO events */
+ case EVENT_GET_IMSI_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception != null) {
+ loge("Exception querying IMSI, Exception:" + ar.exception);
+ break;
+ }
+
+ mImsi = (String) ar.result;
+
+ // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
+ // than 15 (and usually 15).
+ if (mImsi != null && (mImsi.length() < 6 || mImsi.length() > 15)) {
+ loge("invalid IMSI " + mImsi);
+ mImsi = null;
+ }
+
+ log("IMSI: " + mImsi.substring(0, 6) + "xxxxxxxxx");
+
+ String operatorNumeric = getRUIMOperatorNumeric();
+ if (operatorNumeric != null) {
+ if(operatorNumeric.length() <= 6){
+ MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
+ }
+ }
+ break;
+
+ case EVENT_GET_CDMA_SUBSCRIPTION_DONE:
+ ar = (AsyncResult)msg.obj;
+ String localTemp[] = (String[])ar.result;
+ if (ar.exception != null) {
+ break;
+ }
+
+ mMyMobileNumber = localTemp[0];
+ mMin2Min1 = localTemp[3];
+ mPrlVersion = localTemp[4];
+
+ log("MDN: " + mMyMobileNumber + " MIN: " + mMin2Min1);
+
+ break;
+
+ case EVENT_GET_ICCID_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ iccid = IccUtils.bcdToString(data, 0, data.length);
+
+ log("iccid: " + iccid);
+
+ break;
+
+ case EVENT_UPDATE_DONE:
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception != null) {
+ Log.i(LOG_TAG, "RuimRecords update failed", ar.exception);
+ }
+ break;
+
+ case EVENT_GET_ALL_SMS_DONE:
+ case EVENT_MARK_SMS_READ_DONE:
+ case EVENT_SMS_ON_RUIM:
+ case EVENT_GET_SMS_DONE:
+ Log.w(LOG_TAG, "Event not supported: " + msg.what);
+ break;
+
+ // TODO: probably EF_CST should be read instead
+ case EVENT_GET_SST_DONE:
+ log("Event EVENT_GET_SST_DONE Received");
+ break;
+
+ case EVENT_RUIM_REFRESH:
+ isRecordLoadResponse = false;
+ ar = (AsyncResult)msg.obj;
+ if (ar.exception == null) {
+ handleRuimRefresh((IccRefreshResponse)ar.result);
+ }
+ break;
+
+ }}catch (RuntimeException exc) {
+ // I don't want these exceptions to be fatal
+ Log.w(LOG_TAG, "Exception parsing RUIM record", exc);
+ } finally {
+ // Count up record load responses even if they are fails
+ if (isRecordLoadResponse) {
+ onRecordLoaded();
+ }
+ }
+ }
+
+ @Override
+ protected void onRecordLoaded() {
+ // One record loaded successfully or failed, In either case
+ // we need to update the recordsToLoad count
+ recordsToLoad -= 1;
+ if (DBG) log("RuimRecords:onRecordLoaded " + recordsToLoad + " requested: " + recordsRequested);
+
+ if (recordsToLoad == 0 && recordsRequested == true) {
+ onAllRecordsLoaded();
+ } else if (recordsToLoad < 0) {
+ loge("RuimRecords: recordsToLoad <0, programmer error suspected");
+ recordsToLoad = 0;
+ }
+ }
+
+ @Override
+ protected void onAllRecordsLoaded() {
+ // Further records that can be inserted are Operator/OEM dependent
+
+ String operator = getRUIMOperatorNumeric();
+ log("RuimRecords: onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +
+ operator + "'");
+ SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
+
+ if (mImsi != null) {
+ SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
+ MccTable.countryCodeForMcc(Integer.parseInt(mImsi.substring(0,3))));
+ }
+ recordsLoadedRegistrants.notifyRegistrants(
+ new AsyncResult(null, null, null));
+ mParentCard.broadcastIccStateChangedIntent(
+ IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
+ }
+
+ @Override
+ public void onReady() {
+ /* broadcast intent ICC_READY here so that we can make sure
+ READY is sent before IMSI ready
+ */
+
+ mParentCard.broadcastIccStateChangedIntent(
+ IccCardConstants.INTENT_VALUE_ICC_READY, null);
+
+ fetchRuimRecords();
+
+ mCi.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE));
+ }
+
+
+ private void fetchRuimRecords() {
+ recordsRequested = true;
+
+ Log.v(LOG_TAG, "RuimRecords:fetchRuimRecords " + recordsToLoad);
+
+ mCi.getIMSI(obtainMessage(EVENT_GET_IMSI_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_ICCID,
+ obtainMessage(EVENT_GET_ICCID_DONE));
+ recordsToLoad++;
+
+ log("RuimRecords:fetchRuimRecords " + recordsToLoad + " requested: " + recordsRequested);
+ // Further records that can be inserted are Operator/OEM dependent
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * No Display rule for RUIMs yet.
+ */
+ @Override
+ public int getDisplayRule(String plmn) {
+ // TODO together with spn
+ return 0;
+ }
+
+ @Override
+ 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) {
+ // C.S0015-B v2, 4.5.12
+ // range: 0-99
+ countWaiting = 0xff;
+ }
+ countVoiceMessages = countWaiting;
+
+ mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
+ }
+
+ private void handleRuimRefresh(IccRefreshResponse refreshResponse) {
+ if (refreshResponse == null) {
+ if (DBG) log("handleRuimRefresh received without input");
+ return;
+ }
+
+ if (refreshResponse.aid != null &&
+ !refreshResponse.aid.equals(mParentCard.getAid())) {
+ // This is for different app. Ignore.
+ return;
+ }
+
+ switch (refreshResponse.refreshResult) {
+ case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE:
+ if (DBG) log("handleRuimRefresh with SIM_REFRESH_FILE_UPDATED");
+ adnCache.reset();
+ fetchRuimRecords();
+ break;
+ case IccRefreshResponse.REFRESH_RESULT_INIT:
+ if (DBG) log("handleRuimRefresh with SIM_REFRESH_INIT");
+ // need to reload all files (that we care about)
+ fetchRuimRecords();
+ break;
+ case IccRefreshResponse.REFRESH_RESULT_RESET:
+ if (DBG) log("handleRuimRefresh with SIM_REFRESH_RESET");
+ mCi.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
+ if (DBG) log("handleRuimRefresh with unknown operation");
+ break;
+ }
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[RuimRecords] " + s);
+ }
+
+ @Override
+ protected void loge(String s) {
+ Log.e(LOG_TAG, "[RuimRecords] " + s);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/src/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
new file mode 100644
index 0000000..9cd059d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
@@ -0,0 +1,222 @@
+/*
+ * 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.cdma;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.IccConstants;
+import com.android.internal.telephony.IccSmsInterfaceManager;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.SMSDispatcher;
+import com.android.internal.telephony.SmsRawData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
+
+/**
+ * RuimSmsInterfaceManager to provide an inter-process communication to
+ * access Sms in Ruim.
+ */
+public class RuimSmsInterfaceManager extends IccSmsInterfaceManager {
+ static final String LOG_TAG = "CDMA";
+ static final boolean DBG = true;
+
+ 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 = buildValidRawData((ArrayList<byte[]>) ar.result);
+ } else {
+ if(DBG) log("Cannot load Sms records");
+ if (mSms != null)
+ mSms.clear();
+ }
+ mLock.notifyAll();
+ }
+ break;
+ }
+ }
+ };
+
+ public RuimSmsInterfaceManager(CDMAPhone phone, SMSDispatcher dispatcher) {
+ super(phone);
+ mDispatcher = dispatcher;
+ }
+
+ public void dispose() {
+ }
+
+ protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
+ if(DBG) Log.d(LOG_TAG, "RuimSmsInterfaceManager finalized");
+ }
+
+ /**
+ * Update the specified message on the RUIM.
+ *
+ * @param index record index of message to update
+ * @param status new message status (STATUS_ON_ICC_READ,
+ * STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
+ * STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
+ * @param pdu the raw PDU to store
+ * @return success or not
+ *
+ */
+ public boolean
+ updateMessageOnIccEf(int index, int status, byte[] pdu) {
+ if (DBG) log("updateMessageOnIccEf: index=" + index +
+ " status=" + status + " ==> " +
+ "("+ pdu + ")");
+ enforceReceiveAndSend("Updating message on RUIM");
+ synchronized(mLock) {
+ mSuccess = false;
+ Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
+
+ if (status == STATUS_ON_ICC_FREE) {
+ // Special case FREE: call deleteSmsOnRuim instead of
+ // manipulating the RUIM record
+ mPhone.mCM.deleteSmsOnRuim(index, response);
+ } else {
+ byte[] record = makeSmsRecordData(status, pdu);
+ mPhone.getIccFileHandler().updateEFLinearFixed(
+ IccConstants.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 RUIM.
+ *
+ * @param pdu the raw PDU to store
+ * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
+ * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+ * @return success or not
+ *
+ */
+ public boolean copyMessageToIccEf(int status, byte[] pdu, byte[] smsc) {
+ //NOTE smsc not used in RUIM
+ if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
+ "pdu=("+ Arrays.toString(pdu) + ")");
+ enforceReceiveAndSend("Copying message to RUIM");
+ synchronized(mLock) {
+ mSuccess = false;
+ Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
+
+ mPhone.mCM.writeSmsToRuim(status, IccUtils.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 RUIM.
+ */
+ public List<SmsRawData> getAllMessagesFromIccEf() {
+ if (DBG) log("getAllMessagesFromEF");
+
+ Context context = mPhone.getContext();
+
+ context.enforceCallingPermission(
+ "android.permission.RECEIVE_SMS",
+ "Reading messages from RUIM");
+ synchronized(mLock) {
+ Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
+ mPhone.getIccFileHandler().loadEFLinearFixedAll(IccConstants.EF_SMS, response);
+
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to load from the RUIM");
+ }
+ }
+ return mSms;
+ }
+
+ public boolean enableCellBroadcast(int messageIdentifier) {
+ // Not implemented
+ Log.e(LOG_TAG, "Error! Not implemented for CDMA.");
+ return false;
+ }
+
+ public boolean disableCellBroadcast(int messageIdentifier) {
+ // Not implemented
+ Log.e(LOG_TAG, "Error! Not implemented for CDMA.");
+ return false;
+ }
+
+ public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+ // Not implemented
+ Log.e(LOG_TAG, "Error! Not implemented for CDMA.");
+ return false;
+ }
+
+ public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+ // Not implemented
+ Log.e(LOG_TAG, "Error! Not implemented for CDMA.");
+ return false;
+ }
+
+ protected void log(String msg) {
+ Log.d(LOG_TAG, "[RuimSmsInterfaceManager] " + msg);
+ }
+}
+
diff --git a/src/java/com/android/internal/telephony/cdma/SignalToneUtil.java b/src/java/com/android/internal/telephony/cdma/SignalToneUtil.java
new file mode 100644
index 0000000..a149e72
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/SignalToneUtil.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2009 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.cdma;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import android.util.Log;
+import android.media.ToneGenerator;
+
+public class SignalToneUtil {
+ /** A marker that isn't a valid TONE */
+ public static final int CDMA_INVALID_TONE = -1;
+
+ // public final int int IS95_CONST_IR_SIGNAL_TYPE_TYPE;
+ static public final int IS95_CONST_IR_SIGNAL_TONE = 0;
+ static public final int IS95_CONST_IR_SIGNAL_ISDN = 1;
+ static public final int IS95_CONST_IR_SIGNAL_IS54B = 2;
+ static public final int IS95_CONST_IR_SIGNAL_USR_DEFD_ALERT = 4;
+
+ // public final int int IS95_CONST_IR_ALERT_PITCH_TYPE;
+ static public final int IS95_CONST_IR_ALERT_MED = 0;
+ static public final int IS95_CONST_IR_ALERT_HIGH = 1;
+ static public final int IS95_CONST_IR_ALERT_LOW = 2;
+
+ // Based on 3GPP2 C.S0005-E, seciton 3.7.5.5 Signal,
+ // set TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN to 0 to avoid
+ // the alert pitch to be involved in hash calculation for
+ // signal type other than IS54B.
+ static public final int TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN = 0;
+
+ // public final int int IS95_CONST_IR_SIGNAL_TYPE;
+ static public final int IS95_CONST_IR_SIG_ISDN_NORMAL = 0;
+ static public final int IS95_CONST_IR_SIG_ISDN_INTGRP = 1;
+ static public final int IS95_CONST_IR_SIG_ISDN_SP_PRI = 2;
+ static public final int IS95_CONST_IR_SIG_ISDN_PAT_3 = 3;
+ static public final int IS95_CONST_IR_SIG_ISDN_PING = 4;
+ static public final int IS95_CONST_IR_SIG_ISDN_PAT_5 = 5;
+ static public final int IS95_CONST_IR_SIG_ISDN_PAT_6 = 6;
+ static public final int IS95_CONST_IR_SIG_ISDN_PAT_7 = 7;
+ static public final int IS95_CONST_IR_SIG_ISDN_OFF = 15;
+ static public final int IS95_CONST_IR_SIG_TONE_DIAL = 0;
+ static public final int IS95_CONST_IR_SIG_TONE_RING = 1;
+ static public final int IS95_CONST_IR_SIG_TONE_INT = 2;
+ static public final int IS95_CONST_IR_SIG_TONE_ABB_INT = 3;
+ static public final int IS95_CONST_IR_SIG_TONE_REORDER = 4;
+ static public final int IS95_CONST_IR_SIG_TONE_ABB_RE = 5;
+ static public final int IS95_CONST_IR_SIG_TONE_BUSY = 6;
+ static public final int IS95_CONST_IR_SIG_TONE_CONFIRM = 7;
+ static public final int IS95_CONST_IR_SIG_TONE_ANSWER = 8;
+ static public final int IS95_CONST_IR_SIG_TONE_CALL_W = 9;
+ static public final int IS95_CONST_IR_SIG_TONE_PIP = 10;
+ static public final int IS95_CONST_IR_SIG_TONE_NO_TONE = 63;
+ static public final int IS95_CONST_IR_SIG_IS54B_NO_TONE = 0;
+ static public final int IS95_CONST_IR_SIG_IS54B_L = 1;
+ static public final int IS95_CONST_IR_SIG_IS54B_SS = 2;
+ static public final int IS95_CONST_IR_SIG_IS54B_SSL = 3;
+ static public final int IS95_CONST_IR_SIG_IS54B_SS_2 = 4;
+ static public final int IS95_CONST_IR_SIG_IS54B_SLS = 5;
+ static public final int IS95_CONST_IR_SIG_IS54B_S_X4 = 6;
+ static public final int IS95_CONST_IR_SIG_IS54B_PBX_L = 7;
+ static public final int IS95_CONST_IR_SIG_IS54B_PBX_SS = 8;
+ static public final int IS95_CONST_IR_SIG_IS54B_PBX_SSL = 9;
+ static public final int IS95_CONST_IR_SIG_IS54B_PBX_SLS = 10;
+ static public final int IS95_CONST_IR_SIG_IS54B_PBX_S_X4 = 11;
+ static public final int IS95_CONST_IR_SIG_TONE_ABBR_ALRT = 0;
+
+ // Hashmap to map signalInfo To AudioTone
+ static private HashMap<Integer, Integer> hm = new HashMap<Integer, Integer>();
+
+ private static Integer signalParamHash(int signalType, int alertPitch, int signal) {
+ if ((signalType < 0) || (signalType > 256) || (alertPitch > 256) ||
+ (alertPitch < 0) || (signal > 256) || (signal < 0)) {
+ return new Integer(CDMA_INVALID_TONE);
+ }
+ // Based on 3GPP2 C.S0005-E, seciton 3.7.5.5 Signal,
+ // the alert pitch field is ignored by the mobile station unless
+ // SIGNAL_TYPE is '10',IS-54B Alerting.
+ // Set alert pitch to TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN
+ // so the alert pitch is not involved in hash calculation
+ // when signal type is not IS-54B.
+ if (signalType != IS95_CONST_IR_SIGNAL_IS54B) {
+ alertPitch = TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN;
+ }
+ return new Integer(signalType * 256 * 256 + alertPitch * 256 + signal);
+ }
+
+ public static int getAudioToneFromSignalInfo(int signalType, int alertPitch, int signal) {
+ Integer result = hm.get(signalParamHash(signalType, alertPitch, signal));
+ if (result == null) {
+ return CDMA_INVALID_TONE;
+ }
+ return result;
+ }
+
+ static {
+
+ /* SIGNAL_TYPE_ISDN */
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_ISDN, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_ISDN_NORMAL), ToneGenerator.TONE_CDMA_CALL_SIGNAL_ISDN_NORMAL);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_ISDN, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_ISDN_INTGRP),
+ ToneGenerator.TONE_CDMA_CALL_SIGNAL_ISDN_INTERGROUP);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_ISDN, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_ISDN_SP_PRI), ToneGenerator.TONE_CDMA_CALL_SIGNAL_ISDN_SP_PRI);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_ISDN, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_ISDN_PAT_3), ToneGenerator.TONE_CDMA_CALL_SIGNAL_ISDN_PAT3);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_ISDN, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_ISDN_PING), ToneGenerator.TONE_CDMA_CALL_SIGNAL_ISDN_PING_RING);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_ISDN, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_ISDN_PAT_5), ToneGenerator.TONE_CDMA_CALL_SIGNAL_ISDN_PAT5);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_ISDN, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_ISDN_PAT_6), ToneGenerator.TONE_CDMA_CALL_SIGNAL_ISDN_PAT6);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_ISDN, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_ISDN_PAT_7), ToneGenerator.TONE_CDMA_CALL_SIGNAL_ISDN_PAT7);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_ISDN, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_ISDN_OFF), ToneGenerator.TONE_CDMA_SIGNAL_OFF);
+
+ /* SIGNAL_TYPE_TONE */
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_DIAL), ToneGenerator.TONE_CDMA_DIAL_TONE_LITE);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_RING), ToneGenerator.TONE_CDMA_NETWORK_USA_RINGBACK);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_INT), ToneGenerator.TONE_SUP_INTERCEPT);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_ABB_INT), ToneGenerator.TONE_SUP_INTERCEPT_ABBREV);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_REORDER), ToneGenerator.TONE_CDMA_REORDER);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_ABB_RE), ToneGenerator.TONE_CDMA_ABBR_REORDER);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_BUSY), ToneGenerator.TONE_CDMA_NETWORK_BUSY);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_CONFIRM), ToneGenerator.TONE_SUP_CONFIRM);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_ANSWER), ToneGenerator.TONE_CDMA_ANSWER);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_CALL_W), ToneGenerator.TONE_CDMA_NETWORK_CALLWAITING);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_PIP), ToneGenerator.TONE_CDMA_PIP);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_TONE, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_TONE_NO_TONE), ToneGenerator.TONE_CDMA_SIGNAL_OFF);
+
+ /* SIGNAL_TYPE_IS54B */
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_L), ToneGenerator.TONE_CDMA_HIGH_L);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_L), ToneGenerator.TONE_CDMA_MED_L);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_L), ToneGenerator.TONE_CDMA_LOW_L);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_SS), ToneGenerator.TONE_CDMA_HIGH_SS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_SS), ToneGenerator.TONE_CDMA_MED_SS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_SS), ToneGenerator.TONE_CDMA_LOW_SS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_SSL), ToneGenerator.TONE_CDMA_HIGH_SSL);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_SSL), ToneGenerator.TONE_CDMA_MED_SSL);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_SSL), ToneGenerator.TONE_CDMA_LOW_SSL);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_SS_2), ToneGenerator.TONE_CDMA_HIGH_SS_2);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_SS_2), ToneGenerator.TONE_CDMA_MED_SS_2);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_SS_2), ToneGenerator.TONE_CDMA_LOW_SS_2);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_SLS), ToneGenerator.TONE_CDMA_HIGH_SLS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_SLS), ToneGenerator.TONE_CDMA_MED_SLS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_SLS), ToneGenerator.TONE_CDMA_LOW_SLS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_S_X4), ToneGenerator.TONE_CDMA_HIGH_S_X4);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_S_X4), ToneGenerator.TONE_CDMA_MED_S_X4);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_S_X4), ToneGenerator.TONE_CDMA_LOW_S_X4);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_PBX_L), ToneGenerator.TONE_CDMA_HIGH_PBX_L);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_PBX_L), ToneGenerator.TONE_CDMA_MED_PBX_L);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_PBX_L), ToneGenerator.TONE_CDMA_LOW_PBX_L);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_PBX_SS), ToneGenerator.TONE_CDMA_HIGH_PBX_SS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_PBX_SS), ToneGenerator.TONE_CDMA_MED_PBX_SS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_PBX_SS), ToneGenerator.TONE_CDMA_LOW_PBX_SS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_PBX_SSL), ToneGenerator.TONE_CDMA_HIGH_PBX_SSL);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_PBX_SSL), ToneGenerator.TONE_CDMA_MED_PBX_SSL);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_PBX_SSL), ToneGenerator.TONE_CDMA_LOW_PBX_SSL);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_PBX_SLS), ToneGenerator.TONE_CDMA_HIGH_PBX_SLS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_PBX_SLS), ToneGenerator.TONE_CDMA_MED_PBX_SLS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_PBX_SLS), ToneGenerator.TONE_CDMA_LOW_PBX_SLS);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_HIGH,
+ IS95_CONST_IR_SIG_IS54B_PBX_S_X4), ToneGenerator.TONE_CDMA_HIGH_PBX_S_X4);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_MED,
+ IS95_CONST_IR_SIG_IS54B_PBX_S_X4), ToneGenerator.TONE_CDMA_MED_PBX_S_X4);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, IS95_CONST_IR_ALERT_LOW,
+ IS95_CONST_IR_SIG_IS54B_PBX_S_X4), ToneGenerator.TONE_CDMA_LOW_PBX_S_X4);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_IS54B, TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN,
+ IS95_CONST_IR_SIG_IS54B_NO_TONE), ToneGenerator.TONE_CDMA_SIGNAL_OFF);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_USR_DEFD_ALERT,
+ TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN, IS95_CONST_IR_SIG_TONE_ABBR_ALRT),
+ ToneGenerator.TONE_CDMA_ABBR_ALERT);
+
+ hm.put(signalParamHash(IS95_CONST_IR_SIGNAL_USR_DEFD_ALERT,
+ TAPIAMSSCDMA_SIGNAL_PITCH_UNKNOWN, IS95_CONST_IR_SIG_TONE_NO_TONE),
+ ToneGenerator.TONE_CDMA_ABBR_ALERT);
+
+ }
+
+ // suppress default constructor for noninstantiability
+ private SignalToneUtil() {
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/SmsMessage.java b/src/java/com/android/internal/telephony/cdma/SmsMessage.java
new file mode 100644
index 0000000..6254a50
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -0,0 +1,998 @@
+/*
+ * 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.cdma;
+
+import android.os.Parcel;
+import android.os.SystemProperties;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.util.Log;
+
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.HexDump;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * TODO(cleanup): these constants are disturbing... are they not just
+ * different interpretations on one number? And if we did not have
+ * terrible class name overlap, they would not need to be directly
+ * imported like this. The class in this file could just as well be
+ * named CdmaSmsMessage, could it not?
+ */
+
+/**
+ * TODO(cleanup): internally returning null in many places makes
+ * debugging very hard (among many other reasons) and should be made
+ * more meaningful (replaced with exceptions for example). Null
+ * returns should only occur at the very outside of the module/class
+ * scope.
+ */
+
+/**
+ * A Short Message Service message.
+ *
+ */
+public class SmsMessage extends SmsMessageBase {
+ static final String LOG_TAG = "CDMA";
+ static private final String LOGGABLE_TAG = "CDMA:SMS";
+
+ private final static byte TELESERVICE_IDENTIFIER = 0x00;
+ private final static byte SERVICE_CATEGORY = 0x01;
+ private final static byte ORIGINATING_ADDRESS = 0x02;
+ private final static byte ORIGINATING_SUB_ADDRESS = 0x03;
+ private final static byte DESTINATION_ADDRESS = 0x04;
+ private final static byte DESTINATION_SUB_ADDRESS = 0x05;
+ private final static byte BEARER_REPLY_OPTION = 0x06;
+ private final static byte CAUSE_CODES = 0x07;
+ private final static byte BEARER_DATA = 0x08;
+
+ /**
+ * Status of a previously submitted SMS.
+ * This field applies to SMS Delivery Acknowledge messages. 0 indicates success;
+ * Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0.
+ * See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values.
+ */
+ private int status;
+
+ /** Specifies if a return of an acknowledgment is requested for send SMS */
+ private static final int RETURN_NO_ACK = 0;
+ private static final int RETURN_ACK = 1;
+
+ private SmsEnvelope mEnvelope;
+ private BearerData mBearerData;
+
+ public static class SubmitPdu extends SubmitPduBase {
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU.
+ * Note: In CDMA the PDU is just a byte representation of the received Sms.
+ */
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ SmsMessage msg = new SmsMessage();
+
+ try {
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Create a "raw" CDMA SmsMessage from a Parcel that was forged in ril.cpp.
+ * Note: Only primitive fields are set.
+ */
+ public static SmsMessage newFromParcel(Parcel p) {
+ // Note: Parcel.readByte actually reads one Int and masks to byte
+ SmsMessage msg = new SmsMessage();
+ SmsEnvelope env = new SmsEnvelope();
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ CdmaSmsSubaddress subaddr = new CdmaSmsSubaddress();
+ byte[] data;
+ byte count;
+ int countInt;
+ int addressDigitMode;
+
+ //currently not supported by the modem-lib: env.mMessageType
+ env.teleService = p.readInt(); //p_cur->uTeleserviceID
+
+ if (0 != p.readByte()) { //p_cur->bIsServicePresent
+ env.messageType = SmsEnvelope.MESSAGE_TYPE_BROADCAST;
+ }
+ else {
+ if (SmsEnvelope.TELESERVICE_NOT_SET == env.teleService) {
+ // assume type ACK
+ env.messageType = SmsEnvelope.MESSAGE_TYPE_ACKNOWLEDGE;
+ } else {
+ env.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+ }
+ }
+ env.serviceCategory = p.readInt(); //p_cur->uServicecategory
+
+ // address
+ addressDigitMode = p.readInt();
+ addr.digitMode = (byte) (0xFF & addressDigitMode); //p_cur->sAddress.digit_mode
+ addr.numberMode = (byte) (0xFF & p.readInt()); //p_cur->sAddress.number_mode
+ addr.ton = p.readInt(); //p_cur->sAddress.number_type
+ addr.numberPlan = (byte) (0xFF & p.readInt()); //p_cur->sAddress.number_plan
+ count = p.readByte(); //p_cur->sAddress.number_of_digits
+ addr.numberOfDigits = count;
+ data = new byte[count];
+ //p_cur->sAddress.digits[digitCount]
+ for (int index=0; index < count; index++) {
+ data[index] = p.readByte();
+
+ // convert the value if it is 4-bit DTMF to 8 bit
+ if (addressDigitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
+ data[index] = msg.convertDtmfToAscii(data[index]);
+ }
+ }
+
+ addr.origBytes = data;
+
+ subaddr.type = p.readInt(); // p_cur->sSubAddress.subaddressType
+ subaddr.odd = p.readByte(); // p_cur->sSubAddress.odd
+ count = p.readByte(); // p_cur->sSubAddress.number_of_digits
+
+ if (count < 0) {
+ count = 0;
+ }
+
+ // p_cur->sSubAddress.digits[digitCount] :
+
+ data = new byte[count];
+
+ for (int index = 0; index < count; ++index) {
+ data[index] = p.readByte();
+ }
+
+ subaddr.origBytes = data;
+
+ /* currently not supported by the modem-lib:
+ env.bearerReply
+ env.replySeqNo
+ env.errorClass
+ env.causeCode
+ */
+
+ // bearer data
+ countInt = p.readInt(); //p_cur->uBearerDataLen
+ if (countInt < 0) {
+ countInt = 0;
+ }
+
+ data = new byte[countInt];
+ for (int index=0; index < countInt; index++) {
+ data[index] = p.readByte();
+ }
+ // BD gets further decoded when accessed in SMSDispatcher
+ env.bearerData = data;
+
+ // link the the filled objects to the SMS
+ env.origAddress = addr;
+ env.origSubaddress = subaddr;
+ msg.originatingAddress = addr;
+ msg.mEnvelope = env;
+
+ // create byte stream representation for transportation through the layers.
+ msg.createPdu();
+
+ return msg;
+ }
+
+ /**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 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.indexOnIcc = index;
+
+ // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
+ // or STORED_UNSENT
+ // See 3GPP2 C.S0023 3.4.27
+ if ((data[0] & 1) == 0) {
+ Log.w(LOG_TAG, "SMS parsing failed: Trying to parse a free record");
+ return null;
+ } else {
+ msg.statusOnIcc = data[0] & 0x07;
+ }
+
+ // Second byte is the MSG_LEN, length of the message
+ // See 3GPP2 C.S0023 3.4.27
+ int size = data[1];
+
+ // Note: Data may include trailing FF's. That's OK; message
+ // should still parse correctly.
+ byte[] pdu = new byte[size];
+ System.arraycopy(data, 2, pdu, 0, size);
+ // the message has to be parsed before it can be displayed
+ // see gsm.SmsMessage
+ msg.parsePduFromEfRecord(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ public static int getTPLayerLengthForPDU(String pdu) {
+ Log.w(LOG_TAG, "getTPLayerLengthForPDU: is not supported in CDMA mode.");
+ return 0;
+ }
+
+ /**
+ * TODO(cleanup): why do getSubmitPdu methods take an scAddr input
+ * and do nothing with it? GSM allows us to specify a SC (eg,
+ * when responding to an SMS that explicitly requests the response
+ * is sent to a specific SC), or pass null to use the default
+ * value. Is there no similar notion in CDMA? Or do we just not
+ * have it hooked up?
+ */
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddr Service Centre address. Null means use default.
+ * @param destAddr Address of the recipient.
+ * @param message String representation of the message payload.
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @param smsHeader Array containing the data for the User Data Header, preceded
+ * by the Element Identifiers.
+ * @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 scAddr, String destAddr, String message,
+ boolean statusReportRequested, SmsHeader smsHeader) {
+
+ /**
+ * TODO(cleanup): Do we really want silent failure like this?
+ * Would it not be much more reasonable to make sure we don't
+ * call this function if we really want nothing done?
+ */
+ if (message == null || destAddr == null) {
+ return null;
+ }
+
+ UserData uData = new UserData();
+ uData.payloadStr = message;
+ uData.userDataHeader = smsHeader;
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address and port.
+ *
+ * @param scAddr Service Centre address. null == use default
+ * @param destAddr the address of the destination for the message
+ * @param destPort the port to deliver the message to at the
+ * destination
+ * @param data the data 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 scAddr, String destAddr, int destPort,
+ byte[] data, boolean statusReportRequested) {
+
+ /**
+ * TODO(cleanup): this is not a general-purpose SMS creation
+ * method, but rather something specialized to messages
+ * containing OCTET encoded (meaning non-human-readable) user
+ * data. The name should reflect that, and not just overload.
+ */
+
+ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+ portAddrs.destPort = destPort;
+ portAddrs.origPort = 0;
+ portAddrs.areEightBits = false;
+
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.portAddrs = portAddrs;
+
+ UserData uData = new UserData();
+ uData.userDataHeader = smsHeader;
+ uData.msgEncoding = UserData.ENCODING_OCTET;
+ uData.msgEncodingSet = true;
+ uData.payload = data;
+
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+ *
+ * @param destAddr the address of the destination for the message
+ * @param userData the data for the message
+ * @param statusReportRequested Indicates whether a report is requested for this 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 destAddr, UserData userData,
+ boolean statusReportRequested) {
+ return privateGetSubmitPdu(destAddr, statusReportRequested, userData);
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ public int getProtocolIdentifier() {
+ Log.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode.");
+ // (3GPP TS 23.040): "no interworking, but SME to SME protocol":
+ return 0;
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ public boolean isReplace() {
+ Log.w(LOG_TAG, "isReplace: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ public boolean isCphsMwiMessage() {
+ Log.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isMWIClearMessage() {
+ return ((mBearerData != null) && (mBearerData.numberOfMessages == 0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isMWISetMessage() {
+ return ((mBearerData != null) && (mBearerData.numberOfMessages > 0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isMwiDontStore() {
+ return ((mBearerData != null) &&
+ (mBearerData.numberOfMessages > 0) &&
+ (mBearerData.userData == null));
+ }
+
+ /**
+ * Returns the status for a previously submitted message.
+ * For not interfering with status codes from GSM, this status code is
+ * shifted to the bits 31-16.
+ */
+ public int getStatus() {
+ return (status << 16);
+ }
+
+ /** Return true iff the bearer data message type is DELIVERY_ACK. */
+ public boolean isStatusReportMessage() {
+ return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK);
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ public boolean isReplyPathPresent() {
+ Log.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * Calculate the number of septets needed to encode the message.
+ *
+ * @param messageBody the message to encode
+ * @param use7bitOnly ignore (but still count) illegal characters if true
+ * @return TextEncodingDetails
+ */
+ public static TextEncodingDetails calculateLength(CharSequence messageBody,
+ boolean use7bitOnly) {
+ return BearerData.calcTextEncodingDetails(messageBody, use7bitOnly);
+ }
+
+ /**
+ * Returns the teleservice type of the message.
+ * @return the teleservice:
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP}
+ */
+ /* package */ int getTeleService() {
+ return mEnvelope.teleService;
+ }
+
+ /**
+ * Returns the message type of the message.
+ * @return the message type:
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE},
+ */
+ /* package */ int getMessageType() {
+ return mEnvelope.messageType;
+ }
+
+ /**
+ * Decodes pdu to an empty SMS object.
+ * In the CDMA case the pdu is just an internal byte stream representation
+ * of the SMS Java-object.
+ * @see #createPdu()
+ */
+ private void parsePdu(byte[] pdu) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+ DataInputStream dis = new DataInputStream(bais);
+ byte length;
+ int bearerDataLength;
+ SmsEnvelope env = new SmsEnvelope();
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+
+ try {
+ env.messageType = dis.readInt();
+ env.teleService = dis.readInt();
+ env.serviceCategory = dis.readInt();
+
+ addr.digitMode = dis.readByte();
+ addr.numberMode = dis.readByte();
+ addr.ton = dis.readByte();
+ addr.numberPlan = dis.readByte();
+
+ length = dis.readByte();
+ addr.numberOfDigits = length;
+ addr.origBytes = new byte[length];
+ dis.read(addr.origBytes, 0, length); // digits
+
+ env.bearerReply = dis.readInt();
+ // CauseCode values:
+ env.replySeqNo = dis.readByte();
+ env.errorClass = dis.readByte();
+ env.causeCode = dis.readByte();
+
+ //encoded BearerData:
+ bearerDataLength = dis.readInt();
+ env.bearerData = new byte[bearerDataLength];
+ dis.read(env.bearerData, 0, bearerDataLength);
+ dis.close();
+ } catch (Exception ex) {
+ Log.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex);
+ }
+
+ // link the filled objects to this SMS
+ originatingAddress = addr;
+ env.origAddress = addr;
+ mEnvelope = env;
+ mPdu = pdu;
+
+ parseSms();
+ }
+
+ /**
+ * Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0
+ */
+ private void parsePduFromEfRecord(byte[] pdu) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+ DataInputStream dis = new DataInputStream(bais);
+ SmsEnvelope env = new SmsEnvelope();
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress();
+
+ try {
+ env.messageType = dis.readByte();
+
+ while (dis.available() > 0) {
+ int parameterId = dis.readByte();
+ int parameterLen = dis.readByte();
+ byte[] parameterData = new byte[parameterLen];
+
+ switch (parameterId) {
+ case TELESERVICE_IDENTIFIER:
+ /*
+ * 16 bit parameter that identifies which upper layer
+ * service access point is sending or should receive
+ * this message
+ */
+ env.teleService = dis.readUnsignedShort();
+ Log.i(LOG_TAG, "teleservice = " + env.teleService);
+ break;
+ case SERVICE_CATEGORY:
+ /*
+ * 16 bit parameter that identifies type of service as
+ * in 3GPP2 C.S0015-0 Table 3.4.3.2-1
+ */
+ env.serviceCategory = dis.readUnsignedShort();
+ break;
+ case ORIGINATING_ADDRESS:
+ case DESTINATION_ADDRESS:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream addrBis = new BitwiseInputStream(parameterData);
+ addr.digitMode = addrBis.read(1);
+ addr.numberMode = addrBis.read(1);
+ int numberType = 0;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ numberType = addrBis.read(3);
+ addr.ton = numberType;
+
+ if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK)
+ addr.numberPlan = addrBis.read(4);
+ }
+
+ addr.numberOfDigits = addrBis.read(8);
+
+ byte[] data = new byte[addr.numberOfDigits];
+ byte b = 0x00;
+
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
+ /* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */
+ for (int index = 0; index < addr.numberOfDigits; index++) {
+ b = (byte) (0xF & addrBis.read(4));
+ // convert the value if it is 4-bit DTMF to 8
+ // bit
+ data[index] = convertDtmfToAscii(b);
+ }
+ } else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) {
+ for (int index = 0; index < addr.numberOfDigits; index++) {
+ b = (byte) (0xFF & addrBis.read(8));
+ data[index] = b;
+ }
+
+ } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) {
+ if (numberType == 2)
+ Log.e(LOG_TAG, "TODO: Originating Addr is email id");
+ else
+ Log.e(LOG_TAG,
+ "TODO: Originating Addr is data network address");
+ } else {
+ Log.e(LOG_TAG, "Originating Addr is of incorrect type");
+ }
+ } else {
+ Log.e(LOG_TAG, "Incorrect Digit mode");
+ }
+ addr.origBytes = data;
+ Log.i(LOG_TAG, "Originating Addr=" + addr.toString());
+ break;
+ case ORIGINATING_SUB_ADDRESS:
+ case DESTINATION_SUB_ADDRESS:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData);
+ subAddr.type = subAddrBis.read(3);
+ subAddr.odd = subAddrBis.readByteArray(1)[0];
+ int subAddrLen = subAddrBis.read(8);
+ byte[] subdata = new byte[subAddrLen];
+ for (int index = 0; index < subAddrLen; index++) {
+ b = (byte) (0xFF & subAddrBis.read(4));
+ // convert the value if it is 4-bit DTMF to 8 bit
+ subdata[index] = convertDtmfToAscii(b);
+ }
+ subAddr.origBytes = subdata;
+ break;
+ case BEARER_REPLY_OPTION:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData);
+ env.bearerReply = replyOptBis.read(6);
+ break;
+ case CAUSE_CODES:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream ccBis = new BitwiseInputStream(parameterData);
+ env.replySeqNo = ccBis.readByteArray(6)[0];
+ env.errorClass = ccBis.readByteArray(2)[0];
+ if (env.errorClass != 0x00)
+ env.causeCode = ccBis.readByteArray(8)[0];
+ break;
+ case BEARER_DATA:
+ dis.read(parameterData, 0, parameterLen);
+ env.bearerData = parameterData;
+ break;
+ default:
+ throw new Exception("unsupported parameterId (" + parameterId + ")");
+ }
+ }
+ bais.close();
+ dis.close();
+ } catch (Exception ex) {
+ Log.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex);
+ }
+
+ // link the filled objects to this SMS
+ originatingAddress = addr;
+ env.origAddress = addr;
+ env.origSubaddress = subAddr;
+ mEnvelope = env;
+ mPdu = pdu;
+
+ parseSms();
+ }
+
+ /**
+ * Parses a SMS message from its BearerData stream. (mobile-terminated only)
+ */
+ protected void parseSms() {
+ // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6
+ // It contains only an 8-bit number with the number of messages waiting
+ if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) {
+ mBearerData = new BearerData();
+ if (mEnvelope.bearerData != null) {
+ mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0];
+ }
+ if (false) {
+ Log.d(LOG_TAG, "parseSms: get MWI " +
+ Integer.toString(mBearerData.numberOfMessages));
+ }
+ return;
+ }
+ mBearerData = BearerData.decode(mEnvelope.bearerData);
+ if (Log.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Log.d(LOG_TAG, "MT raw BearerData = '" +
+ HexDump.toHexString(mEnvelope.bearerData) + "'");
+ Log.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData);
+ }
+ messageRef = mBearerData.messageId;
+ if (mBearerData.userData != null) {
+ userData = mBearerData.userData.payload;
+ userDataHeader = mBearerData.userData.userDataHeader;
+ messageBody = mBearerData.userData.payloadStr;
+ }
+
+ if (originatingAddress != null) {
+ originatingAddress.address = new String(originatingAddress.origBytes);
+ if (false) Log.v(LOG_TAG, "SMS originating address: "
+ + originatingAddress.address);
+ }
+
+ if (mBearerData.msgCenterTimeStamp != null) {
+ scTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true);
+ }
+
+ if (false) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
+
+ // Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) {
+ // The BearerData MsgStatus subparameter should only be
+ // included for DELIVERY_ACK messages. If it occurred for
+ // other messages, it would be unclear what the status
+ // being reported refers to. The MsgStatus subparameter
+ // is primarily useful to indicate error conditions -- a
+ // message without this subparameter is assumed to
+ // indicate successful delivery (status == 0).
+ if (! mBearerData.messageStatusSet) {
+ Log.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" +
+ (userData == null ? "also missing" : "does have") +
+ " userData).");
+ status = 0;
+ } else {
+ status = mBearerData.errorClass << 8;
+ status |= mBearerData.messageStatus;
+ }
+ } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER) {
+ throw new RuntimeException("Unsupported message type: " + mBearerData.messageType);
+ }
+
+ if (messageBody != null) {
+ if (false) Log.v(LOG_TAG, "SMS message body: '" + messageBody + "'");
+ parseMessageBody();
+ } else if ((userData != null) && (false)) {
+ Log.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(userData) + "'");
+ }
+ }
+
+ /**
+ * Parses a broadcast SMS, possibly containing a CMAS alert.
+ */
+ SmsCbMessage parseBroadcastSms() {
+ BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
+ if (bData == null) {
+ Log.w(LOG_TAG, "BearerData.decode() returned null");
+ return null;
+ }
+
+ if (Log.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Log.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
+ }
+
+ String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
+ SmsCbLocation location = new SmsCbLocation(plmn);
+
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
+ SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location,
+ mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr,
+ bData.priority, null, bData.cmasWarningInfo);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SmsConstants.MessageClass getMessageClass() {
+ if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) {
+ return SmsConstants.MessageClass.CLASS_0;
+ } else {
+ return SmsConstants.MessageClass.UNKNOWN;
+ }
+ }
+
+ /**
+ * Calculate the next message id, starting at 1 and iteratively
+ * incrementing within the range 1..65535 remembering the state
+ * via a persistent system property. (See C.S0015-B, v2.0,
+ * 4.3.1.5) Since this routine is expected to be accessed via via
+ * binder-call, and hence should be thread-safe, it has been
+ * synchronized.
+ */
+ private synchronized static int getNextMessageId() {
+ // Testing and dialog with partners has indicated that
+ // msgId==0 is (sometimes?) treated specially by lower levels.
+ // Specifically, the ID is not preserved for delivery ACKs.
+ // Hence, avoid 0 -- constraining the range to 1..65535.
+ int msgId = SystemProperties.getInt(TelephonyProperties.PROPERTY_CDMA_MSG_ID, 1);
+ String nextMsgId = Integer.toString((msgId % 0xFFFF) + 1);
+ SystemProperties.set(TelephonyProperties.PROPERTY_CDMA_MSG_ID, nextMsgId);
+ if (Log.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Log.d(LOG_TAG, "next " + TelephonyProperties.PROPERTY_CDMA_MSG_ID + " = " + nextMsgId);
+ Log.d(LOG_TAG, "readback gets " +
+ SystemProperties.get(TelephonyProperties.PROPERTY_CDMA_MSG_ID));
+ }
+ return msgId;
+ }
+
+ /**
+ * Creates BearerData and Envelope from parameters for a Submit SMS.
+ * @return byte stream for SubmitPdu.
+ */
+ private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+ UserData userData) {
+
+ /**
+ * TODO(cleanup): give this function a more meaningful name.
+ */
+
+ /**
+ * TODO(cleanup): Make returning null from the getSubmitPdu
+ * variations meaningful -- clean up the error feedback
+ * mechanism, and avoid null pointer exceptions.
+ */
+
+ /**
+ * North America Plus Code :
+ * Convert + code to 011 and dial out for international SMS
+ */
+ CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode(destAddrStr));
+ if (destAddr == null) return null;
+
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
+
+ bearerData.messageId = getNextMessageId();
+
+ bearerData.deliveryAckReq = statusReportRequested;
+ bearerData.userAckReq = false;
+ bearerData.readAckReq = false;
+ bearerData.reportReq = false;
+
+ bearerData.userData = userData;
+
+ byte[] encodedBearerData = BearerData.encode(bearerData);
+ if (Log.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Log.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
+ Log.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
+ }
+ if (encodedBearerData == null) return null;
+
+ int teleservice = bearerData.hasUserDataHeader ?
+ SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;
+
+ SmsEnvelope envelope = new SmsEnvelope();
+ envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+ envelope.teleService = teleservice;
+ envelope.destAddress = destAddr;
+ envelope.bearerReply = RETURN_ACK;
+ envelope.bearerData = encodedBearerData;
+
+ /**
+ * TODO(cleanup): envelope looks to be a pointless class, get
+ * rid of it. Also -- most of the envelope fields set here
+ * are ignored, why?
+ */
+
+ try {
+ /**
+ * TODO(cleanup): reference a spec and get rid of the ugly comments
+ */
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeInt(envelope.teleService);
+ dos.writeInt(0); //servicePresent
+ dos.writeInt(0); //serviceCategory
+ dos.write(destAddr.digitMode);
+ dos.write(destAddr.numberMode);
+ dos.write(destAddr.ton); // number_type
+ dos.write(destAddr.numberPlan);
+ dos.write(destAddr.numberOfDigits);
+ dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
+ // Subaddress is not supported.
+ dos.write(0); //subaddressType
+ dos.write(0); //subaddr_odd
+ dos.write(0); //subaddr_nbr_of_digits
+ dos.write(encodedBearerData.length);
+ dos.write(encodedBearerData, 0, encodedBearerData.length);
+ dos.close();
+
+ SubmitPdu pdu = new SubmitPdu();
+ pdu.encodedMessage = baos.toByteArray();
+ pdu.encodedScAddress = null;
+ return pdu;
+ } catch(IOException ex) {
+ Log.e(LOG_TAG, "creating SubmitPdu failed: " + ex);
+ }
+ return null;
+ }
+
+ /**
+ * Creates byte array (pseudo pdu) from SMS object.
+ * Note: Do not call this method more than once per object!
+ */
+ private void createPdu() {
+ SmsEnvelope env = mEnvelope;
+ CdmaSmsAddress addr = env.origAddress;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
+
+ try {
+ dos.writeInt(env.messageType);
+ dos.writeInt(env.teleService);
+ dos.writeInt(env.serviceCategory);
+
+ dos.writeByte(addr.digitMode);
+ dos.writeByte(addr.numberMode);
+ dos.writeByte(addr.ton);
+ dos.writeByte(addr.numberPlan);
+ dos.writeByte(addr.numberOfDigits);
+ dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
+
+ dos.writeInt(env.bearerReply);
+ // CauseCode values:
+ dos.writeByte(env.replySeqNo);
+ dos.writeByte(env.errorClass);
+ dos.writeByte(env.causeCode);
+ //encoded BearerData:
+ dos.writeInt(env.bearerData.length);
+ dos.write(env.bearerData, 0, env.bearerData.length);
+ dos.close();
+
+ /**
+ * TODO(cleanup) -- The mPdu field is managed in
+ * a fragile manner, and it would be much nicer if
+ * accessing the serialized representation used a less
+ * fragile mechanism. Maybe the getPdu method could
+ * generate a representation if there was not yet one?
+ */
+
+ mPdu = baos.toByteArray();
+ } catch (IOException ex) {
+ Log.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex);
+ }
+ }
+
+ /**
+ * Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character
+ */
+ private byte convertDtmfToAscii(byte dtmfDigit) {
+ byte asciiDigit;
+
+ switch (dtmfDigit) {
+ case 0: asciiDigit = 68; break; // 'D'
+ case 1: asciiDigit = 49; break; // '1'
+ case 2: asciiDigit = 50; break; // '2'
+ case 3: asciiDigit = 51; break; // '3'
+ case 4: asciiDigit = 52; break; // '4'
+ case 5: asciiDigit = 53; break; // '5'
+ case 6: asciiDigit = 54; break; // '6'
+ case 7: asciiDigit = 55; break; // '7'
+ case 8: asciiDigit = 56; break; // '8'
+ case 9: asciiDigit = 57; break; // '9'
+ case 10: asciiDigit = 48; break; // '0'
+ case 11: asciiDigit = 42; break; // '*'
+ case 12: asciiDigit = 35; break; // '#'
+ case 13: asciiDigit = 65; break; // 'A'
+ case 14: asciiDigit = 66; break; // 'B'
+ case 15: asciiDigit = 67; break; // 'C'
+ default:
+ asciiDigit = 32; // Invalid DTMF code
+ break;
+ }
+
+ return asciiDigit;
+ }
+
+ /** This function shall be called to get the number of voicemails.
+ * @hide
+ */
+ /*package*/ int getNumOfVoicemails() {
+ return mBearerData.numberOfMessages;
+ }
+
+ /**
+ * Returns a byte array that can be use to uniquely identify a received SMS message.
+ * C.S0015-B 4.3.1.6 Unique Message Identification.
+ *
+ * @return byte array uniquely identifying the message.
+ * @hide
+ */
+ /* package */ byte[] getIncomingSmsFingerprint() {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ output.write(mEnvelope.teleService);
+ output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length);
+ output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length);
+ output.write(mEnvelope.origSubaddress.origBytes, 0,
+ mEnvelope.origSubaddress.origBytes.length);
+
+ return output.toByteArray();
+ }
+
+ /**
+ * Returns the list of service category program data, if present.
+ * @return a list of CdmaSmsCbProgramData objects, or null if not present
+ */
+ List<CdmaSmsCbProgramData> getSmsCbProgramData() {
+ return mBearerData.serviceCategoryProgramData;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/TtyIntent.java b/src/java/com/android/internal/telephony/cdma/TtyIntent.java
new file mode 100644
index 0000000..4907aa9
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/TtyIntent.java
@@ -0,0 +1,67 @@
+/*
+ * 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.cdma;
+
+public class TtyIntent {
+
+ private static final String TAG = "TtyIntent";
+
+
+ /** Event for TTY mode change */
+
+ /**
+ * Broadcast intent action indicating that the TTY has either been
+ * enabled or disabled. An intent extra provides this state as a boolean,
+ * where {@code true} means enabled.
+ * @see #TTY_ENABLED
+ *
+ * {@hide}
+ */
+ public static final String TTY_ENABLED_CHANGE_ACTION =
+ "com.android.internal.telephony.cdma.intent.action.TTY_ENABLED_CHANGE";
+
+ /**
+ * The lookup key for a boolean that indicates whether TTY mode is enabled or
+ * disabled. {@code true} means TTY mode is enabled. Retrieve it with
+ * {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ *
+ * {@hide}
+ */
+ public static final String TTY_ENABLED = "ttyEnabled";
+
+ /**
+ * Broadcast intent action indicating that the TTY preferred operating mode
+ * has changed. An intent extra provides the new mode as an int.
+ * @see #TTY_PREFFERED_MODE
+ *
+ * {@hide}
+ */
+ public static final String TTY_PREFERRED_MODE_CHANGE_ACTION =
+ "com.android.internal.telephony.cdma.intent.action.TTY_PREFERRED_MODE_CHANGE";
+
+ /**
+ * The lookup key for an int that indicates preferred TTY mode.
+ * Valid modes are:
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_OFF}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_FULL}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_HCO}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
+ *
+ * {@hide}
+ */
+ public static final String TTY_PREFFERED_MODE = "ttyPreferredMode";
+}
diff --git a/src/java/com/android/internal/telephony/cdma/package.html b/src/java/com/android/internal/telephony/cdma/package.html
new file mode 100644
index 0000000..4eb1f9c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides classes to control or read data from CDMA phones.
+@hide
+</BODY>
+</HTML>
diff --git a/src/java/com/android/internal/telephony/cdma/sms/BearerData.java b/src/java/com/android/internal/telephony/cdma/sms/BearerData.java
new file mode 100755
index 0000000..7e7347f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -0,0 +1,1938 @@
+/*
+ * 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.cdma.sms;
+
+import android.content.res.Resources;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbMessage;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.text.format.Time;
+import android.util.Log;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.BitwiseOutputStream;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * An object to encode and decode CDMA SMS bearer data.
+ */
+public final class BearerData {
+ private final static String LOG_TAG = "SMS";
+
+ /**
+ * Bearer Data Subparameter Identifiers
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
+ * NOTE: Commented subparameter types are not implemented.
+ */
+ private final static byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00;
+ private final static byte SUBPARAM_USER_DATA = 0x01;
+ private final static byte SUBPARAM_USER_RESPONSE_CODE = 0x02;
+ private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP = 0x03;
+ private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE = 0x04;
+ private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE = 0x05;
+ private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE = 0x06;
+ private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE = 0x07;
+ private final static byte SUBPARAM_PRIORITY_INDICATOR = 0x08;
+ private final static byte SUBPARAM_PRIVACY_INDICATOR = 0x09;
+ private final static byte SUBPARAM_REPLY_OPTION = 0x0A;
+ private final static byte SUBPARAM_NUMBER_OF_MESSAGES = 0x0B;
+ private final static byte SUBPARAM_ALERT_ON_MESSAGE_DELIVERY = 0x0C;
+ private final static byte SUBPARAM_LANGUAGE_INDICATOR = 0x0D;
+ private final static byte SUBPARAM_CALLBACK_NUMBER = 0x0E;
+ private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE = 0x0F;
+ //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA = 0x10;
+ private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX = 0x11;
+ private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12;
+ private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13;
+ private final static byte SUBPARAM_MESSAGE_STATUS = 0x14;
+ //private final static byte SUBPARAM_TP_FAILURE_CAUSE = 0x15;
+ //private final static byte SUBPARAM_ENHANCED_VMN = 0x16;
+ //private final static byte SUBPARAM_ENHANCED_VMN_ACK = 0x17;
+
+ /**
+ * Supported message types for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
+ */
+ public static final int MESSAGE_TYPE_DELIVER = 0x01;
+ public static final int MESSAGE_TYPE_SUBMIT = 0x02;
+ public static final int MESSAGE_TYPE_CANCELLATION = 0x03;
+ public static final int MESSAGE_TYPE_DELIVERY_ACK = 0x04;
+ public static final int MESSAGE_TYPE_USER_ACK = 0x05;
+ public static final int MESSAGE_TYPE_READ_ACK = 0x06;
+ public static final int MESSAGE_TYPE_DELIVER_REPORT = 0x07;
+ public static final int MESSAGE_TYPE_SUBMIT_REPORT = 0x08;
+
+ public int messageType;
+
+ /**
+ * 16-bit value indicating the message ID, which increments modulo 65536.
+ * (Special rules apply for WAP-messages.)
+ * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ */
+ public int messageId;
+
+ /**
+ * Supported priority modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+ */
+ public static final int PRIORITY_NORMAL = 0x0;
+ public static final int PRIORITY_INTERACTIVE = 0x1;
+ public static final int PRIORITY_URGENT = 0x2;
+ public static final int PRIORITY_EMERGENCY = 0x3;
+
+ public boolean priorityIndicatorSet = false;
+ public int priority = PRIORITY_NORMAL;
+
+ /**
+ * Supported privacy modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.10-1)
+ */
+ public static final int PRIVACY_NOT_RESTRICTED = 0x0;
+ public static final int PRIVACY_RESTRICTED = 0x1;
+ public static final int PRIVACY_CONFIDENTIAL = 0x2;
+ public static final int PRIVACY_SECRET = 0x3;
+
+ public boolean privacyIndicatorSet = false;
+ public int privacy = PRIVACY_NOT_RESTRICTED;
+
+ /**
+ * Supported alert priority modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.13-1)
+ */
+ public static final int ALERT_DEFAULT = 0x0;
+ public static final int ALERT_LOW_PRIO = 0x1;
+ public static final int ALERT_MEDIUM_PRIO = 0x2;
+ public static final int ALERT_HIGH_PRIO = 0x3;
+
+ public boolean alertIndicatorSet = false;
+ public int alert = ALERT_DEFAULT;
+
+ /**
+ * Supported display modes for CDMA SMS messages. Display mode is
+ * a 2-bit value used to indicate to the mobile station when to
+ * display the received message. (See 3GPP2 C.S0015-B, v2,
+ * 4.5.16)
+ */
+ public static final int DISPLAY_MODE_IMMEDIATE = 0x0;
+ public static final int DISPLAY_MODE_DEFAULT = 0x1;
+ public static final int DISPLAY_MODE_USER = 0x2;
+
+ public boolean displayModeSet = false;
+ public int displayMode = DISPLAY_MODE_DEFAULT;
+
+ /**
+ * Language Indicator values. NOTE: the spec (3GPP2 C.S0015-B,
+ * v2, 4.5.14) is ambiguous as to the meaning of this field, as it
+ * refers to C.R1001-D but that reference has been crossed out.
+ * It would seem reasonable to assume the values from C.R1001-F
+ * (table 9.2-1) are to be used instead.
+ */
+ public static final int LANGUAGE_UNKNOWN = 0x00;
+ public static final int LANGUAGE_ENGLISH = 0x01;
+ public static final int LANGUAGE_FRENCH = 0x02;
+ public static final int LANGUAGE_SPANISH = 0x03;
+ public static final int LANGUAGE_JAPANESE = 0x04;
+ public static final int LANGUAGE_KOREAN = 0x05;
+ public static final int LANGUAGE_CHINESE = 0x06;
+ public static final int LANGUAGE_HEBREW = 0x07;
+
+ public boolean languageIndicatorSet = false;
+ public int language = LANGUAGE_UNKNOWN;
+
+ /**
+ * SMS Message Status Codes. The first component of the Message
+ * status indicates if an error has occurred and whether the error
+ * is considered permanent or temporary. The second component of
+ * the Message status indicates the cause of the error (if any).
+ * (See 3GPP2 C.S0015-B, v2.0, 4.5.21)
+ */
+ /* no-error codes */
+ public static final int ERROR_NONE = 0x00;
+ public static final int STATUS_ACCEPTED = 0x00;
+ public static final int STATUS_DEPOSITED_TO_INTERNET = 0x01;
+ public static final int STATUS_DELIVERED = 0x02;
+ public static final int STATUS_CANCELLED = 0x03;
+ /* temporary-error and permanent-error codes */
+ public static final int ERROR_TEMPORARY = 0x02;
+ public static final int STATUS_NETWORK_CONGESTION = 0x04;
+ public static final int STATUS_NETWORK_ERROR = 0x05;
+ public static final int STATUS_UNKNOWN_ERROR = 0x1F;
+ /* permanent-error codes */
+ public static final int ERROR_PERMANENT = 0x03;
+ public static final int STATUS_CANCEL_FAILED = 0x06;
+ public static final int STATUS_BLOCKED_DESTINATION = 0x07;
+ public static final int STATUS_TEXT_TOO_LONG = 0x08;
+ public static final int STATUS_DUPLICATE_MESSAGE = 0x09;
+ public static final int STATUS_INVALID_DESTINATION = 0x0A;
+ public static final int STATUS_MESSAGE_EXPIRED = 0x0D;
+ /* undefined-status codes */
+ public static final int ERROR_UNDEFINED = 0xFF;
+ public static final int STATUS_UNDEFINED = 0xFF;
+
+ public boolean messageStatusSet = false;
+ public int errorClass = ERROR_UNDEFINED;
+ public int messageStatus = STATUS_UNDEFINED;
+
+ /**
+ * 1-bit value that indicates whether a User Data Header (UDH) is present.
+ * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ *
+ * NOTE: during encoding, this value will be set based on the
+ * presence of a UDH in the structured data, any existing setting
+ * will be overwritten.
+ */
+ public boolean hasUserDataHeader;
+
+ /**
+ * provides the information for the user data
+ * (e.g. padding bits, user data, user data header, etc)
+ * (See 3GPP2 C.S.0015-B, v2, 4.5.2)
+ */
+ public UserData userData;
+
+ /**
+ * The User Response Code subparameter is used in the SMS User
+ * Acknowledgment Message to respond to previously received short
+ * messages. This message center-specific element carries the
+ * identifier of a predefined response. (See 3GPP2 C.S.0015-B, v2,
+ * 4.5.3)
+ */
+ public boolean userResponseCodeSet = false;
+ public int userResponseCode;
+
+ /**
+ * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4
+ */
+ public static class TimeStamp extends Time {
+
+ public TimeStamp() {
+ super(TimeZone.getDefault().getID()); // 3GPP2 timestamps use the local timezone
+ }
+
+ public static TimeStamp fromByteArray(byte[] data) {
+ TimeStamp ts = new TimeStamp();
+ // C.S0015-B v2.0, 4.5.4: range is 1996-2095
+ int year = IccUtils.cdmaBcdByteToInt(data[0]);
+ if (year > 99 || year < 0) return null;
+ ts.year = year >= 96 ? year + 1900 : year + 2000;
+ int month = IccUtils.cdmaBcdByteToInt(data[1]);
+ if (month < 1 || month > 12) return null;
+ ts.month = month - 1;
+ int day = IccUtils.cdmaBcdByteToInt(data[2]);
+ if (day < 1 || day > 31) return null;
+ ts.monthDay = day;
+ int hour = IccUtils.cdmaBcdByteToInt(data[3]);
+ if (hour < 0 || hour > 23) return null;
+ ts.hour = hour;
+ int minute = IccUtils.cdmaBcdByteToInt(data[4]);
+ if (minute < 0 || minute > 59) return null;
+ ts.minute = minute;
+ int second = IccUtils.cdmaBcdByteToInt(data[5]);
+ if (second < 0 || second > 59) return null;
+ ts.second = second;
+ return ts;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("TimeStamp ");
+ builder.append("{ year=" + year);
+ builder.append(", month=" + month);
+ builder.append(", day=" + monthDay);
+ builder.append(", hour=" + hour);
+ builder.append(", minute=" + minute);
+ builder.append(", second=" + second);
+ builder.append(" }");
+ return builder.toString();
+ }
+ }
+
+ public TimeStamp msgCenterTimeStamp;
+ public TimeStamp validityPeriodAbsolute;
+ public TimeStamp deferredDeliveryTimeAbsolute;
+
+ /**
+ * Relative time is specified as one byte, the value of which
+ * falls into a series of ranges, as specified below. The idea is
+ * that shorter time intervals allow greater precision -- the
+ * value means minutes from zero until the MINS_LIMIT (inclusive),
+ * upon which it means hours until the HOURS_LIMIT, and so
+ * forth. (See 3GPP2 C.S0015-B, v2, 4.5.6-1)
+ */
+ public static final int RELATIVE_TIME_MINS_LIMIT = 143;
+ public static final int RELATIVE_TIME_HOURS_LIMIT = 167;
+ public static final int RELATIVE_TIME_DAYS_LIMIT = 196;
+ public static final int RELATIVE_TIME_WEEKS_LIMIT = 244;
+ public static final int RELATIVE_TIME_INDEFINITE = 245;
+ public static final int RELATIVE_TIME_NOW = 246;
+ public static final int RELATIVE_TIME_MOBILE_INACTIVE = 247;
+ public static final int RELATIVE_TIME_RESERVED = 248;
+
+ public boolean validityPeriodRelativeSet;
+ public int validityPeriodRelative;
+ public boolean deferredDeliveryTimeRelativeSet;
+ public int deferredDeliveryTimeRelative;
+
+ /**
+ * The Reply Option subparameter contains 1-bit values which
+ * indicate whether SMS acknowledgment is requested or not. (See
+ * 3GPP2 C.S0015-B, v2, 4.5.11)
+ */
+ public boolean userAckReq;
+ public boolean deliveryAckReq;
+ public boolean readAckReq;
+ public boolean reportReq;
+
+ /**
+ * The Number of Messages subparameter (8-bit value) is a decimal
+ * number in the 0 to 99 range representing the number of messages
+ * stored at the Voice Mail System. This element is used by the
+ * Voice Mail Notification service. (See 3GPP2 C.S0015-B, v2,
+ * 4.5.12)
+ */
+ public int numberOfMessages;
+
+ /**
+ * The Message Deposit Index subparameter is assigned by the
+ * message center as a unique index to the contents of the User
+ * Data subparameter in each message sent to a particular mobile
+ * station. The mobile station, when replying to a previously
+ * received short message which included a Message Deposit Index
+ * subparameter, may include the Message Deposit Index of the
+ * received message to indicate to the message center that the
+ * original contents of the message are to be included in the
+ * reply. (See 3GPP2 C.S0015-B, v2, 4.5.18)
+ */
+ public int depositIndex;
+
+ /**
+ * 4-bit or 8-bit value that indicates the number to be dialed in reply to a
+ * received SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 4.5.15)
+ */
+ public CdmaSmsAddress callbackNumber;
+
+ /**
+ * CMAS warning notification information.
+ * @see #decodeCmasUserData(BearerData, int)
+ */
+ public SmsCbCmasInfo cmasWarningInfo;
+
+ /**
+ * The Service Category Program Data subparameter is used to enable and disable
+ * SMS broadcast service categories to display. If this subparameter is present,
+ * this field will contain a list of one or more
+ * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the
+ * operation(s) to perform.
+ */
+ public List<CdmaSmsCbProgramData> serviceCategoryProgramData;
+
+ private static class CodingException extends Exception {
+ public CodingException(String s) {
+ super(s);
+ }
+ }
+
+ /**
+ * Returns the language indicator as a two-character ISO 639 string.
+ * @return a two character ISO 639 language code
+ */
+ public String getLanguage() {
+ return getLanguageCodeForValue(language);
+ }
+
+ /**
+ * Converts a CDMA language indicator value to an ISO 639 two character language code.
+ * @param languageValue the CDMA language value to convert
+ * @return the two character ISO 639 language code for the specified value, or null if unknown
+ */
+ private static String getLanguageCodeForValue(int languageValue) {
+ switch (languageValue) {
+ case LANGUAGE_ENGLISH:
+ return "en";
+
+ case LANGUAGE_FRENCH:
+ return "fr";
+
+ case LANGUAGE_SPANISH:
+ return "es";
+
+ case LANGUAGE_JAPANESE:
+ return "ja";
+
+ case LANGUAGE_KOREAN:
+ return "ko";
+
+ case LANGUAGE_CHINESE:
+ return "zh";
+
+ case LANGUAGE_HEBREW:
+ return "he";
+
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("BearerData ");
+ builder.append("{ messageType=" + messageType);
+ builder.append(", messageId=" + (int)messageId);
+ builder.append(", priority=" + (priorityIndicatorSet ? priority : "unset"));
+ builder.append(", privacy=" + (privacyIndicatorSet ? privacy : "unset"));
+ builder.append(", alert=" + (alertIndicatorSet ? alert : "unset"));
+ builder.append(", displayMode=" + (displayModeSet ? displayMode : "unset"));
+ builder.append(", language=" + (languageIndicatorSet ? language : "unset"));
+ builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset"));
+ builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset"));
+ builder.append(", msgCenterTimeStamp=" +
+ ((msgCenterTimeStamp != null) ? msgCenterTimeStamp : "unset"));
+ builder.append(", validityPeriodAbsolute=" +
+ ((validityPeriodAbsolute != null) ? validityPeriodAbsolute : "unset"));
+ builder.append(", validityPeriodRelative=" +
+ ((validityPeriodRelativeSet) ? validityPeriodRelative : "unset"));
+ builder.append(", deferredDeliveryTimeAbsolute=" +
+ ((deferredDeliveryTimeAbsolute != null) ? deferredDeliveryTimeAbsolute : "unset"));
+ builder.append(", deferredDeliveryTimeRelative=" +
+ ((deferredDeliveryTimeRelativeSet) ? deferredDeliveryTimeRelative : "unset"));
+ builder.append(", userAckReq=" + userAckReq);
+ builder.append(", deliveryAckReq=" + deliveryAckReq);
+ builder.append(", readAckReq=" + readAckReq);
+ builder.append(", reportReq=" + reportReq);
+ builder.append(", numberOfMessages=" + numberOfMessages);
+ builder.append(", callbackNumber=" + callbackNumber);
+ builder.append(", depositIndex=" + depositIndex);
+ builder.append(", hasUserDataHeader=" + hasUserDataHeader);
+ builder.append(", userData=" + userData);
+ builder.append(" }");
+ return builder.toString();
+ }
+
+ private static void encodeMessageId(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 3);
+ outStream.write(4, bData.messageType);
+ outStream.write(8, bData.messageId >> 8);
+ outStream.write(8, bData.messageId);
+ outStream.write(1, bData.hasUserDataHeader ? 1 : 0);
+ outStream.skip(3);
+ }
+
+ private static int countAsciiSeptets(CharSequence msg, boolean force) {
+ int msgLen = msg.length();
+ if (force) return msgLen;
+ for (int i = 0; i < msgLen; i++) {
+ if (UserData.charToAscii.get(msg.charAt(i), -1) == -1) {
+ return -1;
+ }
+ }
+ return msgLen;
+ }
+
+ /**
+ * Calculate the message text encoding length, fragmentation, and other details.
+ *
+ * @param msg message text
+ * @param force7BitEncoding ignore (but still count) illegal characters if true
+ * @return septet count, or -1 on failure
+ */
+ public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg,
+ boolean force7BitEncoding) {
+ TextEncodingDetails ted;
+ int septets = countAsciiSeptets(msg, force7BitEncoding);
+ if (septets != -1 && septets <= SmsConstants.MAX_USER_DATA_SEPTETS) {
+ ted = new TextEncodingDetails();
+ ted.msgCount = 1;
+ ted.codeUnitCount = septets;
+ ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets;
+ ted.codeUnitSize = SmsConstants.ENCODING_7BIT;
+ } else {
+ ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength(
+ msg, force7BitEncoding);
+ if (ted.msgCount == 1 && ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
+ // We don't support single-segment EMS, so calculate for 16-bit
+ // TODO: Consider supporting single-segment EMS
+ ted.codeUnitCount = msg.length();
+ int octets = ted.codeUnitCount * 2;
+ if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
+ ted.msgCount = (octets + (SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER - 1)) /
+ SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+ ted.codeUnitsRemaining = ((ted.msgCount *
+ SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2;
+ } else {
+ ted.msgCount = 1;
+ ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets)/2;
+ }
+ ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
+ }
+ }
+ return ted;
+ }
+
+ private static byte[] encode7bitAscii(String msg, boolean force)
+ throws CodingException
+ {
+ try {
+ BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length());
+ int msgLen = msg.length();
+ for (int i = 0; i < msgLen; i++) {
+ int charCode = UserData.charToAscii.get(msg.charAt(i), -1);
+ if (charCode == -1) {
+ if (force) {
+ outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR);
+ } else {
+ throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")");
+ }
+ } else {
+ outStream.write(7, charCode);
+ }
+ }
+ return outStream.toByteArray();
+ } catch (BitwiseOutputStream.AccessException ex) {
+ throw new CodingException("7bit ASCII encode failed: " + ex);
+ }
+ }
+
+ private static byte[] encodeUtf16(String msg)
+ throws CodingException
+ {
+ try {
+ return msg.getBytes("utf-16be");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("UTF-16 encode failed: " + ex);
+ }
+ }
+
+ private static class Gsm7bitCodingResult {
+ int septets;
+ byte[] data;
+ }
+
+ private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force)
+ throws CodingException
+ {
+ try {
+ /*
+ * TODO(cleanup): It would be nice if GsmAlphabet provided
+ * an option to produce just the data without prepending
+ * the septet count, as this function is really just a
+ * wrapper to strip that off. Not to mention that the
+ * septet count is generally known prior to invocation of
+ * the encoder. Note that it cannot be derived from the
+ * resulting array length, since that cannot distinguish
+ * if the last contains either 1 or 8 valid bits.
+ *
+ * TODO(cleanup): The BitwiseXStreams could also be
+ * extended with byte-wise reversed endianness read/write
+ * routines to allow a corresponding implementation of
+ * stringToGsm7BitPacked, and potentially directly support
+ * access to the main bitwise stream from encode/decode.
+ */
+ byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0);
+ Gsm7bitCodingResult result = new Gsm7bitCodingResult();
+ result.data = new byte[fullData.length - 1];
+ System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1);
+ result.septets = fullData[0] & 0x00FF;
+ return result;
+ } catch (com.android.internal.telephony.EncodeException ex) {
+ throw new CodingException("7bit GSM encode failed: " + ex);
+ }
+ }
+
+ private static void encode7bitEms(UserData uData, byte[] udhData, boolean force)
+ throws CodingException
+ {
+ int udhBytes = udhData.length + 1; // Add length octet.
+ int udhSeptets = ((udhBytes * 8) + 6) / 7;
+ Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force);
+ uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+ uData.msgEncodingSet = true;
+ uData.numFields = gcr.septets;
+ uData.payload = gcr.data;
+ uData.payload[0] = (byte)udhData.length;
+ System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
+ }
+
+ private static void encode16bitEms(UserData uData, byte[] udhData)
+ throws CodingException
+ {
+ byte[] payload = encodeUtf16(uData.payloadStr);
+ int udhBytes = udhData.length + 1; // Add length octet.
+ int udhCodeUnits = (udhBytes + 1) / 2;
+ int udhPadding = udhBytes % 2;
+ int payloadCodeUnits = payload.length / 2;
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ uData.msgEncodingSet = true;
+ uData.numFields = udhCodeUnits + payloadCodeUnits;
+ uData.payload = new byte[uData.numFields * 2];
+ uData.payload[0] = (byte)udhData.length;
+ System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
+ System.arraycopy(payload, 0, uData.payload, udhBytes + udhPadding, payload.length);
+ }
+
+ private static void encodeEmsUserDataPayload(UserData uData)
+ throws CodingException
+ {
+ byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader);
+ if (uData.msgEncodingSet) {
+ if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
+ encode7bitEms(uData, headerData, true);
+ } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
+ encode16bitEms(uData, headerData);
+ } else {
+ throw new CodingException("unsupported EMS user data encoding (" +
+ uData.msgEncoding + ")");
+ }
+ } else {
+ try {
+ encode7bitEms(uData, headerData, false);
+ } catch (CodingException ex) {
+ encode16bitEms(uData, headerData);
+ }
+ }
+ }
+
+ private static void encodeUserDataPayload(UserData uData)
+ throws CodingException
+ {
+ if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) {
+ Log.e(LOG_TAG, "user data with null payloadStr");
+ uData.payloadStr = "";
+ }
+
+ if (uData.userDataHeader != null) {
+ encodeEmsUserDataPayload(uData);
+ return;
+ }
+
+ if (uData.msgEncodingSet) {
+ if (uData.msgEncoding == UserData.ENCODING_OCTET) {
+ if (uData.payload == null) {
+ Log.e(LOG_TAG, "user data with octet encoding but null payload");
+ uData.payload = new byte[0];
+ uData.numFields = 0;
+ } else {
+ uData.numFields = uData.payload.length;
+ }
+ } else {
+ if (uData.payloadStr == null) {
+ Log.e(LOG_TAG, "non-octet user data with null payloadStr");
+ uData.payloadStr = "";
+ }
+ if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
+ Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true);
+ uData.payload = gcr.data;
+ uData.numFields = gcr.septets;
+ } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
+ uData.payload = encode7bitAscii(uData.payloadStr, true);
+ uData.numFields = uData.payloadStr.length();
+ } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
+ uData.payload = encodeUtf16(uData.payloadStr);
+ uData.numFields = uData.payloadStr.length();
+ } else {
+ throw new CodingException("unsupported user data encoding (" +
+ uData.msgEncoding + ")");
+ }
+ }
+ } else {
+ try {
+ uData.payload = encode7bitAscii(uData.payloadStr, false);
+ uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
+ } catch (CodingException ex) {
+ uData.payload = encodeUtf16(uData.payloadStr);
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ }
+ uData.numFields = uData.payloadStr.length();
+ uData.msgEncodingSet = true;
+ }
+ }
+
+ private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException, CodingException
+ {
+ /*
+ * TODO(cleanup): Do we really need to set userData.payload as
+ * a side effect of encoding? If not, we could avoid data
+ * copies by passing outStream directly.
+ */
+ encodeUserDataPayload(bData.userData);
+ bData.hasUserDataHeader = bData.userData.userDataHeader != null;
+
+ if (bData.userData.payload.length > SmsConstants.MAX_USER_DATA_BYTES) {
+ throw new CodingException("encoded user data too large (" +
+ bData.userData.payload.length +
+ " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)");
+ }
+
+ /*
+ * TODO(cleanup): figure out what the right answer is WRT paddingBits field
+ *
+ * userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7);
+ * userData.paddingBits = 0; // XXX this seems better, but why?
+ *
+ */
+ int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits;
+ int paramBits = dataBits + 13;
+ if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+ (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+ paramBits += 8;
+ }
+ int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
+ int paddingBits = (paramBytes * 8) - paramBits;
+ outStream.write(8, paramBytes);
+ outStream.write(5, bData.userData.msgEncoding);
+ if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+ (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+ outStream.write(8, bData.userData.msgType);
+ }
+ outStream.write(8, bData.userData.numFields);
+ outStream.writeByteArray(dataBits, bData.userData.payload);
+ if (paddingBits > 0) outStream.write(paddingBits, 0);
+ }
+
+ private static void encodeReplyOption(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(1, bData.userAckReq ? 1 : 0);
+ outStream.write(1, bData.deliveryAckReq ? 1 : 0);
+ outStream.write(1, bData.readAckReq ? 1 : 0);
+ outStream.write(1, bData.reportReq ? 1 : 0);
+ outStream.write(4, 0);
+ }
+
+ private static byte[] encodeDtmfSmsAddress(String address) {
+ int digits = address.length();
+ int dataBits = digits * 4;
+ int dataBytes = (dataBits / 8);
+ dataBytes += (dataBits % 8) > 0 ? 1 : 0;
+ byte[] rawData = new byte[dataBytes];
+ for (int i = 0; i < digits; i++) {
+ char c = address.charAt(i);
+ int val = 0;
+ if ((c >= '1') && (c <= '9')) val = c - '0';
+ else if (c == '0') val = 10;
+ else if (c == '*') val = 11;
+ else if (c == '#') val = 12;
+ else return null;
+ rawData[i / 2] |= val << (4 - ((i % 2) * 4));
+ }
+ return rawData;
+ }
+
+ /*
+ * TODO(cleanup): CdmaSmsAddress encoding should make use of
+ * CdmaSmsAddress.parse provided that DTMF encoding is unified,
+ * and the difference in 4-bit vs. 8-bit is resolved.
+ */
+
+ private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException {
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ try {
+ addr.origBytes = addr.address.getBytes("US-ASCII");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("invalid SMS address, cannot convert to ASCII");
+ }
+ } else {
+ addr.origBytes = encodeDtmfSmsAddress(addr.address);
+ }
+ }
+
+ private static void encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException, CodingException
+ {
+ CdmaSmsAddress addr = bData.callbackNumber;
+ encodeCdmaSmsAddress(addr);
+ int paramBits = 9;
+ int dataBits = 0;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ paramBits += 7;
+ dataBits = addr.numberOfDigits * 8;
+ } else {
+ dataBits = addr.numberOfDigits * 4;
+ }
+ paramBits += dataBits;
+ int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
+ int paddingBits = (paramBytes * 8) - paramBits;
+ outStream.write(8, paramBytes);
+ outStream.write(1, addr.digitMode);
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ outStream.write(3, addr.ton);
+ outStream.write(4, addr.numberPlan);
+ }
+ outStream.write(8, addr.numberOfDigits);
+ outStream.writeByteArray(dataBits, addr.origBytes);
+ if (paddingBits > 0) outStream.write(paddingBits, 0);
+ }
+
+ private static void encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.errorClass);
+ outStream.write(6, bData.messageStatus);
+ }
+
+ private static void encodeMsgCount(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(8, bData.numberOfMessages);
+ }
+
+ private static void encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(8, bData.validityPeriodRelative);
+ }
+
+ private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.privacy);
+ outStream.skip(6);
+ }
+
+ private static void encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(8, bData.language);
+ }
+
+ private static void encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.displayMode);
+ outStream.skip(6);
+ }
+
+ private static void encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.priority);
+ outStream.skip(6);
+ }
+
+ private static void encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.alert);
+ outStream.skip(6);
+ }
+
+ /**
+ * Create serialized representation for BearerData object.
+ * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+ *
+ * @param bData an instance of BearerData.
+ *
+ * @return byte array of raw encoded SMS bearer data.
+ */
+ public static byte[] encode(BearerData bData) {
+ bData.hasUserDataHeader = ((bData.userData != null) &&
+ (bData.userData.userDataHeader != null));
+ try {
+ BitwiseOutputStream outStream = new BitwiseOutputStream(200);
+ outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
+ encodeMessageId(bData, outStream);
+ if (bData.userData != null) {
+ outStream.write(8, SUBPARAM_USER_DATA);
+ encodeUserData(bData, outStream);
+ }
+ if (bData.callbackNumber != null) {
+ outStream.write(8, SUBPARAM_CALLBACK_NUMBER);
+ encodeCallbackNumber(bData, outStream);
+ }
+ if (bData.userAckReq || bData.deliveryAckReq || bData.readAckReq || bData.reportReq) {
+ outStream.write(8, SUBPARAM_REPLY_OPTION);
+ encodeReplyOption(bData, outStream);
+ }
+ if (bData.numberOfMessages != 0) {
+ outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES);
+ encodeMsgCount(bData, outStream);
+ }
+ if (bData.validityPeriodRelativeSet) {
+ outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE);
+ encodeValidityPeriodRel(bData, outStream);
+ }
+ if (bData.privacyIndicatorSet) {
+ outStream.write(8, SUBPARAM_PRIVACY_INDICATOR);
+ encodePrivacyIndicator(bData, outStream);
+ }
+ if (bData.languageIndicatorSet) {
+ outStream.write(8, SUBPARAM_LANGUAGE_INDICATOR);
+ encodeLanguageIndicator(bData, outStream);
+ }
+ if (bData.displayModeSet) {
+ outStream.write(8, SUBPARAM_MESSAGE_DISPLAY_MODE);
+ encodeDisplayMode(bData, outStream);
+ }
+ if (bData.priorityIndicatorSet) {
+ outStream.write(8, SUBPARAM_PRIORITY_INDICATOR);
+ encodePriorityIndicator(bData, outStream);
+ }
+ if (bData.alertIndicatorSet) {
+ outStream.write(8, SUBPARAM_ALERT_ON_MESSAGE_DELIVERY);
+ encodeMsgDeliveryAlert(bData, outStream);
+ }
+ if (bData.messageStatusSet) {
+ outStream.write(8, SUBPARAM_MESSAGE_STATUS);
+ encodeMsgStatus(bData, outStream);
+ }
+ return outStream.toByteArray();
+ } catch (BitwiseOutputStream.AccessException ex) {
+ Log.e(LOG_TAG, "BearerData encode failed: " + ex);
+ } catch (CodingException ex) {
+ Log.e(LOG_TAG, "BearerData encode failed: " + ex);
+ }
+ return null;
+ }
+
+ private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 3 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.messageType = inStream.read(4);
+ bData.messageId = inStream.read(8) << 8;
+ bData.messageId |= inStream.read(8);
+ bData.hasUserDataHeader = (inStream.read(1) == 1);
+ inStream.skip(3);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException
+ {
+ int paramBits = inStream.read(8) * 8;
+ bData.userData = new UserData();
+ bData.userData.msgEncoding = inStream.read(5);
+ bData.userData.msgEncodingSet = true;
+ bData.userData.msgType = 0;
+ int consumedBits = 5;
+ if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+ (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+ bData.userData.msgType = inStream.read(8);
+ consumedBits += 8;
+ }
+ bData.userData.numFields = inStream.read(8);
+ consumedBits += 8;
+ int dataBits = paramBits - consumedBits;
+ bData.userData.payload = inStream.readByteArray(dataBits);
+ return true;
+ }
+
+ private static String decodeUtf8(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ if (numFields < 0 || (numFields + offset) > data.length) {
+ throw new CodingException("UTF-8 decode failed: offset or length out of range");
+ }
+ try {
+ return new String(data, offset, numFields, "UTF-8");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("UTF-8 decode failed: " + ex);
+ }
+ }
+
+ private static String decodeUtf16(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ int byteCount = numFields * 2;
+ if (byteCount < 0 || (byteCount + offset) > data.length) {
+ throw new CodingException("UTF-16 decode failed: offset or length out of range");
+ }
+ try {
+ return new String(data, offset, byteCount, "utf-16be");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("UTF-16 decode failed: " + ex);
+ }
+ }
+
+ private static String decode7bitAscii(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ try {
+ offset *= 8;
+ StringBuffer strBuf = new StringBuffer(numFields);
+ BitwiseInputStream inStream = new BitwiseInputStream(data);
+ int wantedBits = (offset * 8) + (numFields * 7);
+ if (inStream.available() < wantedBits) {
+ throw new CodingException("insufficient data (wanted " + wantedBits +
+ " bits, but only have " + inStream.available() + ")");
+ }
+ inStream.skip(offset);
+ for (int i = 0; i < numFields; i++) {
+ int charCode = inStream.read(7);
+ if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) &&
+ (charCode <= UserData.ASCII_MAP_MAX_INDEX)) {
+ strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]);
+ } else if (charCode == UserData.ASCII_NL_INDEX) {
+ strBuf.append('\n');
+ } else if (charCode == UserData.ASCII_CR_INDEX) {
+ strBuf.append('\r');
+ } else {
+ /* For other charCodes, they are unprintable, and so simply use SPACE. */
+ strBuf.append(' ');
+ }
+ }
+ return strBuf.toString();
+ } catch (BitwiseInputStream.AccessException ex) {
+ throw new CodingException("7bit ASCII decode failed: " + ex);
+ }
+ }
+
+ private static String decode7bitGsm(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ // Start reading from the next 7-bit aligned boundary after offset.
+ int offsetBits = offset * 8;
+ int offsetSeptets = (offsetBits + 6) / 7;
+ numFields -= offsetSeptets;
+ int paddingBits = (offsetSeptets * 7) - offsetBits;
+ String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits,
+ 0, 0);
+ if (result == null) {
+ throw new CodingException("7bit GSM decoding failed");
+ }
+ return result;
+ }
+
+ private static String decodeLatin(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ if (numFields < 0 || (numFields + offset) > data.length) {
+ throw new CodingException("ISO-8859-1 decode failed: offset or length out of range");
+ }
+ try {
+ return new String(data, offset, numFields, "ISO-8859-1");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("ISO-8859-1 decode failed: " + ex);
+ }
+ }
+
+ private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)
+ throws CodingException
+ {
+ int offset = 0;
+ if (hasUserDataHeader) {
+ int udhLen = userData.payload[0] & 0x00FF;
+ offset += udhLen + 1;
+ byte[] headerData = new byte[udhLen];
+ System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
+ userData.userDataHeader = SmsHeader.fromByteArray(headerData);
+ }
+ switch (userData.msgEncoding) {
+ case UserData.ENCODING_OCTET:
+ /*
+ * Octet decoding depends on the carrier service.
+ */
+ boolean decodingtypeUTF8 = Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_sms_utf8_support);
+
+ // Strip off any padding bytes, meaning any differences between the length of the
+ // array and the target length specified by numFields. This is to avoid any
+ // confusion by code elsewhere that only considers the payload array length.
+ byte[] payload = new byte[userData.numFields];
+ int copyLen = userData.numFields < userData.payload.length
+ ? userData.numFields : userData.payload.length;
+
+ System.arraycopy(userData.payload, 0, payload, 0, copyLen);
+ userData.payload = payload;
+
+ if (!decodingtypeUTF8) {
+ // There are many devices in the market that send 8bit text sms (latin encoded) as
+ // octet encoded.
+ userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+ } else {
+ userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
+ }
+ break;
+
+ case UserData.ENCODING_IA5:
+ case UserData.ENCODING_7BIT_ASCII:
+ userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_UNICODE_16:
+ userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_GSM_7BIT_ALPHABET:
+ userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_LATIN:
+ userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+ break;
+ default:
+ throw new CodingException("unsupported user data encoding ("
+ + userData.msgEncoding + ")");
+ }
+ }
+
+ /**
+ * IS-91 Voice Mail message decoding
+ * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+ * (For character encodings, see TIA/EIA/IS-91, Annex B)
+ *
+ * Protocol Summary: The user data payload may contain 3-14
+ * characters. The first two characters are parsed as a number
+ * and indicate the number of voicemails. The third character is
+ * either a SPACE or '!' to indicate normal or urgent priority,
+ * respectively. Any following characters are treated as normal
+ * text user data payload.
+ *
+ * Note that the characters encoding is 6-bit packed.
+ */
+ private static void decodeIs91VoicemailStatus(BearerData bData)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ int dataLen = inStream.available() / 6; // 6-bit packed character encoding.
+ int numFields = bData.userData.numFields;
+ if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
+ throw new CodingException("IS-91 voicemail status decoding failed");
+ }
+ try {
+ StringBuffer strbuf = new StringBuffer(dataLen);
+ while (inStream.available() >= 6) {
+ strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
+ }
+ String data = strbuf.toString();
+ bData.numberOfMessages = Integer.parseInt(data.substring(0, 2));
+ char prioCode = data.charAt(2);
+ if (prioCode == ' ') {
+ bData.priority = PRIORITY_NORMAL;
+ } else if (prioCode == '!') {
+ bData.priority = PRIORITY_URGENT;
+ } else {
+ throw new CodingException("IS-91 voicemail status decoding failed: " +
+ "illegal priority setting (" + prioCode + ")");
+ }
+ bData.priorityIndicatorSet = true;
+ bData.userData.payloadStr = data.substring(3, numFields - 3);
+ } catch (java.lang.NumberFormatException ex) {
+ throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
+ } catch (java.lang.IndexOutOfBoundsException ex) {
+ throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
+ }
+ }
+
+ /**
+ * IS-91 Short Message decoding
+ * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+ * (For character encodings, see TIA/EIA/IS-91, Annex B)
+ *
+ * Protocol Summary: The user data payload may contain 1-14
+ * characters, which are treated as normal text user data payload.
+ * Note that the characters encoding is 6-bit packed.
+ */
+ private static void decodeIs91ShortMessage(BearerData bData)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ int dataLen = inStream.available() / 6; // 6-bit packed character encoding.
+ int numFields = bData.userData.numFields;
+ // dataLen may be > 14 characters due to octet padding
+ if ((numFields > 14) || (dataLen < numFields)) {
+ throw new CodingException("IS-91 short message decoding failed");
+ }
+ StringBuffer strbuf = new StringBuffer(dataLen);
+ for (int i = 0; i < numFields; i++) {
+ strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
+ }
+ bData.userData.payloadStr = strbuf.toString();
+ }
+
+ /**
+ * IS-91 CLI message (callback number) decoding
+ * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+ *
+ * Protocol Summary: The data payload may contain 1-32 digits,
+ * encoded using standard 4-bit DTMF, which are treated as a
+ * callback number.
+ */
+ private static void decodeIs91Cli(BearerData bData) throws CodingException {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ int dataLen = inStream.available() / 4; // 4-bit packed DTMF digit encoding.
+ int numFields = bData.userData.numFields;
+ if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
+ throw new CodingException("IS-91 voicemail status decoding failed");
+ }
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF;
+ addr.origBytes = bData.userData.payload;
+ addr.numberOfDigits = (byte)numFields;
+ decodeSmsAddress(addr);
+ bData.callbackNumber = addr;
+ }
+
+ private static void decodeIs91(BearerData bData)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ switch (bData.userData.msgType) {
+ case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS:
+ decodeIs91VoicemailStatus(bData);
+ break;
+ case UserData.IS91_MSG_TYPE_CLI:
+ decodeIs91Cli(bData);
+ break;
+ case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL:
+ case UserData.IS91_MSG_TYPE_SHORT_MESSAGE:
+ decodeIs91ShortMessage(bData);
+ break;
+ default:
+ throw new CodingException("unsupported IS-91 message type (" +
+ bData.userData.msgType + ")");
+ }
+ }
+
+ private static boolean decodeReplyOption(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.userAckReq = (inStream.read(1) == 1);
+ bData.deliveryAckReq = (inStream.read(1) == 1);
+ bData.readAckReq = (inStream.read(1) == 1);
+ bData.reportReq = (inStream.read(1) == 1);
+ inStream.skip(4);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "REPLY_OPTION decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeMsgCount(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDepositIndex(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 2 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static String decodeDtmfSmsAddress(byte[] rawData, int numFields)
+ throws CodingException
+ {
+ /* DTMF 4-bit digit encoding, defined in at
+ * 3GPP2 C.S005-D, v2.0, table 2.7.1.3.2.4-4 */
+ StringBuffer strBuf = new StringBuffer(numFields);
+ for (int i = 0; i < numFields; i++) {
+ int val = 0x0F & (rawData[i / 2] >>> (4 - ((i % 2) * 4)));
+ if ((val >= 1) && (val <= 9)) strBuf.append(Integer.toString(val, 10));
+ else if (val == 10) strBuf.append('0');
+ else if (val == 11) strBuf.append('*');
+ else if (val == 12) strBuf.append('#');
+ else throw new CodingException("invalid SMS address DTMF code (" + val + ")");
+ }
+ return strBuf.toString();
+ }
+
+ private static void decodeSmsAddress(CdmaSmsAddress addr) throws CodingException {
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ try {
+ /* As specified in 3GPP2 C.S0015-B, v2, 4.5.15 -- actually
+ * just 7-bit ASCII encoding, with the MSB being zero. */
+ addr.address = new String(addr.origBytes, 0, addr.origBytes.length, "US-ASCII");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("invalid SMS address ASCII code");
+ }
+ } else {
+ addr.address = decodeDtmfSmsAddress(addr.origBytes, addr.numberOfDigits);
+ }
+ }
+
+ private static boolean decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ int paramBits = inStream.read(8) * 8;
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.digitMode = inStream.read(1);
+ byte fieldBits = 4;
+ byte consumedBits = 1;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ addr.ton = inStream.read(3);
+ addr.numberPlan = inStream.read(4);
+ fieldBits = 8;
+ consumedBits += 7;
+ }
+ addr.numberOfDigits = inStream.read(8);
+ consumedBits += 8;
+ int remainingBits = paramBits - consumedBits;
+ int dataBits = addr.numberOfDigits * fieldBits;
+ int paddingBits = remainingBits - dataBits;
+ if (remainingBits < dataBits) {
+ throw new CodingException("CALLBACK_NUMBER subparam encoding size error (" +
+ "remainingBits + " + remainingBits + ", dataBits + " +
+ dataBits + ", paddingBits + " + paddingBits + ")");
+ }
+ addr.origBytes = inStream.readByteArray(dataBits);
+ inStream.skip(paddingBits);
+ decodeSmsAddress(addr);
+ bData.callbackNumber = addr;
+ return true;
+ }
+
+ private static boolean decodeMsgStatus(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.errorClass = inStream.read(2);
+ bData.messageStatus = inStream.read(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "MESSAGE_STATUS decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.messageStatusSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeValidityAbs(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray(
+ inStream.readByteArray(6 * 8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeValidityRel(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.deferredDeliveryTimeRelative = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.deferredDeliveryTimeRelativeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.validityPeriodRelative = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.validityPeriodRelativeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.privacy = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "PRIVACY_INDICATOR decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.privacyIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.language = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "LANGUAGE_INDICATOR decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.languageIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDisplayMode(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.displayMode = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "DISPLAY_MODE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.displayModeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.priority = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "PRIORITY_INDICATOR decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.priorityIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.alert = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.alertIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.userResponseCode = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.userResponseCodeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeServiceCategoryProgramData(BearerData bData,
+ BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException
+ {
+ if (inStream.available() < 13) {
+ throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+ + inStream.available() + " bits available");
+ }
+
+ int paramBits = inStream.read(8) * 8;
+ int msgEncoding = inStream.read(5);
+ paramBits -= 5;
+
+ if (inStream.available() < paramBits) {
+ throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+ + inStream.available() + " bits available (" + paramBits + " bits expected)");
+ }
+
+ ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>();
+
+ final int CATEGORY_FIELD_MIN_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ while (paramBits >= CATEGORY_FIELD_MIN_SIZE) {
+ int operation = inStream.read(4);
+ int category = (inStream.read(8) << 8) | inStream.read(8);
+ String language = getLanguageCodeForValue(inStream.read(8));
+ int maxMessages = inStream.read(8);
+ int alertOption = inStream.read(4);
+ int numFields = inStream.read(8);
+ paramBits -= CATEGORY_FIELD_MIN_SIZE;
+
+ int textBits = getBitsForNumFields(msgEncoding, numFields);
+ if (paramBits < textBits) {
+ throw new CodingException("category name is " + textBits + " bits in length,"
+ + " but there are only " + paramBits + " bits available");
+ }
+
+ UserData userData = new UserData();
+ userData.msgEncoding = msgEncoding;
+ userData.msgEncodingSet = true;
+ userData.numFields = numFields;
+ userData.payload = inStream.readByteArray(textBits);
+ paramBits -= textBits;
+
+ decodeUserDataPayload(userData, false);
+ String categoryName = userData.payloadStr;
+ CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category,
+ language, maxMessages, alertOption, categoryName);
+ programDataList.add(programData);
+
+ decodeSuccess = true;
+ }
+
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Log.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ')');
+ }
+
+ inStream.skip(paramBits);
+ bData.serviceCategoryProgramData = programDataList;
+ return decodeSuccess;
+ }
+
+ private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
+ switch (serviceCategory) {
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
+ return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
+ return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
+ return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
+ return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+ default:
+ return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+ }
+ }
+
+ /**
+ * Calculates the number of bits to read for the specified number of encoded characters.
+ * @param msgEncoding the message encoding to use
+ * @param numFields the number of characters to read. For Shift-JIS and Korean encodings,
+ * this is the number of bytes to read.
+ * @return the number of bits to read from the stream
+ * @throws CodingException if the specified encoding is not supported
+ */
+ private static int getBitsForNumFields(int msgEncoding, int numFields)
+ throws CodingException {
+ switch (msgEncoding) {
+ case UserData.ENCODING_OCTET:
+ case UserData.ENCODING_SHIFT_JIS:
+ case UserData.ENCODING_KOREAN:
+ case UserData.ENCODING_LATIN:
+ case UserData.ENCODING_LATIN_HEBREW:
+ return numFields * 8;
+
+ case UserData.ENCODING_IA5:
+ case UserData.ENCODING_7BIT_ASCII:
+ case UserData.ENCODING_GSM_7BIT_ALPHABET:
+ return numFields * 7;
+
+ case UserData.ENCODING_UNICODE_16:
+ return numFields * 16;
+
+ default:
+ throw new CodingException("unsupported message encoding (" + msgEncoding + ')');
+ }
+ }
+
+ /**
+ * CMAS message decoding.
+ * (See TIA-1149-0-1, CMAS over CDMA)
+ *
+ * @param serviceCategory is the service category from the SMS envelope
+ */
+ private static void decodeCmasUserData(BearerData bData, int serviceCategory)
+ throws BitwiseInputStream.AccessException, CodingException {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ if (inStream.available() < 8) {
+ throw new CodingException("emergency CB with no CMAE_protocol_version");
+ }
+ int protocolVersion = inStream.read(8);
+ if (protocolVersion != 0) {
+ throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
+ }
+
+ int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
+ int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+ int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+ int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+ int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+ int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+
+ while (inStream.available() >= 16) {
+ int recordType = inStream.read(8);
+ int recordLen = inStream.read(8);
+ switch (recordType) {
+ case 0: // Type 0 elements (Alert text)
+ UserData alertUserData = new UserData();
+ alertUserData.msgEncoding = inStream.read(5);
+ alertUserData.msgEncodingSet = true;
+ alertUserData.msgType = 0;
+
+ int numFields; // number of chars to decode
+ switch (alertUserData.msgEncoding) {
+ case UserData.ENCODING_OCTET:
+ case UserData.ENCODING_LATIN:
+ numFields = recordLen - 1; // subtract 1 byte for encoding
+ break;
+
+ case UserData.ENCODING_IA5:
+ case UserData.ENCODING_7BIT_ASCII:
+ case UserData.ENCODING_GSM_7BIT_ALPHABET:
+ numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding
+ break;
+
+ case UserData.ENCODING_UNICODE_16:
+ numFields = (recordLen - 1) / 2;
+ break;
+
+ default:
+ numFields = 0; // unsupported encoding
+ }
+
+ alertUserData.numFields = numFields;
+ alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
+ decodeUserDataPayload(alertUserData, false);
+ bData.userData = alertUserData;
+ break;
+
+ case 1: // Type 1 elements
+ category = inStream.read(8);
+ responseType = inStream.read(8);
+ severity = inStream.read(4);
+ urgency = inStream.read(4);
+ certainty = inStream.read(4);
+ inStream.skip(recordLen * 8 - 28);
+ break;
+
+ default:
+ Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
+ inStream.skip(recordLen * 8);
+ break;
+ }
+ }
+
+ bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
+ urgency, certainty);
+ }
+
+ /**
+ * Create BearerData object from serialized representation.
+ * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+ *
+ * @param smsData byte array of raw encoded SMS bearer data.
+ * @return an instance of BearerData.
+ */
+ public static BearerData decode(byte[] smsData) {
+ return decode(smsData, 0);
+ }
+
+ private static boolean isCmasAlertCategory(int category) {
+ return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
+ && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE;
+ }
+
+ /**
+ * Create BearerData object from serialized representation.
+ * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+ *
+ * @param smsData byte array of raw encoded SMS bearer data.
+ * @param serviceCategory the envelope service category (for CMAS alert handling)
+ * @return an instance of BearerData.
+ */
+ public static BearerData decode(byte[] smsData, int serviceCategory) {
+ try {
+ BitwiseInputStream inStream = new BitwiseInputStream(smsData);
+ BearerData bData = new BearerData();
+ int foundSubparamMask = 0;
+ while (inStream.available() > 0) {
+ int subparamId = inStream.read(8);
+ int subparamIdBit = 1 << subparamId;
+ if ((foundSubparamMask & subparamIdBit) != 0) {
+ throw new CodingException("illegal duplicate subparameter (" +
+ subparamId + ")");
+ }
+ boolean decodeSuccess;
+ switch (subparamId) {
+ case SUBPARAM_MESSAGE_IDENTIFIER:
+ decodeSuccess = decodeMessageId(bData, inStream);
+ break;
+ case SUBPARAM_USER_DATA:
+ decodeSuccess = decodeUserData(bData, inStream);
+ break;
+ case SUBPARAM_USER_RESPONSE_CODE:
+ decodeSuccess = decodeUserResponseCode(bData, inStream);
+ break;
+ case SUBPARAM_REPLY_OPTION:
+ decodeSuccess = decodeReplyOption(bData, inStream);
+ break;
+ case SUBPARAM_NUMBER_OF_MESSAGES:
+ decodeSuccess = decodeMsgCount(bData, inStream);
+ break;
+ case SUBPARAM_CALLBACK_NUMBER:
+ decodeSuccess = decodeCallbackNumber(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_STATUS:
+ decodeSuccess = decodeMsgStatus(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_CENTER_TIME_STAMP:
+ decodeSuccess = decodeMsgCenterTimeStamp(bData, inStream);
+ break;
+ case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE:
+ decodeSuccess = decodeValidityAbs(bData, inStream);
+ break;
+ case SUBPARAM_VALIDITY_PERIOD_RELATIVE:
+ decodeSuccess = decodeValidityRel(bData, inStream);
+ break;
+ case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE:
+ decodeSuccess = decodeDeferredDeliveryAbs(bData, inStream);
+ break;
+ case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE:
+ decodeSuccess = decodeDeferredDeliveryRel(bData, inStream);
+ break;
+ case SUBPARAM_PRIVACY_INDICATOR:
+ decodeSuccess = decodePrivacyIndicator(bData, inStream);
+ break;
+ case SUBPARAM_LANGUAGE_INDICATOR:
+ decodeSuccess = decodeLanguageIndicator(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_DISPLAY_MODE:
+ decodeSuccess = decodeDisplayMode(bData, inStream);
+ break;
+ case SUBPARAM_PRIORITY_INDICATOR:
+ decodeSuccess = decodePriorityIndicator(bData, inStream);
+ break;
+ case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY:
+ decodeSuccess = decodeMsgDeliveryAlert(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_DEPOSIT_INDEX:
+ decodeSuccess = decodeDepositIndex(bData, inStream);
+ break;
+ case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA:
+ decodeSuccess = decodeServiceCategoryProgramData(bData, inStream);
+ break;
+ default:
+ throw new CodingException("unsupported bearer data subparameter ("
+ + subparamId + ")");
+ }
+ if (decodeSuccess) foundSubparamMask |= subparamIdBit;
+ }
+ if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
+ throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
+ }
+ if (bData.userData != null) {
+ if (isCmasAlertCategory(serviceCategory)) {
+ decodeCmasUserData(bData, serviceCategory);
+ } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
+ if ((foundSubparamMask ^
+ (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
+ (1 << SUBPARAM_USER_DATA))
+ != 0) {
+ Log.e(LOG_TAG, "IS-91 must occur without extra subparams (" +
+ foundSubparamMask + ")");
+ }
+ decodeIs91(bData);
+ } else {
+ decodeUserDataPayload(bData.userData, bData.hasUserDataHeader);
+ }
+ }
+ return bData;
+ } catch (BitwiseInputStream.AccessException ex) {
+ Log.e(LOG_TAG, "BearerData decode failed: " + ex);
+ } catch (CodingException ex) {
+ Log.e(LOG_TAG, "BearerData decode failed: " + ex);
+ }
+ return null;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java b/src/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
new file mode 100644
index 0000000..5f2e561
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
@@ -0,0 +1,228 @@
+/*
+ * 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.cdma.sms;
+
+import android.util.SparseBooleanArray;
+
+import com.android.internal.telephony.SmsAddress;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.util.HexDump;
+
+public class CdmaSmsAddress extends SmsAddress {
+
+ /**
+ * Digit Mode Indicator is a 1-bit value that indicates whether
+ * the address digits are 4-bit DTMF codes or 8-bit codes. (See
+ * 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ static public final int DIGIT_MODE_4BIT_DTMF = 0x00;
+ static public final int DIGIT_MODE_8BIT_CHAR = 0x01;
+
+ public int digitMode;
+
+ /**
+ * Number Mode Indicator is 1-bit value that indicates whether the
+ * address type is a data network address or not. (See 3GPP2
+ * C.S0015-B, v2, 3.4.3.3)
+ */
+ static public final int NUMBER_MODE_NOT_DATA_NETWORK = 0x00;
+ static public final int NUMBER_MODE_DATA_NETWORK = 0x01;
+
+ public int numberMode;
+
+ /**
+ * Number Types for data networks.
+ * (See 3GPP2 C.S005-D, table2.7.1.3.2.4-2 for complete table)
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3 for data network subset)
+ * NOTE: value is stored in the parent class ton field.
+ */
+ static public final int TON_UNKNOWN = 0x00;
+ static public final int TON_INTERNATIONAL_OR_IP = 0x01;
+ static public final int TON_NATIONAL_OR_EMAIL = 0x02;
+ static public final int TON_NETWORK = 0x03;
+ static public final int TON_SUBSCRIBER = 0x04;
+ static public final int TON_ALPHANUMERIC = 0x05;
+ static public final int TON_ABBREVIATED = 0x06;
+ static public final int TON_RESERVED = 0x07;
+
+ /**
+ * Maximum lengths for fields as defined in ril_cdma_sms.h.
+ */
+ static public final int SMS_ADDRESS_MAX = 36;
+ static public final int SMS_SUBADDRESS_MAX = 36;
+
+ /**
+ * This field shall be set to the number of address digits
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ public int numberOfDigits;
+
+ /**
+ * Numbering Plan identification is a 0 or 4-bit value that
+ * indicates which numbering plan identification is set. (See
+ * 3GPP2, C.S0015-B, v2, 3.4.3.3 and C.S005-D, table2.7.1.3.2.4-3)
+ */
+ static public final int NUMBERING_PLAN_UNKNOWN = 0x0;
+ static public final int NUMBERING_PLAN_ISDN_TELEPHONY = 0x1;
+ //static protected final int NUMBERING_PLAN_DATA = 0x3;
+ //static protected final int NUMBERING_PLAN_TELEX = 0x4;
+ //static protected final int NUMBERING_PLAN_PRIVATE = 0x9;
+
+ public int numberPlan;
+
+ /**
+ * NOTE: the parsed string address and the raw byte array values
+ * are stored in the parent class address and origBytes fields,
+ * respectively.
+ */
+
+ public CdmaSmsAddress(){
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("CdmaSmsAddress ");
+ builder.append("{ digitMode=" + digitMode);
+ builder.append(", numberMode=" + numberMode);
+ builder.append(", numberPlan=" + numberPlan);
+ builder.append(", numberOfDigits=" + numberOfDigits);
+ builder.append(", ton=" + ton);
+ builder.append(", address=\"" + address + "\"");
+ builder.append(", origBytes=" + HexDump.toHexString(origBytes));
+ builder.append(" }");
+ return builder.toString();
+ }
+
+ /*
+ * TODO(cleanup): Refactor the parsing for addresses to better
+ * share code and logic with GSM. Also, gather all DTMF/BCD
+ * processing code in one place.
+ */
+
+ private static byte[] parseToDtmf(String address) {
+ int digits = address.length();
+ byte[] result = new byte[digits];
+ for (int i = 0; i < digits; i++) {
+ char c = address.charAt(i);
+ int val = 0;
+ if ((c >= '1') && (c <= '9')) val = c - '0';
+ else if (c == '0') val = 10;
+ else if (c == '*') val = 11;
+ else if (c == '#') val = 12;
+ else return null;
+ result[i] = (byte)val;
+ }
+ return result;
+ }
+
+ private static final char[] numericCharsDialable = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#'
+ };
+
+ private static final char[] numericCharsSugar = {
+ '(', ')', ' ', '-', '+', '.', '/', '\\'
+ };
+
+ private static final SparseBooleanArray numericCharDialableMap = new SparseBooleanArray (
+ numericCharsDialable.length + numericCharsSugar.length);
+ static {
+ for (int i = 0; i < numericCharsDialable.length; i++) {
+ numericCharDialableMap.put(numericCharsDialable[i], true);
+ }
+ for (int i = 0; i < numericCharsSugar.length; i++) {
+ numericCharDialableMap.put(numericCharsSugar[i], false);
+ }
+ }
+
+ /**
+ * Given a numeric address string, return the string without
+ * syntactic sugar, meaning parens, spaces, hyphens/minuses, or
+ * plus signs. If the input string contains non-numeric
+ * non-punctuation characters, return null.
+ */
+ private static String filterNumericSugar(String address) {
+ StringBuilder builder = new StringBuilder();
+ int len = address.length();
+ for (int i = 0; i < len; i++) {
+ char c = address.charAt(i);
+ int mapIndex = numericCharDialableMap.indexOfKey(c);
+ if (mapIndex < 0) return null;
+ if (! numericCharDialableMap.valueAt(mapIndex)) continue;
+ builder.append(c);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Given a string, return the string without whitespace,
+ * including CR/LF.
+ */
+ private static String filterWhitespace(String address) {
+ StringBuilder builder = new StringBuilder();
+ int len = address.length();
+ for (int i = 0; i < len; i++) {
+ char c = address.charAt(i);
+ if ((c == ' ') || (c == '\r') || (c == '\n') || (c == '\t')) continue;
+ builder.append(c);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Given a string, create a corresponding CdmaSmsAddress object.
+ *
+ * The result will be null if the input string is not
+ * representable using printable ASCII.
+ *
+ * For numeric addresses, the string is cleaned up by removing
+ * common punctuation. For alpha addresses, the string is cleaned
+ * up by removing whitespace.
+ */
+ public static CdmaSmsAddress parse(String address) {
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.address = address;
+ addr.ton = CdmaSmsAddress.TON_UNKNOWN;
+ byte[] origBytes = null;
+ String filteredAddr = filterNumericSugar(address);
+ if (filteredAddr != null) {
+ origBytes = parseToDtmf(filteredAddr);
+ }
+ if (origBytes != null) {
+ addr.digitMode = DIGIT_MODE_4BIT_DTMF;
+ addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+ if (address.indexOf('+') != -1) {
+ addr.ton = TON_INTERNATIONAL_OR_IP;
+ }
+ } else {
+ filteredAddr = filterWhitespace(address);
+ origBytes = UserData.stringToAscii(filteredAddr);
+ if (origBytes == null) {
+ return null;
+ }
+ addr.digitMode = DIGIT_MODE_8BIT_CHAR;
+ addr.numberMode = NUMBER_MODE_DATA_NETWORK;
+ if (address.indexOf('@') != -1) {
+ addr.ton = TON_NATIONAL_OR_EMAIL;
+ }
+ }
+ addr.origBytes = origBytes;
+ addr.numberOfDigits = origBytes.length;
+ return addr;
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/cdma/sms/CdmaSmsSubaddress.java b/src/java/com/android/internal/telephony/cdma/sms/CdmaSmsSubaddress.java
new file mode 100644
index 0000000..f9cebf5
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/sms/CdmaSmsSubaddress.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project. All rights reserved.
+ * Copyright (C) 2010 Code Aurora Forum. All rights reserved.
+ *
+ * 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.cdma.sms;
+
+public class CdmaSmsSubaddress {
+ public int type;
+
+ public byte odd;
+
+ public byte[] origBytes;
+}
+
diff --git a/src/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/src/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
new file mode 100644
index 0000000..f73df56
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -0,0 +1,130 @@
+/*
+ * 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.cdma.sms;
+
+
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+
+public final class SmsEnvelope {
+ /**
+ * Message Types
+ * (See 3GPP2 C.S0015-B 3.4.1)
+ */
+ static public final int MESSAGE_TYPE_POINT_TO_POINT = 0x00;
+ static public final int MESSAGE_TYPE_BROADCAST = 0x01;
+ static public final int MESSAGE_TYPE_ACKNOWLEDGE = 0x02;
+
+ /**
+ * Supported Teleservices
+ * (See 3GPP2 N.S0005 and TIA-41)
+ */
+ static public final int TELESERVICE_NOT_SET = 0x0000;
+ static public final int TELESERVICE_WMT = 0x1002;
+ static public final int TELESERVICE_VMN = 0x1003;
+ static public final int TELESERVICE_WAP = 0x1004;
+ static public final int TELESERVICE_WEMT = 0x1005;
+ static public final int TELESERVICE_SCPT = 0x1006;
+
+ /**
+ * The following are defined as extensions to the standard teleservices
+ */
+ // Voice mail notification through Message Waiting Indication in CDMA mode or Analog mode.
+ // Defined in 3GPP2 C.S-0005, 3.7.5.6, an Info Record containing an 8-bit number with the
+ // number of messages waiting, it's used by some CDMA carriers for a voice mail count.
+ static public final int TELESERVICE_MWI = 0x40000;
+
+ // Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
+ // static final int SERVICE_CATEGORY_EMERGENCY = 0x0001;
+ //...
+
+ // CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1
+ public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT = 0x1000;
+ public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT = 0x1001;
+ public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT = 0x1002;
+ public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 0x1003;
+ public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE = 0x1004;
+ public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE = 0x10ff;
+
+ /**
+ * Provides the type of a SMS message like point to point, broadcast or acknowledge
+ */
+ public int messageType;
+
+ /**
+ * The 16-bit Teleservice parameter identifies which upper layer service access point is sending
+ * or receiving the message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.1)
+ */
+ public int teleService = TELESERVICE_NOT_SET;
+
+ /**
+ * The 16-bit service category parameter identifies the type of service provided
+ * by the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.2)
+ */
+ public int serviceCategory;
+
+ /**
+ * The origination address identifies the originator of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ public CdmaSmsAddress origAddress;
+
+ /**
+ * The destination address identifies the target of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ public CdmaSmsAddress destAddress;
+
+ /**
+ * The origination subaddress identifies the originator of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.4)
+ */
+ public CdmaSmsSubaddress origSubaddress;
+
+ /**
+ * The 6-bit bearer reply parameter is used to request the return of a
+ * SMS Acknowledge Message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.5)
+ */
+ public int bearerReply;
+
+ /**
+ * Cause Code values:
+ * The cause code parameters are an indication whether an SMS error has occurred and if so,
+ * whether the condition is considered temporary or permanent.
+ * ReplySeqNo 6-bit value,
+ * ErrorClass 2-bit value,
+ * CauseCode 0-bit or 8-bit value
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.6)
+ */
+ public byte replySeqNo;
+ public byte errorClass;
+ public byte causeCode;
+
+ /**
+ * encoded bearer data
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.7)
+ */
+ public byte[] bearerData;
+
+ public SmsEnvelope() {
+ // nothing to see here
+ }
+
+}
+
diff --git a/src/java/com/android/internal/telephony/cdma/sms/UserData.java b/src/java/com/android/internal/telephony/cdma/sms/UserData.java
new file mode 100644
index 0000000..599c2b3
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/sms/UserData.java
@@ -0,0 +1,165 @@
+/*
+ * 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.cdma.sms;
+
+import android.util.SparseIntArray;
+
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.util.HexDump;
+
+public class UserData {
+
+ /**
+ * User data encoding types.
+ * (See 3GPP2 C.R1001-F, v1.0, table 9.1-1)
+ */
+ public static final int ENCODING_OCTET = 0x00;
+ public static final int ENCODING_IS91_EXTENDED_PROTOCOL = 0x01;
+ public static final int ENCODING_7BIT_ASCII = 0x02;
+ public static final int ENCODING_IA5 = 0x03;
+ public static final int ENCODING_UNICODE_16 = 0x04;
+ public static final int ENCODING_SHIFT_JIS = 0x05;
+ public static final int ENCODING_KOREAN = 0x06;
+ public static final int ENCODING_LATIN_HEBREW = 0x07;
+ public static final int ENCODING_LATIN = 0x08;
+ public static final int ENCODING_GSM_7BIT_ALPHABET = 0x09;
+ public static final int ENCODING_GSM_DCS = 0x0A;
+
+ /**
+ * IS-91 message types.
+ * (See TIA/EIS/IS-91-A-ENGL 1999, table 3.7.1.1-3)
+ */
+ public static final int IS91_MSG_TYPE_VOICEMAIL_STATUS = 0x82;
+ public static final int IS91_MSG_TYPE_SHORT_MESSAGE_FULL = 0x83;
+ public static final int IS91_MSG_TYPE_CLI = 0x84;
+ public static final int IS91_MSG_TYPE_SHORT_MESSAGE = 0x85;
+
+ /**
+ * US ASCII character mapping table.
+ *
+ * This table contains only the printable ASCII characters, with a
+ * 0x20 offset, meaning that the ASCII SPACE character is at index
+ * 0, with the resulting code of 0x20.
+ *
+ * Note this mapping is also equivalent to that used by both the
+ * IA5 and the IS-91 encodings. For the former this is defined
+ * using CCITT Rec. T.50 Tables 1 and 3. For the latter IS 637 B,
+ * Table 4.3.1.4.1-1 -- and note the encoding uses only 6 bits,
+ * and hence only maps entries up to the '_' character.
+ *
+ */
+ public static final char[] ASCII_MAP = {
+ ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'};
+
+ /**
+ * Character to use when forced to encode otherwise unencodable
+ * characters, meaning those not in the respective ASCII or GSM
+ * 7-bit encoding tables. Current choice is SPACE, which is 0x20
+ * in both the GSM-7bit and ASCII-7bit encodings.
+ */
+ static final byte UNENCODABLE_7_BIT_CHAR = 0x20;
+
+ /**
+ * Only elements between these indices in the ASCII table are printable.
+ */
+ public static final int PRINTABLE_ASCII_MIN_INDEX = 0x20;
+ public static final int ASCII_NL_INDEX = 0x0A;
+ public static final int ASCII_CR_INDEX = 0x0D;
+ public static final SparseIntArray charToAscii = new SparseIntArray();
+ static {
+ for (int i = 0; i < ASCII_MAP.length; i++) {
+ charToAscii.put(ASCII_MAP[i], PRINTABLE_ASCII_MIN_INDEX + i);
+ }
+ charToAscii.put('\n', ASCII_NL_INDEX);
+ charToAscii.put('\r', ASCII_CR_INDEX);
+ }
+
+ /*
+ * TODO(cleanup): Move this very generic functionality somewhere
+ * more general.
+ */
+ /**
+ * Given a string generate a corresponding ASCII-encoded byte
+ * array, but limited to printable characters. If the input
+ * contains unprintable characters, return null.
+ */
+ public static byte[] stringToAscii(String str) {
+ int len = str.length();
+ byte[] result = new byte[len];
+ for (int i = 0; i < len; i++) {
+ int charCode = charToAscii.get(str.charAt(i), -1);
+ if (charCode == -1) return null;
+ result[i] = (byte)charCode;
+ }
+ return result;
+ }
+
+ /**
+ * Mapping for ASCII values less than 32 are flow control signals
+ * and not used here.
+ */
+ public static final int ASCII_MAP_BASE_INDEX = 0x20;
+ public static final int ASCII_MAP_MAX_INDEX = ASCII_MAP_BASE_INDEX + ASCII_MAP.length - 1;
+
+ /**
+ * Contains the data header of the user data
+ */
+ public SmsHeader userDataHeader;
+
+ /**
+ * Contains the data encoding type for the SMS message
+ */
+ public int msgEncoding;
+ public boolean msgEncodingSet = false;
+
+ public int msgType;
+
+ /**
+ * Number of invalid bits in the last byte of data.
+ */
+ public int paddingBits;
+
+ public int numFields;
+
+ /**
+ * Contains the user data of a SMS message
+ * (See 3GPP2 C.S0015-B, v2, 4.5.2)
+ */
+ public byte[] payload;
+ public String payloadStr;
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("UserData ");
+ builder.append("{ msgEncoding=" + (msgEncodingSet ? msgEncoding : "unset"));
+ builder.append(", msgType=" + msgType);
+ builder.append(", paddingBits=" + paddingBits);
+ builder.append(", numFields=" + numFields);
+ builder.append(", userDataHeader=" + userDataHeader);
+ builder.append(", payload='" + HexDump.toHexString(payload) + "'");
+ builder.append(", payloadStr='" + payloadStr + "'");
+ builder.append(" }");
+ return builder.toString();
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/cdma/sms/package.html b/src/java/com/android/internal/telephony/cdma/sms/package.html
new file mode 100644
index 0000000..b2bc736
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cdma/sms/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides CDMA-specific features for text/data/PDU SMS messages
+@hide
+</BODY>
+</HTML>
diff --git a/src/java/com/android/internal/telephony/gsm/CallFailCause.java b/src/java/com/android/internal/telephony/gsm/CallFailCause.java
new file mode 100644
index 0000000..af2ad48
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/CallFailCause.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.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 {
+ // Unassigned/Unobtainable number
+ static final int UNOBTAINABLE_NUMBER = 1;
+
+ 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/src/java/com/android/internal/telephony/gsm/GSMPhone.java b/src/java/com/android/internal/telephony/gsm/GSMPhone.java
new file mode 100644
index 0000000..8c5368e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -0,0 +1,1508 @@
+/*
+ * 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.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.telephony.CellLocation;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import com.android.internal.telephony.CallTracker;
+import android.text.TextUtils;
+import android.util.Log;
+
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_REGISTRATION;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL_CONDITIONAL;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_NO_REPLY;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_NOT_REACHABLE;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_BUSY;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_BASEBAND_VERSION;
+
+import com.android.internal.telephony.cat.CatService;
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallForwardInfo;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccPhoneBookInterfaceManager;
+import com.android.internal.telephony.IccSmsInterfaceManager;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.OperatorInfo;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.PhoneSubInfo;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.IccVmNotSupportedException;
+import com.android.internal.telephony.ServiceStateTracker;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@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 = true;
+ private static final boolean VDBG = false; /* STOP SHIP if true */
+
+ // Key used to read/write current ciphering state
+ public static final String CIPHERING_KEY = "ciphering_key";
+ // Key used to read/write voice mail number
+ public static final String VM_NUMBER = "vm_number_key";
+ // Key used to read/write the SIM IMSI used for storing the voice mail
+ public static final String VM_SIM_IMSI = "vm_sim_imsi_key";
+
+ // Instance Variables
+ GsmCallTracker mCT;
+ GsmServiceStateTracker mSST;
+ 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 String mImei;
+ private String mImeiSv;
+ private String mVmNumber;
+
+
+ // 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, ci, unitTestMode);
+
+ if (ci instanceof SimulatedRadioControl) {
+ mSimulatedRadioControl = (SimulatedRadioControl) ci;
+ }
+
+ mCM.setPhoneType(PhoneConstants.PHONE_TYPE_GSM);
+ mIccCard.set(UiccController.getInstance(this).getIccCard());
+ mIccRecords = mIccCard.get().getIccRecords();
+ mCT = new GsmCallTracker(this);
+ mSST = new GsmServiceStateTracker (this);
+ mSMS = new GsmSMSDispatcher(this, mSmsStorageMonitor, mSmsUsageMonitor);
+ mDataConnectionTracker = new GsmDataConnectionTracker (this);
+ if (!unitTestMode) {
+ mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this);
+ mSimSmsIntManager = new SimSmsInterfaceManager(this, mSMS);
+ mSubInfo = new PhoneSubInfo(this);
+ }
+
+ mCM.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+ registerForSimRecordEvents();
+ mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+ mCM.registerForOn(this, EVENT_RADIO_ON, null);
+ mCM.setOnUSSD(this, EVENT_USSD, null);
+ mCM.setOnSuppServiceNotification(this, EVENT_SSN, null);
+ mSST.registerForNetworkAttached(this, 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);
+ }
+ }
+
+ //Change the system property
+ SystemProperties.set(TelephonyProperties.CURRENT_ACTIVE_PHONE,
+ new Integer(PhoneConstants.PHONE_TYPE_GSM).toString());
+ }
+
+ @Override
+ public void dispose() {
+ synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+ super.dispose();
+
+ //Unregister from all former registered events
+ mCM.unregisterForAvailable(this); //EVENT_RADIO_AVAILABLE
+ unregisterForSimRecordEvents();
+ mCM.unregisterForOffOrNotAvailable(this); //EVENT_RADIO_OFF_OR_NOT_AVAILABLE
+ mCM.unregisterForOn(this); //EVENT_RADIO_ON
+ mSST.unregisterForNetworkAttached(this); //EVENT_REGISTERED_TO_NETWORK
+ mCM.unSetOnUSSD(this);
+ mCM.unSetOnSuppServiceNotification(this);
+
+ mPendingMMIs.clear();
+
+ //Force all referenced classes to unregister their former registered events
+ mCT.dispose();
+ mDataConnectionTracker.dispose();
+ mSST.dispose();
+ mSimPhoneBookIntManager.dispose();
+ mSimSmsIntManager.dispose();
+ mSubInfo.dispose();
+ }
+ }
+
+ @Override
+ public void removeReferences() {
+ Log.d(LOG_TAG, "removeReferences");
+ mSimulatedRadioControl = null;
+ mSimPhoneBookIntManager = null;
+ mSimSmsIntManager = null;
+ mSubInfo = null;
+ mCT = null;
+ mSST = null;
+ super.removeReferences();
+ }
+
+ protected void finalize() {
+ if(LOCAL_DEBUG) Log.d(LOG_TAG, "GSMPhone finalized");
+ }
+
+
+ public ServiceState
+ getServiceState() {
+ return mSST.ss;
+ }
+
+ public CellLocation getCellLocation() {
+ return mSST.cellLoc;
+ }
+
+ public PhoneConstants.State getState() {
+ return mCT.state;
+ }
+
+ public String getPhoneName() {
+ return "GSM";
+ }
+
+ public int getPhoneType() {
+ return PhoneConstants.PHONE_TYPE_GSM;
+ }
+
+ public SignalStrength getSignalStrength() {
+ return mSST.mSignalStrength;
+ }
+
+ public CallTracker getCallTracker() {
+ return mCT;
+ }
+
+ public ServiceStateTracker getServiceStateTracker() {
+ return mSST;
+ }
+
+ public List<? extends MmiCode>
+ getPendingMmiCodes() {
+ return mPendingMMIs;
+ }
+
+ public PhoneConstants.DataState getDataConnectionState(String apnType) {
+ PhoneConstants.DataState ret = PhoneConstants.DataState.DISCONNECTED;
+
+ if (mSST == null) {
+ // Radio Technology Change is ongoning, dispose() and removeReferences() have
+ // already been called
+
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ } 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 = PhoneConstants.DataState.DISCONNECTED;
+ } else if (mDataConnectionTracker.isApnTypeEnabled(apnType) == false ||
+ mDataConnectionTracker.isApnTypeActive(apnType) == false) {
+ //TODO: isApnTypeActive() is just checking whether ApnContext holds
+ // Dataconnection or not. Checking each ApnState below should
+ // provide the same state. Calling isApnTypeActive() can be removed.
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ } else { /* mSST.gprsState == ServiceState.STATE_IN_SERVICE */
+ switch (mDataConnectionTracker.getState(apnType)) {
+ case FAILED:
+ case IDLE:
+ ret = PhoneConstants.DataState.DISCONNECTED;
+ break;
+
+ case CONNECTED:
+ case DISCONNECTING:
+ if ( mCT.state != PhoneConstants.State.IDLE
+ && !mSST.isConcurrentVoiceAndDataAllowed()) {
+ ret = PhoneConstants.DataState.SUSPENDED;
+ } else {
+ ret = PhoneConstants.DataState.CONNECTED;
+ }
+ break;
+
+ case INITING:
+ case CONNECTING:
+ case SCANNING:
+ ret = PhoneConstants.DataState.CONNECTING;
+ break;
+ }
+ }
+
+ return ret;
+ }
+
+ public DataActivityState getDataActivityState() {
+ DataActivityState ret = DataActivityState.NONE;
+
+ if (mSST.getCurrentGprsState() == ServiceState.STATE_IN_SERVICE) {
+ switch (mDataConnectionTracker.getActivity()) {
+ 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 {@link PhoneConstants.State}
+ */
+ /*package*/ void notifyPhoneStateChanged() {
+ mNotifier.notifyPhoneState(this);
+ }
+
+ /**
+ * Notify registrants of a change in the call state. This notifies changes in {@link Call.State}
+ * Use this when changes in the precise call state are needed, else use notifyPhoneStateChanged.
+ */
+ /*package*/ void notifyPreciseCallStateChanged() {
+ /* we'd love it if this was package-scoped*/
+ super.notifyPreciseCallStateChangedP();
+ }
+
+ /*package*/ void
+ notifyNewRingingConnection(Connection c) {
+ /* we'd love it if this was package-scoped*/
+ super.notifyNewRingingConnectionP(c);
+ }
+
+ /*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);
+ }
+
+ public void
+ notifyCallForwardingIndicator() {
+ mNotifier.notifyCallForwardingChanged(this);
+ }
+
+ // override for allowing access from other classes of this package
+ /**
+ * {@inheritDoc}
+ */
+ public final void
+ setSystemProperty(String property, String value) {
+ super.setSystemProperty(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 GsmCall
+ getForegroundCall() {
+ return mCT.foregroundCall;
+ }
+
+ public GsmCall
+ getBackgroundCall() {
+ return mCT.backgroundCall;
+ }
+
+ public GsmCall
+ getRingingCall() {
+ return mCT.ringingCall;
+ }
+
+ private boolean handleCallDeflectionIncallSupplementaryService(
+ String dialString) throws CallStateException {
+ if (dialString.length() > 1) {
+ return false;
+ }
+
+ if (getRingingCall().getState() != GsmCall.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() != GsmCall.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 <= GsmCallTracker.MAX_CONNECTIONS) {
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "MmiCode 1: hangupConnectionByIndex " +
+ callIndex);
+ mCT.hangupConnectionByIndex(call, callIndex);
+ }
+ } else {
+ if (call.getState() != GsmCall.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 <= GsmCallTracker.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() != GsmCall.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() {
+ GsmCall.State foregroundCallState = getForegroundCall().getState();
+ GsmCall.State backgroundCallState = getBackgroundCall().getState();
+ GsmCall.State ringingCallState = getRingingCall().getState();
+
+ return (foregroundCallState.isAlive() ||
+ backgroundCallState.isAlive() ||
+ ringingCallState.isAlive());
+ }
+
+ public Connection
+ dial(String dialString) throws CallStateException {
+ return dial(dialString, null);
+ }
+
+ public Connection
+ dial (String dialString, UUSInfo uusInfo) 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;
+ }
+
+ // Only look at the Network portion for mmi
+ String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
+ GsmMmiCode mmi = GsmMmiCode.newFromDialString(networkPortion, this);
+ if (LOCAL_DEBUG) Log.d(LOG_TAG,
+ "dialing w/ mmi '" + mmi + "'...");
+
+ if (mmi == null) {
+ return mCT.dial(newDialString, uusInfo);
+ } else if (mmi.isTemporaryModeCLIR()) {
+ return mCT.dial(mmi.dialingNumber, mmi.getCLIRMode(), uusInfo);
+ } 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 == PhoneConstants.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
+ sendBurstDtmf(String dtmfString) {
+ Log.e(LOG_TAG, "[GSMPhone] sendBurstDtmf() is a CDMA method");
+ }
+
+ public void
+ setRadioPower(boolean power) {
+ mSST.setRadioPower(power);
+ }
+
+ private void storeVoiceMailNumber(String number) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(VM_NUMBER, number);
+ editor.apply();
+ setVmSimImsi(getSubscriberId());
+ }
+
+ public String getVoiceMailNumber() {
+ // Read from the SIM. If its null, try reading from the shared preference area.
+ String number = mIccRecords.getVoiceMailNumber();
+ if (TextUtils.isEmpty(number)) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ number = sp.getString(VM_NUMBER, null);
+ }
+ return number;
+ }
+
+ private String getVmSimImsi() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ return sp.getString(VM_SIM_IMSI, null);
+ }
+
+ private void setVmSimImsi(String imsi) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(VM_SIM_IMSI, imsi);
+ editor.apply();
+ }
+
+ public String getVoiceMailAlphaTag() {
+ String ret;
+
+ ret = mIccRecords.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 getImei() {
+ return mImei;
+ }
+
+ public String getEsn() {
+ Log.e(LOG_TAG, "[GSMPhone] getEsn() is a CDMA method");
+ return "0";
+ }
+
+ public String getMeid() {
+ Log.e(LOG_TAG, "[GSMPhone] getMeid() is a CDMA method");
+ return "0";
+ }
+
+ public String getSubscriberId() {
+ return mIccRecords.getIMSI();
+ }
+
+ public String getLine1Number() {
+ return mIccRecords.getMsisdnNumber();
+ }
+
+ @Override
+ public String getMsisdn() {
+ return mIccRecords.getMsisdnNumber();
+ }
+
+ public String getLine1AlphaTag() {
+ return mIccRecords.getMsisdnAlphaTag();
+ }
+
+ public void setLine1Number(String alphaTag, String number, Message onComplete) {
+ mIccRecords.setMsisdnNumber(alphaTag, number, onComplete);
+ }
+
+ public void setVoiceMailNumber(String alphaTag,
+ String voiceMailNumber,
+ Message onComplete) {
+
+ Message resp;
+ mVmNumber = voiceMailNumber;
+ resp = obtainMessage(EVENT_SET_VM_NUMBER_DONE, 0, 0, onComplete);
+ mIccRecords.setVoiceMailNumber(alphaTag, mVmNumber, resp);
+ }
+
+ 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;
+ }
+ }
+
+ protected 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 = 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 = 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,
+ obtainMessage(EVENT_SET_CLIR_COMPLETE, commandInterfaceCLIRMode, 0, onComplete));
+ }
+
+ public void getCallWaiting(Message onComplete) {
+ //As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
+ //class parameter in call waiting interrogation to network
+ mCM.queryCallWaiting(CommandsInterface.SERVICE_CLASS_NONE, onComplete);
+ }
+
+ public void setCallWaiting(boolean enable, Message onComplete) {
+ mCM.setCallWaiting(enable, CommandsInterface.SERVICE_CLASS_VOICE, onComplete);
+ }
+
+ 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. operatorAlphaLong is the
+ * corresponding operator name.
+ */
+ private static class NetworkSelectMessage {
+ public Message message;
+ public String operatorNumeric;
+ public String operatorAlphaLong;
+ }
+
+ 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 = "";
+ nsm.operatorAlphaLong = "";
+
+ // get the message
+ Message msg = 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(OperatorInfo 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.getOperatorNumeric();
+ nsm.operatorAlphaLong = network.getOperatorAlphaLong();
+
+ // get the message
+ Message msg = obtainMessage(EVENT_SET_NETWORK_MANUAL_COMPLETE, nsm);
+
+ mCM.setNetworkSelectionModeManual(network.getOperatorNumeric(), msg);
+ }
+
+ 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 getDataCallList(Message response) {
+ mCM.getDataCallList(response);
+ }
+
+ public void updateServiceLocation() {
+ mSST.enableSingleLocationUpdate();
+ }
+
+ public void enableLocationUpdates() {
+ mSST.enableLocationUpdates();
+ }
+
+ public void disableLocationUpdates() {
+ mSST.disableLocationUpdates();
+ }
+
+ public boolean getDataRoamingEnabled() {
+ return mDataConnectionTracker.getDataOnRoamingEnabled();
+ }
+
+ public void setDataRoamingEnabled(boolean enable) {
+ mDataConnectionTracker.setDataOnRoamingEnabled(enable);
+ }
+
+ /**
+ * 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.
+ */
+ protected void syncClirSetting() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+ int clirSetting = sp.getInt(CLIR_KEY, -1);
+ if (clirSetting >= 0) {
+ mCM.setCLIR(clirSetting, null);
+ }
+ }
+
+ @Override
+ 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:
+ updateCurrentCarrierInProvider();
+
+ // Check if this is a different SIM than the previous one. If so unset the
+ // voice mail number.
+ String imsi = getVmSimImsi();
+ String imsiFromSIM = getSubscriberId();
+ if (imsi != null && imsiFromSIM != null && !imsiFromSIM.equals(imsi)) {
+ storeVoiceMailNumber(null);
+ setVmSimImsi(null);
+ }
+
+ 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 = mPendingMMIs.size() - 1; i >= 0; 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) {
+ mIccRecords.setVoiceCallForwardingFlag(1, msg.arg1 == 1);
+ }
+ onComplete = (Message) ar.userObj;
+ if (onComplete != null) {
+ AsyncResult.forMessage(onComplete, ar.result, ar.exception);
+ onComplete.sendToTarget();
+ }
+ break;
+
+ case EVENT_SET_VM_NUMBER_DONE:
+ ar = (AsyncResult)msg.obj;
+ if (IccVmNotSupportedException.class.isInstance(ar.exception)) {
+ storeVoiceMailNumber(mVmNumber);
+ ar.exception = null;
+ }
+ 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_NEW_ICC_SMS:
+ ar = (AsyncResult)msg.obj;
+ mSMS.dispatchMessage((SmsMessage)ar.result);
+ break;
+
+ case EVENT_SET_NETWORK_AUTOMATIC:
+ ar = (AsyncResult)msg.obj;
+ setNetworkSelectionModeAutomatic((Message)ar.result);
+ break;
+
+ case EVENT_ICC_RECORD_EVENTS:
+ ar = (AsyncResult)msg.obj;
+ processIccRecordEvents((Integer)ar.result);
+ 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;
+
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ private void processIccRecordEvents(int eventCode) {
+ switch (eventCode) {
+ case SIMRecords.EVENT_CFI:
+ notifyCallForwardingIndicator();
+ break;
+ case SIMRecords.EVENT_MWI:
+ notifyMessageWaitingIndicator();
+ break;
+ }
+ }
+
+ /**
+ * Sets the "current" field in the telephony provider according to the SIM's operator
+ *
+ * @return true for success; false otherwise.
+ */
+ boolean updateCurrentCarrierInProvider() {
+ if (mIccRecords != null) {
+ try {
+ Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
+ ContentValues map = new ContentValues();
+ map.put(Telephony.Carriers.NUMERIC, mIccRecords.getOperatorNumeric());
+ mContext.getContentResolver().insert(uri, map);
+ return true;
+ } catch (SQLException e) {
+ Log.e(LOG_TAG, "Can't store current operator", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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);
+ editor.putString(NETWORK_SELECTION_NAME_KEY, nsm.operatorAlphaLong);
+
+ // 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).
+ */
+ public 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
+ mIccRecords.setVoiceCallForwardingFlag(1, false);
+ } else {
+ for (int i = 0, s = infos.length; i < s; i++) {
+ if ((infos[i].serviceClass & SERVICE_CLASS_VOICE) != 0) {
+ mIccRecords.setVoiceCallForwardingFlag(1, (infos[i].status == 1));
+ // should only have the one
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Retrieves the PhoneSubInfo of the GSMPhone
+ */
+ public PhoneSubInfo getPhoneSubInfo(){
+ return mSubInfo;
+ }
+
+ /**
+ * Retrieves the IccSmsInterfaceManager of the GSMPhone
+ */
+ public IccSmsInterfaceManager getIccSmsInterfaceManager(){
+ return mSimSmsIntManager;
+ }
+
+ /**
+ * Retrieves the IccPhoneBookInterfaceManager of the GSMPhone
+ */
+ public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
+ return mSimPhoneBookIntManager;
+ }
+
+ /**
+ * Activate or deactivate cell broadcast SMS.
+ *
+ * @param activate 0 = activate, 1 = deactivate
+ * @param response Callback message is empty on completion
+ */
+ public void activateCellBroadcastSms(int activate, Message response) {
+ Log.e(LOG_TAG, "[GSMPhone] activateCellBroadcastSms() is obsolete; use SmsManager");
+ response.sendToTarget();
+ }
+
+ /**
+ * Query the current configuration of cdma cell broadcast SMS.
+ *
+ * @param response Callback message is empty on completion
+ */
+ public void getCellBroadcastSmsConfig(Message response) {
+ Log.e(LOG_TAG, "[GSMPhone] getCellBroadcastSmsConfig() is obsolete; use SmsManager");
+ response.sendToTarget();
+ }
+
+ /**
+ * Configure cdma cell broadcast SMS.
+ *
+ * @param response Callback message is empty on completion
+ */
+ public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
+ Log.e(LOG_TAG, "[GSMPhone] setCellBroadcastSmsConfig() is obsolete; use SmsManager");
+ response.sendToTarget();
+ }
+
+ public boolean isCspPlmnEnabled() {
+ return mIccRecords.isCspPlmnEnabled();
+ }
+
+ private void registerForSimRecordEvents() {
+ mIccRecords.registerForNetworkSelectionModeAutomatic(
+ this, EVENT_SET_NETWORK_AUTOMATIC, null);
+ mIccRecords.registerForNewSms(this, EVENT_NEW_ICC_SMS, null);
+ mIccRecords.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null);
+ mIccRecords.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null);
+ }
+
+ private void unregisterForSimRecordEvents() {
+ mIccRecords.unregisterForNetworkSelectionModeAutomatic(this);
+ mIccRecords.unregisterForNewSms(this);
+ mIccRecords.unregisterForRecordsEvents(this);
+ mIccRecords.unregisterForRecordsLoaded(this);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("GSMPhone extends:");
+ super.dump(fd, pw, args);
+ pw.println(" mCT=" + mCT);
+ pw.println(" mSST=" + mSST);
+ pw.println(" mPendingMMIs=" + mPendingMMIs);
+ pw.println(" mSimPhoneBookIntManager=" + mSimPhoneBookIntManager);
+ pw.println(" mSimSmsIntManager=" + mSimSmsIntManager);
+ pw.println(" mSubInfo=" + mSubInfo);
+ if (VDBG) pw.println(" mImei=" + mImei);
+ if (VDBG) pw.println(" mImeiSv=" + mImeiSv);
+ pw.println(" mVmNumber=" + mVmNumber);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmCall.java b/src/java/com/android/internal/telephony/gsm/GsmCall.java
new file mode 100644
index 0000000..58124a2
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmCall.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.gsm;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.DriverCall;
+import com.android.internal.telephony.Phone;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+class GsmCall extends Call {
+ /*************************** Instance Variables **************************/
+
+ /*package*/ ArrayList<Connection> connections = new ArrayList<Connection>();
+ /*package*/ GsmCallTracker 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 (GsmCallTracker owner) {
+ this.owner = owner;
+ }
+
+ public void dispose() {
+ }
+
+ /************************** Overridden from Call *************************/
+
+ public List<Connection>
+ getConnections() {
+ // FIXME should return Collections.unmodifiableList();
+ return connections;
+ }
+
+ public Phone
+ getPhone() {
+ return owner.phone;
+ }
+
+ 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(Connection conn, DriverCall dc) {
+ connections.add(conn);
+
+ state = stateFromDCState (dc.state);
+ }
+
+ /*package*/ void
+ attachFake(Connection 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() == GsmCallTracker.MAX_CONNECTIONS_PER_CALL;
+ }
+
+ //***** Called from GsmCallTracker
+
+
+ /**
+ * 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();
+ }
+ state = State.DISCONNECTING;
+ }
+
+ /**
+ * 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/src/java/com/android/internal/telephony/gsm/GsmCallTracker.java b/src/java/com/android/internal/telephony/gsm/GsmCallTracker.java
new file mode 100644
index 0000000..fc7fe8a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmCallTracker.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 android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.gsm.GsmCellLocation;
+import android.util.EventLog;
+import android.util.Log;
+
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallTracker;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.DriverCall;
+import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.gsm.CallFailCause;
+import com.android.internal.telephony.gsm.GSMPhone;
+import com.android.internal.telephony.gsm.GsmCall;
+import com.android.internal.telephony.gsm.GsmConnection;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public final class GsmCallTracker extends CallTracker {
+ static final String LOG_TAG = "GSM";
+ private static final boolean REPEAT_POLLING = false;
+
+ private static final boolean DBG_POLL = false;
+
+ //***** Constants
+
+ 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 during 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;
+
+ boolean desiredMute = false; // false = mute off
+
+ PhoneConstants.State state = PhoneConstants.State.IDLE;
+
+
+
+ //***** Events
+
+
+ //***** Constructors
+
+ GsmCallTracker (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);
+ }
+
+ public void dispose() {
+ //Unregister for all events
+ cm.unregisterForCallStateChanged(this);
+ cm.unregisterForOn(this);
+ cm.unregisterForNotAvailable(this);
+
+ for(GsmConnection c : connections) {
+ try {
+ if(c != null) hangup(c);
+ } catch (CallStateException ex) {
+ Log.e(LOG_TAG, "unexpected error on hangup during dispose");
+ }
+ }
+
+ try {
+ if(pendingMO != null) hangup(pendingMO);
+ } catch (CallStateException ex) {
+ Log.e(LOG_TAG, "unexpected error on hangup during dispose");
+ }
+
+ clearDisconnected();
+ }
+
+ protected void finalize() {
+ Log.d(LOG_TAG, "GsmCallTracker finalized");
+ }
+
+ //***** 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 unregisterForVoiceCallStarted(Handler h) {
+ voiceCallStartedRegistrants.remove(h);
+ }
+
+ public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
+ Registrant r = new Registrant(h, what, obj);
+ voiceCallEndedRegistrants.add(r);
+ }
+
+ public void unregisterForVoiceCallEnded(Handler h) {
+ voiceCallEndedRegistrants.remove(h);
+ }
+
+ 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
+ */
+ synchronized Connection
+ dial (String dialString, int clirMode, UUSInfo uusInfo) 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() == GsmCall.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() != GsmCall.State.IDLE) {
+ //we should have failed in !canDial() above before we get here
+ throw new CallStateException("cannot dial in current state");
+ }
+
+ pendingMO = new GsmConnection(phone.getContext(), checkForTestEmergencyNumber(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, uusInfo, obtainCompleteMessage());
+ }
+
+ updatePhoneState();
+ phone.notifyPreciseCallStateChanged();
+
+ return pendingMO;
+ }
+
+ Connection
+ dial(String dialString) throws CallStateException {
+ return dial(dialString, CommandsInterface.CLIR_DEFAULT, null);
+ }
+
+ Connection
+ dial(String dialString, UUSInfo uusInfo) throws CallStateException {
+ return dial(dialString, CommandsInterface.CLIR_DEFAULT, uusInfo);
+ }
+
+ Connection
+ dial(String dialString, int clirMode) throws CallStateException {
+ return dial(dialString, clirMode, null);
+ }
+
+ 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() == GsmCall.State.INCOMING) {
+ Log.i("phone", "acceptCall: incoming...");
+ // Always unmute when answering a new call
+ setMute(false);
+ cm.acceptCall(obtainCompleteMessage());
+ } else if (ringingCall.getState() == GsmCall.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() == GsmCall.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.notifyPreciseCallStateChanged();
+ }
+
+ boolean
+ canConference() {
+ return foregroundCall.getState() == GsmCall.State.ACTIVE
+ && backgroundCall.getState() == GsmCall.State.HOLDING
+ && !backgroundCall.isFull()
+ && !foregroundCall.isFull();
+ }
+
+ boolean
+ canDial() {
+ boolean ret;
+ int serviceState = phone.getServiceState().getState();
+ String disableCall = SystemProperties.get(
+ TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
+
+ ret = (serviceState != ServiceState.STATE_POWER_OFF)
+ && pendingMO == null
+ && !ringingCall.isRinging()
+ && !disableCall.equals("true")
+ && (!foregroundCall.getState().isAlive()
+ || !backgroundCall.getState().isAlive());
+
+ return ret;
+ }
+
+ boolean
+ canTransfer() {
+ return foregroundCall.getState() == GsmCall.State.ACTIVE
+ && backgroundCall.getState() == GsmCall.State.HOLDING;
+ }
+
+ //***** Private Instance Methods
+
+ private void
+ internalClearDisconnected() {
+ ringingCall.clearDisconnected();
+ foregroundCall.clearDisconnected();
+ backgroundCall.clearDisconnected();
+ }
+
+ /**
+ * 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);
+ }
+
+ 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,"GsmCallTracker.pendingOperations < 0");
+ pendingOperations = 0;
+ }
+ }
+
+ private void
+ updatePhoneState() {
+ PhoneConstants.State oldState = state;
+
+ if (ringingCall.isRinging()) {
+ state = PhoneConstants.State.RINGING;
+ } else if (pendingMO != null ||
+ !(foregroundCall.isIdle() && backgroundCall.isIdle())) {
+ state = PhoneConstants.State.OFFHOOK;
+ } else {
+ state = PhoneConstants.State.IDLE;
+ }
+
+ if (state == PhoneConstants.State.IDLE && oldState != state) {
+ voiceCallEndedRegistrants.notifyRegistrants(
+ new AsyncResult(null, null, null));
+ } else if (oldState == PhoneConstants.State.IDLE && oldState != state) {
+ voiceCallStartedRegistrants.notifyRegistrants (
+ new AsyncResult(null, null, null));
+ }
+
+ if (state != oldState) {
+ phone.notifyPhoneStateChanged();
+ }
+ }
+
+ protected synchronized 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(phone.getContext(), 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 (phone.getContext(), 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.notifyPreciseCallStateChanged();
+ }
+
+ //dumpState();
+ }
+
+ 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 ("GsmConnection " + conn
+ + "does not belong to GsmCallTracker " + 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,"GsmCallTracker WARN: hangup() on absent connection "
+ + conn);
+ }
+ }
+
+ conn.onHangupLocal();
+ }
+
+ /*package*/ void
+ separate (GsmConnection conn) throws CallStateException {
+ if (conn.owner != this) {
+ throw new CallStateException ("GsmConnection " + conn
+ + "does not belong to GsmCallTracker " + 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,"GsmCallTracker 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 ("GsmCall " + call +
+ "does not belong to GsmCallTracker " + this);
+ }
+
+ call.onHangupLocal();
+ phone.notifyPreciseCallStateChanged();
+ }
+
+ /* 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];
+ }
+ // Log the causeCode if its not normal
+ if (causeCode == CallFailCause.NO_CIRCUIT_AVAIL ||
+ causeCode == CallFailCause.TEMPORARY_FAILURE ||
+ causeCode == CallFailCause.SWITCHING_CONGESTION ||
+ causeCode == CallFailCause.CHANNEL_NOT_AVAIL ||
+ causeCode == CallFailCause.QOS_NOT_AVAIL ||
+ causeCode == CallFailCause.BEARER_NOT_AVAIL ||
+ causeCode == CallFailCause.ERROR_UNSPECIFIED) {
+ GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
+ EventLog.writeEvent(EventLogTags.CALL_DROP,
+ causeCode, loc != null ? loc.getCid() : -1,
+ TelephonyManager.getDefault().getNetworkType());
+ }
+
+ for (int i = 0, s = droppedDuringPoll.size()
+ ; i < s ; i++
+ ) {
+ GsmConnection conn = droppedDuringPoll.get(i);
+
+ conn.onRemoteDisconnect(causeCode);
+ }
+
+ updatePhoneState();
+
+ phone.notifyPreciseCallStateChanged();
+ 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;
+ }
+ }
+
+ protected void log(String msg) {
+ Log.d(LOG_TAG, "[GsmCallTracker] " + msg);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("GsmCallTracker extends:");
+ super.dump(fd, pw, args);
+ pw.println("connections: length=" + connections.length);
+ for(int i=0; i < connections.length; i++) {
+ pw.printf(" connections[%d]=%s\n", i, connections[i]);
+ }
+ pw.println(" voiceCallEndedRegistrants=" + voiceCallEndedRegistrants);
+ pw.println(" voiceCallStartedRegistrants=" + voiceCallStartedRegistrants);
+ pw.println(" droppedDuringPoll: size=" + droppedDuringPoll.size());
+ for(int i = 0; i < droppedDuringPoll.size(); i++) {
+ pw.printf( " droppedDuringPoll[%d]=%s\n", i, droppedDuringPoll.get(i));
+ }
+ pw.println(" ringingCall=" + ringingCall);
+ pw.println(" foregroundCall=" + foregroundCall);
+ pw.println(" backgroundCall=" + backgroundCall);
+ pw.println(" pendingMO=" + pendingMO);
+ pw.println(" hangupPendingMO=" + hangupPendingMO);
+ pw.println(" phone=" + phone);
+ pw.println(" desiredMute=" + desiredMute);
+ pw.println(" state=" + state);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmConnection.java b/src/java/com/android/internal/telephony/gsm/GsmConnection.java
new file mode 100644
index 0000000..9fc94a5
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmConnection.java
@@ -0,0 +1,761 @@
+/*
+ * 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.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Registrant;
+import android.os.SystemClock;
+import android.util.Log;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.*;
+
+/**
+ * {@hide}
+ */
+public class GsmConnection extends Connection {
+ static final String LOG_TAG = "GSM";
+
+ //***** Instance Variables
+
+ GsmCallTracker 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 GsmCallTracker.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;
+ int numberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
+ UUSInfo uusInfo;
+
+ Handler h;
+
+ private PowerManager.WakeLock mPartialWakeLock;
+
+ //***** Event Constants
+ static final int EVENT_DTMF_DONE = 1;
+ static final int EVENT_PAUSE_DONE = 2;
+ static final int EVENT_NEXT_POST_DIAL = 3;
+ static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
+
+ //***** Constants
+ static final int PAUSE_DELAY_FIRST_MILLIS = 100;
+ static final int PAUSE_DELAY_MILLIS = 3 * 1000;
+ static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*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;
+ case EVENT_WAKE_LOCK_TIMEOUT:
+ releaseWakeLock();
+ break;
+ }
+ }
+ }
+
+ //***** Constructors
+
+ /** This is probably an MT call that we first saw in a CLCC response */
+ /*package*/
+ GsmConnection (Context context, DriverCall dc, GsmCallTracker ct, int index) {
+ createWakeLock(context);
+ acquireWakeLock();
+
+ owner = ct;
+ h = new MyHandler(owner.getLooper());
+
+ address = dc.number;
+
+ isIncoming = dc.isMT;
+ createTime = System.currentTimeMillis();
+ cnapName = dc.name;
+ cnapNamePresentation = dc.namePresentation;
+ numberPresentation = dc.numberPresentation;
+ uusInfo = dc.uusInfo;
+
+ this.index = index;
+
+ parent = parentFromDCState (dc.state);
+ parent.attach(this, dc);
+ }
+
+ /** This is an MO call, created when dialing */
+ /*package*/
+ GsmConnection (Context context, String dialString, GsmCallTracker ct, GsmCall parent) {
+ createWakeLock(context);
+ acquireWakeLock();
+
+ owner = ct;
+ h = new MyHandler(owner.getLooper());
+
+ this.dialString = dialString;
+
+ this.address = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
+ this.postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
+
+ index = -1;
+
+ isIncoming = false;
+ cnapName = null;
+ cnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
+ numberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
+ createTime = System.currentTimeMillis();
+
+ this.parent = parent;
+ parent.attachFake(this, GsmCall.State.DIALING);
+ }
+
+ public void dispose() {
+ }
+
+ 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 getAddress() {
+ return address;
+ }
+
+ public GsmCall 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() != GsmCall.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 GsmCall.State getState() {
+ if (disconnected) {
+ return GsmCall.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, "GsmConnection.proceedAfterWaitChar(): Expected "
+ + "getPostDialState() to be WAIT but was " + postDialState);
+ return;
+ }
+
+ setPostDialState(PostDialState.STARTED);
+
+ processNextPostDialChar();
+ }
+
+ public void proceedAfterWildChar(String str) {
+ if (postDialState != PostDialState.WILD) {
+ Log.w(LOG_TAG, "GsmConnection.proceedAfterWaitChar(): Expected "
+ + "getPostDialState() to be WILD but was " + postDialState);
+ return;
+ }
+
+ setPostDialState(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() {
+ setPostDialState(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.UNOBTAINABLE_NUMBER:
+ return DisconnectCause.UNOBTAINABLE_NUMBER;
+
+ 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.getIccCard().getState() != IccCardConstants.State.READY) {
+ return DisconnectCause.ICC_ERROR;
+ } else if (causeCode == CallFailCause.ERROR_UNSPECIFIED) {
+ if (phone.mSST.mRestrictedState.isCsRestricted()) {
+ return DisconnectCause.CS_RESTRICTED;
+ } else if (phone.mSST.mRestrictedState.isCsEmergencyRestricted()) {
+ return DisconnectCause.CS_RESTRICTED_EMERGENCY;
+ } else if (phone.mSST.mRestrictedState.isCsNormalRestricted()) {
+ return DisconnectCause.CS_RESTRICTED_NORMAL;
+ } else {
+ return DisconnectCause.ERROR_UNSPECIFIED;
+ }
+ } else if (causeCode == CallFailCause.NORMAL_CLEARING) {
+ return DisconnectCause.NORMAL;
+ } else {
+ // If nothing else matches, report unknown call drop reason
+ // to app, not NORMAL call end.
+ return DisconnectCause.ERROR_UNSPECIFIED;
+ }
+ }
+ }
+
+ /*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 (false) Log.d(LOG_TAG,
+ "[GSMConn] onDisconnect: cause=" + cause);
+
+ owner.phone.notifyDisconnect(this);
+
+ if (parent != null) {
+ parent.connectionDisconnected(this);
+ }
+ }
+ releaseWakeLock();
+ }
+
+ // 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() == GsmCall.State.HOLDING);
+
+ newParent = parentFromDCState(dc.state);
+
+ if (!equalsHandlesNulls(address, dc.number)) {
+ if (Phone.DEBUG_PHONE) log("update: phone # changed!");
+ address = dc.number;
+ changed = true;
+ }
+
+ // A null cnapName should be the same as ""
+ if (TextUtils.isEmpty(dc.name)) {
+ if (!TextUtils.isEmpty(cnapName)) {
+ changed = true;
+ cnapName = "";
+ }
+ } else if (!dc.name.equals(cnapName)) {
+ changed = true;
+ cnapName = dc.name;
+ }
+
+ if (Phone.DEBUG_PHONE) log("--dssds----"+cnapName);
+ cnapNamePresentation = dc.namePresentation;
+ numberPresentation = dc.numberPresentation;
+
+ 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() == GsmCall.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, GsmCall.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();
+ }
+ releaseWakeLock();
+ }
+
+ 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) {
+ setPostDialState(PostDialState.WAIT);
+ } else if (c == PhoneNumberUtils.WILD) {
+ setPostDialState(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);
+ }
+
+ @Override
+ protected void finalize()
+ {
+ /**
+ * It is understood that This finializer is not guaranteed
+ * to be called and the release lock call is here just in
+ * case there is some path that doesn't call onDisconnect
+ * and or onConnectedInOrOut.
+ */
+ if (mPartialWakeLock.isHeld()) {
+ Log.e(LOG_TAG, "[GSMConn] UNEXPECTED; mPartialWakeLock is held when finalizing.");
+ }
+ releaseWakeLock();
+ }
+
+ 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) {
+ setPostDialState(PostDialState.COMPLETE);
+
+ // notifyMessage.arg1 is 0 on complete
+ c = 0;
+ } else {
+ boolean isValid;
+
+ setPostDialState(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();
+ }
+ }
+
+
+ /** "connecting" means "has never been ACTIVE" for both incoming
+ * and outgoing calls
+ */
+ private boolean
+ isConnectingInOrOut() {
+ return parent == null || parent == owner.ringingCall
+ || parent.state == GsmCall.State.DIALING
+ || parent.state == GsmCall.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);
+ }
+ }
+
+ /**
+ * Set post dial state and acquire wake lock while switching to "started"
+ * state, the wake lock will be released if state switches out of "started"
+ * state or after WAKE_LOCK_TIMEOUT_MILLIS.
+ * @param s new PostDialState
+ */
+ private void setPostDialState(PostDialState s) {
+ if (postDialState != PostDialState.STARTED
+ && s == PostDialState.STARTED) {
+ acquireWakeLock();
+ Message msg = h.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
+ h.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
+ } else if (postDialState == PostDialState.STARTED
+ && s != PostDialState.STARTED) {
+ h.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
+ releaseWakeLock();
+ }
+ postDialState = s;
+ }
+
+ private void
+ createWakeLock(Context context) {
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+ }
+
+ private void
+ acquireWakeLock() {
+ log("acquireWakeLock");
+ mPartialWakeLock.acquire();
+ }
+
+ private void
+ releaseWakeLock() {
+ synchronized(mPartialWakeLock) {
+ if (mPartialWakeLock.isHeld()) {
+ log("releaseWakeLock");
+ mPartialWakeLock.release();
+ }
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[GSMConn] " + msg);
+ }
+
+ @Override
+ public int getNumberPresentation() {
+ return numberPresentation;
+ }
+
+ @Override
+ public UUSInfo getUUSInfo() {
+ return uusInfo;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmDataConnection.java b/src/java/com/android/internal/telephony/gsm/GsmDataConnection.java
new file mode 100644
index 0000000..156574d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmDataConnection.java
@@ -0,0 +1,165 @@
+/*
+ * 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.Message;
+import android.util.Log;
+import android.util.Patterns;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.DataConnectionTracker;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.RetryManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * {@hide}
+ */
+public class GsmDataConnection extends DataConnection {
+
+ private static final String LOG_TAG = "GSM";
+
+ //***** Instance Variables
+ protected int mProfileId = RILConstants.DATA_PROFILE_DEFAULT;
+ //***** Constructor
+ private GsmDataConnection(PhoneBase phone, String name, int id, RetryManager rm,
+ DataConnectionTracker dct) {
+ super(phone, name, id, rm, dct);
+ }
+
+ /**
+ * Create the connection object
+ *
+ * @param phone the Phone
+ * @param id the connection id
+ * @param rm the RetryManager
+ * @return GsmDataConnection that was created.
+ */
+ static GsmDataConnection makeDataConnection(PhoneBase phone, int id, RetryManager rm,
+ DataConnectionTracker dct) {
+ GsmDataConnection gsmDc = new GsmDataConnection(phone,
+ "GsmDC-" + mCount.incrementAndGet(), id, rm, dct);
+ gsmDc.start();
+ if (DBG) gsmDc.log("Made " + gsmDc.getName());
+ return gsmDc;
+ }
+
+ /**
+ * Begin setting up a data connection, calls setupDataCall
+ * and the ConnectionParams will be returned with the
+ * EVENT_SETUP_DATA_CONNECTION_DONE AsyncResul.userObj.
+ *
+ * @param cp is the connection parameters
+ */
+ @Override
+ protected
+ void onConnect(ConnectionParams cp) {
+ mApn = cp.apn;
+
+ if (DBG) log("Connecting to carrier: '" + mApn.carrier
+ + "' APN: '" + mApn.apn
+ + "' proxy: '" + mApn.proxy + "' port: '" + mApn.port);
+
+ createTime = -1;
+ lastFailTime = -1;
+ lastFailCause = FailCause.NONE;
+
+ // msg.obj will be returned in AsyncResult.userObj;
+ Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
+ msg.obj = cp;
+
+ int authType = mApn.authType;
+ if (authType == -1) {
+ authType = TextUtils.isEmpty(mApn.user) ? RILConstants.SETUP_DATA_AUTH_NONE
+ : RILConstants.SETUP_DATA_AUTH_PAP_CHAP;
+ }
+
+ String protocol;
+ if (phone.getServiceState().getRoaming()) {
+ protocol = mApn.roamingProtocol;
+ } else {
+ protocol = mApn.protocol;
+ }
+
+ phone.mCM.setupDataCall(
+ Integer.toString(getRilRadioTechnology(RILConstants.SETUP_DATA_TECH_GSM)),
+ Integer.toString(mProfileId),
+ mApn.apn, mApn.user, mApn.password,
+ Integer.toString(authType),
+ protocol, msg);
+ }
+
+ public void setProfileId(int profileId) {
+ mProfileId = profileId;
+ }
+
+ public int getProfileId() {
+ return mProfileId;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + getName() + ": State=" + getCurrentState().getName() +
+ " apnSetting=" + mApn + " apnList= " + mApnList + " RefCount=" + mRefCount +
+ " cid=" + cid + " create=" + createTime + " lastFail=" + lastFailTime +
+ " lastFailCause=" + lastFailCause + "}";
+ }
+
+ @Override
+ protected boolean isDnsOk(String[] domainNameServers) {
+ if (NULL_IP.equals(domainNameServers[0]) && NULL_IP.equals(domainNameServers[1])
+ && !phone.isDnsCheckDisabled()) {
+ // Work around a race condition where QMI does not fill in DNS:
+ // Deactivate PDP and let DataConnectionTracker retry.
+ // Do not apply the race condition workaround for MMS APN
+ // if Proxy is an IP-address.
+ // Otherwise, the default APN will not be restored anymore.
+ if (!mApn.types[0].equals(PhoneConstants.APN_TYPE_MMS)
+ || !isIpAddress(mApn.mmsProxy)) {
+ log(String.format(
+ "isDnsOk: return false apn.types[0]=%s APN_TYPE_MMS=%s isIpAddress(%s)=%s",
+ mApn.types[0], PhoneConstants.APN_TYPE_MMS, mApn.mmsProxy,
+ isIpAddress(mApn.mmsProxy)));
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[" + getName() + "] " + s);
+ }
+
+ private boolean isIpAddress(String address) {
+ if (address == null) return false;
+
+ return Patterns.IP_ADDRESS.matcher(address).matches();
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("GsmDataConnection extends:");
+ super.dump(fd, pw, args);
+ pw.println(" mProfileId=" + mProfileId);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/src/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
new file mode 100644
index 0000000..016513c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -0,0 +1,2642 @@
+/*
+ * 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.PendingIntent;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.net.LinkProperties.CompareResult;
+import android.net.NetworkConfig;
+import android.net.NetworkUtils;
+import android.net.ProxyProperties;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.telephony.CellLocation;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.gsm.GsmCellLocation;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+
+import com.android.internal.telephony.ApnContext;
+import com.android.internal.telephony.ApnSetting;
+import com.android.internal.telephony.DataCallState;
+import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.DataConnection.FailCause;
+import com.android.internal.telephony.DataConnection.UpdateLinkPropertyResult;
+import com.android.internal.telephony.DataConnectionAc;
+import com.android.internal.telephony.DataConnectionTracker;
+import com.android.internal.telephony.DctConstants;
+import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.RetryManager;
+import com.android.internal.util.AsyncChannel;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * {@hide}
+ */
+public final class GsmDataConnectionTracker extends DataConnectionTracker {
+ protected final String LOG_TAG = "GSM";
+ private static final boolean RADIO_TESTS = false;
+
+ /**
+ * Handles changes to the APN db.
+ */
+ private class ApnChangeObserver extends ContentObserver {
+ public ApnChangeObserver () {
+ super(mDataConnectionTracker);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ sendMessage(obtainMessage(DctConstants.EVENT_APN_CHANGED));
+ }
+ }
+
+ //***** Instance Variables
+
+ private boolean mReregisterOnReconnectFailure = false;
+ private ContentResolver mResolver;
+
+ // Recovery action taken in case of data stall
+ private static class RecoveryAction {
+ public static final int GET_DATA_CALL_LIST = 0;
+ public static final int CLEANUP = 1;
+ public static final int REREGISTER = 2;
+ public static final int RADIO_RESTART = 3;
+ public static final int RADIO_RESTART_WITH_PROP = 4;
+
+ private static boolean isAggressiveRecovery(int value) {
+ return ((value == RecoveryAction.CLEANUP) ||
+ (value == RecoveryAction.REREGISTER) ||
+ (value == RecoveryAction.RADIO_RESTART) ||
+ (value == RecoveryAction.RADIO_RESTART_WITH_PROP));
+ }
+ }
+
+ public int getRecoveryAction() {
+ int action = Settings.System.getInt(mPhone.getContext().getContentResolver(),
+ "radio.data.stall.recovery.action", RecoveryAction.GET_DATA_CALL_LIST);
+ if (VDBG) log("getRecoveryAction: " + action);
+ return action;
+ }
+ public void putRecoveryAction(int action) {
+ Settings.System.putInt(mPhone.getContext().getContentResolver(),
+ "radio.data.stall.recovery.action", action);
+ if (VDBG) log("putRecoveryAction: " + action);
+ }
+
+ //***** Constants
+
+ private static final int POLL_PDP_MILLIS = 5 * 1000;
+
+ private static final String INTENT_RECONNECT_ALARM =
+ "com.android.internal.telephony.gprs-reconnect";
+ private static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "reconnect_alarm_extra_type";
+ private static final String INTENT_RECONNECT_ALARM_EXTRA_RETRY_COUNT =
+ "reconnect_alaram_extra_retry_count";
+
+ private static final String INTENT_DATA_STALL_ALARM =
+ "com.android.internal.telephony.gprs-data-stall";
+
+ static final Uri PREFERAPN_NO_UPDATE_URI =
+ Uri.parse("content://telephony/carriers/preferapn_no_update");
+ static final String APN_ID = "apn_id";
+ private boolean canSetPreferApn = false;
+
+ private static final boolean DATA_STALL_SUSPECTED = true;
+ private static final boolean DATA_STALL_NOT_SUSPECTED = false;
+
+ @Override
+ protected void onActionIntentReconnectAlarm(Intent intent) {
+ String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON);
+ int connectionId = intent.getIntExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, -1);
+ int retryCount = intent.getIntExtra(INTENT_RECONNECT_ALARM_EXTRA_RETRY_COUNT, 0);
+
+ DataConnectionAc dcac= mDataConnectionAsyncChannels.get(connectionId);
+
+ if (DBG) {
+ log("onActionIntentReconnectAlarm: mState=" + mState + " reason=" + reason +
+ " connectionId=" + connectionId + " retryCount=" + retryCount);
+ }
+
+ if (dcac != null) {
+ for (ApnContext apnContext : dcac.getApnListSync()) {
+ apnContext.setDataConnectionAc(null);
+ apnContext.setDataConnection(null);
+ apnContext.setReason(reason);
+ apnContext.setRetryCount(retryCount);
+ if (apnContext.getState() ==DctConstants.State.FAILED) {
+ apnContext.setState(DctConstants.State.IDLE);
+ }
+ sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, apnContext));
+ }
+ // Alram had expired. Clear pending intent recorded on the DataConnection.
+ dcac.setReconnectIntentSync(null);
+ }
+ }
+
+ /** Watches for changes to the APN db. */
+ private ApnChangeObserver mApnObserver;
+
+ //***** Constructor
+
+ public GsmDataConnectionTracker(PhoneBase p) {
+ super(p);
+ if (DBG) log("GsmDCT.constructor");
+ p.mCM.registerForAvailable (this, DctConstants.EVENT_RADIO_AVAILABLE, null);
+ p.mCM.registerForOffOrNotAvailable(this, DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE,
+ null);
+ p.mIccRecords.registerForRecordsLoaded(this, DctConstants.EVENT_RECORDS_LOADED, null);
+ p.mCM.registerForDataNetworkStateChanged (this, DctConstants.EVENT_DATA_STATE_CHANGED,
+ null);
+ p.getCallTracker().registerForVoiceCallEnded (this, DctConstants.EVENT_VOICE_CALL_ENDED,
+ null);
+ p.getCallTracker().registerForVoiceCallStarted (this, DctConstants.EVENT_VOICE_CALL_STARTED,
+ null);
+ p.getServiceStateTracker().registerForDataConnectionAttached(this,
+ DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null);
+ p.getServiceStateTracker().registerForDataConnectionDetached(this,
+ DctConstants.EVENT_DATA_CONNECTION_DETACHED, null);
+ p.getServiceStateTracker().registerForRoamingOn(this, DctConstants.EVENT_ROAMING_ON, null);
+ p.getServiceStateTracker().registerForRoamingOff(this, DctConstants.EVENT_ROAMING_OFF,
+ null);
+ p.getServiceStateTracker().registerForPsRestrictedEnabled(this,
+ DctConstants.EVENT_PS_RESTRICT_ENABLED, null);
+ p.getServiceStateTracker().registerForPsRestrictedDisabled(this,
+ DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
+
+ // install reconnect intent filter for this data connection.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(INTENT_DATA_STALL_ALARM);
+ p.getContext().registerReceiver(mIntentReceiver, filter, null, p);
+
+ mDataConnectionTracker = this;
+ mResolver = mPhone.getContext().getContentResolver();
+
+ mApnObserver = new ApnChangeObserver();
+ p.getContext().getContentResolver().registerContentObserver(
+ Telephony.Carriers.CONTENT_URI, true, mApnObserver);
+
+ initApnContextsAndDataConnection();
+ broadcastMessenger();
+ }
+
+ @Override
+ public void dispose() {
+ if (DBG) log("GsmDCT.dispose");
+ cleanUpAllConnections(false, null);
+
+ super.dispose();
+
+ //Unregister for all events
+ mPhone.mCM.unregisterForAvailable(this);
+ mPhone.mCM.unregisterForOffOrNotAvailable(this);
+ mPhone.mIccRecords.unregisterForRecordsLoaded(this);
+ mPhone.mCM.unregisterForDataNetworkStateChanged(this);
+ mPhone.getCallTracker().unregisterForVoiceCallEnded(this);
+ mPhone.getCallTracker().unregisterForVoiceCallStarted(this);
+ mPhone.getServiceStateTracker().unregisterForDataConnectionAttached(this);
+ mPhone.getServiceStateTracker().unregisterForDataConnectionDetached(this);
+ mPhone.getServiceStateTracker().unregisterForRoamingOn(this);
+ mPhone.getServiceStateTracker().unregisterForRoamingOff(this);
+ mPhone.getServiceStateTracker().unregisterForPsRestrictedEnabled(this);
+ mPhone.getServiceStateTracker().unregisterForPsRestrictedDisabled(this);
+
+ mPhone.getContext().getContentResolver().unregisterContentObserver(this.mApnObserver);
+ mApnContexts.clear();
+
+ destroyDataConnections();
+ }
+
+ @Override
+ public boolean isApnTypeActive(String type) {
+ ApnContext apnContext = mApnContexts.get(type);
+ if (apnContext == null) return false;
+
+ return (apnContext.getDataConnection() != null);
+ }
+
+ @Override
+ protected boolean isDataPossible(String apnType) {
+ ApnContext apnContext = mApnContexts.get(apnType);
+ if (apnContext == null) {
+ return false;
+ }
+ boolean apnContextIsEnabled = apnContext.isEnabled();
+ DctConstants.State apnContextState = apnContext.getState();
+ boolean apnTypePossible = !(apnContextIsEnabled &&
+ (apnContextState == DctConstants.State.FAILED));
+ boolean dataAllowed = isDataAllowed();
+ boolean possible = dataAllowed && apnTypePossible;
+
+ if (DBG) {
+ log(String.format("isDataPossible(%s): possible=%b isDataAllowed=%b " +
+ "apnTypePossible=%b apnContextisEnabled=%b apnContextState()=%s",
+ apnType, possible, dataAllowed, apnTypePossible,
+ apnContextIsEnabled, apnContextState));
+ }
+ return possible;
+ }
+
+ @Override
+ protected void finalize() {
+ if(DBG) log("finalize");
+ }
+
+ @Override
+ protected String getActionIntentReconnectAlarm() {
+ return INTENT_RECONNECT_ALARM;
+ }
+
+ @Override
+ protected String getActionIntentDataStallAlarm() {
+ return INTENT_DATA_STALL_ALARM;
+ }
+
+ private ApnContext addApnContext(String type) {
+ ApnContext apnContext = new ApnContext(type, LOG_TAG);
+ apnContext.setDependencyMet(false);
+ mApnContexts.put(type, apnContext);
+ return apnContext;
+ }
+
+ protected void initApnContextsAndDataConnection() {
+ boolean defaultEnabled = SystemProperties.getBoolean(DEFALUT_DATA_ON_BOOT_PROP, true);
+ // Load device network attributes from resources
+ String[] networkConfigStrings = mPhone.getContext().getResources().getStringArray(
+ com.android.internal.R.array.networkAttributes);
+ for (String networkConfigString : networkConfigStrings) {
+ NetworkConfig networkConfig = new NetworkConfig(networkConfigString);
+ ApnContext apnContext = null;
+
+ switch (networkConfig.type) {
+ case ConnectivityManager.TYPE_MOBILE:
+ apnContext = addApnContext(PhoneConstants.APN_TYPE_DEFAULT);
+ apnContext.setEnabled(defaultEnabled);
+ break;
+ case ConnectivityManager.TYPE_MOBILE_MMS:
+ apnContext = addApnContext(PhoneConstants.APN_TYPE_MMS);
+ break;
+ case ConnectivityManager.TYPE_MOBILE_SUPL:
+ apnContext = addApnContext(PhoneConstants.APN_TYPE_SUPL);
+ break;
+ case ConnectivityManager.TYPE_MOBILE_DUN:
+ apnContext = addApnContext(PhoneConstants.APN_TYPE_DUN);
+ break;
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ apnContext = addApnContext(PhoneConstants.APN_TYPE_HIPRI);
+ ApnContext defaultContext = mApnContexts.get(PhoneConstants.APN_TYPE_DEFAULT);
+ if (defaultContext != null) {
+ applyNewState(apnContext, apnContext.isEnabled(),
+ defaultContext.getDependencyMet());
+ } else {
+ // the default will set the hipri dep-met when it is created
+ }
+ continue;
+ case ConnectivityManager.TYPE_MOBILE_FOTA:
+ apnContext = addApnContext(PhoneConstants.APN_TYPE_FOTA);
+ break;
+ case ConnectivityManager.TYPE_MOBILE_IMS:
+ apnContext = addApnContext(PhoneConstants.APN_TYPE_IMS);
+ break;
+ case ConnectivityManager.TYPE_MOBILE_CBS:
+ apnContext = addApnContext(PhoneConstants.APN_TYPE_CBS);
+ break;
+ default:
+ // skip unknown types
+ continue;
+ }
+ if (apnContext != null) {
+ // set the prop, but also apply the newly set enabled and dependency values
+ onSetDependencyMet(apnContext.getApnType(), networkConfig.dependencyMet);
+ }
+ }
+ }
+
+ @Override
+ protected LinkProperties getLinkProperties(String apnType) {
+ ApnContext apnContext = mApnContexts.get(apnType);
+ if (apnContext != null) {
+ DataConnectionAc dcac = apnContext.getDataConnectionAc();
+ if (dcac != null) {
+ if (DBG) log("return link properites for " + apnType);
+ return dcac.getLinkPropertiesSync();
+ }
+ }
+ if (DBG) log("return new LinkProperties");
+ return new LinkProperties();
+ }
+
+ @Override
+ protected LinkCapabilities getLinkCapabilities(String apnType) {
+ ApnContext apnContext = mApnContexts.get(apnType);
+ if (apnContext!=null) {
+ DataConnectionAc dataConnectionAc = apnContext.getDataConnectionAc();
+ if (dataConnectionAc != null) {
+ if (DBG) log("get active pdp is not null, return link Capabilities for " + apnType);
+ return dataConnectionAc.getLinkCapabilitiesSync();
+ }
+ }
+ if (DBG) log("return new LinkCapabilities");
+ return new LinkCapabilities();
+ }
+
+ @Override
+ // Return all active apn types
+ public String[] getActiveApnTypes() {
+ if (DBG) log("get all active apn types");
+ ArrayList<String> result = new ArrayList<String>();
+
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.isReady()) {
+ result.add(apnContext.getApnType());
+ }
+ }
+
+ return (String[])result.toArray(new String[0]);
+ }
+
+ @Override
+ // Return active apn of specific apn type
+ public String getActiveApnString(String apnType) {
+ if (DBG) log( "get active apn string for type:" + apnType);
+ ApnContext apnContext = mApnContexts.get(apnType);
+ if (apnContext != null) {
+ ApnSetting apnSetting = apnContext.getApnSetting();
+ if (apnSetting != null) {
+ return apnSetting.apn;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isApnTypeEnabled(String apnType) {
+ ApnContext apnContext = mApnContexts.get(apnType);
+ if (apnContext == null) {
+ return false;
+ }
+ return apnContext.isEnabled();
+ }
+
+ @Override
+ protected void setState(DctConstants.State s) {
+ if (DBG) log("setState should not be used in GSM" + s);
+ }
+
+ // Return state of specific apn type
+ @Override
+ public DctConstants.State getState(String apnType) {
+ ApnContext apnContext = mApnContexts.get(apnType);
+ if (apnContext != null) {
+ return apnContext.getState();
+ }
+ return DctConstants.State.FAILED;
+ }
+
+ // Return state of overall
+ public DctConstants.State getOverallState() {
+ boolean isConnecting = false;
+ boolean isFailed = true; // All enabled Apns should be FAILED.
+ boolean isAnyEnabled = false;
+
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.isEnabled()) {
+ isAnyEnabled = true;
+ switch (apnContext.getState()) {
+ case CONNECTED:
+ case DISCONNECTING:
+ if (DBG) log("overall state is CONNECTED");
+ return DctConstants.State.CONNECTED;
+ case CONNECTING:
+ case INITING:
+ isConnecting = true;
+ isFailed = false;
+ break;
+ case IDLE:
+ case SCANNING:
+ isFailed = false;
+ break;
+ }
+ }
+ }
+
+ if (!isAnyEnabled) { // Nothing enabled. return IDLE.
+ if (DBG) log( "overall state is IDLE");
+ return DctConstants.State.IDLE;
+ }
+
+ if (isConnecting) {
+ if (DBG) log( "overall state is CONNECTING");
+ return DctConstants.State.CONNECTING;
+ } else if (!isFailed) {
+ if (DBG) log( "overall state is IDLE");
+ return DctConstants.State.IDLE;
+ } else {
+ if (DBG) log( "overall state is FAILED");
+ return DctConstants.State.FAILED;
+ }
+ }
+
+ /**
+ * Ensure that we are connected to an APN of the specified type.
+ *
+ * @param type the APN type
+ * @return Success is indicated by {@code PhoneConstants.APN_ALREADY_ACTIVE} or
+ * {@code PhoneConstants.APN_REQUEST_STARTED}. In the latter case, a
+ * broadcast will be sent by the ConnectivityManager when a
+ * connection to the APN has been established.
+ */
+ @Override
+ public synchronized int enableApnType(String apnType) {
+ ApnContext apnContext = mApnContexts.get(apnType);
+ if (apnContext == null || !isApnTypeAvailable(apnType)) {
+ if (DBG) log("enableApnType: " + apnType + " is type not available");
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ }
+
+ // If already active, return
+ if (DBG) log("enableApnType: " + apnType + " mState(" + apnContext.getState() + ")");
+
+ if (apnContext.getState() == DctConstants.State.CONNECTED) {
+ if (DBG) log("enableApnType: return APN_ALREADY_ACTIVE");
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ }
+ setEnabled(apnTypeToId(apnType), true);
+ if (DBG) {
+ log("enableApnType: new apn request for type " + apnType +
+ " return APN_REQUEST_STARTED");
+ }
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+
+ // A new APN has gone active and needs to send events to catch up with the
+ // current condition
+ private void notifyApnIdUpToCurrent(String reason, ApnContext apnContext, String type) {
+ switch (apnContext.getState()) {
+ case IDLE:
+ case INITING:
+ break;
+ case CONNECTING:
+ case SCANNING:
+ mPhone.notifyDataConnection(reason, type, PhoneConstants.DataState.CONNECTING);
+ break;
+ case CONNECTED:
+ case DISCONNECTING:
+ mPhone.notifyDataConnection(reason, type, PhoneConstants.DataState.CONNECTING);
+ mPhone.notifyDataConnection(reason, type, PhoneConstants.DataState.CONNECTED);
+ break;
+ }
+ }
+
+ @Override
+ public synchronized int disableApnType(String type) {
+ if (DBG) log("disableApnType:" + type);
+ ApnContext apnContext = mApnContexts.get(type);
+
+ if (apnContext != null) {
+ setEnabled(apnTypeToId(type), false);
+ if (apnContext.getState() != DctConstants.State.IDLE && apnContext.getState()
+ != DctConstants.State.FAILED) {
+ if (DBG) log("diableApnType: return APN_REQUEST_STARTED");
+ return PhoneConstants.APN_REQUEST_STARTED;
+ } else {
+ if (DBG) log("disableApnType: return APN_ALREADY_INACTIVE");
+ return PhoneConstants.APN_ALREADY_INACTIVE;
+ }
+
+ } else {
+ if (DBG) {
+ log("disableApnType: no apn context was found, return APN_REQUEST_FAILED");
+ }
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+ }
+
+ @Override
+ protected boolean isApnTypeAvailable(String type) {
+ if (type.equals(PhoneConstants.APN_TYPE_DUN) && fetchDunApn() != null) {
+ return true;
+ }
+
+ if (mAllApns != null) {
+ for (ApnSetting apn : mAllApns) {
+ if (apn.canHandleType(type)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Report on whether data connectivity is enabled for any APN.
+ * @return {@code false} if data connectivity has been explicitly disabled,
+ * {@code true} otherwise.
+ */
+ @Override
+ public boolean getAnyDataEnabled() {
+ synchronized (mDataEnabledLock) {
+ if (!(mInternalDataEnabled && mUserDataEnabled && sPolicyDataEnabled)) return false;
+ for (ApnContext apnContext : mApnContexts.values()) {
+ // Make sure we dont have a context that going down
+ // and is explicitly disabled.
+ if (isDataAllowed(apnContext)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private boolean isDataAllowed(ApnContext apnContext) {
+ return apnContext.isReady() && isDataAllowed();
+ }
+
+ //****** Called from ServiceStateTracker
+ /**
+ * Invoked when ServiceStateTracker observes a transition from GPRS
+ * attach to detach.
+ */
+ protected void onDataConnectionDetached() {
+ /*
+ * We presently believe it is unnecessary to tear down the PDP context
+ * when GPRS detaches, but we should stop the network polling.
+ */
+ if (DBG) log ("onDataConnectionDetached: stop polling and notify detached");
+ stopNetStatPoll();
+ stopDataStallAlarm();
+ notifyDataConnection(Phone.REASON_DATA_DETACHED);
+ }
+
+ private void onDataConnectionAttached() {
+ if (DBG) log("onDataConnectionAttached");
+ if (getOverallState() == DctConstants.State.CONNECTED) {
+ if (DBG) log("onDataConnectionAttached: start polling notify attached");
+ startNetStatPoll();
+ startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
+ notifyDataConnection(Phone.REASON_DATA_ATTACHED);
+ } else {
+ // update APN availability so that APN can be enabled.
+ notifyOffApnsOfAvailability(Phone.REASON_DATA_ATTACHED);
+ }
+
+ setupDataOnReadyApns(Phone.REASON_DATA_ATTACHED);
+ }
+
+ @Override
+ protected boolean isDataAllowed() {
+ final boolean internalDataEnabled;
+ synchronized (mDataEnabledLock) {
+ internalDataEnabled = mInternalDataEnabled;
+ }
+
+ int gprsState = mPhone.getServiceStateTracker().getCurrentDataConnectionState();
+ boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
+
+ boolean allowed =
+ (gprsState == ServiceState.STATE_IN_SERVICE || mAutoAttachOnCreation) &&
+ mPhone.mIccRecords.getRecordsLoaded() &&
+ (mPhone.getState() == PhoneConstants.State.IDLE ||
+ mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) &&
+ internalDataEnabled &&
+ (!mPhone.getServiceState().getRoaming() || getDataOnRoamingEnabled()) &&
+ !mIsPsRestricted &&
+ desiredPowerState;
+ if (!allowed && DBG) {
+ String reason = "";
+ if (!((gprsState == ServiceState.STATE_IN_SERVICE) || mAutoAttachOnCreation)) {
+ reason += " - gprs= " + gprsState;
+ }
+ if (!mPhone.mIccRecords.getRecordsLoaded()) reason += " - SIM not loaded";
+ if (mPhone.getState() != PhoneConstants.State.IDLE &&
+ !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
+ reason += " - PhoneState= " + mPhone.getState();
+ reason += " - Concurrent voice and data not allowed";
+ }
+ if (!internalDataEnabled) reason += " - mInternalDataEnabled= false";
+ if (mPhone.getServiceState().getRoaming() && !getDataOnRoamingEnabled()) {
+ reason += " - Roaming and data roaming not enabled";
+ }
+ if (mIsPsRestricted) reason += " - mIsPsRestricted= true";
+ if (!desiredPowerState) reason += " - desiredPowerState= false";
+ if (DBG) log("isDataAllowed: not allowed due to" + reason);
+ }
+ return allowed;
+ }
+
+ private void setupDataOnReadyApns(String reason) {
+ // Stop reconnect alarms on all data connections pending
+ // retry. Reset ApnContext state to IDLE.
+ for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+ if (dcac.getReconnectIntentSync() != null) {
+ cancelReconnectAlarm(dcac);
+ }
+ // update retry config for existing calls to match up
+ // ones for the new RAT.
+ if (dcac.dataConnection != null) {
+ Collection<ApnContext> apns = dcac.getApnListSync();
+
+ boolean hasDefault = false;
+ for (ApnContext apnContext : apns) {
+ if (apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DEFAULT)) {
+ hasDefault = true;
+ break;
+ }
+ }
+ configureRetry(dcac.dataConnection, hasDefault, 0);
+ }
+ }
+
+ // Be sure retry counts for Apncontexts and DC's are sync'd.
+ // When DCT/ApnContexts are refactored and we cleanup retrying
+ // this won't be needed.
+ resetAllRetryCounts();
+
+ // Only check for default APN state
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.getState() == DctConstants.State.FAILED) {
+ // By this time, alarms for all failed Apns
+ // should be stopped if any.
+ // Make sure to set the state back to IDLE
+ // so that setup data can happen.
+ apnContext.setState(DctConstants.State.IDLE);
+ }
+ if (apnContext.isReady()) {
+ if (apnContext.getState() == DctConstants.State.IDLE) {
+ apnContext.setReason(reason);
+ trySetupData(apnContext);
+ }
+ }
+ }
+ }
+
+ private boolean trySetupData(String reason, String type) {
+ if (DBG) {
+ log("trySetupData: " + type + " due to " + (reason == null ? "(unspecified)" : reason)
+ + " isPsRestricted=" + mIsPsRestricted);
+ }
+
+ if (type == null) {
+ type = PhoneConstants.APN_TYPE_DEFAULT;
+ }
+
+ ApnContext apnContext = mApnContexts.get(type);
+
+ if (apnContext == null ){
+ if (DBG) log("trySetupData new apn context for type:" + type);
+ apnContext = new ApnContext(type, LOG_TAG);
+ mApnContexts.put(type, apnContext);
+ }
+ apnContext.setReason(reason);
+
+ return trySetupData(apnContext);
+ }
+
+ private boolean trySetupData(ApnContext apnContext) {
+ if (DBG) {
+ log("trySetupData for type:" + apnContext.getApnType() +
+ " due to " + apnContext.getReason());
+ log("trySetupData with mIsPsRestricted=" + mIsPsRestricted);
+ }
+
+ if (mPhone.getSimulatedRadioControl() != null) {
+ // Assume data is connected on the simulator
+ // FIXME this can be improved
+ apnContext.setState(DctConstants.State.CONNECTED);
+ mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
+
+ log("trySetupData: (fix?) We're on the simulator; assuming data is connected");
+ return true;
+ }
+
+ boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
+
+ if ((apnContext.getState() == DctConstants.State.IDLE ||
+ apnContext.getState() == DctConstants.State.SCANNING) &&
+ isDataAllowed(apnContext) && getAnyDataEnabled() && !isEmergency()) {
+
+ if (apnContext.getState() == DctConstants.State.IDLE) {
+ ArrayList<ApnSetting> waitingApns = buildWaitingApns(apnContext.getApnType());
+ if (waitingApns.isEmpty()) {
+ if (DBG) log("trySetupData: No APN found");
+ notifyNoData(GsmDataConnection.FailCause.MISSING_UNKNOWN_APN, apnContext);
+ notifyOffApnsOfAvailability(apnContext.getReason());
+ return false;
+ } else {
+ apnContext.setWaitingApns(waitingApns);
+ if (DBG) {
+ log ("trySetupData: Create from mAllApns : " + apnListToString(mAllApns));
+ }
+ }
+ }
+
+ if (DBG) {
+ log ("Setup watingApns : " + apnListToString(apnContext.getWaitingApns()));
+ }
+ // apnContext.setReason(apnContext.getReason());
+ boolean retValue = setupData(apnContext);
+ notifyOffApnsOfAvailability(apnContext.getReason());
+ return retValue;
+ } else {
+ // TODO: check the condition.
+ if (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DEFAULT)
+ && (apnContext.getState() == DctConstants.State.IDLE
+ || apnContext.getState() == DctConstants.State.SCANNING))
+ mPhone.notifyDataConnectionFailed(apnContext.getReason(), apnContext.getApnType());
+ notifyOffApnsOfAvailability(apnContext.getReason());
+ return false;
+ }
+ }
+
+ @Override
+ // Disabled apn's still need avail/unavail notificiations - send them out
+ protected void notifyOffApnsOfAvailability(String reason) {
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (!apnContext.isReady()) {
+ if (DBG) log("notifyOffApnOfAvailability type:" + apnContext.getApnType());
+ mPhone.notifyDataConnection(reason != null ? reason : apnContext.getReason(),
+ apnContext.getApnType(),
+ PhoneConstants.DataState.DISCONNECTED);
+ } else {
+ if (DBG) {
+ log("notifyOffApnsOfAvailability skipped apn due to isReady==false: " +
+ apnContext.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 GsmDataConnection should be
+ * disconnected.
+ * @param reason reason for the clean up.
+ */
+ protected void cleanUpAllConnections(boolean tearDown, String reason) {
+ if (DBG) log("cleanUpAllConnections: tearDown=" + tearDown + " reason=" + reason);
+
+ for (ApnContext apnContext : mApnContexts.values()) {
+ apnContext.setReason(reason);
+ cleanUpConnection(tearDown, apnContext);
+ }
+
+ stopNetStatPoll();
+ stopDataStallAlarm();
+
+ // TODO: Do we need mRequestedApnType?
+ mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+ }
+
+ /**
+ * Cleanup all connections.
+ *
+ * TODO: Cleanup only a specified connection passed as a parameter.
+ * Also, make sure when you clean up a conn, if it is last apply
+ * logic as though it is cleanupAllConnections
+ *
+ * @param tearDown true if the underlying DataConnection should be disconnected.
+ * @param reason for the clean up.
+ */
+
+ @Override
+ protected void onCleanUpAllConnections(String cause) {
+ cleanUpAllConnections(true, cause);
+ }
+
+ private void cleanUpConnection(boolean tearDown, ApnContext apnContext) {
+
+ if (apnContext == null) {
+ if (DBG) log("cleanUpConnection: apn context is null");
+ return;
+ }
+
+ DataConnectionAc dcac = apnContext.getDataConnectionAc();
+ if (DBG) {
+ log("cleanUpConnection: E tearDown=" + tearDown + " reason=" + apnContext.getReason() +
+ " apnContext=" + apnContext);
+ }
+ if (tearDown) {
+ if (apnContext.isDisconnected()) {
+ // The request is tearDown and but ApnContext is not connected.
+ // If apnContext is not enabled anymore, break the linkage to the DCAC/DC.
+ apnContext.setState(DctConstants.State.IDLE);
+ if (!apnContext.isReady()) {
+ apnContext.setDataConnection(null);
+ apnContext.setDataConnectionAc(null);
+ }
+ } else {
+ // Connection is still there. Try to clean up.
+ if (dcac != null) {
+ if (apnContext.getState() != DctConstants.State.DISCONNECTING) {
+ boolean disconnectAll = false;
+ if (PhoneConstants.APN_TYPE_DUN.equals(apnContext.getApnType())) {
+ ApnSetting dunSetting = fetchDunApn();
+ if (dunSetting != null &&
+ dunSetting.equals(apnContext.getApnSetting())) {
+ if (DBG) log("tearing down dedicated DUN connection");
+ // we need to tear it down - we brought it up just for dun and
+ // other people are camped on it and now dun is done. We need
+ // to stop using it and let the normal apn list get used to find
+ // connections for the remaining desired connections
+ disconnectAll = true;
+ }
+ }
+ if (DBG) {
+ log("cleanUpConnection: tearing down" + (disconnectAll ? " all" :""));
+ }
+ Message msg = obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, apnContext);
+ if (disconnectAll) {
+ apnContext.getDataConnection().tearDownAll(apnContext.getReason(), msg);
+ } else {
+ apnContext.getDataConnection().tearDown(apnContext.getReason(), msg);
+ }
+ apnContext.setState(DctConstants.State.DISCONNECTING);
+ }
+ } else {
+ // apn is connected but no reference to dcac.
+ // Should not be happen, but reset the state in case.
+ apnContext.setState(DctConstants.State.IDLE);
+ mPhone.notifyDataConnection(apnContext.getReason(),
+ apnContext.getApnType());
+ }
+ }
+ } else {
+ // force clean up the data connection.
+ if (dcac != null) dcac.resetSync();
+ apnContext.setState(DctConstants.State.IDLE);
+ mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
+ apnContext.setDataConnection(null);
+ apnContext.setDataConnectionAc(null);
+ }
+
+ // make sure reconnection alarm is cleaned up if there is no ApnContext
+ // associated to the connection.
+ if (dcac != null) {
+ Collection<ApnContext> apnList = dcac.getApnListSync();
+ if (apnList.isEmpty()) {
+ cancelReconnectAlarm(dcac);
+ }
+ }
+ if (DBG) {
+ log("cleanUpConnection: X tearDown=" + tearDown + " reason=" + apnContext.getReason() +
+ " apnContext=" + apnContext + " dc=" + apnContext.getDataConnection());
+ }
+ }
+
+ /**
+ * Cancels the alarm associated with DCAC.
+ *
+ * @param DataConnectionAc on which the alarm should be stopped.
+ */
+ private void cancelReconnectAlarm(DataConnectionAc dcac) {
+ if (dcac == null) return;
+
+ PendingIntent intent = dcac.getReconnectIntentSync();
+
+ if (intent != null) {
+ AlarmManager am =
+ (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+ am.cancel(intent);
+ dcac.setReconnectIntentSync(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] = PhoneConstants.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.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),
+ NetworkUtils.trimV4AddrZeros(
+ cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY))),
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT)),
+ NetworkUtils.trimV4AddrZeros(
+ cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC))),
+ NetworkUtils.trimV4AddrZeros(
+ 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)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)),
+ types,
+ cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL)),
+ cursor.getString(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.ROAMING_PROTOCOL)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.CARRIER_ENABLED)) == 1,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.BEARER)));
+ result.add(apn);
+ } while (cursor.moveToNext());
+ }
+ if (DBG) log("createApnList: X result=" + result);
+ return result;
+ }
+
+ private boolean dataConnectionNotInUse(DataConnectionAc dcac) {
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.getDataConnectionAc() == dcac) return false;
+ }
+ return true;
+ }
+
+ private GsmDataConnection findFreeDataConnection() {
+ for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+ if (dcac.isInactiveSync() && dataConnectionNotInUse(dcac)) {
+ DataConnection dc = dcac.dataConnection;
+ if (DBG) {
+ log("findFreeDataConnection: found free GsmDataConnection=" +
+ " dcac=" + dcac + " dc=" + dc);
+ }
+ return (GsmDataConnection) dc;
+ }
+ }
+ log("findFreeDataConnection: NO free GsmDataConnection");
+ return null;
+ }
+
+ protected GsmDataConnection findReadyDataConnection(ApnSetting apn) {
+ if (apn == null) {
+ return null;
+ }
+ if (DBG) {
+ log("findReadyDataConnection: apn string <" + apn + ">" +
+ " dcacs.size=" + mDataConnectionAsyncChannels.size());
+ }
+ for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+ ApnSetting apnSetting = dcac.getApnSettingSync();
+ if (DBG) {
+ log("findReadyDataConnection: dc apn string <" +
+ (apnSetting != null ? (apnSetting.toString()) : "null") + ">");
+ }
+ if ((apnSetting != null) && TextUtils.equals(apnSetting.toString(), apn.toString())) {
+ DataConnection dc = dcac.dataConnection;
+ if (DBG) {
+ log("findReadyDataConnection: found ready GsmDataConnection=" +
+ " dcac=" + dcac + " dc=" + dc);
+ }
+ return (GsmDataConnection) dc;
+ }
+ }
+ return null;
+ }
+
+
+ private boolean setupData(ApnContext apnContext) {
+ if (DBG) log("setupData: apnContext=" + apnContext);
+ ApnSetting apn;
+ GsmDataConnection dc;
+
+ int profileId = getApnProfileID(apnContext.getApnType());
+ apn = apnContext.getNextWaitingApn();
+ if (apn == null) {
+ if (DBG) log("setupData: return for no apn found!");
+ return false;
+ }
+
+
+ dc = (GsmDataConnection) checkForConnectionForApnContext(apnContext);
+
+ if (dc == null) {
+ dc = findReadyDataConnection(apn);
+
+ if (dc == null) {
+ if (DBG) log("setupData: No ready GsmDataConnection found!");
+ // TODO: When allocating you are mapping type to id. If more than 1 free,
+ // then could findFreeDataConnection get the wrong one??
+ dc = findFreeDataConnection();
+ }
+
+ if (dc == null) {
+ dc = createDataConnection();
+ }
+
+ if (dc == null) {
+ if (DBG) log("setupData: No free GsmDataConnection found!");
+ return false;
+ }
+ } else {
+ apn = mDataConnectionAsyncChannels.get(dc.getDataConnectionId()).getApnSettingSync();
+ }
+
+ DataConnectionAc dcac = mDataConnectionAsyncChannels.get(dc.getDataConnectionId());
+ dc.setProfileId( profileId ); // assumed no connection sharing on profiled types
+
+ int refCount = dcac.getRefCountSync();
+ if (DBG) log("setupData: init dc and apnContext refCount=" + refCount);
+
+ // configure retry count if no other Apn is using the same connection.
+ if (refCount == 0) {
+ configureRetry(dc, apn.canHandleType(PhoneConstants.APN_TYPE_DEFAULT),
+ apnContext.getRetryCount());
+ }
+ apnContext.setDataConnectionAc(dcac);
+ apnContext.setDataConnection(dc);
+
+ apnContext.setApnSetting(apn);
+ apnContext.setState(DctConstants.State.INITING);
+ mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
+ // If reconnect alarm is active on this DataConnection, wait for the alarm being
+ // fired so that we don't disruppt data retry pattern engaged.
+ if (apnContext.getDataConnectionAc().getReconnectIntentSync() != null) {
+ if (DBG) log("setupData: data reconnection pending");
+ apnContext.setState(DctConstants.State.FAILED);
+ mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
+ return true;
+ }
+
+ Message msg = obtainMessage();
+ msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
+ msg.obj = apnContext;
+ dc.bringUp(msg, apn);
+
+ if (DBG) log("setupData: initing!");
+ return true;
+ }
+
+ /**
+ * Handles changes to the APN database.
+ */
+ private void onApnChanged() {
+ DctConstants.State overallState = getOverallState();
+ boolean isDisconnected = (overallState == DctConstants.State.IDLE ||
+ overallState == DctConstants.State.FAILED);
+
+ if (mPhone instanceof GSMPhone) {
+ // The "current" may no longer be valid. MMS depends on this to send properly. TBD
+ ((GSMPhone)mPhone).updateCurrentCarrierInProvider();
+ }
+
+ // TODO: It'd be nice to only do this if the changed entrie(s)
+ // match the current operator.
+ if (DBG) log("onApnChanged: createAllApnList and cleanUpAllConnections");
+ createAllApnList();
+ cleanUpAllConnections(!isDisconnected, Phone.REASON_APN_CHANGED);
+ if (isDisconnected) {
+ setupDataOnReadyApns(Phone.REASON_APN_CHANGED);
+ }
+ }
+
+ /**
+ * @param cid Connection id provided from RIL.
+ * @return DataConnectionAc associated with specified cid.
+ */
+ private DataConnectionAc findDataConnectionAcByCid(int cid) {
+ for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+ if (dcac.getCidSync() == cid) {
+ return dcac;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param dcacs Collection of DataConnectionAc reported from RIL.
+ * @return List of ApnContext which is connected, but is not present in
+ * data connection list reported from RIL.
+ */
+ private List<ApnContext> findApnContextToClean(Collection<DataConnectionAc> dcacs) {
+ if (dcacs == null) return null;
+
+ if (DBG) log("findApnContextToClean(ar): E dcacs=" + dcacs);
+
+ ArrayList<ApnContext> list = new ArrayList<ApnContext>();
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.getState() == DctConstants.State.CONNECTED) {
+ boolean found = false;
+ for (DataConnectionAc dcac : dcacs) {
+ if (dcac == apnContext.getDataConnectionAc()) {
+ // ApnContext holds the ref to dcac present in data call list.
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // ApnContext does not have dcac reported in data call list.
+ // Fetch all the ApnContexts that map to this dcac which are in
+ // INITING state too.
+ if (DBG) log("findApnContextToClean(ar): Connected apn not found in the list (" +
+ apnContext.toString() + ")");
+ if (apnContext.getDataConnectionAc() != null) {
+ list.addAll(apnContext.getDataConnectionAc().getApnListSync());
+ } else {
+ list.add(apnContext);
+ }
+ }
+ }
+ }
+ if (DBG) log("findApnContextToClean(ar): X list=" + list);
+ return list;
+ }
+
+ /**
+ * @param ar is the result of RIL_REQUEST_DATA_CALL_LIST
+ * or RIL_UNSOL_DATA_CALL_LIST_CHANGED
+ */
+ private void onDataStateChanged (AsyncResult ar) {
+ ArrayList<DataCallState> dataCallStates;
+
+ if (DBG) log("onDataStateChanged(ar): E");
+ dataCallStates = (ArrayList<DataCallState>)(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
+ if (DBG) log("onDataStateChanged(ar): exception; likely radio not available, ignore");
+ return;
+ }
+ if (DBG) log("onDataStateChanged(ar): DataCallState size=" + dataCallStates.size());
+
+ // Create a hash map to store the dataCallState of each DataConnectionAc
+ HashMap<DataCallState, DataConnectionAc> dataCallStateToDcac;
+ dataCallStateToDcac = new HashMap<DataCallState, DataConnectionAc>();
+ for (DataCallState dataCallState : dataCallStates) {
+ DataConnectionAc dcac = findDataConnectionAcByCid(dataCallState.cid);
+
+ if (dcac != null) dataCallStateToDcac.put(dataCallState, dcac);
+ }
+
+ // A list of apns to cleanup, those that aren't in the list we know we have to cleanup
+ List<ApnContext> apnsToCleanup = findApnContextToClean(dataCallStateToDcac.values());
+
+ // Find which connections have changed state and send a notification or cleanup
+ for (DataCallState newState : dataCallStates) {
+ DataConnectionAc dcac = dataCallStateToDcac.get(newState);
+
+ if (dcac == null) {
+ loge("onDataStateChanged(ar): No associated DataConnection ignore");
+ continue;
+ }
+
+ // The list of apn's associated with this DataConnection
+ Collection<ApnContext> apns = dcac.getApnListSync();
+
+ // Find which ApnContexts of this DC are in the "Connected/Connecting" state.
+ ArrayList<ApnContext> connectedApns = new ArrayList<ApnContext>();
+ for (ApnContext apnContext : apns) {
+ if (apnContext.getState() == DctConstants.State.CONNECTED ||
+ apnContext.getState() == DctConstants.State.CONNECTING ||
+ apnContext.getState() == DctConstants.State.INITING) {
+ connectedApns.add(apnContext);
+ }
+ }
+ if (connectedApns.size() == 0) {
+ if (DBG) log("onDataStateChanged(ar): no connected apns");
+ } else {
+ // Determine if the connection/apnContext should be cleaned up
+ // or just a notification should be sent out.
+ if (DBG) log("onDataStateChanged(ar): Found ConnId=" + newState.cid
+ + " newState=" + newState.toString());
+ if (newState.active == 0) {
+ if (DBG) {
+ log("onDataStateChanged(ar): inactive, cleanup apns=" + connectedApns);
+ }
+ apnsToCleanup.addAll(connectedApns);
+ } else {
+ // Its active so update the DataConnections link properties
+ UpdateLinkPropertyResult result =
+ dcac.updateLinkPropertiesDataCallStateSync(newState);
+ if (result.oldLp.equals(result.newLp)) {
+ if (DBG) log("onDataStateChanged(ar): no change");
+ } else {
+ if (result.oldLp.isIdenticalInterfaceName(result.newLp)) {
+ if (! result.oldLp.isIdenticalDnses(result.newLp) ||
+ ! result.oldLp.isIdenticalRoutes(result.newLp) ||
+ ! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
+ ! result.oldLp.isIdenticalAddresses(result.newLp)) {
+ // If the same address type was removed and added we need to cleanup
+ CompareResult<LinkAddress> car =
+ result.oldLp.compareAddresses(result.newLp);
+ if (DBG) {
+ log("onDataStateChanged: oldLp=" + result.oldLp +
+ " newLp=" + result.newLp + " car=" + car);
+ }
+ boolean needToClean = false;
+ for (LinkAddress added : car.added) {
+ for (LinkAddress removed : car.removed) {
+ if (NetworkUtils.addressTypeMatches(removed.getAddress(),
+ added.getAddress())) {
+ needToClean = true;
+ break;
+ }
+ }
+ }
+ if (needToClean) {
+ if (DBG) {
+ log("onDataStateChanged(ar): addr change, cleanup apns=" +
+ connectedApns + " oldLp=" + result.oldLp +
+ " newLp=" + result.newLp);
+ }
+ apnsToCleanup.addAll(connectedApns);
+ } else {
+ if (DBG) log("onDataStateChanged(ar): simple change");
+ for (ApnContext apnContext : connectedApns) {
+ mPhone.notifyDataConnection(
+ PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
+ apnContext.getApnType());
+ }
+ }
+ } else {
+ if (DBG) {
+ log("onDataStateChanged(ar): no changes");
+ }
+ }
+ } else {
+ if (DBG) {
+ log("onDataStateChanged(ar): interface change, cleanup apns="
+ + connectedApns);
+ }
+ apnsToCleanup.addAll(connectedApns);
+ }
+ }
+ }
+ }
+ }
+
+ if (apnsToCleanup.size() != 0) {
+ // Add an event log when the network drops PDP
+ int cid = getCellLocationId();
+ EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cid,
+ TelephonyManager.getDefault().getNetworkType());
+ }
+
+ // Cleanup those dropped connections
+ if (DBG) log("onDataStateChange(ar): apnsToCleanup=" + apnsToCleanup);
+ for (ApnContext apnContext : apnsToCleanup) {
+ cleanUpConnection(true, apnContext);
+ }
+
+ if (DBG) log("onDataStateChanged(ar): X");
+ }
+
+ private void notifyDefaultData(ApnContext apnContext) {
+ if (DBG) {
+ log("notifyDefaultData: type=" + apnContext.getApnType()
+ + ", reason:" + apnContext.getReason());
+ }
+ apnContext.setState(DctConstants.State.CONNECTED);
+ // setState(DctConstants.State.CONNECTED);
+ mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
+ startNetStatPoll();
+ startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
+ // reset reconnect timer
+ apnContext.setRetryCount(0);
+ }
+
+ // TODO: For multiple Active APNs not exactly sure how to do this.
+ protected void gotoIdleAndNotifyDataConnection(String reason) {
+ if (DBG) log("gotoIdleAndNotifyDataConnection: reason=" + reason);
+ notifyDataConnection(reason);
+ mActiveApn = null;
+ }
+
+ private void resetPollStats() {
+ mTxPkts = -1;
+ mRxPkts = -1;
+ mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
+ }
+
+ private void doRecovery() {
+ if (getOverallState() == DctConstants.State.CONNECTED) {
+ // Go through a series of recovery steps, each action transitions to the next action
+ int recoveryAction = getRecoveryAction();
+ switch (recoveryAction) {
+ case RecoveryAction.GET_DATA_CALL_LIST:
+ EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_GET_DATA_CALL_LIST,
+ mSentSinceLastRecv);
+ if (DBG) log("doRecovery() get data call list");
+ mPhone.mCM.getDataCallList(obtainMessage(DctConstants.EVENT_DATA_STATE_CHANGED));
+ putRecoveryAction(RecoveryAction.CLEANUP);
+ break;
+ case RecoveryAction.CLEANUP:
+ EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP, mSentSinceLastRecv);
+ if (DBG) log("doRecovery() cleanup all connections");
+ cleanUpAllConnections(true, Phone.REASON_PDP_RESET);
+ putRecoveryAction(RecoveryAction.REREGISTER);
+ break;
+ case RecoveryAction.REREGISTER:
+ EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_REREGISTER,
+ mSentSinceLastRecv);
+ if (DBG) log("doRecovery() re-register");
+ mPhone.getServiceStateTracker().reRegisterNetwork(null);
+ putRecoveryAction(RecoveryAction.RADIO_RESTART);
+ break;
+ case RecoveryAction.RADIO_RESTART:
+ EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
+ mSentSinceLastRecv);
+ if (DBG) log("restarting radio");
+ putRecoveryAction(RecoveryAction.RADIO_RESTART_WITH_PROP);
+ restartRadio();
+ break;
+ case RecoveryAction.RADIO_RESTART_WITH_PROP:
+ // This is in case radio restart has not recovered the data.
+ // It will set an additional "gsm.radioreset" property to tell
+ // RIL or system to take further action.
+ // The implementation of hard reset recovery action is up to OEM product.
+ // Once gsm.radioreset property is consumed, it is expected to set back
+ // to false by RIL.
+ EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART_WITH_PROP, -1);
+ if (DBG) log("restarting radio with gsm.radioreset to true");
+ SystemProperties.set("gsm.radioreset", "true");
+ // give 1 sec so property change can be notified.
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {}
+ restartRadio();
+ putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
+ break;
+ default:
+ throw new RuntimeException("doRecovery: Invalid recoveryAction=" +
+ recoveryAction);
+ }
+ }
+ }
+
+ @Override
+ protected void startNetStatPoll() {
+ if (getOverallState() == DctConstants.State.CONNECTED && mNetStatPollEnabled == false) {
+ if (DBG) log("startNetStatPoll");
+ resetPollStats();
+ mNetStatPollEnabled = true;
+ mPollNetStat.run();
+ }
+ }
+
+ @Override
+ protected void stopNetStatPoll() {
+ mNetStatPollEnabled = false;
+ removeCallbacks(mPollNetStat);
+ if (DBG) log("stopNetStatPoll");
+ }
+
+ @Override
+ protected void restartRadio() {
+ if (DBG) log("restartRadio: ************TURN OFF RADIO**************");
+ cleanUpAllConnections(true, Phone.REASON_RADIO_TURNED_OFF);
+ mPhone.getServiceStateTracker().powerOffRadioSafely(this);
+ /* 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));
+ }
+
+
+ private void updateDataStallInfo() {
+ long sent, received;
+
+ TxRxSum preTxRxSum = new TxRxSum(mDataStallTxRxSum);
+ mDataStallTxRxSum.updateTxRxSum();
+
+ if (VDBG) {
+ log("updateDataStallInfo: mDataStallTxRxSum=" + mDataStallTxRxSum +
+ " preTxRxSum=" + preTxRxSum);
+ }
+
+ sent = mDataStallTxRxSum.txPkts - preTxRxSum.txPkts;
+ received = mDataStallTxRxSum.rxPkts - preTxRxSum.rxPkts;
+
+ if (RADIO_TESTS) {
+ if (SystemProperties.getBoolean("radio.test.data.stall", false)) {
+ log("updateDataStallInfo: radio.test.data.stall true received = 0;");
+ received = 0;
+ }
+ }
+ if ( sent > 0 && received > 0 ) {
+ if (VDBG) log("updateDataStallInfo: IN/OUT");
+ mSentSinceLastRecv = 0;
+ putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
+ } else if (sent > 0 && received == 0) {
+ if (mPhone.getState() == PhoneConstants.State.IDLE) {
+ mSentSinceLastRecv += sent;
+ } else {
+ mSentSinceLastRecv = 0;
+ }
+ if (DBG) {
+ log("updateDataStallInfo: OUT sent=" + sent +
+ " mSentSinceLastRecv=" + mSentSinceLastRecv);
+ }
+ } else if (sent == 0 && received > 0) {
+ if (VDBG) log("updateDataStallInfo: IN");
+ mSentSinceLastRecv = 0;
+ putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
+ } else {
+ if (VDBG) log("updateDataStallInfo: NONE");
+ }
+ }
+
+ @Override
+ protected void onDataStallAlarm(int tag) {
+ if (mDataStallAlarmTag != tag) {
+ if (DBG) {
+ log("onDataStallAlarm: ignore, tag=" + tag + " expecting " + mDataStallAlarmTag);
+ }
+ return;
+ }
+ updateDataStallInfo();
+
+ int hangWatchdogTrigger = Settings.Secure.getInt(mResolver,
+ Settings.Secure.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
+ NUMBER_SENT_PACKETS_OF_HANG);
+
+ boolean suspectedStall = DATA_STALL_NOT_SUSPECTED;
+ if (mSentSinceLastRecv >= hangWatchdogTrigger) {
+ if (DBG) {
+ log("onDataStallAlarm: tag=" + tag + " do recovery action=" + getRecoveryAction());
+ }
+ suspectedStall = DATA_STALL_SUSPECTED;
+ sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
+ } else {
+ if (VDBG) {
+ log("onDataStallAlarm: tag=" + tag + " Sent " + String.valueOf(mSentSinceLastRecv) +
+ " pkts since last received, < watchdogTrigger=" + hangWatchdogTrigger);
+ }
+ }
+ startDataStallAlarm(suspectedStall);
+ }
+
+
+ private void updateDataActivity() {
+ long sent, received;
+
+ DctConstants.Activity newActivity;
+
+ TxRxSum preTxRxSum = new TxRxSum(mTxPkts, mRxPkts);
+ TxRxSum curTxRxSum = new TxRxSum();
+ curTxRxSum.updateTxRxSum();
+ mTxPkts = curTxRxSum.txPkts;
+ mRxPkts = curTxRxSum.rxPkts;
+
+ if (VDBG) {
+ log("updateDataActivity: curTxRxSum=" + curTxRxSum + " preTxRxSum=" + preTxRxSum);
+ }
+
+ if (mNetStatPollEnabled && (preTxRxSum.txPkts > 0 || preTxRxSum.rxPkts > 0)) {
+ sent = mTxPkts - preTxRxSum.txPkts;
+ received = mRxPkts - preTxRxSum.rxPkts;
+
+ if (VDBG) log("updateDataActivity: sent=" + sent + " received=" + received);
+ if ( sent > 0 && received > 0 ) {
+ newActivity = DctConstants.Activity.DATAINANDOUT;
+ } else if (sent > 0 && received == 0) {
+ newActivity = DctConstants.Activity.DATAOUT;
+ } else if (sent == 0 && received > 0) {
+ newActivity = DctConstants.Activity.DATAIN;
+ } else {
+ newActivity = DctConstants.Activity.NONE;
+ }
+
+ if (mActivity != newActivity && mIsScreenOn) {
+ if (VDBG) log("updateDataActivity: newActivity=" + newActivity);
+ mActivity = newActivity;
+ mPhone.notifyDataActivity();
+ }
+ }
+ }
+
+ private Runnable mPollNetStat = new Runnable()
+ {
+ @Override
+ public void run() {
+ updateDataActivity();
+
+ if (mIsScreenOn) {
+ mNetStatPollPeriod = Settings.Secure.getInt(mResolver,
+ Settings.Secure.PDP_WATCHDOG_POLL_INTERVAL_MS, POLL_NETSTAT_MILLIS);
+ } else {
+ mNetStatPollPeriod = Settings.Secure.getInt(mResolver,
+ Settings.Secure.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
+ POLL_NETSTAT_SCREEN_OFF_MILLIS);
+ }
+
+ if (mNetStatPollEnabled) {
+ mDataConnectionTracker.postDelayed(this, mNetStatPollPeriod);
+ }
+ }
+ };
+
+ /**
+ * Returns true if the last fail cause is something that
+ * seems like it deserves an error notification.
+ * Transient errors are ignored
+ */
+ private boolean shouldPostNotification(GsmDataConnection.FailCause cause) {
+ return (cause != GsmDataConnection.FailCause.UNKNOWN);
+ }
+
+ /**
+ * Return true if data connection need to be setup after disconnected due to
+ * reason.
+ *
+ * @param reason the reason why data is disconnected
+ * @return true if try setup data connection is need for this reason
+ */
+ private boolean retryAfterDisconnected(String reason) {
+ boolean retry = true;
+
+ if ( Phone.REASON_RADIO_TURNED_OFF.equals(reason) ) {
+ retry = false;
+ }
+ return retry;
+ }
+
+ private void reconnectAfterFail(FailCause lastFailCauseCode,
+ ApnContext apnContext, int retryOverride) {
+ if (apnContext == null) {
+ loge("reconnectAfterFail: apnContext == null, impossible");
+ return;
+ }
+ if (DBG) {
+ log("reconnectAfterFail: lastFailCause=" + lastFailCauseCode +
+ " retryOverride=" + retryOverride + " apnContext=" + apnContext);
+ }
+ if ((apnContext.getState() == DctConstants.State.FAILED) &&
+ (apnContext.getDataConnection() != null)) {
+ if (!apnContext.getDataConnection().isRetryNeeded()) {
+ if (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DEFAULT)) {
+ mPhone.notifyDataConnection(Phone.REASON_APN_FAILED, apnContext.getApnType());
+ return;
+ }
+ if (mReregisterOnReconnectFailure) {
+ // We've re-registerd once now just retry forever.
+ apnContext.getDataConnection().retryForeverUsingLastTimeout();
+ } else {
+ // Try to Re-register to the network.
+ if (DBG) log("reconnectAfterFail: activate failed, Reregistering to network");
+ mReregisterOnReconnectFailure = true;
+ mPhone.getServiceStateTracker().reRegisterNetwork(null);
+ apnContext.setRetryCount(0);
+ return;
+ }
+ }
+
+ // If retry needs to be backed off for specific case (determined by RIL/Modem)
+ // use the specified timer instead of pre-configured retry pattern.
+ int nextReconnectDelay = retryOverride;
+ if (nextReconnectDelay < 0) {
+ nextReconnectDelay = apnContext.getDataConnection().getRetryTimer();
+ apnContext.getDataConnection().increaseRetryCount();
+ if (DBG) {
+ log("reconnectAfterFail: increaseRetryCount=" +
+ apnContext.getDataConnection().getRetryCount() +
+ " nextReconnectDelay=" + nextReconnectDelay);
+ }
+ }
+ startAlarmForReconnect(nextReconnectDelay, apnContext);
+
+ if (!shouldPostNotification(lastFailCauseCode)) {
+ if (DBG) {
+ log("reconnectAfterFail: NOT Posting GPRS Unavailable notification "
+ + "-- likely transient error");
+ }
+ } else {
+ notifyNoData(lastFailCauseCode, apnContext);
+ }
+ }
+ }
+
+ private void startAlarmForReconnect(int delay, ApnContext apnContext) {
+
+ DataConnectionAc dcac = apnContext.getDataConnectionAc();
+
+ if ((dcac == null) || (dcac.dataConnection == null)) {
+ // should not happen, but just in case.
+ loge("startAlarmForReconnect: null dcac or dc.");
+ return;
+ }
+
+ AlarmManager am =
+ (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+
+ Intent intent = new Intent(INTENT_RECONNECT_ALARM + '.' +
+ dcac.dataConnection.getDataConnectionId());
+ String reason = apnContext.getReason();
+ intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, reason);
+ int connectionId = dcac.dataConnection.getDataConnectionId();
+ intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, connectionId);
+
+ // TODO: Until a real fix is created, which probably entails pushing
+ // retires into the DC itself, this fix gets the retry count and
+ // puts it in the reconnect alarm. When the reconnect alarm fires
+ // onActionIntentReconnectAlarm is called which will use the value saved
+ // here and save it in the ApnContext and send the EVENT_CONNECT message
+ // which invokes setupData. Then setupData will use the value in the ApnContext
+ // and to tell the DC to set the retry count in the retry manager.
+ int retryCount = dcac.dataConnection.getRetryCount();
+ intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_RETRY_COUNT, retryCount);
+
+ if (DBG) {
+ log("startAlarmForReconnect: next attempt in " + (delay / 1000) + "s" +
+ " reason='" + reason + "' connectionId=" + connectionId +
+ " retryCount=" + retryCount);
+ }
+
+ PendingIntent alarmIntent = PendingIntent.getBroadcast (mPhone.getContext(), 0,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ dcac.setReconnectIntentSync(alarmIntent);
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + delay, alarmIntent);
+
+ }
+
+ private void startDataStallAlarm(boolean suspectedStall) {
+ int nextAction = getRecoveryAction();
+ int delayInMs;
+
+ // If screen is on or data stall is currently suspected, set the alarm
+ // with an aggresive timeout.
+ if (mIsScreenOn || suspectedStall || RecoveryAction.isAggressiveRecovery(nextAction)) {
+ delayInMs = Settings.Secure.getInt(mResolver,
+ Settings.Secure.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
+ DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT);
+ } else {
+ delayInMs = Settings.Secure.getInt(mResolver,
+ Settings.Secure.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
+ DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT);
+ }
+
+ mDataStallAlarmTag += 1;
+ if (VDBG) {
+ log("startDataStallAlarm: tag=" + mDataStallAlarmTag +
+ " delay=" + (delayInMs / 1000) + "s");
+ }
+ AlarmManager am =
+ (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+
+ Intent intent = new Intent(INTENT_DATA_STALL_ALARM);
+ intent.putExtra(DATA_STALL_ALARM_TAG_EXTRA, mDataStallAlarmTag);
+ mDataStallAlarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + delayInMs, mDataStallAlarmIntent);
+ }
+
+ private void stopDataStallAlarm() {
+ AlarmManager am =
+ (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+
+ if (VDBG) {
+ log("stopDataStallAlarm: current tag=" + mDataStallAlarmTag +
+ " mDataStallAlarmIntent=" + mDataStallAlarmIntent);
+ }
+ mDataStallAlarmTag += 1;
+ if (mDataStallAlarmIntent != null) {
+ am.cancel(mDataStallAlarmIntent);
+ mDataStallAlarmIntent = null;
+ }
+ }
+
+ @Override
+ protected void restartDataStallAlarm() {
+ if (isConnected() == false) return;
+ // To be called on screen status change.
+ // Do not cancel the alarm if it is set with aggressive timeout.
+ int nextAction = getRecoveryAction();
+
+ if (RecoveryAction.isAggressiveRecovery(nextAction)) {
+ if (DBG) log("data stall recovery action is pending. not resetting the alarm.");
+ return;
+ }
+ stopDataStallAlarm();
+ startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
+ }
+
+ private void notifyNoData(GsmDataConnection.FailCause lastFailCauseCode,
+ ApnContext apnContext) {
+ if (DBG) log( "notifyNoData: type=" + apnContext.getApnType());
+ apnContext.setState(DctConstants.State.FAILED);
+ if (lastFailCauseCode.isPermanentFail()
+ && (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DEFAULT))) {
+ mPhone.notifyDataConnectionFailed(apnContext.getReason(), apnContext.getApnType());
+ }
+ }
+
+ private void onRecordsLoaded() {
+ if (DBG) log("onRecordsLoaded: createAllApnList");
+ createAllApnList();
+ if (mPhone.mCM.getRadioState().isOn()) {
+ if (DBG) log("onRecordsLoaded: notifying data availability");
+ notifyOffApnsOfAvailability(Phone.REASON_SIM_LOADED);
+ }
+ setupDataOnReadyApns(Phone.REASON_SIM_LOADED);
+ }
+
+ @Override
+ protected void onSetDependencyMet(String apnType, boolean met) {
+ // don't allow users to tweak hipri to work around default dependency not met
+ if (PhoneConstants.APN_TYPE_HIPRI.equals(apnType)) return;
+
+ ApnContext apnContext = mApnContexts.get(apnType);
+ if (apnContext == null) {
+ loge("onSetDependencyMet: ApnContext not found in onSetDependencyMet(" +
+ apnType + ", " + met + ")");
+ return;
+ }
+ applyNewState(apnContext, apnContext.isEnabled(), met);
+ if (PhoneConstants.APN_TYPE_DEFAULT.equals(apnType)) {
+ // tie actions on default to similar actions on HIPRI regarding dependencyMet
+ apnContext = mApnContexts.get(PhoneConstants.APN_TYPE_HIPRI);
+ if (apnContext != null) applyNewState(apnContext, apnContext.isEnabled(), met);
+ }
+ }
+
+ private void applyNewState(ApnContext apnContext, boolean enabled, boolean met) {
+ boolean cleanup = false;
+ boolean trySetup = false;
+ if (DBG) {
+ log("applyNewState(" + apnContext.getApnType() + ", " + enabled +
+ "(" + apnContext.isEnabled() + "), " + met + "(" +
+ apnContext.getDependencyMet() +"))");
+ }
+ if (apnContext.isReady()) {
+ if (enabled && met) return;
+ if (!enabled) {
+ apnContext.setReason(Phone.REASON_DATA_DISABLED);
+ } else {
+ apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_UNMET);
+ }
+ cleanup = true;
+ } else {
+ if (enabled && met) {
+ if (apnContext.isEnabled()) {
+ apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_MET);
+ } else {
+ apnContext.setReason(Phone.REASON_DATA_ENABLED);
+ }
+ if (apnContext.getState() == DctConstants.State.FAILED) {
+ apnContext.setState(DctConstants.State.IDLE);
+ }
+ trySetup = true;
+ }
+ }
+ apnContext.setEnabled(enabled);
+ apnContext.setDependencyMet(met);
+ if (cleanup) cleanUpConnection(true, apnContext);
+ if (trySetup) trySetupData(apnContext);
+ }
+
+ private DataConnection checkForConnectionForApnContext(ApnContext apnContext) {
+ // Loop through all apnContexts looking for one with a conn that satisfies this apnType
+ String apnType = apnContext.getApnType();
+ ApnSetting dunSetting = null;
+
+ if (PhoneConstants.APN_TYPE_DUN.equals(apnType)) {
+ dunSetting = fetchDunApn();
+ }
+
+ DataConnection potential = null;
+ for (ApnContext c : mApnContexts.values()) {
+ DataConnection conn = c.getDataConnection();
+ if (conn != null) {
+ ApnSetting apnSetting = c.getApnSetting();
+ if (dunSetting != null) {
+ if (dunSetting.equals(apnSetting)) {
+ switch (c.getState()) {
+ case CONNECTED:
+ if (DBG) {
+ log("checkForConnectionForApnContext: apnContext=" +
+ apnContext + " found conn=" + conn);
+ }
+ return conn;
+ case CONNECTING:
+ potential = conn;
+ }
+ }
+ } else if (apnSetting != null && apnSetting.canHandleType(apnType)) {
+ switch (c.getState()) {
+ case CONNECTED:
+ if (DBG) {
+ log("checkForConnectionForApnContext: apnContext=" + apnContext +
+ " found conn=" + conn);
+ }
+ return conn;
+ case CONNECTING:
+ potential = conn;
+ }
+ }
+ }
+ }
+ if (potential != null) {
+ if (DBG) {
+ log("checkForConnectionForApnContext: apnContext=" + apnContext +
+ " found conn=" + potential);
+ }
+ return potential;
+ }
+
+ if (DBG) log("checkForConnectionForApnContext: apnContext=" + apnContext + " NO conn");
+ return null;
+ }
+
+ @Override
+ protected void onEnableApn(int apnId, int enabled) {
+ ApnContext apnContext = mApnContexts.get(apnIdToType(apnId));
+ if (apnContext == null) {
+ loge("onEnableApn(" + apnId + ", " + enabled + "): NO ApnContext");
+ return;
+ }
+ // TODO change our retry manager to use the appropriate numbers for the new APN
+ if (DBG) log("onEnableApn: apnContext=" + apnContext + " call applyNewState");
+ applyNewState(apnContext, enabled == DctConstants.ENABLED, apnContext.getDependencyMet());
+ }
+
+ @Override
+ // TODO: We shouldnt need this.
+ protected boolean onTrySetupData(String reason) {
+ if (DBG) log("onTrySetupData: reason=" + reason);
+ setupDataOnReadyApns(reason);
+ return true;
+ }
+
+ protected boolean onTrySetupData(ApnContext apnContext) {
+ if (DBG) log("onTrySetupData: apnContext=" + apnContext);
+ return trySetupData(apnContext);
+ }
+
+ @Override
+ protected void onRoamingOff() {
+ if (DBG) log("onRoamingOff");
+
+ if (mUserDataEnabled == false) return;
+
+ if (getDataOnRoamingEnabled() == false) {
+ notifyOffApnsOfAvailability(Phone.REASON_ROAMING_OFF);
+ setupDataOnReadyApns(Phone.REASON_ROAMING_OFF);
+ } else {
+ notifyDataConnection(Phone.REASON_ROAMING_OFF);
+ }
+ }
+
+ @Override
+ protected void onRoamingOn() {
+ if (mUserDataEnabled == false) return;
+
+ if (getDataOnRoamingEnabled()) {
+ if (DBG) log("onRoamingOn: setup data on roaming");
+ setupDataOnReadyApns(Phone.REASON_ROAMING_ON);
+ notifyDataConnection(Phone.REASON_ROAMING_ON);
+ } else {
+ if (DBG) log("onRoamingOn: Tear down data connection on roaming.");
+ cleanUpAllConnections(true, Phone.REASON_ROAMING_ON);
+ notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON);
+ }
+ }
+
+ @Override
+ protected void onRadioAvailable() {
+ if (DBG) log("onRadioAvailable");
+ if (mPhone.getSimulatedRadioControl() != null) {
+ // Assume data is connected on the simulator
+ // FIXME this can be improved
+ // setState(DctConstants.State.CONNECTED);
+ notifyDataConnection(null);
+
+ log("onRadioAvailable: We're on the simulator; assuming data is connected");
+ }
+
+ if (mPhone.mIccRecords.getRecordsLoaded()) {
+ notifyOffApnsOfAvailability(null);
+ }
+
+ if (getOverallState() != DctConstants.State.IDLE) {
+ cleanUpConnection(true, null);
+ }
+ }
+
+ @Override
+ protected void onRadioOffOrNotAvailable() {
+ // Make sure our reconnect delay starts at the initial value
+ // next time the radio comes on
+
+ resetAllRetryCounts();
+ mReregisterOnReconnectFailure = false;
+
+ if (mPhone.getSimulatedRadioControl() != null) {
+ // Assume data is connected on the simulator
+ // FIXME this can be improved
+ log("We're on the simulator; assuming radio off is meaningless");
+ } else {
+ if (DBG) log("onRadioOffOrNotAvailable: is off and clean up all connections");
+ cleanUpAllConnections(false, Phone.REASON_RADIO_TURNED_OFF);
+ }
+ notifyOffApnsOfAvailability(null);
+ }
+
+ @Override
+ protected void onDataSetupComplete(AsyncResult ar) {
+
+ DataConnection.FailCause cause = DataConnection.FailCause.UNKNOWN;
+ boolean handleError = false;
+ ApnContext apnContext = null;
+
+ if(ar.userObj instanceof ApnContext){
+ apnContext = (ApnContext)ar.userObj;
+ } else {
+ throw new RuntimeException("onDataSetupComplete: No apnContext");
+ }
+
+ if (isDataSetupCompleteOk(ar)) {
+ DataConnectionAc dcac = apnContext.getDataConnectionAc();
+
+ if (RADIO_TESTS) {
+ // Note: To change radio.test.onDSC.null.dcac from command line you need to
+ // adb root and adb remount and from the command line you can only change the
+ // value to 1 once. To change it a second time you can reboot or execute
+ // adb shell stop and then adb shell start. The command line to set the value is:
+ // adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "insert into system (name,value) values ('radio.test.onDSC.null.dcac', '1');"
+ ContentResolver cr = mPhone.getContext().getContentResolver();
+ String radioTestProperty = "radio.test.onDSC.null.dcac";
+ if (Settings.System.getInt(cr, radioTestProperty, 0) == 1) {
+ log("onDataSetupComplete: " + radioTestProperty +
+ " is true, set dcac to null and reset property to false");
+ dcac = null;
+ Settings.System.putInt(cr, radioTestProperty, 0);
+ log("onDataSetupComplete: " + radioTestProperty + "=" +
+ Settings.System.getInt(mPhone.getContext().getContentResolver(),
+ radioTestProperty, -1));
+ }
+ }
+ if (dcac == null) {
+ log("onDataSetupComplete: no connection to DC, handle as error");
+ cause = DataConnection.FailCause.CONNECTION_TO_DATACONNECTIONAC_BROKEN;
+ handleError = true;
+ } else {
+ DataConnection dc = apnContext.getDataConnection();
+ ApnSetting apn = apnContext.getApnSetting();
+ if (DBG) {
+ log("onDataSetupComplete: success apn=" + (apn == null ? "unknown" : apn.apn));
+ }
+ if (apn != null && apn.proxy != null && apn.proxy.length() != 0) {
+ try {
+ String port = apn.port;
+ if (TextUtils.isEmpty(port)) port = "8080";
+ ProxyProperties proxy = new ProxyProperties(apn.proxy,
+ Integer.parseInt(port), null);
+ dcac.setLinkPropertiesHttpProxySync(proxy);
+ } catch (NumberFormatException e) {
+ loge("onDataSetupComplete: NumberFormatException making ProxyProperties (" +
+ apn.port + "): " + e);
+ }
+ }
+
+ // everything is setup
+ if(TextUtils.equals(apnContext.getApnType(),PhoneConstants.APN_TYPE_DEFAULT)) {
+ SystemProperties.set("gsm.defaultpdpcontext.active", "true");
+ if (canSetPreferApn && mPreferredApn == null) {
+ if (DBG) log("onDataSetupComplete: PREFERED APN is null");
+ mPreferredApn = apn;
+ if (mPreferredApn != null) {
+ setPreferredApn(mPreferredApn.id);
+ }
+ }
+ } else {
+ SystemProperties.set("gsm.defaultpdpcontext.active", "false");
+ }
+ notifyDefaultData(apnContext);
+ }
+ } else {
+ cause = (DataConnection.FailCause) (ar.result);
+ if (DBG) {
+ ApnSetting apn = apnContext.getApnSetting();
+ log(String.format("onDataSetupComplete: error apn=%s cause=%s",
+ (apn == null ? "unknown" : apn.apn), cause));
+ }
+ if (cause.isEventLoggable()) {
+ // Log this failure to the Event Logs.
+ int cid = getCellLocationId();
+ EventLog.writeEvent(EventLogTags.PDP_SETUP_FAIL,
+ cause.ordinal(), cid, TelephonyManager.getDefault().getNetworkType());
+ }
+
+ // Count permanent failures and remove the APN we just tried
+ if (cause.isPermanentFail()) apnContext.decWaitingApnsPermFailCount();
+
+ apnContext.removeWaitingApn(apnContext.getApnSetting());
+ if (DBG) {
+ log(String.format("onDataSetupComplete: WaitingApns.size=%d" +
+ " WaitingApnsPermFailureCountDown=%d",
+ apnContext.getWaitingApns().size(),
+ apnContext.getWaitingApnsPermFailCount()));
+ }
+ handleError = true;
+ }
+
+ if (handleError) {
+ // See if there are more APN's to try
+ if (apnContext.getWaitingApns().isEmpty()) {
+ if (apnContext.getWaitingApnsPermFailCount() == 0) {
+ if (DBG) {
+ log("onDataSetupComplete: All APN's had permanent failures, stop retrying");
+ }
+ apnContext.setState(DctConstants.State.FAILED);
+ mPhone.notifyDataConnection(Phone.REASON_APN_FAILED, apnContext.getApnType());
+
+ apnContext.setDataConnection(null);
+ apnContext.setDataConnectionAc(null);
+ } else {
+ if (DBG) log("onDataSetupComplete: Not all permanent failures, retry");
+ // check to see if retry should be overridden for this failure.
+ int retryOverride = -1;
+ if (ar.exception instanceof DataConnection.CallSetupException) {
+ retryOverride =
+ ((DataConnection.CallSetupException)ar.exception).getRetryOverride();
+ }
+ if (retryOverride == RILConstants.MAX_INT) {
+ if (DBG) log("No retry is suggested.");
+ } else {
+ startDelayedRetry(cause, apnContext, retryOverride);
+ }
+ }
+ } else {
+ if (DBG) log("onDataSetupComplete: Try next APN");
+ apnContext.setState(DctConstants.State.SCANNING);
+ // Wait a bit before trying the next APN, so that
+ // we're not tying up the RIL command channel
+ startAlarmForReconnect(APN_DELAY_MILLIS, apnContext);
+ }
+ }
+ }
+
+ /**
+ * Called when EVENT_DISCONNECT_DONE is received.
+ */
+ @Override
+ protected void onDisconnectDone(int connId, AsyncResult ar) {
+ ApnContext apnContext = null;
+
+ if(DBG) log("onDisconnectDone: EVENT_DISCONNECT_DONE connId=" + connId);
+ if (ar.userObj instanceof ApnContext) {
+ apnContext = (ApnContext) ar.userObj;
+ } else {
+ loge("Invalid ar in onDisconnectDone");
+ return;
+ }
+
+ apnContext.setState(DctConstants.State.IDLE);
+
+ mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
+
+ // if all data connection are gone, check whether Airplane mode request was
+ // pending.
+ if (isDisconnected()) {
+ if (mPhone.getServiceStateTracker().processPendingRadioPowerOffAfterDataOff()) {
+ // Radio will be turned off. No need to retry data setup
+ apnContext.setApnSetting(null);
+ apnContext.setDataConnection(null);
+ apnContext.setDataConnectionAc(null);
+ return;
+ }
+ }
+
+ // If APN is still enabled, try to bring it back up automatically
+ if (apnContext.isReady() && retryAfterDisconnected(apnContext.getReason())) {
+ SystemProperties.set("gsm.defaultpdpcontext.active", "false"); // TODO - what the heck? This shoudld go
+ // Wait a bit before trying the next APN, so that
+ // we're not tying up the RIL command channel.
+ // This also helps in any external dependency to turn off the context.
+ startAlarmForReconnect(APN_DELAY_MILLIS, apnContext);
+ } else {
+ apnContext.setApnSetting(null);
+ apnContext.setDataConnection(null);
+ apnContext.setDataConnectionAc(null);
+ }
+ }
+
+ protected void onPollPdp() {
+ if (getOverallState() == DctConstants.State.CONNECTED) {
+ // only poll when connected
+ mPhone.mCM.getDataCallList(this.obtainMessage(DctConstants.EVENT_DATA_STATE_CHANGED));
+ sendMessageDelayed(obtainMessage(DctConstants.EVENT_POLL_PDP), POLL_PDP_MILLIS);
+ }
+ }
+
+ @Override
+ protected void onVoiceCallStarted() {
+ if (DBG) log("onVoiceCallStarted");
+ if (isConnected() && ! mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
+ if (DBG) log("onVoiceCallStarted stop polling");
+ stopNetStatPoll();
+ stopDataStallAlarm();
+ notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
+ }
+ }
+
+ @Override
+ protected void onVoiceCallEnded() {
+ if (DBG) log("onVoiceCallEnded");
+ if (isConnected()) {
+ if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
+ startNetStatPoll();
+ startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
+ notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
+ } else {
+ // clean slate after call end.
+ resetPollStats();
+ }
+ }
+ // reset reconnect timer
+ setupDataOnReadyApns(Phone.REASON_VOICE_CALL_ENDED);
+ }
+
+ @Override
+ protected void onCleanUpConnection(boolean tearDown, int apnId, String reason) {
+ if (DBG) log("onCleanUpConnection");
+ ApnContext apnContext = mApnContexts.get(apnIdToType(apnId));
+ if (apnContext != null) {
+ apnContext.setReason(reason);
+ cleanUpConnection(tearDown, apnContext);
+ }
+ }
+
+ protected boolean isConnected() {
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.getState() ==DctConstants.State.CONNECTED) {
+ // At least one context is connected, return true
+ return true;
+ }
+ }
+ // There are not any contexts connected, return false
+ return false;
+ }
+
+ @Override
+ public boolean isDisconnected() {
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (!apnContext.isDisconnected()) {
+ // At least one context was not disconnected return false
+ return false;
+ }
+ }
+ // All contexts were disconnected so return true
+ return true;
+ }
+
+ @Override
+ protected void notifyDataConnection(String reason) {
+ if (DBG) log("notifyDataConnection: reason=" + reason);
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.isReady()) {
+ if (DBG) log("notifyDataConnection: type:"+apnContext.getApnType());
+ mPhone.notifyDataConnection(reason != null ? reason : apnContext.getReason(),
+ apnContext.getApnType());
+ }
+ }
+ notifyOffApnsOfAvailability(reason);
+ }
+
+ /**
+ * Based on the sim operator numeric, create a list for all possible
+ * Data Connections and setup the preferredApn.
+ */
+ private void createAllApnList() {
+ mAllApns = new ArrayList<ApnSetting>();
+ String operator = mPhone.mIccRecords.getOperatorNumeric();
+ if (operator != null) {
+ String selection = "numeric = '" + operator + "'";
+ // query only enabled apn.
+ // carrier_enabled : 1 means enabled apn, 0 disabled apn.
+ selection += " and carrier_enabled = 1";
+ if (DBG) log("createAllApnList: selection=" + selection);
+
+ Cursor cursor = mPhone.getContext().getContentResolver().query(
+ Telephony.Carriers.CONTENT_URI, null, selection, null, null);
+
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ mAllApns = createApnList(cursor);
+ }
+ cursor.close();
+ }
+ }
+
+ if (mAllApns.isEmpty()) {
+ if (DBG) log("createAllApnList: No APN found for carrier: " + operator);
+ mPreferredApn = null;
+ // TODO: What is the right behaviour?
+ //notifyNoData(GsmDataConnection.FailCause.MISSING_UNKNOWN_APN);
+ } else {
+ mPreferredApn = getPreferredApn();
+ if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {
+ mPreferredApn = null;
+ setPreferredApn(-1);
+ }
+ if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);
+ }
+ if (DBG) log("createAllApnList: X mAllApns=" + mAllApns);
+ }
+
+ /** Return the id for a new data connection */
+ private GsmDataConnection createDataConnection() {
+ if (DBG) log("createDataConnection E");
+
+ RetryManager rm = new RetryManager();
+ int id = mUniqueIdGenerator.getAndIncrement();
+ GsmDataConnection conn = GsmDataConnection.makeDataConnection(mPhone, id, rm, this);
+ mDataConnections.put(id, conn);
+ DataConnectionAc dcac = new DataConnectionAc(conn, LOG_TAG);
+ int status = dcac.fullyConnectSync(mPhone.getContext(), this, conn.getHandler());
+ if (status == AsyncChannel.STATUS_SUCCESSFUL) {
+ mDataConnectionAsyncChannels.put(dcac.dataConnection.getDataConnectionId(), dcac);
+ } else {
+ loge("createDataConnection: Could not connect to dcac.mDc=" + dcac.dataConnection +
+ " status=" + status);
+ }
+
+ // install reconnect intent filter for this data connection.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(INTENT_RECONNECT_ALARM + '.' + id);
+ mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
+
+ if (DBG) log("createDataConnection() X id=" + id + " dc=" + conn);
+ return conn;
+ }
+
+ private void configureRetry(DataConnection dc, boolean forDefault, int retryCount) {
+ if (DBG) {
+ log("configureRetry: forDefault=" + forDefault + " retryCount=" + retryCount +
+ " dc=" + dc);
+ }
+ if (dc == null) return;
+
+ if (!dc.configureRetry(getReryConfig(forDefault))) {
+ if (forDefault) {
+ if (!dc.configureRetry(DEFAULT_DATA_RETRY_CONFIG)) {
+ // Should never happen, log an error and default to a simple linear sequence.
+ loge("configureRetry: Could not configure using " +
+ "DEFAULT_DATA_RETRY_CONFIG=" + DEFAULT_DATA_RETRY_CONFIG);
+ dc.configureRetry(20, 2000, 1000);
+ }
+ } else {
+ if (!dc.configureRetry(SECONDARY_DATA_RETRY_CONFIG)) {
+ // Should never happen, log an error and default to a simple sequence.
+ loge("configureRetry: Could note configure using " +
+ "SECONDARY_DATA_RETRY_CONFIG=" + SECONDARY_DATA_RETRY_CONFIG);
+ dc.configureRetry("max_retries=3, 333, 333, 333");
+ }
+ }
+ }
+ dc.setRetryCount(retryCount);
+ }
+
+ private void destroyDataConnections() {
+ if(mDataConnections != null) {
+ if (DBG) log("destroyDataConnections: clear mDataConnectionList");
+ mDataConnections.clear();
+ } else {
+ if (DBG) log("destroyDataConnections: mDataConnecitonList is empty, ignore");
+ }
+ }
+
+ /**
+ * Build a list of APNs to be used to create PDP's.
+ *
+ * @param requestedApnType
+ * @return waitingApns list to be used to create PDP
+ * error when waitingApns.isEmpty()
+ */
+ private ArrayList<ApnSetting> buildWaitingApns(String requestedApnType) {
+ ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>();
+
+ if (requestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) {
+ ApnSetting dun = fetchDunApn();
+ if (dun != null) {
+ apnList.add(dun);
+ if (DBG) log("buildWaitingApns: X added APN_TYPE_DUN apnList=" + apnList);
+ return apnList;
+ }
+ }
+
+ String operator = mPhone.mIccRecords.getOperatorNumeric();
+ int networkType = mPhone.getServiceState().getNetworkType();
+
+ if (canSetPreferApn && mPreferredApn != null &&
+ mPreferredApn.canHandleType(requestedApnType)) {
+ if (DBG) {
+ log("buildWaitingApns: Preferred APN:" + operator + ":"
+ + mPreferredApn.numeric + ":" + mPreferredApn);
+ }
+ if (mPreferredApn.numeric.equals(operator)) {
+ if (mPreferredApn.bearer == 0 || mPreferredApn.bearer == networkType) {
+ apnList.add(mPreferredApn);
+ if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
+ return apnList;
+ } else {
+ if (DBG) log("buildWaitingApns: no preferred APN");
+ setPreferredApn(-1);
+ mPreferredApn = null;
+ }
+ } else {
+ if (DBG) log("buildWaitingApns: no preferred APN");
+ setPreferredApn(-1);
+ mPreferredApn = null;
+ }
+ }
+ if (mAllApns != null) {
+ for (ApnSetting apn : mAllApns) {
+ if (apn.canHandleType(requestedApnType)) {
+ if (apn.bearer == 0 || apn.bearer == networkType) {
+ if (DBG) log("apn info : " +apn.toString());
+ apnList.add(apn);
+ }
+ }
+ }
+ } else {
+ loge("mAllApns is empty!");
+ }
+ if (DBG) log("buildWaitingApns: X apnList=" + apnList);
+ return apnList;
+ }
+
+ 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();
+ }
+
+ private void startDelayedRetry(GsmDataConnection.FailCause cause,
+ ApnContext apnContext, int retryOverride) {
+ notifyNoData(cause, apnContext);
+ reconnectAfterFail(cause, apnContext, retryOverride);
+ }
+
+ private void setPreferredApn(int pos) {
+ if (!canSetPreferApn) {
+ log("setPreferredApn: X !canSEtPreferApn");
+ return;
+ }
+
+ log("setPreferredApn: delete");
+ ContentResolver resolver = mPhone.getContext().getContentResolver();
+ resolver.delete(PREFERAPN_NO_UPDATE_URI, null, null);
+
+ if (pos >= 0) {
+ log("setPreferredApn: insert");
+ ContentValues values = new ContentValues();
+ values.put(APN_ID, pos);
+ resolver.insert(PREFERAPN_NO_UPDATE_URI, values);
+ }
+ }
+
+ private ApnSetting getPreferredApn() {
+ if (mAllApns.isEmpty()) {
+ log("getPreferredApn: X not found mAllApns.isEmpty");
+ return null;
+ }
+
+ Cursor cursor = mPhone.getContext().getContentResolver().query(
+ PREFERAPN_NO_UPDATE_URI, new String[] { "_id", "name", "apn" },
+ null, null, Telephony.Carriers.DEFAULT_SORT_ORDER);
+
+ if (cursor != null) {
+ canSetPreferApn = true;
+ } else {
+ canSetPreferApn = false;
+ }
+
+ if (canSetPreferApn && cursor.getCount() > 0) {
+ int pos;
+ cursor.moveToFirst();
+ pos = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID));
+ for(ApnSetting p:mAllApns) {
+ if (p.id == pos && p.canHandleType(mRequestedApnType)) {
+ log("getPreferredApn: X found apnSetting" + p);
+ cursor.close();
+ return p;
+ }
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ log("getPreferredApn: X not found");
+ return null;
+ }
+
+ @Override
+ public void handleMessage (Message msg) {
+ if (DBG) log("handleMessage msg=" + msg);
+
+ if (!mPhone.mIsTheCurrentActivePhone || mIsDisposed) {
+ loge("handleMessage: Ignore GSM msgs since GSM phone is inactive");
+ return;
+ }
+
+ switch (msg.what) {
+ case DctConstants.EVENT_RECORDS_LOADED:
+ onRecordsLoaded();
+ break;
+
+ case DctConstants.EVENT_DATA_CONNECTION_DETACHED:
+ onDataConnectionDetached();
+ break;
+
+ case DctConstants.EVENT_DATA_CONNECTION_ATTACHED:
+ onDataConnectionAttached();
+ break;
+
+ case DctConstants.EVENT_DATA_STATE_CHANGED:
+ onDataStateChanged((AsyncResult) msg.obj);
+ break;
+
+ case DctConstants.EVENT_POLL_PDP:
+ onPollPdp();
+ break;
+
+ case DctConstants.EVENT_DO_RECOVERY:
+ doRecovery();
+ break;
+
+ case DctConstants.EVENT_APN_CHANGED:
+ onApnChanged();
+ break;
+
+ case DctConstants.EVENT_PS_RESTRICT_ENABLED:
+ /**
+ * We don't need to explicitly to tear down the PDP context
+ * when PS restricted is enabled. The base band will deactive
+ * PDP context and notify us with PDP_CONTEXT_CHANGED.
+ * But we should stop the network polling and prevent reset PDP.
+ */
+ if (DBG) log("EVENT_PS_RESTRICT_ENABLED " + mIsPsRestricted);
+ stopNetStatPoll();
+ stopDataStallAlarm();
+ mIsPsRestricted = true;
+ break;
+
+ case DctConstants.EVENT_PS_RESTRICT_DISABLED:
+ /**
+ * When PS restrict is removed, we need setup PDP connection if
+ * PDP connection is down.
+ */
+ if (DBG) log("EVENT_PS_RESTRICT_DISABLED " + mIsPsRestricted);
+ mIsPsRestricted = false;
+ if (isConnected()) {
+ startNetStatPoll();
+ startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
+ } else {
+ // TODO: Should all PDN states be checked to fail?
+ if (mState ==DctConstants.State.FAILED) {
+ cleanUpAllConnections(false, Phone.REASON_PS_RESTRICT_ENABLED);
+ resetAllRetryCounts();
+ mReregisterOnReconnectFailure = false;
+ }
+ trySetupData(Phone.REASON_PS_RESTRICT_ENABLED, PhoneConstants.APN_TYPE_DEFAULT);
+ }
+ break;
+ case DctConstants.EVENT_TRY_SETUP_DATA:
+ if (msg.obj instanceof ApnContext) {
+ onTrySetupData((ApnContext)msg.obj);
+ } else if (msg.obj instanceof String) {
+ onTrySetupData((String)msg.obj);
+ } else {
+ loge("EVENT_TRY_SETUP request w/o apnContext or String");
+ }
+ break;
+
+ case DctConstants.EVENT_CLEAN_UP_CONNECTION:
+ boolean tearDown = (msg.arg1 == 0) ? false : true;
+ if (DBG) log("EVENT_CLEAN_UP_CONNECTION tearDown=" + tearDown);
+ if (msg.obj instanceof ApnContext) {
+ cleanUpConnection(tearDown, (ApnContext)msg.obj);
+ } else {
+ loge("EVENT_CLEAN_UP_CONNECTION request w/o apn context");
+ }
+ break;
+ default:
+ // handle the message in the super class DataConnectionTracker
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ protected int getApnProfileID(String apnType) {
+ if (TextUtils.equals(apnType, PhoneConstants.APN_TYPE_IMS)) {
+ return RILConstants.DATA_PROFILE_IMS;
+ } else if (TextUtils.equals(apnType, PhoneConstants.APN_TYPE_FOTA)) {
+ return RILConstants.DATA_PROFILE_FOTA;
+ } else if (TextUtils.equals(apnType, PhoneConstants.APN_TYPE_CBS)) {
+ return RILConstants.DATA_PROFILE_CBS;
+ } else {
+ return RILConstants.DATA_PROFILE_DEFAULT;
+ }
+ }
+
+ private int getCellLocationId() {
+ int cid = -1;
+ CellLocation loc = mPhone.getCellLocation();
+
+ if (loc != null) {
+ if (loc instanceof GsmCellLocation) {
+ cid = ((GsmCellLocation)loc).getCid();
+ } else if (loc instanceof CdmaCellLocation) {
+ cid = ((CdmaCellLocation)loc).getBaseStationId();
+ }
+ }
+ return cid;
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[GsmDCT] "+ s);
+ }
+
+ @Override
+ protected void loge(String s) {
+ Log.e(LOG_TAG, "[GsmDCT] " + s);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("GsmDataConnectionTracker extends:");
+ super.dump(fd, pw, args);
+ pw.println(" RADIO_TESTS=" + RADIO_TESTS);
+ pw.println(" mReregisterOnReconnectFailure=" + mReregisterOnReconnectFailure);
+ pw.println(" mResolver=" + mResolver);
+ pw.println(" canSetPreferApn=" + canSetPreferApn);
+ pw.println(" mApnObserver=" + mApnObserver);
+ pw.println(" getOverallState=" + getOverallState());
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
new file mode 100644
index 0000000..15e6a22
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -0,0 +1,1357 @@
+/*
+ * 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.telephony.PhoneNumberUtils;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.util.Log;
+
+import static com.android.internal.telephony.CommandsInterface.*;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * 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
+
+ // Max Size of the Short Code (aka Short String from TS 22.030 6.5.2)
+ static final int MAX_LENGTH_SHORT_CODE = 2;
+
+ // TS 22.030 6.5.2 Every Short String USSD command will end with #-key
+ // (known as #-String)
+ static final char END_OF_USSD_COMMAND = '#';
+
+ // 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 codes 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 which must not include #, e.g. *SCn*SI#DN format
+*/
+
+ 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;
+ static private String[] sTwoDigitNumberPattern;
+
+ //***** Public Class methods
+
+ /**
+ * Some dial strings in GSM are defined to do non-call setup
+ * things, such as modify or query supplementary 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 (isTwoDigitShortCode(phone.getContext(), dialString)) {
+ //Is a country-specific exception to short codes as defined in TS 22.030, 6.5.3.2
+ ret = null;
+ } 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.getHandler().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;
+
+ }
+
+ static private boolean
+ isTwoDigitShortCode(Context context, String dialString) {
+ Log.d(LOG_TAG, "isTwoDigitShortCode");
+
+ if (dialString == null || dialString.length() != 2) return false;
+
+ if (sTwoDigitNumberPattern == null) {
+ sTwoDigitNumberPattern = context.getResources().getStringArray(
+ com.android.internal.R.array.config_twoDigitNumberPattern);
+ }
+
+ for (String dialnumber : sTwoDigitNumberPattern) {
+ Log.d(LOG_TAG, "Two Digit Number Pattern " + dialnumber);
+ if (dialString.equals(dialnumber)) {
+ Log.d(LOG_TAG, "Two Digit Number Pattern -true");
+ return true;
+ }
+ }
+ Log.d(LOG_TAG, "Two Digit Number Pattern -false");
+ return false;
+ }
+
+ /**
+ * 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:
+ if (dialString == null) {
+ return false;
+ }
+
+ // Illegal dial string characters will give a ZERO length.
+ // At this point we do not want to crash as any application with
+ // call privileges may send a non dial string.
+ // It return false as when the dialString is equal to NULL.
+ if (dialString.length() == 0) {
+ return false;
+ }
+
+ if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext())) {
+ return false;
+ } else {
+ return isShortCodeUSSD(dialString, phone);
+ }
+ }
+
+ /**
+ * Helper function for isShortCode. Returns true if dialString appears to be
+ * a short code and it is a USSD structure
+ *
+ * According to the 3PGG TS 22.030 specification 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'), there
+ * are however exceptions to this rule (see below)
+ *
+ * Exception (1) to Call initiation is: If the user of the device is already in a call
+ * and enters a Short String without any #-key at the end and the length of the Short String is
+ * equal or less then the MAX_LENGTH_SHORT_CODE [constant that is equal to 2]
+ *
+ * The phone shall initiate a USSD/SS commands.
+ *
+ * Exception (2) to Call initiation is: If the user of the device enters one
+ * Digit followed by the #-key. This rule defines this String as the
+ * #-String which is a USSD/SS command.
+ *
+ * The phone shall initiate a USSD/SS command.
+ */
+ static private boolean isShortCodeUSSD(String dialString, GSMPhone phone) {
+ if (dialString != null) {
+ if (phone.isInCall()) {
+ // The maximum length of a Short Code (aka Short String) is 2
+ if (dialString.length() <= MAX_LENGTH_SHORT_CODE) {
+ return true;
+ }
+ }
+
+ // The maximum length of a Short Code (aka Short String) is 2
+ if (dialString.length() <= MAX_LENGTH_SHORT_CODE) {
+ if (dialString.charAt(dialString.length() - 1) == END_OF_USSD_COMMAND) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @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) ||
+ (reason == CommandsInterface.CF_REASON_ALL)) &&
+ (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
+ (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
+
+ int isEnableDesired =
+ ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
+ (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
+
+ Log.d(LOG_TAG, "is CF setCallForward");
+ 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 if (sc.equals(SC_PIN) && phone.getIccCard().getState() ==
+ IccCardConstants.State.PUK_REQUIRED ) {
+ // Sim is puk-locked
+ handlePasswordError(com.android.internal.R.string.needPuk);
+ } else {
+ // pre-checks OK
+ if (sc.equals(SC_PIN)) {
+ phone.mCM.changeIccPin(oldPinOrPuk, newPin,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ } else if (sc.equals(SC_PIN2)) {
+ phone.mCM.changeIccPin2(oldPinOrPuk, newPin,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ } else if (sc.equals(SC_PUK)) {
+ phone.mCM.supplyIccPuk(oldPinOrPuk, newPin,
+ obtainMessage(EVENT_SET_COMPLETE, this));
+ } else if (sc.equals(SC_PUK2)) {
+ phone.mCM.supplyIccPuk2(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 done 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.mIccRecords.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 = getErrorMessage(ar);
+
+ 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 getErrorMessage(AsyncResult ar) {
+
+ if (ar.exception instanceof CommandException) {
+ CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
+ if (err == CommandException.Error.FDN_CHECK_FAILURE) {
+ Log.i(LOG_TAG, "FDN_CHECK_FAILURE");
+ return context.getText(com.android.internal.R.string.mmiFdnError);
+ }
+ }
+
+ return context.getText(com.android.internal.R.string.mmiError);
+ }
+
+ 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 if (err == CommandException.Error.FDN_CHECK_FAILURE) {
+ Log.i(LOG_TAG, "FDN_CHECK_FAILURE");
+ sb.append(context.getText(com.android.internal.R.string.mmiFdnError));
+ } 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(getErrorMessage(ar));
+ } 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.mIccRecords.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(getErrorMessage(ar));
+ } 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.mIccRecords.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 class, 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(getErrorMessage(ar));
+ } 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] for Call Barring is a bit vector of services
+ sb.append(createQueryCallBarringResultMessage(ints[0]));
+ } else if (ints[0] == 1) {
+ // 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;
+ }
+ private CharSequence
+ createQueryCallBarringResultMessage(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.
+ */
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("GsmMmiCode {");
+
+ sb.append("State=" + getState());
+ if (action != null) sb.append(" action=" + action);
+ if (sc != null) sb.append(" sc=" + sc);
+ if (sia != null) sb.append(" sia=" + sia);
+ if (sib != null) sb.append(" sib=" + sib);
+ if (sic != null) sb.append(" sic=" + sic);
+ if (poundString != null) sb.append(" poundString=" + poundString);
+ if (dialingNumber != null) sb.append(" dialingNumber=" + dialingNumber);
+ if (pwd != null) sb.append(" pwd=" + pwd);
+ sb.append("}");
+ return sb.toString();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
new file mode 100644
index 0000000..d6c2a20
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -0,0 +1,471 @@
+/*
+ * 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.PendingIntent.CanceledException;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.telephony.SmsManager;
+import android.telephony.gsm.GsmCellLocation;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SMSDispatcher;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsStorageMonitor;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.internal.telephony.TelephonyProperties;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+public final class GsmSMSDispatcher extends SMSDispatcher {
+ private static final String TAG = "GSM";
+
+ /** Status report received */
+ private static final int EVENT_NEW_SMS_STATUS_REPORT = 100;
+
+ /** New broadcast SMS */
+ private static final int EVENT_NEW_BROADCAST_SMS = 101;
+
+ /** Result of writing SM to UICC (when SMS-PP service is not available). */
+ private static final int EVENT_WRITE_SMS_COMPLETE = 102;
+
+ /** Handler for SMS-PP data download messages to UICC. */
+ private final UsimDataDownloadHandler mDataDownloadHandler;
+
+ public GsmSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
+ SmsUsageMonitor usageMonitor) {
+ super(phone, storageMonitor, usageMonitor);
+ mDataDownloadHandler = new UsimDataDownloadHandler(mCm);
+ mCm.setOnNewGsmSms(this, EVENT_NEW_SMS, null);
+ mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
+ mCm.setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null);
+ }
+
+ @Override
+ public void dispose() {
+ mCm.unSetOnNewGsmSms(this);
+ mCm.unSetOnSmsStatus(this);
+ mCm.unSetOnNewGsmBroadcastSms(this);
+ }
+
+ @Override
+ protected String getFormat() {
+ return SmsConstants.FORMAT_3GPP;
+ }
+
+ /**
+ * Handles 3GPP format-specific events coming from the phone stack.
+ * Other events are handled by {@link SMSDispatcher#handleMessage}.
+ *
+ * @param msg the message to handle
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_NEW_SMS_STATUS_REPORT:
+ handleStatusReport((AsyncResult) msg.obj);
+ break;
+
+ case EVENT_NEW_BROADCAST_SMS:
+ handleBroadcastSms((AsyncResult)msg.obj);
+ break;
+
+ case EVENT_WRITE_SMS_COMPLETE:
+ AsyncResult ar = (AsyncResult) msg.obj;
+ if (ar.exception == null) {
+ Log.d(TAG, "Successfully wrote SMS-PP message to UICC");
+ mCm.acknowledgeLastIncomingGsmSms(true, 0, null);
+ } else {
+ Log.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception);
+ mCm.acknowledgeLastIncomingGsmSms(false,
+ CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null);
+ }
+ break;
+
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ /**
+ * 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 tpStatus = sms.getStatus();
+ 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.
+ if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) {
+ deliveryPendingList.remove(i);
+ }
+ PendingIntent intent = tracker.mDeliveryIntent;
+ Intent fillIn = new Intent();
+ fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString));
+ fillIn.putExtra("format", SmsConstants.FORMAT_3GPP);
+ try {
+ intent.send(mContext, Activity.RESULT_OK, fillIn);
+ } catch (CanceledException ex) {}
+
+ // Only expect to see one tracker matching this messageref
+ break;
+ }
+ }
+ }
+ acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int dispatchMessage(SmsMessageBase smsb) {
+
+ // If sms is null, means there was a parsing error.
+ if (smsb == null) {
+ Log.e(TAG, "dispatchMessage: message is null");
+ return Intents.RESULT_SMS_GENERIC_ERROR;
+ }
+
+ SmsMessage sms = (SmsMessage) smsb;
+
+ if (sms.isTypeZero()) {
+ // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
+ // Displayed/Stored/Notified. They should only be acknowledged.
+ Log.d(TAG, "Received short message type 0, Don't display or store it. Send Ack");
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ // Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1.
+ if (sms.isUsimDataDownload()) {
+ UsimServiceTable ust = mPhone.getUsimServiceTable();
+ // If we receive an SMS-PP message before the UsimServiceTable has been loaded,
+ // assume that the data download service is not present. This is very unlikely to
+ // happen because the IMS connection will not be established until after the ISIM
+ // records have been loaded, after the USIM service table has been loaded.
+ if (ust != null && ust.isAvailable(
+ UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) {
+ Log.d(TAG, "Received SMS-PP data download, sending to UICC.");
+ return mDataDownloadHandler.startDataDownload(sms);
+ } else {
+ Log.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC.");
+ String smsc = IccUtils.bytesToHexString(
+ PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
+ sms.getServiceCenterAddress()));
+ mCm.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc,
+ IccUtils.bytesToHexString(sms.getPdu()),
+ obtainMessage(EVENT_WRITE_SMS_COMPLETE));
+ return Activity.RESULT_OK; // acknowledge after response from write to USIM
+ }
+ }
+
+ if (mSmsReceiveDisabled) {
+ // Device doesn't support SMS service,
+ Log.d(TAG, "Received short message on device which doesn't support "
+ + "SMS service. Ignored.");
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ // Special case the message waiting indicator messages
+ boolean handled = false;
+ if (sms.isMWISetMessage()) {
+ mPhone.setVoiceMessageWaiting(1, -1); // line 1: unknown number of msgs waiting
+ handled = sms.isMwiDontStore();
+ if (false) {
+ Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
+ }
+ } else if (sms.isMWIClearMessage()) {
+ mPhone.setVoiceMessageWaiting(1, 0); // line 1: no msgs waiting
+ handled = sms.isMwiDontStore();
+ if (false) {
+ Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
+ }
+ }
+
+ if (handled) {
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ if (!mStorageMonitor.isStorageAvailable() &&
+ sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
+ // It's a storable message and there's no storage available. Bail.
+ // (See TS 23.038 for a description of class 0 messages.)
+ return Intents.RESULT_SMS_OUT_OF_MEMORY;
+ }
+
+ return dispatchNormalMessage(smsb);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void sendData(String destAddr, String scAddr, int destPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
+ scAddr, destAddr, destPort, data, (deliveryIntent != null));
+ if (pdu != null) {
+ sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
+ destAddr);
+ } else {
+ Log.e(TAG, "GsmSMSDispatcher.sendData(): getSubmitPdu() returned null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void sendText(String destAddr, String scAddr, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
+ scAddr, destAddr, text, (deliveryIntent != null));
+ if (pdu != null) {
+ sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
+ destAddr);
+ } else {
+ Log.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
+ boolean use7bitOnly) {
+ return SmsMessage.calculateLength(messageBody, use7bitOnly);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
+ String message, SmsHeader smsHeader, int encoding,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
+ SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
+ message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
+ encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
+ if (pdu != null) {
+ sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
+ destinationAddress);
+ } else {
+ Log.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void sendSms(SmsTracker tracker) {
+ HashMap<String, Object> 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(IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), reply);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
+ mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
+ }
+
+ private static int resultToCause(int rc) {
+ switch (rc) {
+ case Activity.RESULT_OK:
+ case Intents.RESULT_SMS_HANDLED:
+ // Cause code is ignored on success.
+ return 0;
+ case Intents.RESULT_SMS_OUT_OF_MEMORY:
+ return CommandsInterface.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED;
+ case Intents.RESULT_SMS_GENERIC_ERROR:
+ default:
+ return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR;
+ }
+ }
+
+ /**
+ * Holds all info about a message page needed to assemble a complete
+ * concatenated message
+ */
+ private static final class SmsCbConcatInfo {
+
+ private final SmsCbHeader mHeader;
+ private final SmsCbLocation mLocation;
+
+ public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
+ mHeader = header;
+ mLocation = location;
+ }
+
+ @Override
+ public int hashCode() {
+ return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof SmsCbConcatInfo) {
+ SmsCbConcatInfo other = (SmsCbConcatInfo)obj;
+
+ // Two pages match if they have the same serial number (which includes the
+ // geographical scope and update number), and both pages belong to the same
+ // location (PLMN, plus LAC and CID if these are part of the geographical scope).
+ return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
+ && mLocation.equals(other.mLocation);
+ }
+
+ return false;
+ }
+
+ /**
+ * Compare the location code for this message to the current location code. The match is
+ * relative to the geographical scope of the message, which determines whether the LAC
+ * and Cell ID are saved in mLocation or set to -1 to match all values.
+ *
+ * @param plmn the current PLMN
+ * @param lac the current Location Area (GSM) or Service Area (UMTS)
+ * @param cid the current Cell ID
+ * @return true if this message is valid for the current location; false otherwise
+ */
+ public boolean matchesLocation(String plmn, int lac, int cid) {
+ return mLocation.isInLocationArea(plmn, lac, cid);
+ }
+ }
+
+ // This map holds incomplete concatenated messages waiting for assembly
+ private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
+ new HashMap<SmsCbConcatInfo, byte[][]>();
+
+ /**
+ * Handle 3GPP format SMS-CB message.
+ * @param ar the AsyncResult containing the received PDUs
+ */
+ private void handleBroadcastSms(AsyncResult ar) {
+ try {
+ byte[] receivedPdu = (byte[])ar.result;
+
+ if (false) {
+ for (int i = 0; i < receivedPdu.length; i += 8) {
+ StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
+ for (int j = i; j < i + 8 && j < receivedPdu.length; j++) {
+ int b = receivedPdu[j] & 0xff;
+ if (b < 0x10) {
+ sb.append('0');
+ }
+ sb.append(Integer.toHexString(b)).append(' ');
+ }
+ Log.d(TAG, sb.toString());
+ }
+ }
+
+ SmsCbHeader header = new SmsCbHeader(receivedPdu);
+ String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
+ GsmCellLocation cellLocation = (GsmCellLocation) mPhone.getCellLocation();
+ int lac = cellLocation.getLac();
+ int cid = cellLocation.getCid();
+
+ SmsCbLocation location;
+ switch (header.getGeographicalScope()) {
+ case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
+ location = new SmsCbLocation(plmn, lac, -1);
+ break;
+
+ case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
+ case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
+ location = new SmsCbLocation(plmn, lac, cid);
+ break;
+
+ case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
+ default:
+ location = new SmsCbLocation(plmn);
+ break;
+ }
+
+ byte[][] pdus;
+ int pageCount = header.getNumberOfPages();
+ if (pageCount > 1) {
+ // Multi-page message
+ SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
+
+ // Try to find other pages of the same message
+ pdus = mSmsCbPageMap.get(concatInfo);
+
+ if (pdus == null) {
+ // This is the first page of this message, make room for all
+ // pages and keep until complete
+ pdus = new byte[pageCount][];
+
+ mSmsCbPageMap.put(concatInfo, pdus);
+ }
+
+ // Page parameter is one-based
+ pdus[header.getPageIndex() - 1] = receivedPdu;
+
+ for (int i = 0; i < pdus.length; i++) {
+ if (pdus[i] == null) {
+ // Still missing pages, exit
+ return;
+ }
+ }
+
+ // Message complete, remove and dispatch
+ mSmsCbPageMap.remove(concatInfo);
+ } else {
+ // Single page message
+ pdus = new byte[1][];
+ pdus[0] = receivedPdu;
+ }
+
+ SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus);
+ dispatchBroadcastMessage(message);
+
+ // Remove messages that are out of scope to prevent the map from
+ // growing indefinitely, containing incomplete messages that were
+ // never assembled
+ Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();
+
+ while (iter.hasNext()) {
+ SmsCbConcatInfo info = iter.next();
+
+ if (!info.matchesLocation(plmn, lac, cid)) {
+ iter.remove();
+ }
+ }
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in decoding SMS CB pdu", e);
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
new file mode 100644
index 0000000..56cf53a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -0,0 +1,1727 @@
+/*
+ * 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.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.DataConnectionTracker;
+import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.IccCardStatus;
+import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.RestrictedState;
+import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+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.res.Resources;
+import android.database.ContentObserver;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.gsm.GsmCellLocation;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.TimeZone;
+
+/**
+ * {@hide}
+ */
+final class GsmServiceStateTracker extends ServiceStateTracker {
+ static final String LOG_TAG = "GSM";
+ static final boolean DBG = true;
+
+ GSMPhone phone;
+ GsmCellLocation cellLoc;
+ GsmCellLocation newCellLoc;
+ int mPreferredNetworkType;
+
+ private int gprsState = ServiceState.STATE_OUT_OF_SERVICE;
+ private int newGPRSState = ServiceState.STATE_OUT_OF_SERVICE;
+ private int mMaxDataCalls = 1;
+ private int mNewMaxDataCalls = 1;
+ private int mReasonDataDenied = -1;
+ private int mNewReasonDataDenied = -1;
+
+ /**
+ * GSM roaming status solely based on TS 27.007 7.2 CREG. Only used by
+ * handlePollStateResult to store CREG roaming result.
+ */
+ private boolean mGsmRoaming = false;
+
+ /**
+ * Data roaming status solely based on TS 27.007 10.1.19 CGREG. Only used by
+ * handlePollStateResult to store CGREG roaming result.
+ */
+ private boolean mDataRoaming = false;
+
+ /**
+ * Mark when service state is in emergency call only mode
+ */
+ private boolean mEmergencyOnly = false;
+
+ /**
+ * 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 mNeedFixZoneAfterNitz = false;
+ private int mZoneOffset;
+ private boolean mZoneDst;
+ private long mZoneTime;
+ private boolean mGotCountryCode = false;
+ private ContentResolver cr;
+
+ /** Boolean is true is setTimeFromNITZString was called */
+ private boolean mNitzUpdatedTime = 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;
+
+ /** Started the recheck process after finding gprs should registered but not. */
+ private boolean mStartedGprsRegCheck = false;
+
+ /** Already sent the event-log for no gprs register. */
+ private boolean mReportedGprsNoReg = false;
+
+ /**
+ * The Notification object given to the NotificationManager.
+ */
+ private Notification mNotification;
+
+ /** Wake lock used while setting time of day. */
+ private PowerManager.WakeLock mWakeLock;
+ private static final String WAKELOCK_TAG = "ServiceStateTracker";
+
+ /** 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;
+
+ /** waiting period before recheck gprs and voice registration. */
+ static final int DEFAULT_GPRS_CHECK_PERIOD_MILLIS = 60 * 1000;
+
+ /** Notification type. */
+ static final int PS_ENABLED = 1001; // Access Control blocks data service
+ static final int PS_DISABLED = 1002; // Access Control enables data service
+ static final int CS_ENABLED = 1003; // Access Control blocks all voice/sms service
+ static final int CS_DISABLED = 1004; // Access Control enables all voice/sms service
+ static final int CS_NORMAL_ENABLED = 1005; // Access Control blocks normal voice/sms service
+ static final int CS_EMERGENCY_ENABLED = 1006; // Access Control blocks emergency call service
+
+ /** Notification id. */
+ static final int PS_NOTIFICATION = 888; // Id to update and cancel PS restricted
+ static final int CS_NOTIFICATION = 999; // Id to update and cancel CS restricted
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED)) {
+ // update emergency string whenever locale changed
+ updateSpnDisplay();
+ }
+ }
+ };
+
+ private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ Log.i("GsmServiceStateTracker", "Auto time state changed");
+ revertToNitzTime();
+ }
+ };
+
+ private ContentObserver mAutoTimeZoneObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ Log.i("GsmServiceStateTracker", "Auto time zone state changed");
+ revertToNitzTimeZone();
+ }
+ };
+
+ public GsmServiceStateTracker(GSMPhone phone) {
+ super();
+
+ this.phone = phone;
+ cm = phone.mCM;
+ ss = new ServiceState();
+ newSS = new ServiceState();
+ cellLoc = new GsmCellLocation();
+ newCellLoc = new GsmCellLocation();
+ mSignalStrength = new SignalStrength();
+
+ PowerManager powerManager =
+ (PowerManager)phone.getContext().getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+
+ cm.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+ cm.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
+
+ cm.registerForVoiceNetworkStateChanged(this, EVENT_NETWORK_STATE_CHANGED, null);
+ cm.setOnNITZTime(this, EVENT_NITZ_TIME, null);
+ cm.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
+ cm.setOnRestrictedStateChanged(this, EVENT_RESTRICTED_STATE_CHANGED, null);
+ phone.getIccCard().registerForReady(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);
+
+ cr = phone.getContext().getContentResolver();
+ cr.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.AUTO_TIME), true,
+ mAutoTimeObserver);
+ cr.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.AUTO_TIME_ZONE), true,
+ mAutoTimeZoneObserver);
+
+ setSignalStrengthDefaultValues();
+ mNeedToRegForSimLoaded = true;
+
+ // Monitor locale change
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ phone.getContext().registerReceiver(mIntentReceiver, filter);
+
+ // Gsm doesn't support OTASP so its not needed
+ phone.notifyOtaspChanged(OTASP_NOT_NEEDED);
+ }
+
+ public void dispose() {
+ // Unregister for all events.
+ cm.unregisterForAvailable(this);
+ cm.unregisterForRadioStateChanged(this);
+ cm.unregisterForVoiceNetworkStateChanged(this);
+ phone.getIccCard().unregisterForReady(this);
+ phone.mIccRecords.unregisterForRecordsLoaded(this);
+ cm.unSetOnSignalStrengthUpdate(this);
+ cm.unSetOnRestrictedStateChanged(this);
+ cm.unSetOnNITZTime(this);
+ cr.unregisterContentObserver(this.mAutoTimeObserver);
+ cr.unregisterContentObserver(this.mAutoTimeZoneObserver);
+ }
+
+ protected void finalize() {
+ if(DBG) log("finalize");
+ }
+
+ @Override
+ protected Phone getPhone() {
+ return phone;
+ }
+
+ public void handleMessage (Message msg) {
+ AsyncResult ar;
+ int[] ints;
+ String[] strings;
+ Message message;
+
+ if (!phone.mIsTheCurrentActivePhone) {
+ Log.e(LOG_TAG, "Received message " + msg +
+ "[" + msg.what + "] while being destroyed. Ignoring.");
+ return;
+ }
+ switch (msg.what) {
+ case EVENT_RADIO_AVAILABLE:
+ //this is unnecessary
+ //setPowerStateToDesired();
+ break;
+
+ case EVENT_SIM_READY:
+ // Set the network type, in case the radio does not restore it.
+ cm.setCurrentPreferredNetworkType();
+
+ // 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.mIccRecords.registerForRecordsLoaded(this,
+ EVENT_SIM_RECORDS_LOADED, null);
+ mNeedToRegForSimLoaded = false;
+ }
+
+ boolean skipRestoringSelection = phone.getContext().getResources().getBoolean(
+ com.android.internal.R.bool.skip_restoring_network_selection);
+
+ if (!skipRestoringSelection) {
+ // 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);
+ }
+ }
+ cellLoc.setLacAndCid(lac, cid);
+ phone.notifyLocationChanged();
+ }
+
+ // Release any temporary cell lock, which could have been
+ // acquired to allow a single-shot location update.
+ disableSingleLocationUpdate();
+ 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];
+ long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
+
+ 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) {
+ cm.getVoiceRegistrationState(obtainMessage(EVENT_GET_LOC_DONE, 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 = RILConstants.NETWORK_MODE_GLOBAL;
+ }
+
+ message = obtainMessage(EVENT_SET_PREFERRED_NETWORK_TYPE, ar.userObj);
+ int toggledNetworkType = RILConstants.NETWORK_MODE_GLOBAL;
+
+ cm.setPreferredNetworkType(toggledNetworkType, message);
+ break;
+
+ case EVENT_CHECK_REPORT_GPRS:
+ if (ss != null && !isGprsConsistent(gprsState, ss.getState())) {
+
+ // Can't register data service while voice service is ok
+ // i.e. CREG is ok while CGREG is not
+ // possible a network or baseband side error
+ GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
+ EventLog.writeEvent(EventLogTags.DATA_NETWORK_REGISTRATION_FAIL,
+ ss.getOperatorNumeric(), loc != null ? loc.getCid() : -1);
+ mReportedGprsNoReg = true;
+ }
+ mStartedGprsRegCheck = false;
+ break;
+
+ case EVENT_RESTRICTED_STATE_CHANGED:
+ // This is a notification from
+ // CommandsInterface.setOnRestrictedStateChanged
+
+ if (DBG) log("EVENT_RESTRICTED_STATE_CHANGED");
+
+ ar = (AsyncResult) msg.obj;
+
+ onRestrictedStateChanged(ar);
+ break;
+
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ protected 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 gracefully
+ DataConnectionTracker dcTracker = phone.mDataConnectionTracker;
+ powerOffRadioSafely(dcTracker);
+ } // Otherwise, we're in the desired state
+ }
+
+ @Override
+ protected void hangupAndPowerOff() {
+ // hang up all active voice calls
+ if (phone.isInCall()) {
+ phone.mCT.ringingCall.hangupIfAlive();
+ phone.mCT.backgroundCall.hangupIfAlive();
+ phone.mCT.foregroundCall.hangupIfAlive();
+ }
+
+ cm.setRadioPower(false, null);
+ }
+
+ protected void updateSpnDisplay() {
+ int rule = phone.mIccRecords.getDisplayRule(ss.getOperatorNumeric());
+ String spn = phone.mIccRecords.getServiceProviderName();
+ String plmn = ss.getOperatorAlphaLong();
+
+ // For emergency calls only, pass the EmergencyCallsOnly string via EXTRA_PLMN
+ if (mEmergencyOnly && cm.getRadioState().isOn()) {
+ plmn = Resources.getSystem().
+ getText(com.android.internal.R.string.emergency_calls_only).toString();
+ if (DBG) log("updateSpnDisplay: emergency only and radio is on plmn='" + plmn + "'");
+ }
+
+ if (rule != curSpnRule
+ || !TextUtils.equals(spn, curSpn)
+ || !TextUtils.equals(plmn, curPlmn)) {
+ boolean showSpn = !mEmergencyOnly && !TextUtils.isEmpty(spn)
+ && (rule & SIMRecords.SPN_RULE_SHOW_SPN) == SIMRecords.SPN_RULE_SHOW_SPN;
+ boolean showPlmn = !TextUtils.isEmpty(plmn) &&
+ (rule & SIMRecords.SPN_RULE_SHOW_PLMN) == SIMRecords.SPN_RULE_SHOW_PLMN;
+
+ if (DBG) {
+ log(String.format("updateSpnDisplay: changed sending intent" + " rule=" + rule +
+ " showPlmn='%b' plmn='%s' showSpn='%b' spn='%s'",
+ showPlmn, plmn, showSpn, spn));
+ }
+ Intent intent = new Intent(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(TelephonyIntents.EXTRA_SHOW_SPN, showSpn);
+ intent.putExtra(TelephonyIntents.EXTRA_SPN, spn);
+ intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn);
+ intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn);
+ phone.getContext().sendStickyBroadcast(intent);
+ }
+
+ curSpnRule = rule;
+ curSpn = spn;
+ curPlmn = plmn;
+ }
+
+ /**
+ * Handle the result of one of the pollState()-related requests
+ */
+ protected 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) {
+ loge("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;
+ int reasonRegStateDenied = -1;
+ int psc = -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);
+ }
+ }
+ if (states.length > 14) {
+ if (states[14] != null && states[14].length() > 0) {
+ psc = Integer.parseInt(states[14], 16);
+ }
+ }
+ } catch (NumberFormatException ex) {
+ loge("error parsing RegistrationState: " + ex);
+ }
+ }
+
+ mGsmRoaming = regCodeIsRoaming(regState);
+ newSS.setState (regCodeToServiceState(regState));
+
+ if (regState == 10 || regState == 12 || regState == 13 || regState == 14) {
+ mEmergencyOnly = true;
+ } else {
+ mEmergencyOnly = false;
+ }
+
+ // LAC and CID are -1 if not avail
+ newCellLoc.setLacAndCid(lac, cid);
+ newCellLoc.setPsc(psc);
+ break;
+
+ case EVENT_POLL_STATE_GPRS:
+ states = (String[])ar.result;
+
+ int type = 0;
+ regState = -1;
+ mNewReasonDataDenied = -1;
+ mNewMaxDataCalls = 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]);
+ }
+ if ((states.length >= 5 ) && (regState == 3)) {
+ mNewReasonDataDenied = Integer.parseInt(states[4]);
+ }
+ if (states.length >= 6) {
+ mNewMaxDataCalls = Integer.parseInt(states[5]);
+ }
+ } catch (NumberFormatException ex) {
+ loge("error parsing GprsRegistrationState: " + ex);
+ }
+ }
+ newGPRSState = regCodeToServiceState(regState);
+ mDataRoaming = regCodeIsRoaming(regState);
+ mNewRilRadioTechnology = type;
+ newSS.setRadioTechnology(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) {
+ loge("Exception while polling service state. Probably malformed RIL response." + ex);
+ }
+
+ pollingContext[0]--;
+
+ if (pollingContext[0] == 0) {
+ /**
+ * Since the roaming states of gsm service (from +CREG) and
+ * data service (from +CGREG) could be different, the new SS
+ * is set roaming while either one is roaming.
+ *
+ * There is an exception for the above rule. The new SS is not set
+ * as roaming while gsm service reports roaming but indeed it is
+ * not roaming between operators.
+ */
+ boolean roaming = (mGsmRoaming || mDataRoaming);
+ if (mGsmRoaming && !isRoamingBetweenOperators(mGsmRoaming, newSS)) {
+ roaming = false;
+ }
+ newSS.setRoaming(roaming);
+ newSS.setEmergencyOnly(mEmergencyOnly);
+ pollStateDone();
+ }
+ }
+
+ private void setSignalStrengthDefaultValues() {
+ // TODO Make a constructor only has boolean gsm as parameter
+ mSignalStrength = new SignalStrength(99, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, SignalStrength.INVALID_SNR, -1, true);
+ }
+
+ /**
+ * 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();
+ setSignalStrengthDefaultValues();
+ mGotCountryCode = false;
+ mNitzUpdatedTime = false;
+ pollStateDone();
+ break;
+
+ case RADIO_OFF:
+ newSS.setStateOff();
+ newCellLoc.setStateInvalid();
+ setSignalStrengthDefaultValues();
+ mGotCountryCode = false;
+ mNitzUpdatedTime = 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.getDataRegistrationState(
+ obtainMessage(
+ EVENT_POLL_STATE_GPRS, pollingContext));
+
+ pollingContext[0]++;
+ cm.getVoiceRegistrationState(
+ obtainMessage(
+ EVENT_POLL_STATE_REGISTRATION, pollingContext));
+
+ pollingContext[0]++;
+ cm.getNetworkSelectionMode(
+ obtainMessage(
+ EVENT_POLL_STATE_NETWORK_SELECTION_MODE, pollingContext));
+ break;
+ }
+ }
+
+ private void pollStateDone() {
+ if (DBG) {
+ log("Poll ServiceState done: " +
+ " oldSS=[" + ss + "] newSS=[" + newSS +
+ "] oldGprs=" + gprsState + " newData=" + newGPRSState +
+ " oldMaxDataCalls=" + mMaxDataCalls +
+ " mNewMaxDataCalls=" + mNewMaxDataCalls +
+ " oldReasonDataDenied=" + mReasonDataDenied +
+ " mNewReasonDataDenied=" + mNewReasonDataDenied +
+ " oldType=" + ServiceState.rilRadioTechnologyToString(mRilRadioTechnology) +
+ " newType=" + ServiceState.rilRadioTechnologyToString(mNewRilRadioTechnology));
+ }
+
+ 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 hasRadioTechnologyChanged = mRilRadioTechnology != mNewRilRadioTechnology;
+
+ boolean hasChanged = !newSS.equals(ss);
+
+ boolean hasRoamingOn = !ss.getRoaming() && newSS.getRoaming();
+
+ boolean hasRoamingOff = ss.getRoaming() && !newSS.getRoaming();
+
+ boolean hasLocationChanged = !newCellLoc.equals(cellLoc);
+
+ // Add an event log when connection state changes
+ if (ss.getState() != newSS.getState() || gprsState != newGPRSState) {
+ EventLog.writeEvent(EventLogTags.GSM_SERVICE_STATE_CHANGE,
+ ss.getState(), gprsState, newSS.getState(), newGPRSState);
+ }
+
+ ServiceState tss;
+ tss = ss;
+ ss = newSS;
+ newSS = tss;
+ // clean slate for next time
+ newSS.setStateOutOfService();
+
+ GsmCellLocation tcl = cellLoc;
+ cellLoc = newCellLoc;
+ newCellLoc = tcl;
+
+ // Add an event log when network type switched
+ // TODO: we may add filtering to reduce the event logged,
+ // i.e. check preferred network setting, only switch to 2G, etc
+ if (hasRadioTechnologyChanged) {
+ int cid = -1;
+ GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
+ if (loc != null) cid = loc.getCid();
+ EventLog.writeEvent(EventLogTags.GSM_RAT_SWITCHED, cid, mRilRadioTechnology,
+ mNewRilRadioTechnology);
+ if (DBG) {
+ log("RAT switched " + ServiceState.rilRadioTechnologyToString(mRilRadioTechnology) +
+ " -> " + ServiceState.rilRadioTechnologyToString(mNewRilRadioTechnology) +
+ " at cell " + cid);
+ }
+ }
+
+ gprsState = newGPRSState;
+ mReasonDataDenied = mNewReasonDataDenied;
+ mMaxDataCalls = mNewMaxDataCalls;
+ mRilRadioTechnology = mNewRilRadioTechnology;
+ // this new state has been applied - forget it until we get a new new state
+ mNewRilRadioTechnology = 0;
+
+
+ newSS.setStateOutOfService(); // clean slate for next time
+
+ if (hasRadioTechnologyChanged) {
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE,
+ ServiceState.rilRadioTechnologyToString(mRilRadioTechnology));
+ }
+
+ if (hasRegistered) {
+ mNetworkAttachedRegistrants.notifyRegistrants();
+
+ if (DBG) {
+ log("pollStateDone: registering current mNitzUpdatedTime=" +
+ mNitzUpdatedTime + " changing to false");
+ }
+ mNitzUpdatedTime = false;
+ }
+
+ if (hasChanged) {
+ String operatorNumeric;
+
+ updateSpnDisplay();
+
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
+ ss.getOperatorAlphaLong());
+
+ String prevOperatorNumeric =
+ SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
+ operatorNumeric = ss.getOperatorNumeric();
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
+
+ if (operatorNumeric == null) {
+ if (DBG) log("operatorNumeric is null");
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
+ mGotCountryCode = false;
+ mNitzUpdatedTime = false;
+ } else {
+ String iso = "";
+ String mcc = operatorNumeric.substring(0, 3);
+ try{
+ iso = MccTable.countryCodeForMcc(Integer.parseInt(mcc));
+ } catch ( NumberFormatException ex){
+ loge("pollStateDone: countryCodeForMcc error" + ex);
+ } catch ( StringIndexOutOfBoundsException ex) {
+ loge("pollStateDone: countryCodeForMcc error" + ex);
+ }
+
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, iso);
+ mGotCountryCode = true;
+
+ TimeZone zone = null;
+
+ if (!mNitzUpdatedTime && !mcc.equals("000") && !TextUtils.isEmpty(iso) &&
+ getAutoTimeZone()) {
+
+ // Test both paths if ignore nitz is true
+ boolean testOneUniqueOffsetPath = SystemProperties.getBoolean(
+ TelephonyProperties.PROPERTY_IGNORE_NITZ, false) &&
+ ((SystemClock.uptimeMillis() & 1) == 0);
+
+ ArrayList<TimeZone> uniqueZones = TimeUtils.getTimeZonesWithUniqueOffsets(iso);
+ if ((uniqueZones.size() == 1) || testOneUniqueOffsetPath) {
+ zone = uniqueZones.get(0);
+ if (DBG) {
+ log("pollStateDone: no nitz but one TZ for iso-cc=" + iso +
+ " with zone.getID=" + zone.getID() +
+ " testOneUniqueOffsetPath=" + testOneUniqueOffsetPath);
+ }
+ setAndBroadcastNetworkSetTimeZone(zone.getID());
+ } else {
+ if (DBG) {
+ log("pollStateDone: there are " + uniqueZones.size() +
+ " unique offsets for iso-cc='" + iso +
+ " testOneUniqueOffsetPath=" + testOneUniqueOffsetPath +
+ "', do nothing");
+ }
+ }
+ }
+
+ if (shouldFixTimeZoneNow(phone, operatorNumeric, prevOperatorNumeric,
+ mNeedFixZoneAfterNitz)) {
+ // 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 (DBG) {
+ log("pollStateDone: fix time zone zoneName='" + zoneName +
+ "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
+ " iso-cc='" + iso +
+ "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso));
+ }
+
+ // "(mZoneOffset == 0) && (mZoneDst == false) &&
+ // (Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)"
+ // means that we received a NITZ string telling
+ // it is in GMT+0 w/ DST time zone
+ // BUT iso tells is NOT, e.g, a wrong NITZ reporting
+ // local time w/ 0 offset.
+ if ((mZoneOffset == 0) && (mZoneDst == false) &&
+ (zoneName != null) && (zoneName.length() > 0) &&
+ (Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)) {
+ zone = TimeZone.getDefault();
+ if (mNeedFixZoneAfterNitz) {
+ // For wrong NITZ reporting local time w/ 0 offset,
+ // need adjust time to reflect default timezone setting
+ long ctm = System.currentTimeMillis();
+ long tzOffset = zone.getOffset(ctm);
+ if (DBG) {
+ log("pollStateDone: tzOffset=" + tzOffset + " ltod=" +
+ TimeUtils.logTimeOfDay(ctm));
+ }
+ if (getAutoTime()) {
+ long adj = ctm - tzOffset;
+ if (DBG) log("pollStateDone: adj ltod=" +
+ TimeUtils.logTimeOfDay(adj));
+ setAndBroadcastNetworkSetTime(adj);
+ } else {
+ // Adjust the saved NITZ time to account for tzOffset.
+ mSavedTime = mSavedTime - tzOffset;
+ }
+ }
+ if (DBG) log("pollStateDone: using default TimeZone");
+ } 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);
+ if (DBG) log("pollStateDone: using NITZ TimeZone");
+ } else {
+ zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, iso);
+ if (DBG) log("pollStateDone: using getTimeZone(off, dst, time, iso)");
+ }
+
+ mNeedFixZoneAfterNitz = false;
+
+ if (zone != null) {
+ log("pollStateDone: zone != null zone.getID=" + zone.getID());
+ if (getAutoTimeZone()) {
+ setAndBroadcastNetworkSetTimeZone(zone.getID());
+ }
+ saveNitzTimeZone(zone.getID());
+ } else {
+ log("pollStateDone: zone == null");
+ }
+ }
+ }
+
+ phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING,
+ ss.getRoaming() ? "true" : "false");
+
+ phone.notifyServiceStateChanged(ss);
+ }
+
+ if (hasGprsAttached) {
+ mAttachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasGprsDetached) {
+ mDetachedRegistrants.notifyRegistrants();
+ }
+
+ if (hasRadioTechnologyChanged) {
+ phone.notifyDataConnection(Phone.REASON_NW_TYPE_CHANGED);
+ }
+
+ if (hasRoamingOn) {
+ mRoamingOnRegistrants.notifyRegistrants();
+ }
+
+ if (hasRoamingOff) {
+ mRoamingOffRegistrants.notifyRegistrants();
+ }
+
+ if (hasLocationChanged) {
+ phone.notifyLocationChanged();
+ }
+
+ if (! isGprsConsistent(gprsState, ss.getState())) {
+ if (!mStartedGprsRegCheck && !mReportedGprsNoReg) {
+ mStartedGprsRegCheck = true;
+
+ int check_period = Settings.Secure.getInt(
+ phone.getContext().getContentResolver(),
+ Settings.Secure.GPRS_REGISTER_CHECK_PERIOD_MS,
+ DEFAULT_GPRS_CHECK_PERIOD_MILLIS);
+ sendMessageDelayed(obtainMessage(EVENT_CHECK_REPORT_GPRS),
+ check_period);
+ }
+ } else {
+ mReportedGprsNoReg = false;
+ }
+ }
+
+ /**
+ * Check if GPRS got registered while voice is registered.
+ *
+ * @param gprsState for GPRS registration state, i.e. CGREG in GSM
+ * @param serviceState for voice registration state, i.e. CREG in GSM
+ * @return false if device only register to voice but not gprs
+ */
+ private boolean isGprsConsistent(int gprsState, int serviceState) {
+ return !((serviceState == ServiceState.STATE_IN_SERVICE) &&
+ (gprsState != ServiceState.STATE_IN_SERVICE));
+ }
+
+ /**
+ * 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("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 Don't poll signal strength if screen is off
+ sendMessageDelayed(msg, POLL_PERIOD_MILLIS);
+ }
+
+ /**
+ * Send signal-strength-changed notification if changed.
+ * Called both for solicited and unsolicited signal strength updates.
+ */
+ private void onSignalStrengthResult(AsyncResult ar) {
+ SignalStrength oldSignalStrength = mSignalStrength;
+ int rssi = 99;
+ int lteSignalStrength = -1;
+ int lteRsrp = -1;
+ int lteRsrq = -1;
+ int lteRssnr = SignalStrength.INVALID_SNR;
+ int lteCqi = -1;
+
+ if (ar.exception != null) {
+ // -1 = unknown
+ // most likely radio is resetting/disconnected
+ setSignalStrengthDefaultValues();
+ } 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];
+ lteSignalStrength = ints[7];
+ lteRsrp = ints[8];
+ lteRsrq = ints[9];
+ lteRssnr = ints[10];
+ lteCqi = ints[11];
+ } else {
+ loge("Bogus signal strength response");
+ rssi = 99;
+ }
+ }
+
+ mSignalStrength = new SignalStrength(rssi, -1, -1, -1,
+ -1, -1, -1, lteSignalStrength, lteRsrp, lteRsrq, lteRssnr, lteCqi, true);
+
+ if (!mSignalStrength.equals(oldSignalStrength)) {
+ try { // This takes care of delayed EVENT_POLL_SIGNAL_STRENGTH (scheduled after
+ // POLL_PERIOD_MILLIS) during Radio Technology Change)
+ phone.notifySignalStrength();
+ } catch (NullPointerException ex) {
+ log("onSignalStrengthResult() Phone already destroyed: " + ex
+ + "SignalStrength not notified");
+ }
+ }
+ }
+
+ /**
+ * Set restricted state based on the OnRestrictedStateChanged notification
+ * If any voice or packet restricted state changes, trigger a UI
+ * notification and notify registrants when sim is ready.
+ *
+ * @param ar an int value of RIL_RESTRICTED_STATE_*
+ */
+ private void onRestrictedStateChanged(AsyncResult ar) {
+ RestrictedState newRs = new RestrictedState();
+
+ if (DBG) log("onRestrictedStateChanged: E rs "+ mRestrictedState);
+
+ if (ar.exception == null) {
+ int[] ints = (int[])ar.result;
+ int state = ints[0];
+
+ newRs.setCsEmergencyRestricted(
+ ((state & RILConstants.RIL_RESTRICTED_STATE_CS_EMERGENCY) != 0) ||
+ ((state & RILConstants.RIL_RESTRICTED_STATE_CS_ALL) != 0) );
+ //ignore the normal call and data restricted state before SIM READY
+ if (phone.getIccCard().getState() == IccCardConstants.State.READY) {
+ newRs.setCsNormalRestricted(
+ ((state & RILConstants.RIL_RESTRICTED_STATE_CS_NORMAL) != 0) ||
+ ((state & RILConstants.RIL_RESTRICTED_STATE_CS_ALL) != 0) );
+ newRs.setPsRestricted(
+ (state & RILConstants.RIL_RESTRICTED_STATE_PS_ALL)!= 0);
+ }
+
+ if (DBG) log("onRestrictedStateChanged: new rs "+ newRs);
+
+ if (!mRestrictedState.isPsRestricted() && newRs.isPsRestricted()) {
+ mPsRestrictEnabledRegistrants.notifyRegistrants();
+ setNotification(PS_ENABLED);
+ } else if (mRestrictedState.isPsRestricted() && !newRs.isPsRestricted()) {
+ mPsRestrictDisabledRegistrants.notifyRegistrants();
+ setNotification(PS_DISABLED);
+ }
+
+ /**
+ * There are two kind of cs restriction, normal and emergency. So
+ * there are 4 x 4 combinations in current and new restricted states
+ * and we only need to notify when state is changed.
+ */
+ if (mRestrictedState.isCsRestricted()) {
+ if (!newRs.isCsRestricted()) {
+ // remove all restriction
+ setNotification(CS_DISABLED);
+ } else if (!newRs.isCsNormalRestricted()) {
+ // remove normal restriction
+ setNotification(CS_EMERGENCY_ENABLED);
+ } else if (!newRs.isCsEmergencyRestricted()) {
+ // remove emergency restriction
+ setNotification(CS_NORMAL_ENABLED);
+ }
+ } else if (mRestrictedState.isCsEmergencyRestricted() &&
+ !mRestrictedState.isCsNormalRestricted()) {
+ if (!newRs.isCsRestricted()) {
+ // remove all restriction
+ setNotification(CS_DISABLED);
+ } else if (newRs.isCsRestricted()) {
+ // enable all restriction
+ setNotification(CS_ENABLED);
+ } else if (newRs.isCsNormalRestricted()) {
+ // remove emergency restriction and enable normal restriction
+ setNotification(CS_NORMAL_ENABLED);
+ }
+ } else if (!mRestrictedState.isCsEmergencyRestricted() &&
+ mRestrictedState.isCsNormalRestricted()) {
+ if (!newRs.isCsRestricted()) {
+ // remove all restriction
+ setNotification(CS_DISABLED);
+ } else if (newRs.isCsRestricted()) {
+ // enable all restriction
+ setNotification(CS_ENABLED);
+ } else if (newRs.isCsEmergencyRestricted()) {
+ // remove normal restriction and enable emergency restriction
+ setNotification(CS_EMERGENCY_ENABLED);
+ }
+ } else {
+ if (newRs.isCsRestricted()) {
+ // enable all restriction
+ setNotification(CS_ENABLED);
+ } else if (newRs.isCsEmergencyRestricted()) {
+ // enable emergency restriction
+ setNotification(CS_EMERGENCY_ENABLED);
+ } else if (newRs.isCsNormalRestricted()) {
+ // enable normal restriction
+ setNotification(CS_NORMAL_ENABLED);
+ }
+ }
+
+ mRestrictedState = newRs;
+ }
+ log("onRestrictedStateChanged: X rs "+ mRestrictedState);
+ }
+
+ /** 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
+ case 10:// same as 0, but indicates that emergency call is possible.
+ case 12:// same as 2, but indicates that emergency call is possible.
+ case 13:// same as 3, but indicates that emergency call is possible.
+ case 14:// same as 4, but indicates that emergency call is possible.
+ return ServiceState.STATE_OUT_OF_SERVICE;
+
+ case 1:
+ return ServiceState.STATE_IN_SERVICE;
+
+ case 5:
+ // in service, roam
+ return ServiceState.STATE_IN_SERVICE;
+
+ default:
+ loge("regCodeToServiceState: 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(TelephonyProperties.PROPERTY_ICC_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(
+ TelephonyProperties.PROPERTY_ICC_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.
+ */
+ int getCurrentGprsState() {
+ return gprsState;
+ }
+
+ public int getCurrentDataConnectionState() {
+ return gprsState;
+ }
+
+ /**
+ * @return true if phone is camping on a technology (eg UMTS)
+ * that could support voice and data simultaneously.
+ */
+ public boolean isConcurrentVoiceAndDataAllowed() {
+ return (mRilRadioTechnology >= ServiceState.RIL_RADIO_TECHNOLOGY_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, long nitzReceiveTime) {
+ // "yy/mm/dd,hh:mm:ss(+/-)tz"
+ // tz is in number of quarter-hours
+
+ long start = SystemClock.elapsedRealtime();
+ if (DBG) {log("NITZ: " + nitz + "," + nitzReceiveTime +
+ " start=" + start + " delay=" + (start - 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(TelephonyProperties.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) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
+ // We got the time before the country or the zone has changed
+ // so we don't know how to identify the DST rules yet. Save
+ // the information and hope to fix it up later.
+
+ mNeedFixZoneAfterNitz = true;
+ mZoneOffset = tzOffset;
+ mZoneDst = dst != 0;
+ mZoneTime = c.getTimeInMillis();
+ }
+
+ if (zone != null) {
+ if (getAutoTimeZone()) {
+ setAndBroadcastNetworkSetTimeZone(zone.getID());
+ }
+ saveNitzTimeZone(zone.getID());
+ }
+
+ String ignore = SystemProperties.get("gsm.ignore-nitz");
+ if (ignore != null && ignore.equals("yes")) {
+ log("NITZ: Not setting clock because gsm.ignore-nitz is set");
+ return;
+ }
+
+ try {
+ mWakeLock.acquire();
+
+ if (getAutoTime()) {
+ long millisSinceNitzReceived
+ = SystemClock.elapsedRealtime() - nitzReceiveTime;
+
+ if (millisSinceNitzReceived < 0) {
+ // Sanity check: something is wrong
+ if (DBG) {
+ log("NITZ: not setting time, clock has rolled "
+ + "backwards since NITZ time was received, "
+ + nitz);
+ }
+ return;
+ }
+
+ if (millisSinceNitzReceived > Integer.MAX_VALUE) {
+ // If the time is this far off, something is wrong > 24 days!
+ if (DBG) {
+ log("NITZ: not setting time, processing has taken "
+ + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
+ + " days");
+ }
+ return;
+ }
+
+ // Note: with range checks above, cast to int is safe
+ c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
+
+ if (DBG) {
+ log("NITZ: Setting time of day to " + c.getTime()
+ + " NITZ receive delay(ms): " + millisSinceNitzReceived
+ + " gained(ms): "
+ + (c.getTimeInMillis() - System.currentTimeMillis())
+ + " from " + nitz);
+ }
+
+ setAndBroadcastNetworkSetTime(c.getTimeInMillis());
+ Log.i(LOG_TAG, "NITZ: after Setting time of day");
+ }
+ SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
+ saveNitzTime(c.getTimeInMillis());
+ if (false) {
+ long end = SystemClock.elapsedRealtime();
+ log("NITZ: end=" + end + " dur=" + (end - start));
+ }
+ mNitzUpdatedTime = true;
+ } finally {
+ mWakeLock.release();
+ }
+ } catch (RuntimeException ex) {
+ loge("NITZ: Parsing NITZ time " + nitz + " ex=" + ex);
+ }
+ }
+
+ private boolean getAutoTime() {
+ try {
+ return Settings.System.getInt(phone.getContext().getContentResolver(),
+ Settings.System.AUTO_TIME) > 0;
+ } catch (SettingNotFoundException snfe) {
+ return true;
+ }
+ }
+
+ private boolean getAutoTimeZone() {
+ try {
+ return Settings.System.getInt(phone.getContext().getContentResolver(),
+ Settings.System.AUTO_TIME_ZONE) > 0;
+ } catch (SettingNotFoundException snfe) {
+ return true;
+ }
+ }
+
+ private void saveNitzTimeZone(String zoneId) {
+ mSavedTimeZone = zoneId;
+ }
+
+ private void saveNitzTime(long time) {
+ mSavedTime = time;
+ mSavedAtTime = SystemClock.elapsedRealtime();
+ }
+
+ /**
+ * Set the timezone and send out a sticky broadcast so the system can
+ * determine if the timezone was set by the carrier.
+ *
+ * @param zoneId timezone set by carrier
+ */
+ private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
+ if (DBG) log("setAndBroadcastNetworkSetTimeZone: setTimeZone=" + zoneId);
+ AlarmManager alarm =
+ (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
+ alarm.setTimeZone(zoneId);
+ Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("time-zone", zoneId);
+ phone.getContext().sendStickyBroadcast(intent);
+ if (DBG) {
+ log("setAndBroadcastNetworkSetTimeZone: call alarm.setTimeZone and broadcast zoneId=" +
+ zoneId);
+ }
+ }
+
+ /**
+ * Set the time and Send out a sticky broadcast so the system can determine
+ * if the time was set by the carrier.
+ *
+ * @param time time set by network
+ */
+ private void setAndBroadcastNetworkSetTime(long time) {
+ if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms");
+ SystemClock.setCurrentTimeMillis(time);
+ Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("time", time);
+ phone.getContext().sendStickyBroadcast(intent);
+ }
+
+ private void revertToNitzTime() {
+ if (Settings.System.getInt(phone.getContext().getContentResolver(),
+ Settings.System.AUTO_TIME, 0) == 0) {
+ return;
+ }
+ if (DBG) {
+ log("Reverting to NITZ Time: mSavedTime=" + mSavedTime
+ + " mSavedAtTime=" + mSavedAtTime);
+ }
+ if (mSavedTime != 0 && mSavedAtTime != 0) {
+ setAndBroadcastNetworkSetTime(mSavedTime
+ + (SystemClock.elapsedRealtime() - mSavedAtTime));
+ }
+ }
+
+ private void revertToNitzTimeZone() {
+ if (Settings.System.getInt(phone.getContext().getContentResolver(),
+ Settings.System.AUTO_TIME_ZONE, 0) == 0) {
+ return;
+ }
+ if (DBG) log("Reverting to NITZ TimeZone: tz='" + mSavedTimeZone);
+ if (mSavedTimeZone != null) {
+ setAndBroadcastNetworkSetTimeZone(mSavedTimeZone);
+ }
+ }
+
+ /**
+ * Post a notification to NotificationManager for restricted state
+ *
+ * @param notifyType is one state of PS/CS_*_ENABLE/DISABLE
+ */
+ private void setNotification(int notifyType) {
+
+ if (DBG) log("setNotification: create notification " + notifyType);
+ Context context = phone.getContext();
+
+ mNotification = new Notification();
+ mNotification.when = System.currentTimeMillis();
+ mNotification.flags = Notification.FLAG_AUTO_CANCEL;
+ mNotification.icon = com.android.internal.R.drawable.stat_sys_warning;
+ Intent intent = new Intent();
+ mNotification.contentIntent = PendingIntent
+ .getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+ CharSequence details = "";
+ CharSequence title = context.getText(com.android.internal.R.string.RestrictedChangedTitle);
+ int notificationId = CS_NOTIFICATION;
+
+ switch (notifyType) {
+ case PS_ENABLED:
+ notificationId = PS_NOTIFICATION;
+ details = context.getText(com.android.internal.R.string.RestrictedOnData);;
+ break;
+ case PS_DISABLED:
+ notificationId = PS_NOTIFICATION;
+ break;
+ case CS_ENABLED:
+ details = context.getText(com.android.internal.R.string.RestrictedOnAllVoice);;
+ break;
+ case CS_NORMAL_ENABLED:
+ details = context.getText(com.android.internal.R.string.RestrictedOnNormal);;
+ break;
+ case CS_EMERGENCY_ENABLED:
+ details = context.getText(com.android.internal.R.string.RestrictedOnEmergency);;
+ break;
+ case CS_DISABLED:
+ // do nothing and cancel the notification later
+ break;
+ }
+
+ if (DBG) log("setNotification: put notification " + title + " / " +details);
+ mNotification.tickerText = title;
+ mNotification.setLatestEventInfo(context, title, details,
+ mNotification.contentIntent);
+
+ NotificationManager notificationManager = (NotificationManager)
+ context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (notifyType == PS_DISABLED || notifyType == CS_DISABLED) {
+ // cancel previous post notification
+ notificationManager.cancel(notificationId);
+ } else {
+ // update restricted state notification
+ notificationManager.notify(notificationId, mNotification);
+ }
+ }
+
+ @Override
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[GsmSST] " + s);
+ }
+
+ @Override
+ protected void loge(String s) {
+ Log.e(LOG_TAG, "[GsmSST] " + s);
+ }
+
+ private static void sloge(String s) {
+ Log.e(LOG_TAG, "[GsmSST] " + s);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("GsmServiceStateTracker extends:");
+ super.dump(fd, pw, args);
+ pw.println(" phone=" + phone);
+ pw.println(" cellLoc=" + cellLoc);
+ pw.println(" newCellLoc=" + newCellLoc);
+ pw.println(" mPreferredNetworkType=" + mPreferredNetworkType);
+ pw.println(" gprsState=" + gprsState);
+ pw.println(" newGPRSState=" + newGPRSState);
+ pw.println(" mMaxDataCalls=" + mMaxDataCalls);
+ pw.println(" mNewMaxDataCalls=" + mNewMaxDataCalls);
+ pw.println(" mReasonDataDenied=" + mReasonDataDenied);
+ pw.println(" mNewReasonDataDenied=" + mNewReasonDataDenied);
+ pw.println(" mGsmRoaming=" + mGsmRoaming);
+ pw.println(" mDataRoaming=" + mDataRoaming);
+ pw.println(" mEmergencyOnly=" + mEmergencyOnly);
+ pw.println(" mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz);
+ pw.println(" mZoneOffset=" + mZoneOffset);
+ pw.println(" mZoneDst=" + mZoneDst);
+ pw.println(" mZoneTime=" + mZoneTime);
+ pw.println(" mGotCountryCode=" + mGotCountryCode);
+ pw.println(" mNitzUpdatedTime=" + mNitzUpdatedTime);
+ pw.println(" mSavedTimeZone=" + mSavedTimeZone);
+ pw.println(" mSavedTime=" + mSavedTime);
+ pw.println(" mSavedAtTime=" + mSavedAtTime);
+ pw.println(" mNeedToRegForSimLoaded=" + mNeedToRegForSimLoaded);
+ pw.println(" mStartedGprsRegCheck=" + mStartedGprsRegCheck);
+ pw.println(" mReportedGprsNoReg=" + mReportedGprsNoReg);
+ pw.println(" mNotification=" + mNotification);
+ pw.println(" mWakeLock=" + mWakeLock);
+ pw.println(" curSpn=" + curSpn);
+ pw.println(" curPlmn=" + curPlmn);
+ pw.println(" curSpnRule=" + curSpnRule);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSmsAddress.java b/src/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
new file mode 100644
index 0000000..c163803
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsAddress;
+
+public class GsmSmsAddress extends SmsAddress {
+
+ static final int OFFSET_ADDRESS_LENGTH = 0;
+
+ static final int OFFSET_TOA = 1;
+
+ static final int OFFSET_ADDRESS_VALUE = 2;
+
+ /**
+ * New GsmSmsAddress 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"
+ */
+
+ public GsmSmsAddress(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 address
+ */
+ 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 indicating 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;
+
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/src/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
new file mode 100644
index 0000000..78590fb
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2012 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.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.util.Pair;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsConstants;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
+ * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
+ */
+public class GsmSmsCbMessage {
+
+ /**
+ * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+ */
+ private static final String[] LANGUAGE_CODES_GROUP_0 = {
+ "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu",
+ "pl", null
+ };
+
+ /**
+ * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+ */
+ private static final String[] LANGUAGE_CODES_GROUP_2 = {
+ "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null,
+ null, null
+ };
+
+ private static final char CARRIAGE_RETURN = 0x0d;
+
+ private static final int PDU_BODY_PAGE_LENGTH = 82;
+
+ /** Utility class with only static methods. */
+ private GsmSmsCbMessage() { }
+
+ /**
+ * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
+ *
+ * @param pdus PDU bytes
+ */
+ static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location,
+ byte[][] pdus) throws IllegalArgumentException {
+ if (header.isEtwsPrimaryNotification()) {
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
+ header.getGeographicalScope(), header.getSerialNumber(),
+ location, header.getServiceCategory(),
+ null, "ETWS", SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
+ header.getEtwsInfo(), header.getCmasInfo());
+ } else {
+ String language = null;
+ StringBuilder sb = new StringBuilder();
+ for (byte[] pdu : pdus) {
+ Pair<String, String> p = parseBody(header, pdu);
+ language = p.first;
+ sb.append(p.second);
+ }
+ int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
+ : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
+
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
+ header.getGeographicalScope(), header.getSerialNumber(), location,
+ header.getServiceCategory(), language, sb.toString(), priority,
+ header.getEtwsInfo(), header.getCmasInfo());
+ }
+ }
+
+ /**
+ * Create a new SmsCbMessage object from one or more received PDUs. This is used by some
+ * CellBroadcastReceiver test cases, because SmsCbHeader is now package local.
+ *
+ * @param location the location (geographical scope) for the message
+ * @param pdus PDU bytes
+ */
+ public static SmsCbMessage createSmsCbMessage(SmsCbLocation location, byte[][] pdus)
+ throws IllegalArgumentException {
+ SmsCbHeader header = new SmsCbHeader(pdus[0]);
+ return createSmsCbMessage(header, location, pdus);
+ }
+
+ /**
+ * Parse and unpack the body text according to the encoding in the DCS.
+ * After completing successfully this method will have assigned the body
+ * text into mBody, and optionally the language code into mLanguage
+ *
+ * @param header the message header to use
+ * @param pdu the PDU to decode
+ * @return a Pair of Strings containing the language and body of the message
+ */
+ private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) {
+ int encoding;
+ String language = null;
+ boolean hasLanguageIndicator = false;
+ int dataCodingScheme = header.getDataCodingScheme();
+
+ // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
+ // section 5.
+ switch ((dataCodingScheme & 0xf0) >> 4) {
+ case 0x00:
+ encoding = SmsConstants.ENCODING_7BIT;
+ language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
+ break;
+
+ case 0x01:
+ hasLanguageIndicator = true;
+ if ((dataCodingScheme & 0x0f) == 0x01) {
+ encoding = SmsConstants.ENCODING_16BIT;
+ } else {
+ encoding = SmsConstants.ENCODING_7BIT;
+ }
+ break;
+
+ case 0x02:
+ encoding = SmsConstants.ENCODING_7BIT;
+ language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
+ break;
+
+ case 0x03:
+ encoding = SmsConstants.ENCODING_7BIT;
+ break;
+
+ case 0x04:
+ case 0x05:
+ switch ((dataCodingScheme & 0x0c) >> 2) {
+ case 0x01:
+ encoding = SmsConstants.ENCODING_8BIT;
+ break;
+
+ case 0x02:
+ encoding = SmsConstants.ENCODING_16BIT;
+ break;
+
+ case 0x00:
+ default:
+ encoding = SmsConstants.ENCODING_7BIT;
+ break;
+ }
+ break;
+
+ case 0x06:
+ case 0x07:
+ // Compression not supported
+ case 0x09:
+ // UDH structure not supported
+ case 0x0e:
+ // Defined by the WAP forum not supported
+ throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
+ + dataCodingScheme);
+
+ case 0x0f:
+ if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
+ encoding = SmsConstants.ENCODING_8BIT;
+ } else {
+ encoding = SmsConstants.ENCODING_7BIT;
+ }
+ break;
+
+ default:
+ // Reserved values are to be treated as 7-bit
+ encoding = SmsConstants.ENCODING_7BIT;
+ break;
+ }
+
+ if (header.isUmtsFormat()) {
+ // Payload may contain multiple pages
+ int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
+
+ if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
+ * nrPages) {
+ throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
+ + nrPages + " pages");
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < nrPages; i++) {
+ // Each page is 82 bytes followed by a length octet indicating
+ // the number of useful octets within those 82
+ int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
+ int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
+
+ if (length > PDU_BODY_PAGE_LENGTH) {
+ throw new IllegalArgumentException("Page length " + length
+ + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
+ }
+
+ Pair<String, String> p = unpackBody(pdu, encoding, offset, length,
+ hasLanguageIndicator, language);
+ language = p.first;
+ sb.append(p.second);
+ }
+ return new Pair<String, String>(language, sb.toString());
+ } else {
+ // Payload is one single page
+ int offset = SmsCbHeader.PDU_HEADER_LENGTH;
+ int length = pdu.length - offset;
+
+ return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language);
+ }
+ }
+
+ /**
+ * Unpack body text from the pdu using the given encoding, position and
+ * length within the pdu
+ *
+ * @param pdu The pdu
+ * @param encoding The encoding, as derived from the DCS
+ * @param offset Position of the first byte to unpack
+ * @param length Number of bytes to unpack
+ * @param hasLanguageIndicator true if the body text is preceded by a
+ * language indicator. If so, this method will as a side-effect
+ * assign the extracted language code into mLanguage
+ * @param language the language to return if hasLanguageIndicator is false
+ * @return a Pair of Strings containing the language and body of the message
+ */
+ private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length,
+ boolean hasLanguageIndicator, String language) {
+ String body = null;
+
+ switch (encoding) {
+ case SmsConstants.ENCODING_7BIT:
+ body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
+
+ if (hasLanguageIndicator && body != null && body.length() > 2) {
+ // Language is two GSM characters followed by a CR.
+ // The actual body text is offset by 3 characters.
+ language = body.substring(0, 2);
+ body = body.substring(3);
+ }
+ break;
+
+ case SmsConstants.ENCODING_16BIT:
+ if (hasLanguageIndicator && pdu.length >= offset + 2) {
+ // Language is two GSM characters.
+ // The actual body text is offset by 2 bytes.
+ language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
+ offset += 2;
+ length -= 2;
+ }
+
+ try {
+ body = new String(pdu, offset, (length & 0xfffe), "utf-16");
+ } catch (UnsupportedEncodingException e) {
+ // Apparently it wasn't valid UTF-16.
+ throw new IllegalArgumentException("Error decoding UTF-16 message", e);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (body != null) {
+ // Remove trailing carriage return
+ for (int i = body.length() - 1; i >= 0; i--) {
+ if (body.charAt(i) != CARRIAGE_RETURN) {
+ body = body.substring(0, i + 1);
+ break;
+ }
+ }
+ } else {
+ body = "";
+ }
+
+ return new Pair<String, String>(language, body);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/SIMFileHandler.java b/src/java/com/android/internal/telephony/gsm/SIMFileHandler.java
new file mode 100644
index 0000000..dcc9cfd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SIMFileHandler.java
@@ -0,0 +1,107 @@
+/*
+ * 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.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccCardApplication;
+import com.android.internal.telephony.IccConstants;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.PhoneBase;
+
+/**
+ * {@hide}
+ */
+public final class SIMFileHandler extends IccFileHandler implements IccConstants {
+ static final String LOG_TAG = "GSM";
+
+ //***** Instance Variables
+
+ //***** Constructor
+
+ public SIMFileHandler(IccCard card, String aid, CommandsInterface ci) {
+ super(card, aid, ci);
+ }
+
+ protected void finalize() {
+ Log.d(LOG_TAG, "SIMFileHandler finalized");
+ }
+
+ //***** Overridden from IccFileHandler
+
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ }
+
+ protected String getEFPath(int efid) {
+ // TODO(): DF_GSM can be 7F20 or 7F21 to handle backward compatibility.
+ // Implement this after discussion with OEMs.
+ switch(efid) {
+ case EF_SMS:
+ return MF_SIM + DF_TELECOM;
+
+ case EF_EXT6:
+ case EF_MWIS:
+ case EF_MBI:
+ case EF_SPN:
+ case EF_AD:
+ case EF_MBDN:
+ case EF_PNN:
+ case EF_SPDI:
+ case EF_SST:
+ case EF_CFIS:
+ return MF_SIM + DF_GSM;
+
+ case EF_MAILBOX_CPHS:
+ case EF_VOICE_MAIL_INDICATOR_CPHS:
+ case EF_CFF_CPHS:
+ case EF_SPN_CPHS:
+ case EF_SPN_SHORT_CPHS:
+ case EF_INFO_CPHS:
+ case EF_CSP_CPHS:
+ return MF_SIM + DF_GSM;
+
+ case EF_PBR:
+ // we only support global phonebook.
+ return MF_SIM + DF_TELECOM + DF_PHONEBOOK;
+ }
+ String path = getCommonIccEFPath(efid);
+ if (path == null) {
+ // The EFids in USIM phone book entries are decided by the card manufacturer.
+ // So if we don't match any of the cases above and if its a USIM return
+ // the phone book path.
+ if (mParentCard != null
+ && mParentCard.isApplicationOnIcc(IccCardApplication.AppType.APPTYPE_USIM)) {
+ return MF_SIM + DF_TELECOM + DF_PHONEBOOK;
+ }
+ Log.e(LOG_TAG, "Error: EF Path being returned in null");
+ }
+ return path;
+ }
+
+ protected void logd(String msg) {
+ Log.d(LOG_TAG, "[SIMFileHandler] " + msg);
+ }
+
+ protected void loge(String msg) {
+ Log.e(LOG_TAG, "[SIMFileHandler] " + msg);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/SIMRecords.java b/src/java/com/android/internal/telephony/gsm/SIMRecords.java
new file mode 100755
index 0000000..1ec3ea7
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SIMRecords.java
@@ -0,0 +1,1675 @@
+/*
+ * 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_ICC_OPERATOR_ALPHA;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.telephony.AdnRecord;
+import com.android.internal.telephony.AdnRecordCache;
+import com.android.internal.telephony.AdnRecordLoader;
+import com.android.internal.telephony.BaseCommands;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccRecords;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.IccVmFixedException;
+import com.android.internal.telephony.IccVmNotSupportedException;
+import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.IccRefreshResponse;
+
+import java.util.ArrayList;
+
+
+/**
+ * {@hide}
+ */
+public class SIMRecords extends IccRecords {
+ protected static final String LOG_TAG = "GSM";
+
+ private static final boolean CRASH_RIL = false;
+
+ protected static final boolean DBG = true;
+
+ // ***** Instance Variables
+
+ VoiceMailConstants mVmConfig;
+
+
+ SpnOverride mSpnOverride;
+
+ // ***** Cached SIM State; cleared on channel close
+
+ private String imsi;
+ private boolean callForwardingEnabled;
+
+
+ /**
+ * States 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;
+ boolean mCspPlmnEnabled = true;
+
+ byte[] efMWIS = null;
+ byte[] efCPHS_MWI =null;
+ byte[] mEfCff = null;
+ byte[] mEfCfis = null;
+
+
+ int spnDisplayCondition;
+ // Numeric network codes listed in TS 51.011 EF[SPDI]
+ ArrayList<String> spdiNetworks = null;
+
+ String pnnHomeName = null;
+
+ UsimServiceTable mUsimServiceTable;
+
+ // ***** 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 = 0xA3;
+ 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_RADIO_OFF_OR_NOT_AVAILABLE = 2;
+ protected static final int EVENT_GET_IMSI_DONE = 3;
+ protected 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;
+ protected static final int EVENT_GET_AD_DONE = 9; // Admin data on SIM
+ protected 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;
+ protected 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;
+ private static final int EVENT_GET_CSP_CPHS_DONE = 33;
+
+ // Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length.
+
+ private static final String[] MCCMNC_CODES_HAVING_3DIGITS_MNC = {
+ "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032",
+ "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040",
+ "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750",
+ "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800",
+ "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808",
+ "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816",
+ "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824",
+ "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832",
+ "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840",
+ "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848",
+ "405849", "405850", "405851", "405852", "405853", "405875", "405876", "405877",
+ "405878", "405879", "405880", "405881", "405882", "405883", "405884", "405885",
+ "405886", "405908", "405909", "405910", "405911", "405912", "405913", "405914",
+ "405915", "405916", "405917", "405918", "405919", "405920", "405921", "405922",
+ "405923", "405924", "405925", "405926", "405927", "405928", "405929", "405930",
+ "405931", "405932"
+ };
+
+ // ***** Constructor
+
+ public SIMRecords(IccCard card, Context c, CommandsInterface ci) {
+ super(card, c, ci);
+
+ adnCache = new AdnRecordCache(mFh);
+
+ mVmConfig = new VoiceMailConstants();
+ mSpnOverride = new SpnOverride();
+
+ recordsRequested = false; // No load request is made till SIM ready
+
+ // recordsToLoad is set to 0 because no requests are made yet
+ recordsToLoad = 0;
+
+ mCi.registerForOffOrNotAvailable(
+ this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+ mCi.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null);
+ mCi.registerForIccRefresh(this, EVENT_SIM_REFRESH, null);
+
+ // Start off by setting empty state
+ onRadioOffOrNotAvailable();
+
+ }
+
+ @Override
+ public void dispose() {
+ if (DBG) log("Disposing SIMRecords " + this);
+ //Unregister for all events
+ mCi.unregisterForOffOrNotAvailable( this);
+ mCi.unregisterForIccRefresh(this);
+ mCi.unSetOnSmsOnSim(this);
+ super.dispose();
+ }
+
+ protected void finalize() {
+ if(DBG) log("finalized");
+ }
+
+ protected void onRadioOffOrNotAvailable() {
+ imsi = null;
+ msisdn = null;
+ voiceMailNum = null;
+ countVoiceMessages = 0;
+ mncLength = UNINITIALIZED;
+ iccid = null;
+ // -1 means no EF_SPN found; treat accordingly.
+ spnDisplayCondition = -1;
+ efMWIS = null;
+ efCPHS_MWI = null;
+ spdiNetworks = null;
+ pnnHomeName = null;
+
+ adnCache.reset();
+
+ log("SIMRecords: onRadioOffOrNotAvailable set 'gsm.sim.operator.numeric' to operator=null");
+ SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, null);
+ SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, null);
+ SystemProperties.set(PROPERTY_ICC_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
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getIMSI() {
+ return imsi;
+ }
+
+ public String getMsisdnNumber() {
+ return msisdn;
+ }
+
+ @Override
+ public UsimServiceTable getUsimServiceTable() {
+ return mUsimServiceTable;
+ }
+
+ /**
+ * 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*/ "xxxxxxx");
+
+
+ AdnRecord adn = new AdnRecord(msisdnTag, msisdn);
+
+ new AdnRecordLoader(mFh).updateEF(adn, EF_MSISDN, EF_EXT1, 1, null,
+ obtainMessage(EVENT_SET_MSISDN_DONE, onComplete));
+ }
+
+ public String getMsisdnAlphaTag() {
+ return msisdnTag;
+ }
+
+ public String getVoiceMailNumber() {
+ return voiceMailNum;
+ }
+
+ /**
+ * 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 IccVmFixedException("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(mFh).updateEF(adn, EF_MBDN, EF_EXT6,
+ mailboxIndex, null,
+ obtainMessage(EVENT_SET_MBDN_DONE, onComplete));
+
+ } else if (isCphsMailboxEnabled()) {
+
+ new AdnRecordLoader(mFh).updateEF(adn, EF_MAILBOX_CPHS,
+ EF_EXT1, 1, null,
+ obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, onComplete));
+
+ } else {
+ AsyncResult.forMessage((onComplete)).exception =
+ new IccVmNotSupportedException("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;
+
+ mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
+
+ 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;
+ }
+
+ mFh.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));
+
+ mFh.updateEFTransparent(
+ EF_VOICE_MAIL_INDICATOR_CPHS, efCPHS_MWI,
+ obtainMessage (EVENT_UPDATE_DONE, EF_VOICE_MAIL_INDICATOR_CPHS));
+ }
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ logw("Error saving voice mail state to SIM. Probably malformed SIM record", ex);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean getVoiceCallForwardingFlag() {
+ return callForwardingEnabled;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setVoiceCallForwardingFlag(int line, boolean enable) {
+
+ if (line != 1) return; // only line 1 is supported
+
+ callForwardingEnabled = enable;
+
+ mRecordsEventsRegistrants.notifyResult(EVENT_CFI);
+
+ 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.
+
+ mFh.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);
+ }
+
+ mFh.updateEFTransparent(
+ EF_CFF_CPHS, mEfCff,
+ obtainMessage (EVENT_UPDATE_DONE, EF_CFF_CPHS));
+ }
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ logw("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();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getOperatorNumeric() {
+ if (imsi == null) {
+ log("getOperatorNumeric: IMSI == null");
+ return null;
+ }
+ if (mncLength == UNINITIALIZED || mncLength == UNKNOWN) {
+ log("getSIMOperatorNumeric: bad mncLength");
+ return null;
+ }
+
+ // Length = length of MCC + length of MNC
+ // length of mcc = 3 (TS 23.003 Section 2.2)
+ return imsi.substring(0, 3 + mncLength);
+ }
+
+ // ***** Overridden from Handler
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+ AdnRecord adn;
+
+ byte data[];
+
+ boolean isRecordLoadResponse = false;
+
+ if (mDestroyed) {
+ loge("Received message " + msg + "[" + msg.what + "] " +
+ " while being destroyed. Ignoring.");
+ return;
+ }
+
+ try { switch (msg.what) {
+ 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) {
+ loge("Exception querying IMSI, Exception:" + 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)) {
+ loge("invalid IMSI " + imsi);
+ imsi = null;
+ }
+
+ log("IMSI: " + /* imsi.substring(0, 6) +*/ "xxxxxxx");
+
+ if (((mncLength == UNKNOWN) || (mncLength == 2)) &&
+ ((imsi != null) && (imsi.length() >= 6))) {
+ String mccmncCode = imsi.substring(0, 6);
+ for (String mccmnc : MCCMNC_CODES_HAVING_3DIGITS_MNC) {
+ if (mccmnc.equals(mccmncCode)) {
+ mncLength = 3;
+ break;
+ }
+ }
+ }
+
+ if (mncLength == UNKNOWN) {
+ // the SIM has told us all it knows, but it didn't know the mnc length.
+ // guess using the mcc
+ try {
+ int mcc = Integer.parseInt(imsi.substring(0,3));
+ mncLength = MccTable.smallestDigitsMccForMnc(mcc);
+ } catch (NumberFormatException e) {
+ mncLength = UNKNOWN;
+ loge("Corrupt IMSI!");
+ }
+ }
+
+ if (mncLength != UNKNOWN && mncLength != UNINITIALIZED) {
+ // finally have both the imsi and the mncLength and can parse the imsi properly
+ MccTable.updateMccMncConfiguration(mContext, imsi.substring(0, 3 + mncLength));
+ }
+ mParentCard.broadcastIccStateChangedIntent(
+ IccCardConstants.INTENT_VALUE_ICC_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("EF_MBI: " + IccUtils.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("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(mFh).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(mFh).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:
+ //Resetting the voice mail number and voice mail tag to null
+ //as these should be updated from the data read from EF_MBDN.
+ //If they are not reset, incase of invalid data/exception these
+ //variables are retaining their previous values and are
+ //causing invalid voice mailbox info display to user.
+ voiceMailNum = null;
+ voiceMailTag = null;
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+
+ log("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(mFh).loadFromEF(
+ EF_MAILBOX_CPHS, EF_EXT1, 1,
+ obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
+ }
+ break;
+ }
+
+ adn = (AdnRecord)ar.result;
+
+ log("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(mFh).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("Invalid or missing EF[MSISDN]");
+ break;
+ }
+
+ adn = (AdnRecord)ar.result;
+
+ msisdn = adn.getNumber();
+ msisdnTag = adn.getAlphaTag();
+
+ log("MSISDN: " + /*msisdn*/ "xxxxxxx");
+ 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("EF_MWIS: " + IccUtils.bytesToHexString(data));
+
+ efMWIS = data;
+
+ if ((data[0] & 0xff) == 0xff) {
+ log("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;
+ }
+
+ mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
+ 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;
+ }
+
+ mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
+ }
+ break;
+
+ case EVENT_GET_ICCID_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ iccid = IccUtils.bcdToString(data, 0, data.length);
+
+ log("iccid: " + iccid);
+
+ break;
+
+
+ case EVENT_GET_AD_DONE:
+ try {
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ log("EF_AD: " + IccUtils.bytesToHexString(data));
+
+ if (data.length < 3) {
+ log("Corrupt AD data on SIM");
+ break;
+ }
+
+ if (data.length == 3) {
+ log("MNC length not present in EF_AD");
+ break;
+ }
+
+ mncLength = (int)data[3] & 0xf;
+
+ if (mncLength == 0xf) {
+ mncLength = UNKNOWN;
+ }
+ } finally {
+ if (((mncLength == UNINITIALIZED) || (mncLength == UNKNOWN) ||
+ (mncLength == 2)) && ((imsi != null) && (imsi.length() >= 6))) {
+ String mccmncCode = imsi.substring(0, 6);
+ for (String mccmnc : MCCMNC_CODES_HAVING_3DIGITS_MNC) {
+ if (mccmnc.equals(mccmncCode)) {
+ mncLength = 3;
+ break;
+ }
+ }
+ }
+
+ if (mncLength == UNKNOWN || mncLength == UNINITIALIZED) {
+ if (imsi != null) {
+ try {
+ int mcc = Integer.parseInt(imsi.substring(0,3));
+
+ mncLength = MccTable.smallestDigitsMccForMnc(mcc);
+ } catch (NumberFormatException e) {
+ mncLength = UNKNOWN;
+ loge("Corrupt IMSI!");
+ }
+ } else {
+ // Indicate we got this info, but it didn't contain the length.
+ mncLength = UNKNOWN;
+
+ log("MNC length not present in EF_AD");
+ }
+ }
+ if (imsi != null && mncLength != UNKNOWN) {
+ // finally have both imsi and the length of the mnc and can parse
+ // the imsi properly
+ MccTable.updateMccMncConfiguration(mContext,
+ imsi.substring(0, 3 + mncLength));
+ }
+ }
+ 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("EF_CFF_CPHS: " + IccUtils.bytesToHexString(data));
+ mEfCff = data;
+
+ if (mEfCfis == null) {
+ callForwardingEnabled =
+ ((data[0] & CFF_LINE1_MASK) == CFF_UNCONDITIONAL_ACTIVE);
+
+ mRecordsEventsRegistrants.notifyResult(EVENT_CFI);
+ }
+ 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) {
+ logw("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
+ = IccUtils.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) {
+ loge("Error on SMS_ON_SIM with exp "
+ + ar.exception + " length " + index.length);
+ } else {
+ log("READ EF_SMS RECORD index=" + index[0]);
+ mFh.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 {
+ loge("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;
+ }
+
+ mUsimServiceTable = new UsimServiceTable(data);
+ if (DBG) log("SST: " + mUsimServiceTable);
+ 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: " + IccUtils.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(mFh).
+ 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 (DBG) log("Sim REFRESH with exception: " + ar.exception);
+ if (ar.exception == null) {
+ handleSimRefresh((IccRefreshResponse)ar.result);
+ }
+ break;
+ case EVENT_GET_CFIS_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+ data = (byte[])ar.result;
+
+ if (ar.exception != null) {
+ break;
+ }
+
+ log("EF_CFIS: " + IccUtils.bytesToHexString(data));
+
+ mEfCfis = data;
+
+ // Refer TS 51.011 Section 10.3.46 for the content description
+ callForwardingEnabled = ((data[1] & 0x01) != 0);
+
+ mRecordsEventsRegistrants.notifyResult(EVENT_CFI);
+ break;
+
+ case EVENT_GET_CSP_CPHS_DONE:
+ isRecordLoadResponse = true;
+
+ ar = (AsyncResult)msg.obj;
+
+ if (ar.exception != null) {
+ loge("Exception in fetching EF_CSP data " + ar.exception);
+ break;
+ }
+
+ data = (byte[])ar.result;
+
+ log("EF_CSP: " + IccUtils.bytesToHexString(data));
+ handleEfCspData(data);
+ break;
+
+ default:
+ super.handleMessage(msg); // IccRecords handles generic record load responses
+
+ }}catch (RuntimeException exc) {
+ // I don't want these exceptions to be fatal
+ logw("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(mFh).loadFromEF(EF_MBDN, EF_EXT6,
+ mailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
+ break;
+ case EF_MAILBOX_CPHS:
+ recordsToLoad++;
+ new AdnRecordLoader(mFh).loadFromEF(EF_MAILBOX_CPHS, EF_EXT1,
+ 1, obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
+ break;
+ case EF_CSP_CPHS:
+ recordsToLoad++;
+ log("[CSP] SIM Refresh for EF_CSP_CPHS");
+ mFh.loadEFTransparent(EF_CSP_CPHS,
+ obtainMessage(EVENT_GET_CSP_CPHS_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(IccRefreshResponse refreshResponse){
+ if (refreshResponse == null) {
+ if (DBG) log("handleSimRefresh received without input");
+ return;
+ }
+
+ if (refreshResponse.aid != null &&
+ !refreshResponse.aid.equals(mParentCard.getAid())) {
+ // This is for different app. Ignore.
+ return;
+ }
+
+ switch (refreshResponse.refreshResult) {
+ case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE:
+ if (DBG) log("handleSimRefresh with SIM_FILE_UPDATED");
+ handleFileUpdate(refreshResponse.efId);
+ break;
+ case IccRefreshResponse.REFRESH_RESULT_INIT:
+ if (DBG) log("handleSimRefresh with SIM_REFRESH_INIT");
+ // need to reload all files (that we care about)
+ adnCache.reset();
+ fetchSimRecords();
+ break;
+ case IccRefreshResponse.REFRESH_RESULT_RESET:
+ if (DBG) log("handleSimRefresh with SIM_REFRESH_RESET");
+ mCi.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
+ if (DBG) log("handleSimRefresh with unknown operation");
+ break;
+ }
+ }
+
+ /**
+ * Dispatch 3GPP format message. Overridden for CDMA/LTE phones by
+ * {@link com.android.internal.telephony.cdma.CdmaLteUiccRecords}
+ * to send messages to the secondary 3GPP format SMS dispatcher.
+ */
+ protected int dispatchGsmMessage(SmsMessageBase message) {
+ mNewSmsRegistrants.notifyResult(message);
+ return 0;
+ }
+
+ 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[] pdu = new byte[n - 1];
+ System.arraycopy(ba, 1, pdu, 0, n - 1);
+ SmsMessage message = SmsMessage.createFromPdu(pdu);
+
+ dispatchGsmMessage(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[] pdu = new byte[n - 1];
+ System.arraycopy(ba, 1, pdu, 0, n - 1);
+ SmsMessage message = SmsMessage.createFromPdu(pdu);
+
+ dispatchGsmMessage(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
+ mFh.updateEFLinearFixed(EF_SMS,
+ i, ba, null, obtainMessage(EVENT_MARK_SMS_READ_DONE, i));
+ }
+ }
+ }
+ }
+
+ protected void onRecordLoaded() {
+ // One record loaded successfully or failed, In either case
+ // we need to update the recordsToLoad count
+ recordsToLoad -= 1;
+ if (DBG) log("onRecordLoaded " + recordsToLoad + " requested: " + recordsRequested);
+
+ if (recordsToLoad == 0 && recordsRequested == true) {
+ onAllRecordsLoaded();
+ } else if (recordsToLoad < 0) {
+ loge("recordsToLoad <0, programmer error suspected");
+ recordsToLoad = 0;
+ }
+ }
+
+ protected void onAllRecordsLoaded() {
+ String operator = getOperatorNumeric();
+
+ // Some fields require more than one SIM record to set
+
+ log("SIMRecords: onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +
+ operator + "'");
+ SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
+
+ if (imsi != null) {
+ SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
+ MccTable.countryCodeForMcc(Integer.parseInt(imsi.substring(0,3))));
+ }
+ else {
+ loge("onAllRecordsLoaded: imsi is NULL!");
+ }
+
+ setVoiceMailByCountry(operator);
+ setSpnFromConfig(operator);
+
+ recordsLoadedRegistrants.notifyRegistrants(
+ new AsyncResult(null, null, null));
+ mParentCard.broadcastIccStateChangedIntent(
+ IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
+ }
+
+ //***** Private methods
+
+ private void setSpnFromConfig(String carrier) {
+ if (mSpnOverride.containsCarrier(carrier)) {
+ spn = mSpnOverride.getSpn(carrier);
+ }
+ }
+
+
+ private void setVoiceMailByCountry (String spn) {
+ if (mVmConfig.containsCarrier(spn)) {
+ isVoiceMailFixed = true;
+ voiceMailNum = mVmConfig.getVoiceMailNumber(spn);
+ voiceMailTag = mVmConfig.getVoiceMailTag(spn);
+ }
+ }
+
+ @Override
+ public void onReady() {
+ /* broadcast intent SIM_READY here so that we can make sure
+ READY is sent before IMSI ready
+ */
+ mParentCard.broadcastIccStateChangedIntent(
+ IccCardConstants.INTENT_VALUE_ICC_READY, null);
+
+ fetchSimRecords();
+ }
+
+ protected void fetchSimRecords() {
+ recordsRequested = true;
+
+ if (DBG) log("fetchSimRecords " + recordsToLoad);
+
+ mCi.getIMSIForApp(mParentCard.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
+ recordsToLoad++;
+
+ mFh.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(mFh).loadFromEF(EF_MSISDN, EF_EXT1, 1,
+ obtainMessage(EVENT_GET_MSISDN_DONE));
+ recordsToLoad++;
+
+ // Record number is subscriber profile
+ mFh.loadEFLinearFixed(EF_MBI, 1, obtainMessage(EVENT_GET_MBI_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));
+ recordsToLoad++;
+
+ // Record number is subscriber profile
+ mFh.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]
+ mFh.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.
+ mFh.loadEFLinearFixed(EF_CFIS, 1, obtainMessage(EVENT_GET_CFIS_DONE));
+ recordsToLoad++;
+ mFh.loadEFTransparent(EF_CFF_CPHS, obtainMessage(EVENT_GET_CFF_DONE));
+ recordsToLoad++;
+
+
+ getSpnFsm(true, null);
+
+ mFh.loadEFTransparent(EF_SPDI, obtainMessage(EVENT_GET_SPDI_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFLinearFixed(EF_PNN, 1, obtainMessage(EVENT_GET_PNN_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_INFO_CPHS, obtainMessage(EVENT_GET_INFO_CPHS_DONE));
+ recordsToLoad++;
+
+ mFh.loadEFTransparent(EF_CSP_CPHS,obtainMessage(EVENT_GET_CSP_CPHS_DONE));
+ recordsToLoad++;
+
+ // XXX should seek instead of examining them all
+ if (false) { // XXX
+ mFh.loadEFLinearFixedAll(EF_SMS, obtainMessage(EVENT_GET_ALL_SMS_DONE));
+ recordsToLoad++;
+ }
+
+ if (CRASH_RIL) {
+ String sms = "0107912160130310f20404d0110041007030208054832b0120"
+ + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ + "ffffffffffffffffffffffffffffff";
+ byte[] ba = IccUtils.hexStringToBytes(sms);
+
+ mFh.updateEFLinearFixed(EF_SMS, 1, ba, null,
+ obtainMessage(EVENT_MARK_SMS_READ_DONE, 1));
+ }
+ if (DBG) log("fetchSimRecords " + recordsToLoad + " requested: " + recordsRequested);
+ }
+
+ /**
+ * 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.
+ */
+ @Override
+ public 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(getOperatorNumeric())) {
+ 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
+ *
+ * If the FSM gets restart while waiting for one of
+ * SPN EFs results (i.e. a SIM refresh occurs after issuing
+ * read EF_CPHS_SPN), it will re-initialize only after
+ * receiving and discarding the unfinished SPN EF result.
+ *
+ * @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) {
+ // Check previous state to see if there is outstanding
+ // SPN read
+ if(spnState == Get_Spn_Fsm_State.READ_SPN_3GPP ||
+ spnState == Get_Spn_Fsm_State.READ_SPN_CPHS ||
+ spnState == Get_Spn_Fsm_State.READ_SPN_SHORT_CPHS ||
+ spnState == Get_Spn_Fsm_State.INIT) {
+ // Set INIT then return so the INIT code
+ // will run when the outstanding read done.
+ spnState = Get_Spn_Fsm_State.INIT;
+ return;
+ } else {
+ spnState = Get_Spn_Fsm_State.INIT;
+ }
+ }
+
+ switch(spnState){
+ case INIT:
+ spn = null;
+
+ mFh.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 = IccUtils.adnStringFieldToString(data, 1, data.length - 1);
+
+ if (DBG) log("Load EF_SPN: " + spn
+ + " spnDisplayCondition: " + spnDisplayCondition);
+ SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, spn);
+
+ spnState = Get_Spn_Fsm_State.IDLE;
+ } else {
+ mFh.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 = IccUtils.adnStringFieldToString(
+ data, 0, data.length - 1 );
+
+ if (DBG) log("Load EF_SPN_CPHS: " + spn);
+ SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, spn);
+
+ spnState = Get_Spn_Fsm_State.IDLE;
+ } else {
+ mFh.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 = IccUtils.adnStringFieldToString(
+ data, 0, data.length - 1);
+
+ if (DBG) log("Load EF_SPN_SHORT_CPHS: " + spn);
+ SystemProperties.set(PROPERTY_ICC_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;
+
+ for ( ; tlv.isValidObject() ; tlv.nextObject()) {
+ // Skip SPDI tag, if existant
+ if (tlv.getTag() == TAG_SPDI) {
+ tlv = new SimTlv(tlv.getData(), 0, tlv.getData().length);
+ }
+ // There should only be one TAG_SPDI_PLMN_LIST
+ 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 = IccUtils.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 );
+ }
+
+ protected void log(String s) {
+ Log.d(LOG_TAG, "[SIMRecords] " + s);
+ }
+
+ protected void loge(String s) {
+ Log.e(LOG_TAG, "[SIMRecords] " + s);
+ }
+
+ protected void logw(String s, Throwable tr) {
+ Log.w(LOG_TAG, "[SIMRecords] " + s, tr);
+ }
+
+ protected void logv(String s) {
+ Log.v(LOG_TAG, "[SIMRecords] " + s);
+ }
+
+ /**
+ * Return true if "Restriction of menu options for manual PLMN selection"
+ * bit is set or EF_CSP data is unavailable, return false otherwise.
+ */
+ public boolean isCspPlmnEnabled() {
+ return mCspPlmnEnabled;
+ }
+
+ /**
+ * Parse EF_CSP data and check if
+ * "Restriction of menu options for manual PLMN selection" is
+ * Enabled/Disabled
+ *
+ * @param data EF_CSP hex data.
+ */
+ private void handleEfCspData(byte[] data) {
+ // As per spec CPHS4_2.WW6, CPHS B.4.7.1, EF_CSP contains CPHS defined
+ // 18 bytes (i.e 9 service groups info) and additional data specific to
+ // operator. The valueAddedServicesGroup is not part of standard
+ // services. This is operator specific and can be programmed any where.
+ // Normally this is programmed as 10th service after the standard
+ // services.
+ int usedCspGroups = data.length / 2;
+ // This is the "Servive Group Number" of "Value Added Services Group".
+ byte valueAddedServicesGroup = (byte)0xC0;
+
+ mCspPlmnEnabled = true;
+ for (int i = 0; i < usedCspGroups; i++) {
+ if (data[2 * i] == valueAddedServicesGroup) {
+ log("[CSP] found ValueAddedServicesGroup, value " + data[(2 * i) + 1]);
+ if ((data[(2 * i) + 1] & 0x80) == 0x80) {
+ // Bit 8 is for
+ // "Restriction of menu options for manual PLMN selection".
+ // Operator Selection menu should be enabled.
+ mCspPlmnEnabled = true;
+ } else {
+ mCspPlmnEnabled = false;
+ // Operator Selection menu should be disabled.
+ // Operator Selection Mode should be set to Automatic.
+ log("[CSP] Set Automatic Network Selection");
+ mNetworkSelectionModeAutomaticRegistrants.notifyRegistrants();
+ }
+ return;
+ }
+ }
+
+ log("[CSP] Value Added Service Group (0xC0), not found!");
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
new file mode 100644
index 0000000..35ba0d1
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
@@ -0,0 +1,79 @@
+/*
+** 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 java.util.concurrent.atomic.AtomicBoolean;
+
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.IccPhoneBookInterfaceManager;
+
+/**
+ * SimPhoneBookInterfaceManager to provide an inter-process communication to
+ * access ADN-like SIM records.
+ */
+
+
+public class SimPhoneBookInterfaceManager extends IccPhoneBookInterfaceManager {
+ static final String LOG_TAG = "GSM";
+
+ public SimPhoneBookInterfaceManager(GSMPhone phone) {
+ super(phone);
+ adnCache = phone.mIccRecords.getAdnCache();
+ //NOTE service "simphonebook" added by IccSmsInterfaceManagerProxy
+ }
+
+ public void dispose() {
+ super.dispose();
+ }
+
+ protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
+ if(DBG) Log.d(LOG_TAG, "SimPhoneBookInterfaceManager finalized");
+ }
+
+ public int[] getAdnRecordsSize(int efid) {
+ if (DBG) logd("getAdnRecordsSize: efid=" + efid);
+ synchronized(mLock) {
+ checkThread();
+ recordSize = new int[3];
+
+ //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling
+ AtomicBoolean status = new AtomicBoolean(false);
+ Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, status);
+
+ phone.getIccFileHandler().getEFLinearRecordSize(efid, response);
+ waitForResult(status);
+ }
+
+ return recordSize;
+ }
+
+ protected void logd(String msg) {
+ Log.d(LOG_TAG, "[SimPbInterfaceManager] " + msg);
+ }
+
+ protected void loge(String msg) {
+ Log.e(LOG_TAG, "[SimPbInterfaceManager] " + msg);
+ }
+}
+
diff --git a/src/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/src/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
new file mode 100644
index 0000000..92bf390
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -0,0 +1,365 @@
+/*
+** 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.Context;
+import android.content.pm.PackageManager;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.IccConstants;
+import com.android.internal.telephony.IccSmsInterfaceManager;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.IntRangeManager;
+import com.android.internal.telephony.SMSDispatcher;
+import com.android.internal.telephony.SmsRawData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
+
+/**
+ * SimSmsInterfaceManager to provide an inter-process communication to
+ * access Sms in Sim.
+ */
+public class SimSmsInterfaceManager extends IccSmsInterfaceManager {
+ static final String LOG_TAG = "GSM";
+ static final boolean DBG = true;
+
+ private final Object mLock = new Object();
+ private boolean mSuccess;
+ private List<SmsRawData> mSms;
+ private HashMap<Integer, HashSet<String>> mCellBroadcastSubscriptions =
+ new HashMap<Integer, HashSet<String>>();
+
+ private CellBroadcastRangeManager mCellBroadcastRangeManager =
+ new CellBroadcastRangeManager();
+
+ private static final int EVENT_LOAD_DONE = 1;
+ private static final int EVENT_UPDATE_DONE = 2;
+ private static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
+ private static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
+ private static final int SMS_CB_CODE_SCHEME_MIN = 0;
+ private static final int SMS_CB_CODE_SCHEME_MAX = 255;
+
+ 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 = buildValidRawData((ArrayList<byte[]>) ar.result);
+ } else {
+ if(DBG) log("Cannot load Sms records");
+ if (mSms != null)
+ mSms.clear();
+ }
+ mLock.notifyAll();
+ }
+ break;
+ case EVENT_SET_BROADCAST_ACTIVATION_DONE:
+ case EVENT_SET_BROADCAST_CONFIG_DONE:
+ ar = (AsyncResult) msg.obj;
+ synchronized (mLock) {
+ mSuccess = (ar.exception == null);
+ mLock.notifyAll();
+ }
+ break;
+ }
+ }
+ };
+
+ public SimSmsInterfaceManager(GSMPhone phone, SMSDispatcher dispatcher) {
+ super(phone);
+ mDispatcher = dispatcher;
+ }
+
+ public void dispose() {
+ }
+
+ @Override
+ protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
+ if(DBG) Log.d(LOG_TAG, "SimSmsInterfaceManager finalized");
+ }
+
+ /**
+ * Update the specified message on the SIM.
+ *
+ * @param index record index of message to update
+ * @param status new message status (STATUS_ON_ICC_READ,
+ * STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
+ * STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
+ * @param pdu the raw PDU to store
+ * @return success or not
+ *
+ */
+ public boolean
+ updateMessageOnIccEf(int index, int status, byte[] pdu) {
+ if (DBG) log("updateMessageOnIccEf: index=" + index +
+ " status=" + status + " ==> " +
+ "("+ Arrays.toString(pdu) + ")");
+ enforceReceiveAndSend("Updating message on SIM");
+ synchronized(mLock) {
+ mSuccess = false;
+ Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
+
+ if (status == STATUS_ON_ICC_FREE) {
+ // Special case FREE: call deleteSmsOnSim instead of
+ // manipulating the SIM record
+ mPhone.mCM.deleteSmsOnSim(index, response);
+ } else {
+ byte[] record = makeSmsRecordData(status, pdu);
+ mPhone.getIccFileHandler().updateEFLinearFixed(
+ IccConstants.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_ICC_READ, STATUS_ON_ICC_UNREAD,
+ * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+ * @return success or not
+ *
+ */
+ public boolean copyMessageToIccEf(int status, byte[] pdu, byte[] smsc) {
+ if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
+ "pdu=("+ Arrays.toString(pdu) +
+ "), smsm=(" + Arrays.toString(smsc) +")");
+ enforceReceiveAndSend("Copying message to SIM");
+ synchronized(mLock) {
+ mSuccess = false;
+ Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
+
+ mPhone.mCM.writeSmsToSim(status, IccUtils.bytesToHexString(smsc),
+ IccUtils.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 ICC.
+ *
+ * @return list of SmsRawData of all sms on ICC
+ */
+ public List<SmsRawData> getAllMessagesFromIccEf() {
+ 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.getIccFileHandler().loadEFLinearFixedAll(IccConstants.EF_SMS, response);
+
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to load from the SIM");
+ }
+ }
+ return mSms;
+ }
+
+ public boolean enableCellBroadcast(int messageIdentifier) {
+ return enableCellBroadcastRange(messageIdentifier, messageIdentifier);
+ }
+
+ public boolean disableCellBroadcast(int messageIdentifier) {
+ return disableCellBroadcastRange(messageIdentifier, messageIdentifier);
+ }
+
+ public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+ if (DBG) log("enableCellBroadcastRange");
+
+ Context context = mPhone.getContext();
+
+ context.enforceCallingPermission(
+ "android.permission.RECEIVE_SMS",
+ "Enabling cell broadcast SMS");
+
+ String client = context.getPackageManager().getNameForUid(
+ Binder.getCallingUid());
+
+ if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
+ log("Failed to add cell broadcast subscription for MID range " + startMessageId
+ + " to " + endMessageId + " from client " + client);
+ return false;
+ }
+
+ if (DBG)
+ log("Added cell broadcast subscription for MID range " + startMessageId
+ + " to " + endMessageId + " from client " + client);
+
+ setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
+
+ return true;
+ }
+
+ public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+ if (DBG) log("disableCellBroadcastRange");
+
+ Context context = mPhone.getContext();
+
+ context.enforceCallingPermission(
+ "android.permission.RECEIVE_SMS",
+ "Disabling cell broadcast SMS");
+
+ String client = context.getPackageManager().getNameForUid(
+ Binder.getCallingUid());
+
+ if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
+ log("Failed to remove cell broadcast subscription for MID range " + startMessageId
+ + " to " + endMessageId + " from client " + client);
+ return false;
+ }
+
+ if (DBG)
+ log("Removed cell broadcast subscription for MID range " + startMessageId
+ + " to " + endMessageId + " from client " + client);
+
+ setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
+
+ return true;
+ }
+
+ class CellBroadcastRangeManager extends IntRangeManager {
+ private ArrayList<SmsBroadcastConfigInfo> mConfigList =
+ new ArrayList<SmsBroadcastConfigInfo>();
+
+ /**
+ * Called when the list of enabled ranges has changed. This will be
+ * followed by zero or more calls to {@link #addRange} followed by
+ * a call to {@link #finishUpdate}.
+ */
+ protected void startUpdate() {
+ mConfigList.clear();
+ }
+
+ /**
+ * Called after {@link #startUpdate} to indicate a range of enabled
+ * values.
+ * @param startId the first id included in the range
+ * @param endId the last id included in the range
+ */
+ protected void addRange(int startId, int endId, boolean selected) {
+ mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
+ SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
+ }
+
+ /**
+ * Called to indicate the end of a range update started by the
+ * previous call to {@link #startUpdate}.
+ * @return true if successful, false otherwise
+ */
+ protected boolean finishUpdate() {
+ if (mConfigList.isEmpty()) {
+ return true;
+ } else {
+ SmsBroadcastConfigInfo[] configs =
+ mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]);
+ return setCellBroadcastConfig(configs);
+ }
+ }
+ }
+
+ private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) {
+ if (DBG)
+ log("Calling setGsmBroadcastConfig with " + configs.length + " configurations");
+
+ synchronized (mLock) {
+ Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE);
+
+ mSuccess = false;
+ mPhone.mCM.setGsmBroadcastConfig(configs, response);
+
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to set cell broadcast config");
+ }
+ }
+
+ return mSuccess;
+ }
+
+ private boolean setCellBroadcastActivation(boolean activate) {
+ if (DBG)
+ log("Calling setCellBroadcastActivation(" + activate + ')');
+
+ synchronized (mLock) {
+ Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
+
+ mSuccess = false;
+ mPhone.mCM.setGsmBroadcastActivation(activate, response);
+
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ log("interrupted while trying to set cell broadcast activation");
+ }
+ }
+
+ return mSuccess;
+ }
+
+ @Override
+ protected void log(String msg) {
+ Log.d(LOG_TAG, "[SimSmsInterfaceManager] " + msg);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/SimTlv.java b/src/java/com/android/internal/telephony/gsm/SimTlv.java
new file mode 100644
index 0000000..497cf5f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SimTlv.java
@@ -0,0 +1,118 @@
+/*
+ * 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
+
+ try {
+ if (record[curOffset] == 0 || (record[curOffset] & 0xff) == 0xff) {
+ return false;
+ }
+
+ 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/src/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java b/src/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
new file mode 100644
index 0000000..66e7ce0
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2009 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;
+
+/**
+ * SmsBroadcastConfigInfo defines one configuration of Cell Broadcast
+ * Message (CBM) to be received by the ME
+ *
+ * fromServiceId - toServiceId defines a range of CBM message identifiers
+ * whose value is 0x0000 - 0xFFFF as defined in TS 23.041 9.4.1.2.2 for GMS
+ * and 9.4.4.2.2 for UMTS. All other values can be treated as empty
+ * CBM message ID.
+ *
+ * fromCodeScheme - toCodeScheme defines a range of CBM data coding schemes
+ * whose value is 0x00 - 0xFF as defined in TS 23.041 9.4.1.2.3 for GMS
+ * and 9.4.4.2.3 for UMTS.
+ * All other values can be treated as empty CBM data coding scheme.
+ *
+ * selected false means message types specified in {@code <fromServiceId, toServiceId>}
+ * and {@code <fromCodeScheme, toCodeScheme>} are not accepted, while true means accepted.
+ *
+ */
+public final class SmsBroadcastConfigInfo {
+ private int fromServiceId;
+ private int toServiceId;
+ private int fromCodeScheme;
+ private int toCodeScheme;
+ private boolean selected;
+
+ /**
+ * Initialize the object from rssi and cid.
+ */
+ public SmsBroadcastConfigInfo(int fromId, int toId, int fromScheme,
+ int toScheme, boolean selected) {
+ fromServiceId = fromId;
+ toServiceId = toId;
+ fromCodeScheme = fromScheme;
+ toCodeScheme = toScheme;
+ this.selected = selected;
+ }
+
+ /**
+ * @param fromServiceId the fromServiceId to set
+ */
+ public void setFromServiceId(int fromServiceId) {
+ this.fromServiceId = fromServiceId;
+ }
+
+ /**
+ * @return the fromServiceId
+ */
+ public int getFromServiceId() {
+ return fromServiceId;
+ }
+
+ /**
+ * @param toServiceId the toServiceId to set
+ */
+ public void setToServiceId(int toServiceId) {
+ this.toServiceId = toServiceId;
+ }
+
+ /**
+ * @return the toServiceId
+ */
+ public int getToServiceId() {
+ return toServiceId;
+ }
+
+ /**
+ * @param fromCodeScheme the fromCodeScheme to set
+ */
+ public void setFromCodeScheme(int fromCodeScheme) {
+ this.fromCodeScheme = fromCodeScheme;
+ }
+
+ /**
+ * @return the fromCodeScheme
+ */
+ public int getFromCodeScheme() {
+ return fromCodeScheme;
+ }
+
+ /**
+ * @param toCodeScheme the toCodeScheme to set
+ */
+ public void setToCodeScheme(int toCodeScheme) {
+ this.toCodeScheme = toCodeScheme;
+ }
+
+ /**
+ * @return the toCodeScheme
+ */
+ public int getToCodeScheme() {
+ return toCodeScheme;
+ }
+
+ /**
+ * @param selected the selected to set
+ */
+ public void setSelected(boolean selected) {
+ this.selected = selected;
+ }
+
+ /**
+ * @return the selected
+ */
+ public boolean isSelected() {
+ return selected;
+ }
+
+ @Override
+ public String toString() {
+ return "SmsBroadcastConfigInfo: Id [" +
+ fromServiceId + ',' + toServiceId + "] Code [" +
+ fromCodeScheme + ',' + toCodeScheme + "] " +
+ (selected ? "ENABLED" : "DISABLED");
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/SmsCbConstants.java b/src/java/com/android/internal/telephony/gsm/SmsCbConstants.java
new file mode 100644
index 0000000..ebb4666
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SmsCbConstants.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012 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;
+
+/**
+ * Constants used in SMS Cell Broadcast messages (see 3GPP TS 23.041). This class is used by the
+ * boot-time broadcast channel enable and database upgrade code in CellBroadcastReceiver, so it
+ * is public, but should be avoided in favor of the radio technology independent constants in
+ * {@link android.telephony.SmsCbMessage}, {@link android.telephony.SmsCbEtwsInfo}, and
+ * {@link android.telephony.SmsCbCmasInfo} classes.
+ *
+ * {@hide}
+ */
+public class SmsCbConstants {
+
+ /** Private constructor for utility class. */
+ private SmsCbConstants() { }
+
+ /** Start of PWS Message Identifier range (includes ETWS and CMAS). */
+ public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100;
+
+ /** Bitmask for messages of ETWS type (including future extensions). */
+ public static final int MESSAGE_ID_ETWS_TYPE_MASK = 0xFFF8;
+
+ /** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */
+ public static final int MESSAGE_ID_ETWS_TYPE = 0x1100;
+
+ /** ETWS Message Identifier for earthquake warning message. */
+ public static final int MESSAGE_ID_ETWS_EARTHQUAKE_WARNING = 0x1100;
+
+ /** ETWS Message Identifier for tsunami warning message. */
+ public static final int MESSAGE_ID_ETWS_TSUNAMI_WARNING = 0x1101;
+
+ /** ETWS Message Identifier for earthquake and tsunami combined warning message. */
+ public static final int MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING = 0x1102;
+
+ /** ETWS Message Identifier for test message. */
+ public static final int MESSAGE_ID_ETWS_TEST_MESSAGE = 0x1103;
+
+ /** ETWS Message Identifier for messages related to other emergency types. */
+ public static final int MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE = 0x1104;
+
+ /** Start of CMAS Message Identifier range. */
+ public static final int MESSAGE_ID_CMAS_FIRST_IDENTIFIER = 0x1112;
+
+ /** CMAS Message Identifier for Presidential Level alerts. */
+ public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL = 0x1112;
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED = 0x1113;
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY = 0x1114;
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED = 0x1115;
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY = 0x1116;
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED = 0x1117;
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY = 0x1118;
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED = 0x1119;
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY = 0x111A;
+
+ /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert). */
+ public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY = 0x111B;
+
+ /** CMAS Message Identifier for the Required Monthly Test. */
+ public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST = 0x111C;
+
+ /** CMAS Message Identifier for CMAS Exercise. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE = 0x111D;
+
+ /** CMAS Message Identifier for operator defined use. */
+ public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE = 0x111E;
+
+ /** End of CMAS Message Identifier range (including future extensions). */
+ public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER = 0x112F;
+
+ /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
+ public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER = 0x18FF;
+
+ /** ETWS serial number flag to activate the popup display. */
+ public static final int SERIAL_NUMBER_ETWS_ACTIVATE_POPUP = 0x1000;
+
+ /** ETWS serial number flag to activate the emergency user alert. */
+ public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT = 0x2000;
+
+ /** ETWS warning type value for earthquake. */
+ public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
+
+ /** ETWS warning type value for tsunami. */
+ public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01;
+
+ /** ETWS warning type value for earthquake and tsunami. */
+ public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02;
+
+ /** ETWS warning type value for test broadcast. */
+ public static final int ETWS_WARNING_TYPE_TEST = 0x03;
+
+ /** ETWS warning type value for other notifications. */
+ public static final int ETWS_WARNING_TYPE_OTHER = 0x04;
+}
diff --git a/src/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/src/java/com/android/internal/telephony/gsm/SmsCbHeader.java
new file mode 100644
index 0000000..5692044
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SmsCbHeader.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2010 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.SmsCbCmasInfo;
+import android.telephony.SmsCbEtwsInfo;
+
+import java.util.Arrays;
+
+/**
+ * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by
+ * CellBroadcastReceiver test cases, but should not be used by applications.
+ *
+ * All relevant header information is now sent as a Parcelable
+ * {@link android.telephony.SmsCbMessage} object in the "message" extra of the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or
+ * {@link android.provider.Telephony.Sms.Intents#SMS_EMERGENCY_CB_RECEIVED_ACTION} intent.
+ * The raw PDU is no longer sent to SMS CB applications.
+ */
+class SmsCbHeader {
+
+ /**
+ * Length of SMS-CB header
+ */
+ static final int PDU_HEADER_LENGTH = 6;
+
+ /**
+ * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
+ */
+ static final int FORMAT_GSM = 1;
+
+ /**
+ * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
+ */
+ static final int FORMAT_UMTS = 2;
+
+ /**
+ * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
+ */
+ static final int FORMAT_ETWS_PRIMARY = 3;
+
+ /**
+ * Message type value as defined in 3gpp TS 25.324, section 11.1.
+ */
+ private static final int MESSAGE_TYPE_CBS_MESSAGE = 1;
+
+ /**
+ * Length of GSM pdus
+ */
+ private static final int PDU_LENGTH_GSM = 88;
+
+ /**
+ * Maximum length of ETWS primary message GSM pdus
+ */
+ private static final int PDU_LENGTH_ETWS = 56;
+
+ private final int geographicalScope;
+
+ /** The serial number combines geographical scope, message code, and update number. */
+ private final int serialNumber;
+
+ /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
+ private final int messageIdentifier;
+
+ private final int dataCodingScheme;
+
+ private final int pageIndex;
+
+ private final int nrOfPages;
+
+ private final int format;
+
+ /** ETWS warning notification info. */
+ private final SmsCbEtwsInfo mEtwsInfo;
+
+ /** CMAS warning notification info. */
+ private final SmsCbCmasInfo mCmasInfo;
+
+ public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
+ if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
+ throw new IllegalArgumentException("Illegal PDU");
+ }
+
+ if (pdu.length <= PDU_LENGTH_ETWS) {
+ format = FORMAT_ETWS_PRIMARY;
+ geographicalScope = (pdu[0] & 0xc0) >> 6;
+ serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
+ messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
+ dataCodingScheme = -1;
+ pageIndex = -1;
+ nrOfPages = -1;
+ boolean emergencyUserAlert = (pdu[4] & 0x1) != 0;
+ boolean activatePopup = (pdu[5] & 0x80) != 0;
+ int warningType = (pdu[4] & 0xfe) >> 1;
+ byte[] warningSecurityInfo;
+ // copy the Warning-Security-Information, if present
+ if (pdu.length > PDU_HEADER_LENGTH) {
+ warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length);
+ } else {
+ warningSecurityInfo = null;
+ }
+ mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
+ warningSecurityInfo);
+ mCmasInfo = null;
+ return; // skip the ETWS/CMAS initialization code for regular notifications
+ } else if (pdu.length <= PDU_LENGTH_GSM) {
+ // GSM pdus are no more than 88 bytes
+ format = FORMAT_GSM;
+ geographicalScope = (pdu[0] & 0xc0) >> 6;
+ serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
+ messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
+ dataCodingScheme = pdu[4] & 0xff;
+
+ // Check for invalid page parameter
+ int pageIndex = (pdu[5] & 0xf0) >> 4;
+ int nrOfPages = pdu[5] & 0x0f;
+
+ if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
+ pageIndex = 1;
+ nrOfPages = 1;
+ }
+
+ this.pageIndex = pageIndex;
+ this.nrOfPages = nrOfPages;
+ } else {
+ // UMTS pdus are always at least 90 bytes since the payload includes
+ // a number-of-pages octet and also one length octet per page
+ format = FORMAT_UMTS;
+
+ int messageType = pdu[0];
+
+ if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
+ throw new IllegalArgumentException("Unsupported message type " + messageType);
+ }
+
+ messageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
+ geographicalScope = (pdu[3] & 0xc0) >> 6;
+ serialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
+ dataCodingScheme = pdu[5] & 0xff;
+
+ // We will always consider a UMTS message as having one single page
+ // since there's only one instance of the header, even though the
+ // actual payload may contain several pages.
+ pageIndex = 1;
+ nrOfPages = 1;
+ }
+
+ if (isEtwsMessage()) {
+ boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
+ boolean activatePopup = isEtwsPopupAlert();
+ int warningType = getEtwsWarningType();
+ mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, null);
+ mCmasInfo = null;
+ } else if (isCmasMessage()) {
+ int messageClass = getCmasMessageClass();
+ int severity = getCmasSeverity();
+ int urgency = getCmasUrgency();
+ int certainty = getCmasCertainty();
+ mEtwsInfo = null;
+ mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
+ SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty);
+ } else {
+ mEtwsInfo = null;
+ mCmasInfo = null;
+ }
+ }
+
+ int getGeographicalScope() {
+ return geographicalScope;
+ }
+
+ int getSerialNumber() {
+ return serialNumber;
+ }
+
+ int getServiceCategory() {
+ return messageIdentifier;
+ }
+
+ int getDataCodingScheme() {
+ return dataCodingScheme;
+ }
+
+ int getPageIndex() {
+ return pageIndex;
+ }
+
+ int getNumberOfPages() {
+ return nrOfPages;
+ }
+
+ SmsCbEtwsInfo getEtwsInfo() {
+ return mEtwsInfo;
+ }
+
+ SmsCbCmasInfo getCmasInfo() {
+ return mCmasInfo;
+ }
+
+ /**
+ * Return whether this broadcast is an emergency (PWS) message type.
+ * @return true if this message is emergency type; false otherwise
+ */
+ boolean isEmergencyMessage() {
+ return messageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
+ && messageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
+ }
+
+ /**
+ * Return whether this broadcast is an ETWS emergency message type.
+ * @return true if this message is ETWS emergency type; false otherwise
+ */
+ private boolean isEtwsMessage() {
+ return (messageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
+ == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
+ }
+
+ /**
+ * Return whether this broadcast is an ETWS primary notification.
+ * @return true if this message is an ETWS primary notification; false otherwise
+ */
+ boolean isEtwsPrimaryNotification() {
+ return format == FORMAT_ETWS_PRIMARY;
+ }
+
+ /**
+ * Return whether this broadcast is in UMTS format.
+ * @return true if this message is in UMTS format; false otherwise
+ */
+ boolean isUmtsFormat() {
+ return format == FORMAT_UMTS;
+ }
+
+ /**
+ * Return whether this message is a CMAS emergency message type.
+ * @return true if this message is CMAS emergency type; false otherwise
+ */
+ private boolean isCmasMessage() {
+ return messageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
+ && messageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
+ }
+
+ /**
+ * Return whether the popup alert flag is set for an ETWS warning notification.
+ * This method assumes that the message ID has already been checked for ETWS type.
+ *
+ * @return true if the message code indicates a popup alert should be displayed
+ */
+ private boolean isEtwsPopupAlert() {
+ return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
+ }
+
+ /**
+ * Return whether the emergency user alert flag is set for an ETWS warning notification.
+ * This method assumes that the message ID has already been checked for ETWS type.
+ *
+ * @return true if the message code indicates an emergency user alert
+ */
+ private boolean isEtwsEmergencyUserAlert() {
+ return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0;
+ }
+
+ /**
+ * Returns the warning type for an ETWS warning notification.
+ * This method assumes that the message ID has already been checked for ETWS type.
+ *
+ * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24
+ */
+ private int getEtwsWarningType() {
+ return messageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING;
+ }
+
+ /**
+ * Returns the message class for a CMAS warning notification.
+ * This method assumes that the message ID has already been checked for CMAS type.
+ * @return the CMAS message class as defined in {@link SmsCbCmasInfo}
+ */
+ private int getCmasMessageClass() {
+ switch (messageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
+ return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
+ return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
+ return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
+ return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
+ return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
+
+ default:
+ return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the severity for a CMAS warning notification. This is only available for extreme
+ * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+ * This method assumes that the message ID has already been checked for CMAS type.
+ * @return the CMAS severity as defined in {@link SmsCbCmasInfo}
+ */
+ private int getCmasSeverity() {
+ switch (messageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
+
+ default:
+ return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the urgency for a CMAS warning notification. This is only available for extreme
+ * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+ * This method assumes that the message ID has already been checked for CMAS type.
+ * @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
+ */
+ private int getCmasUrgency() {
+ switch (messageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
+
+ default:
+ return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the certainty for a CMAS warning notification. This is only available for extreme
+ * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+ * This method assumes that the message ID has already been checked for CMAS type.
+ * @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
+ */
+ private int getCmasCertainty() {
+ switch (messageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
+
+ default:
+ return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbHeader{GS=" + geographicalScope + ", serialNumber=0x" +
+ Integer.toHexString(serialNumber) +
+ ", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) +
+ ", DCS=0x" + Integer.toHexString(dataCodingScheme) +
+ ", page " + pageIndex + " of " + nrOfPages + '}';
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/SmsMessage.java b/src/java/com/android/internal/telephony/gsm/SmsMessage.java
new file mode 100644
index 0000000..1ed1478
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -0,0 +1,1180 @@
+/*
+ * 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;
+import android.text.format.Time;
+import android.util.Log;
+
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+
+import static com.android.internal.telephony.SmsConstants.MessageClass;
+import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
+import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+
+/**
+ * A Short Message Service message.
+ *
+ */
+public class SmsMessage extends SmsMessageBase {
+ static final String LOG_TAG = "GSM";
+
+ private MessageClass messageClass;
+
+ /**
+ * TP-Message-Type-Indicator
+ * 9.2.3
+ */
+ private int mti;
+
+ /** TP-Protocol-Identifier (TP-PID) */
+ private int protocolIdentifier;
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ private int dataCodingScheme;
+
+ // TP-Reply-Path
+ // e.g. 23.040 9.2.2.1
+ private boolean replyPathPresent = false;
+
+ // "Message Marked for Automatic Deletion Group"
+ // 23.038 Section 4
+ private boolean automaticDeletion;
+
+ /** True if Status Report is for SMS-SUBMIT; false for SMS-COMMAND. */
+ private boolean forSubmit;
+
+ /** The address of the receiver. */
+ private GsmSmsAddress recipientAddress;
+
+ /** Time when SMS-SUBMIT was delivered from SC to MSE. */
+ private 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.
+ */
+ private int status;
+
+ /**
+ * TP-Status - status of a previously submitted SMS.
+ * This field is true iff the message is a SMS-STATUS-REPORT message.
+ */
+ private boolean isStatusReportMessage = false;
+
+ public static class SubmitPdu extends SubmitPduBase {
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated
+ * by TP_PID field set to value 0x40
+ */
+ public boolean isTypeZero() {
+ return (protocolIdentifier == 0x40);
+ }
+
+ /**
+ * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+ * +CMT unsolicited response (PDU mode, of course)
+ * +CMT: [&lt;alpha>],<length><CR><LF><pdu>
+ *
+ * Only public for debugging
+ *
+ * {@hide}
+ */
+ public static SmsMessage newFromCMT(String[] lines) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(IccUtils.hexStringToBytes(lines[1]));
+ return msg;
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /** @hide */
+ public static SmsMessage newFromCDS(String line) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(IccUtils.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.indexOnIcc = 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.statusOnIcc = 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 = Integer.parseInt(pdu.substring(0, 2), 16);
+
+ return len - smscLen - 1;
+ }
+
+ /**
+ * 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) {
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
+ ENCODING_UNKNOWN, 0, 0);
+ }
+
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message using the
+ * specified encoding.
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @param encoding Encoding defined by constants in
+ * com.android.internal.telephony.SmsConstants.ENCODING_*
+ * @param languageTable
+ * @param languageShiftTable
+ * @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, int encoding,
+ int languageTable, int languageShiftTable) {
+
+ // Perform null parameter checks.
+ if (message == null || destinationAddress == null) {
+ return null;
+ }
+
+ if (encoding == ENCODING_UNKNOWN) {
+ // Find the best encoding to use
+ TextEncodingDetails ted = calculateLength(message, false);
+ encoding = ted.codeUnitSize;
+ languageTable = ted.languageTable;
+ languageShiftTable = ted.languageShiftTable;
+
+ if (encoding == ENCODING_7BIT &&
+ (languageTable != 0 || languageShiftTable != 0)) {
+ if (header != null) {
+ SmsHeader smsHeader = SmsHeader.fromByteArray(header);
+ if (smsHeader.languageTable != languageTable
+ || smsHeader.languageShiftTable != languageShiftTable) {
+ Log.w(LOG_TAG, "Updating language table in SMS header: "
+ + smsHeader.languageTable + " -> " + languageTable + ", "
+ + smsHeader.languageShiftTable + " -> " + languageShiftTable);
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ header = SmsHeader.toByteArray(smsHeader);
+ }
+ } else {
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ header = SmsHeader.toByteArray(smsHeader);
+ }
+ }
+ }
+
+ 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);
+
+ // User Data (and length)
+ byte[] userData;
+ try {
+ if (encoding == ENCODING_7BIT) {
+ userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
+ languageTable, languageShiftTable);
+ } else { //assume UCS-2
+ try {
+ userData = encodeUCS2(message, header);
+ } catch(UnsupportedEncodingException uex) {
+ Log.e(LOG_TAG,
+ "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+ } catch (EncodeException ex) {
+ // Encoding to the 7-bit alphabet failed. Let's see if we can
+ // send it as a UCS-2 encoded message
+ try {
+ userData = encodeUCS2(message, header);
+ encoding = ENCODING_16BIT;
+ } catch(UnsupportedEncodingException uex) {
+ Log.e(LOG_TAG,
+ "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+
+ if (encoding == ENCODING_7BIT) {
+ if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
+ // Message too long
+ Log.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
+ return null;
+ }
+ // TP-Data-Coding-Scheme
+ // Default encoding, uncompressed
+ // To test writing messages to the SIM card, change this value 0x00
+ // to 0x12, which means "bits 1 and 0 contain message class, and the
+ // class is 2". Note that this takes effect for the sender. In other
+ // words, messages sent by the phone with this change will end up on
+ // the receiver's SIM card. You can then send messages to yourself
+ // (on a phone with this change) and they'll end up on the SIM card.
+ bo.write(0x00);
+ } else { // assume UCS-2
+ if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
+ // Message too long
+ Log.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
+ return null;
+ }
+ // TP-Data-Coding-Scheme
+ // UCS-2 encoding, uncompressed
+ bo.write(0x08);
+ }
+
+ // (no TP-Validity-Period)
+ bo.write(userData, 0, userData.length);
+ ret.encodedMessage = bo.toByteArray();
+ return ret;
+ }
+
+ /**
+ * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
+ *
+ * @return
+ * @throws UnsupportedEncodingException
+ */
+ private static byte[] encodeUCS2(String message, byte[] header)
+ throws UnsupportedEncodingException {
+ byte[] userData, textPart;
+ textPart = message.getBytes("utf-16be");
+
+ if (header != null) {
+ // Need 1 byte for UDHL
+ userData = new byte[header.length + textPart.length + 1];
+
+ userData[0] = (byte)header.length;
+ System.arraycopy(header, 0, userData, 1, header.length);
+ System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
+ }
+ else {
+ userData = textPart;
+ }
+ byte[] ret = new byte[userData.length+1];
+ ret[0] = (byte) (userData.length & 0xff );
+ System.arraycopy(userData, 0, ret, 1, userData.length);
+ return ret;
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested) {
+
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param destinationPort the port to deliver the message to at the
+ * destination
+ * @param data the data 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, int destinationPort, byte[] data,
+ boolean statusReportRequested) {
+
+ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+ portAddrs.destPort = destinationPort;
+ portAddrs.origPort = 0;
+ portAddrs.areEightBits = false;
+
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.portAddrs = portAddrs;
+
+ byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
+
+ if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
+ Log.e(LOG_TAG, "SMS data message may only contain "
+ + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " 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)
+
+ // Total size
+ bo.write(data.length + smsHeaderData.length + 1);
+
+ // User data header
+ bo.write(smsHeaderData.length);
+ bo.write(smsHeaderData, 0, smsHeaderData.length);
+
+ // 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 (false) 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;
+ }
+
+ private static class PduParser {
+ byte pdu[];
+ int cur;
+ SmsHeader userDataHeader;
+ byte[] userData;
+ int mUserDataSeptetPadding;
+ int mUserDataSize;
+
+ 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
+ */
+ GsmSmsAddress getAddress() {
+ GsmSmsAddress 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 GsmSmsAddress(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 = IccUtils.gsmBcdByteToInt(pdu[cur++]);
+ int month = IccUtils.gsmBcdByteToInt(pdu[cur++]);
+ int day = IccUtils.gsmBcdByteToInt(pdu[cur++]);
+ int hour = IccUtils.gsmBcdByteToInt(pdu[cur++]);
+ int minute = IccUtils.gsmBcdByteToInt(pdu[cur++]);
+ int second = IccUtils.gsmBcdByteToInt(pdu[cur++]);
+
+ // For the timezone, the most significant bit of the
+ // least significant 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 = IccUtils.gsmBcdByteToInt((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;
+ int userDataHeaderLength = 0;
+
+ if (hasUserDataHeader) {
+ userDataHeaderLength = pdu[offset++] & 0xff;
+
+ byte[] udh = new byte[userDataHeaderLength];
+ System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
+ userDataHeader = SmsHeader.fromByteArray(udh);
+ offset += userDataHeaderLength;
+
+ int headerBits = (userDataHeaderLength + 1) * 8;
+ headerSeptets = headerBits / 7;
+ headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
+ mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
+ }
+
+ int bufferLen;
+ if (dataInSeptets) {
+ /*
+ * Here we just create the user data length to be the remainder of
+ * the pdu minus the user data header, since userDataLength means
+ * the number of uncompressed septets.
+ */
+ bufferLen = pdu.length - offset;
+ } else {
+ /*
+ * userDataLength is the count of octets, so just subtract the
+ * user data header.
+ */
+ bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
+ if (bufferLen < 0) {
+ bufferLen = 0;
+ }
+ }
+
+ userData = new byte[bufferLen];
+ System.arraycopy(pdu, offset, userData, 0, userData.length);
+ cur = offset;
+
+ if (dataInSeptets) {
+ // Return the number of septets
+ int count = userDataLength - headerSeptets;
+ // If count < 0, return 0 (means UDL was probably incorrect)
+ return count < 0 ? 0 : count;
+ } 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 beginning of the user data
+ * array before the start of the septets.
+ *
+ * @return the number of padding bits at the beginning of the user data
+ * array before the start of the septets
+ */
+ int getUserDataSeptetPadding() {
+ return mUserDataSeptetPadding;
+ }
+
+ /**
+ * Returns an object representing the user data headers
+ *
+ * {@hide}
+ */
+ SmsHeader getUserDataHeader() {
+ return userDataHeader;
+ }
+
+ /**
+ * Interprets the user data payload as packed 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, int languageTable,
+ int languageShiftTable) {
+ String ret;
+
+ ret = GsmAlphabet.gsm7BitPackedToString(pdu, cur, septetCount,
+ mUserDataSeptetPadding, languageTable, languageShiftTable);
+
+ 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;
+ }
+
+ /**
+ * Interprets the user data payload as KSC-5601 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 getUserDataKSC5601(int byteCount) {
+ String ret;
+
+ try {
+ ret = new String(pdu, cur, byteCount, "KSC5601");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+ }
+
+ cur += byteCount;
+ return ret;
+ }
+
+ boolean moreDataPresent() {
+ return (pdu.length > cur);
+ }
+ }
+
+ /**
+ * Calculate the number of septets needed to encode the message.
+ *
+ * @param msgBody the message to encode
+ * @param use7bitOnly ignore (but still count) illegal characters if true
+ * @return TextEncodingDetails
+ */
+ public static TextEncodingDetails calculateLength(CharSequence msgBody,
+ boolean use7bitOnly) {
+ TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(msgBody, use7bitOnly);
+ if (ted == null) {
+ ted = new TextEncodingDetails();
+ int octets = msgBody.length() * 2;
+ ted.codeUnitCount = msgBody.length();
+ if (octets > MAX_USER_DATA_BYTES) {
+ ted.msgCount = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) /
+ MAX_USER_DATA_BYTES_WITH_HEADER;
+ ted.codeUnitsRemaining = ((ted.msgCount *
+ MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2;
+ } else {
+ ted.msgCount = 1;
+ ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2;
+ }
+ ted.codeUnitSize = ENCODING_16BIT;
+ }
+ return ted;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getProtocolIdentifier() {
+ return protocolIdentifier;
+ }
+
+ /**
+ * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
+ * @return the TP-DCS field of the SMS header
+ */
+ int getDataCodingScheme() {
+ return dataCodingScheme;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isReplace() {
+ return (protocolIdentifier & 0xc0) == 0x40
+ && (protocolIdentifier & 0x3f) > 0
+ && (protocolIdentifier & 0x3f) < 8;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isCphsMwiMessage() {
+ return ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear()
+ || ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMWIClearMessage() {
+ if (isMwi && !mwiSense) {
+ return true;
+ }
+
+ return originatingAddress != null
+ && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMWISetMessage() {
+ if (isMwi && mwiSense) {
+ return true;
+ }
+
+ return originatingAddress != null
+ && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ 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;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getStatus() {
+ return status;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isStatusReportMessage() {
+ return isStatusReportMessage;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isReplyPathPresent() {
+ return replyPathPresent;
+ }
+
+ /**
+ * TS 27.005 3.1, &lt;pdu&gt; 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
+ * hex 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 message:");
+ // Log.d(LOG_TAG, s);
+
+ PduParser p = new PduParser(pdu);
+
+ scAddress = p.getSCAddress();
+
+ if (scAddress != null) {
+ if (false) 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:
+ case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
+ //This should be processed in the same way as MTI == 0 (Deliver)
+ 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 (false) 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 (false) {
+ Log.v(LOG_TAG, "SMS TP-PID:" + protocolIdentifier
+ + " data coding scheme: " + dataCodingScheme);
+ }
+
+ scTimeMillis = p.getSCTimestampMillis();
+
+ if (false) 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 if ((dataCodingScheme & 0xC0) == 0x80) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+ // 0x80..0xBF == Reserved coding groups
+ if (dataCodingScheme == 0x84) {
+ // This value used for KSC5601 by carriers in Korea.
+ encodingType = ENCODING_KSC5601;
+ } else {
+ Log.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
+ + (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,
+ hasUserDataHeader ? userDataHeader.languageTable : 0,
+ hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
+ break;
+
+ case ENCODING_16BIT:
+ messageBody = p.getUserDataUCS2(count);
+ break;
+
+ case ENCODING_KSC5601:
+ messageBody = p.getUserDataKSC5601(count);
+ break;
+ }
+
+ if (false) 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;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MessageClass getMessageClass() {
+ return messageClass;
+ }
+
+ /**
+ * Returns true if this is a (U)SIM data download type SM.
+ * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
+ *
+ * @return true if this is a USIM data download message; false otherwise
+ */
+ boolean isUsimDataDownload() {
+ return messageClass == MessageClass.CLASS_2 &&
+ (protocolIdentifier == 0x7f || protocolIdentifier == 0x7c);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/SpnOverride.java b/src/java/com/android/internal/telephony/gsm/SpnOverride.java
new file mode 100644
index 0000000..918c2d2
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/SpnOverride.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2009 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.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.os.Environment;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+
+public class SpnOverride {
+ private HashMap<String, String> CarrierSpnMap;
+
+
+ static final String LOG_TAG = "GSM";
+ static final String PARTNER_SPN_OVERRIDE_PATH ="etc/spn-conf.xml";
+
+ SpnOverride () {
+ CarrierSpnMap = new HashMap<String, String>();
+ loadSpnOverrides();
+ }
+
+ boolean containsCarrier(String carrier) {
+ return CarrierSpnMap.containsKey(carrier);
+ }
+
+ String getSpn(String carrier) {
+ return CarrierSpnMap.get(carrier);
+ }
+
+ private void loadSpnOverrides() {
+ FileReader spnReader;
+
+ final File spnFile = new File(Environment.getRootDirectory(),
+ PARTNER_SPN_OVERRIDE_PATH);
+
+ try {
+ spnReader = new FileReader(spnFile);
+ } catch (FileNotFoundException e) {
+ Log.w(LOG_TAG, "Can't open " +
+ Environment.getRootDirectory() + "/" + PARTNER_SPN_OVERRIDE_PATH);
+ return;
+ }
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(spnReader);
+
+ XmlUtils.beginDocument(parser, "spnOverrides");
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ String name = parser.getName();
+ if (!"spnOverride".equals(name)) {
+ break;
+ }
+
+ String numeric = parser.getAttributeValue(null, "numeric");
+ String data = parser.getAttributeValue(null, "spn");
+
+ CarrierSpnMap.put(numeric, data);
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(LOG_TAG, "Exception in spn-conf parser " + e);
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Exception in spn-conf parser " + e);
+ }
+ }
+
+}
diff --git a/src/java/com/android/internal/telephony/gsm/SuppServiceNotification.java b/src/java/com/android/internal/telephony/gsm/SuppServiceNotification.java
new file mode 100644
index 0000000..e68655e
--- /dev/null
+++ b/src/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/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java b/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java
new file mode 100644
index 0000000..f47ff1b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2011 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.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Telephony.Sms.Intents;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccIoResult;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.cat.ComprehensionTlvTag;
+
+/**
+ * Handler for SMS-PP data download messages.
+ * See 3GPP TS 31.111 section 7.1.1
+ */
+public class UsimDataDownloadHandler extends Handler {
+ private static final String TAG = "UsimDataDownloadHandler";
+
+ /** BER-TLV tag for SMS-PP download. TS 31.111 section 9.1. */
+ private static final int BER_SMS_PP_DOWNLOAD_TAG = 0xd1;
+
+ /** Device identity value for UICC (destination). */
+ private static final int DEV_ID_UICC = 0x81;
+
+ /** Device identity value for network (source). */
+ private static final int DEV_ID_NETWORK = 0x83;
+
+ /** Message containing new SMS-PP message to process. */
+ private static final int EVENT_START_DATA_DOWNLOAD = 1;
+
+ /** Response to SMS-PP download envelope command. */
+ private static final int EVENT_SEND_ENVELOPE_RESPONSE = 2;
+
+ private final CommandsInterface mCI;
+
+ public UsimDataDownloadHandler(CommandsInterface commandsInterface) {
+ mCI = commandsInterface;
+ }
+
+ /**
+ * Start an SMS-PP data download for the specified message. Can be called from a different
+ * thread than this Handler is running on.
+ *
+ * @param smsMessage the message to process
+ * @return Activity.RESULT_OK on success; Intents.RESULT_SMS_GENERIC_ERROR on failure
+ */
+ public int startDataDownload(SmsMessage smsMessage) {
+ if (sendMessage(obtainMessage(EVENT_START_DATA_DOWNLOAD, smsMessage))) {
+ return Activity.RESULT_OK; // we will send SMS ACK/ERROR based on UICC response
+ } else {
+ Log.e(TAG, "startDataDownload failed to send message to start data download.");
+ return Intents.RESULT_SMS_GENERIC_ERROR;
+ }
+ }
+
+ private void handleDataDownload(SmsMessage smsMessage) {
+ int dcs = smsMessage.getDataCodingScheme();
+ int pid = smsMessage.getProtocolIdentifier();
+ byte[] pdu = smsMessage.getPdu(); // includes SC address
+
+ int scAddressLength = pdu[0] & 0xff;
+ int tpduIndex = scAddressLength + 1; // start of TPDU
+ int tpduLength = pdu.length - tpduIndex;
+
+ int bodyLength = getEnvelopeBodyLength(scAddressLength, tpduLength);
+
+ // Add 1 byte for SMS-PP download tag and 1-2 bytes for BER-TLV length.
+ // See ETSI TS 102 223 Annex C for encoding of length and tags.
+ int totalLength = bodyLength + 1 + (bodyLength > 127 ? 2 : 1);
+
+ byte[] envelope = new byte[totalLength];
+ int index = 0;
+
+ // SMS-PP download tag and length (assumed to be < 256 bytes).
+ envelope[index++] = (byte) BER_SMS_PP_DOWNLOAD_TAG;
+ if (bodyLength > 127) {
+ envelope[index++] = (byte) 0x81; // length 128-255 encoded as 0x81 + length
+ }
+ envelope[index++] = (byte) bodyLength;
+
+ // Device identities TLV
+ envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value());
+ envelope[index++] = (byte) 2;
+ envelope[index++] = (byte) DEV_ID_NETWORK;
+ envelope[index++] = (byte) DEV_ID_UICC;
+
+ // Address TLV (if present). Encoded length is assumed to be < 127 bytes.
+ if (scAddressLength != 0) {
+ envelope[index++] = (byte) ComprehensionTlvTag.ADDRESS.value();
+ envelope[index++] = (byte) scAddressLength;
+ System.arraycopy(pdu, 1, envelope, index, scAddressLength);
+ index += scAddressLength;
+ }
+
+ // SMS TPDU TLV. Length is assumed to be < 256 bytes.
+ envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.SMS_TPDU.value());
+ if (tpduLength > 127) {
+ envelope[index++] = (byte) 0x81; // length 128-255 encoded as 0x81 + length
+ }
+ envelope[index++] = (byte) tpduLength;
+ System.arraycopy(pdu, tpduIndex, envelope, index, tpduLength);
+ index += tpduLength;
+
+ // Verify that we calculated the payload size correctly.
+ if (index != envelope.length) {
+ Log.e(TAG, "startDataDownload() calculated incorrect envelope length, aborting.");
+ acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR);
+ return;
+ }
+
+ String encodedEnvelope = IccUtils.bytesToHexString(envelope);
+ mCI.sendEnvelopeWithStatus(encodedEnvelope, obtainMessage(
+ EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid }));
+ }
+
+ /**
+ * Return the size in bytes of the envelope to send to the UICC, excluding the
+ * SMS-PP download tag byte and length byte(s). If the size returned is <= 127,
+ * the BER-TLV length will be encoded in 1 byte, otherwise 2 bytes are required.
+ *
+ * @param scAddressLength the length of the SMSC address, or zero if not present
+ * @param tpduLength the length of the TPDU from the SMS-PP message
+ * @return the number of bytes to allocate for the envelope command
+ */
+ private static int getEnvelopeBodyLength(int scAddressLength, int tpduLength) {
+ // Add 4 bytes for device identities TLV + 1 byte for SMS TPDU tag byte
+ int length = tpduLength + 5;
+ // Add 1 byte for TPDU length, or 2 bytes if length > 127
+ length += (tpduLength > 127 ? 2 : 1);
+ // Add length of address tag, if present (+ 2 bytes for tag and length)
+ if (scAddressLength != 0) {
+ length = length + 2 + scAddressLength;
+ }
+ return length;
+ }
+
+ /**
+ * Handle the response to the ENVELOPE command.
+ * @param response UICC response encoded as hexadecimal digits. First two bytes are the
+ * UICC SW1 and SW2 status bytes.
+ */
+ private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid) {
+ int sw1 = response.sw1;
+ int sw2 = response.sw2;
+
+ boolean success;
+ if ((sw1 == 0x90 && sw2 == 0x00) || sw1 == 0x91) {
+ Log.d(TAG, "USIM data download succeeded: " + response.toString());
+ success = true;
+ } else if (sw1 == 0x93 && sw2 == 0x00) {
+ Log.e(TAG, "USIM data download failed: Toolkit busy");
+ acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY);
+ return;
+ } else if (sw1 == 0x62 || sw1 == 0x63) {
+ Log.e(TAG, "USIM data download failed: " + response.toString());
+ success = false;
+ } else {
+ Log.e(TAG, "Unexpected SW1/SW2 response from UICC: " + response.toString());
+ success = false;
+ }
+
+ byte[] responseBytes = response.payload;
+ if (responseBytes == null || responseBytes.length == 0) {
+ if (success) {
+ mCI.acknowledgeLastIncomingGsmSms(true, 0, null);
+ } else {
+ acknowledgeSmsWithError(
+ CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
+ }
+ return;
+ }
+
+ byte[] smsAckPdu;
+ int index = 0;
+ if (success) {
+ smsAckPdu = new byte[responseBytes.length + 5];
+ smsAckPdu[index++] = 0x00; // TP-MTI, TP-UDHI
+ smsAckPdu[index++] = 0x07; // TP-PI: TP-PID, TP-DCS, TP-UDL present
+ } else {
+ smsAckPdu = new byte[responseBytes.length + 6];
+ smsAckPdu[index++] = 0x00; // TP-MTI, TP-UDHI
+ smsAckPdu[index++] = (byte)
+ CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR; // TP-FCS
+ smsAckPdu[index++] = 0x07; // TP-PI: TP-PID, TP-DCS, TP-UDL present
+ }
+
+ smsAckPdu[index++] = (byte) pid;
+ smsAckPdu[index++] = (byte) dcs;
+
+ if (is7bitDcs(dcs)) {
+ int septetCount = responseBytes.length * 8 / 7;
+ smsAckPdu[index++] = (byte) septetCount;
+ } else {
+ smsAckPdu[index++] = (byte) responseBytes.length;
+ }
+
+ System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length);
+
+ mCI.acknowledgeIncomingGsmSmsWithPdu(success,
+ IccUtils.bytesToHexString(smsAckPdu), null);
+ }
+
+ private void acknowledgeSmsWithError(int cause) {
+ mCI.acknowledgeLastIncomingGsmSms(false, cause, null);
+ }
+
+ /**
+ * Returns whether the DCS is 7 bit. If so, set TP-UDL to the septet count of TP-UD;
+ * otherwise, set TP-UDL to the octet count of TP-UD.
+ * @param dcs the TP-Data-Coding-Scheme field from the original download SMS
+ * @return true if the DCS specifies 7 bit encoding; false otherwise
+ */
+ private static boolean is7bitDcs(int dcs) {
+ // See 3GPP TS 23.038 section 4
+ return ((dcs & 0x8C) == 0x00) || ((dcs & 0xF4) == 0xF0);
+ }
+
+ /**
+ * Handle UICC envelope response and send SMS acknowledgement.
+ *
+ * @param msg the message to handle
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_START_DATA_DOWNLOAD:
+ handleDataDownload((SmsMessage) msg.obj);
+ break;
+
+ case EVENT_SEND_ENVELOPE_RESPONSE:
+ AsyncResult ar = (AsyncResult) msg.obj;
+
+ if (ar.exception != null) {
+ Log.e(TAG, "UICC Send Envelope failure, exception: " + ar.exception);
+ acknowledgeSmsWithError(
+ CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
+ return;
+ }
+
+ int[] dcsPid = (int[]) ar.userObj;
+ sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, dcsPid[0], dcsPid[1]);
+ break;
+
+ default:
+ Log.e(TAG, "Ignoring unexpected message, what=" + msg.what);
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
new file mode 100755
index 0000000..8f5a420
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2009 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.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.AdnRecord;
+import com.android.internal.telephony.AdnRecordCache;
+import com.android.internal.telephony.IccConstants;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.PhoneBase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class implements reading and parsing USIM records.
+ * Refer to Spec 3GPP TS 31.102 for more details.
+ *
+ * {@hide}
+ */
+public class UsimPhoneBookManager extends Handler implements IccConstants {
+ private static final String LOG_TAG = "GSM";
+ private static final boolean DBG = true;
+ private PbrFile mPbrFile;
+ private Boolean mIsPbrPresent;
+ private IccFileHandler mFh;
+ private AdnRecordCache mAdnCache;
+ private Object mLock = new Object();
+ private ArrayList<AdnRecord> mPhoneBookRecords;
+ private boolean mEmailPresentInIap = false;
+ private int mEmailTagNumberInIap = 0;
+ private ArrayList<byte[]> mIapFileRecord;
+ private ArrayList<byte[]> mEmailFileRecord;
+ private Map<Integer, ArrayList<String>> mEmailsForAdnRec;
+ private boolean mRefreshCache = false;
+
+ private static final int EVENT_PBR_LOAD_DONE = 1;
+ private static final int EVENT_USIM_ADN_LOAD_DONE = 2;
+ private static final int EVENT_IAP_LOAD_DONE = 3;
+ private static final int EVENT_EMAIL_LOAD_DONE = 4;
+
+ private static final int USIM_TYPE1_TAG = 0xA8;
+ private static final int USIM_TYPE2_TAG = 0xA9;
+ private static final int USIM_TYPE3_TAG = 0xAA;
+ private static final int USIM_EFADN_TAG = 0xC0;
+ private static final int USIM_EFIAP_TAG = 0xC1;
+ private static final int USIM_EFEXT1_TAG = 0xC2;
+ private static final int USIM_EFSNE_TAG = 0xC3;
+ private static final int USIM_EFANR_TAG = 0xC4;
+ private static final int USIM_EFPBC_TAG = 0xC5;
+ private static final int USIM_EFGRP_TAG = 0xC6;
+ private static final int USIM_EFAAS_TAG = 0xC7;
+ private static final int USIM_EFGSD_TAG = 0xC8;
+ private static final int USIM_EFUID_TAG = 0xC9;
+ private static final int USIM_EFEMAIL_TAG = 0xCA;
+ private static final int USIM_EFCCP1_TAG = 0xCB;
+
+ public UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache) {
+ mFh = fh;
+ mPhoneBookRecords = new ArrayList<AdnRecord>();
+ mPbrFile = null;
+ // We assume its present, after the first read this is updated.
+ // So we don't have to read from UICC if its not present on subsequent reads.
+ mIsPbrPresent = true;
+ mAdnCache = cache;
+ }
+
+ public void reset() {
+ mPhoneBookRecords.clear();
+ mIapFileRecord = null;
+ mEmailFileRecord = null;
+ mPbrFile = null;
+ mIsPbrPresent = true;
+ mRefreshCache = false;
+ }
+
+ public ArrayList<AdnRecord> loadEfFilesFromUsim() {
+ synchronized (mLock) {
+ if (!mPhoneBookRecords.isEmpty()) {
+ if (mRefreshCache) {
+ mRefreshCache = false;
+ refreshCache();
+ }
+ return mPhoneBookRecords;
+ }
+
+ if (!mIsPbrPresent) return null;
+
+ // Check if the PBR file is present in the cache, if not read it
+ // from the USIM.
+ if (mPbrFile == null) {
+ readPbrFileAndWait();
+ }
+
+ if (mPbrFile == null) return null;
+
+ int numRecs = mPbrFile.mFileIds.size();
+ for (int i = 0; i < numRecs; i++) {
+ readAdnFileAndWait(i);
+ readEmailFileAndWait(i);
+ }
+ // All EF files are loaded, post the response.
+ }
+ return mPhoneBookRecords;
+ }
+
+ private void refreshCache() {
+ if (mPbrFile == null) return;
+ mPhoneBookRecords.clear();
+
+ int numRecs = mPbrFile.mFileIds.size();
+ for (int i = 0; i < numRecs; i++) {
+ readAdnFileAndWait(i);
+ }
+ }
+
+ public void invalidateCache() {
+ mRefreshCache = true;
+ }
+
+ private void readPbrFileAndWait() {
+ mFh.loadEFLinearFixedAll(EF_PBR, obtainMessage(EVENT_PBR_LOAD_DONE));
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait");
+ }
+ }
+
+ private void readEmailFileAndWait(int recNum) {
+ Map <Integer,Integer> fileIds;
+ fileIds = mPbrFile.mFileIds.get(recNum);
+ if (fileIds == null) return;
+
+ if (fileIds.containsKey(USIM_EFEMAIL_TAG)) {
+ int efid = fileIds.get(USIM_EFEMAIL_TAG);
+ // Check if the EFEmail is a Type 1 file or a type 2 file.
+ // If mEmailPresentInIap is true, its a type 2 file.
+ // So we read the IAP file and then read the email records.
+ // instead of reading directly.
+ if (mEmailPresentInIap) {
+ readIapFileAndWait(fileIds.get(USIM_EFIAP_TAG));
+ if (mIapFileRecord == null) {
+ Log.e(LOG_TAG, "Error: IAP file is empty");
+ return;
+ }
+ }
+ // Read the EFEmail file.
+ mFh.loadEFLinearFixedAll(fileIds.get(USIM_EFEMAIL_TAG),
+ obtainMessage(EVENT_EMAIL_LOAD_DONE));
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Interrupted Exception in readEmailFileAndWait");
+ }
+
+ if (mEmailFileRecord == null) {
+ Log.e(LOG_TAG, "Error: Email file is empty");
+ return;
+ }
+ updatePhoneAdnRecord();
+ }
+
+ }
+
+ private void readIapFileAndWait(int efid) {
+ mFh.loadEFLinearFixedAll(efid, obtainMessage(EVENT_IAP_LOAD_DONE));
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Interrupted Exception in readIapFileAndWait");
+ }
+ }
+
+ private void updatePhoneAdnRecord() {
+ if (mEmailFileRecord == null) return;
+ int numAdnRecs = mPhoneBookRecords.size();
+ if (mIapFileRecord != null) {
+ // The number of records in the IAP file is same as the number of records in ADN file.
+ // The order of the pointers in an EFIAP shall be the same as the order of file IDs
+ // that appear in the TLV object indicated by Tag 'A9' in the reference file record.
+ // i.e value of mEmailTagNumberInIap
+
+ for (int i = 0; i < numAdnRecs; i++) {
+ byte[] record = null;
+ try {
+ record = mIapFileRecord.get(i);
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(LOG_TAG, "Error: Improper ICC card: No IAP record for ADN, continuing");
+ break;
+ }
+ int recNum = record[mEmailTagNumberInIap];
+
+ if (recNum != -1) {
+ String[] emails = new String[1];
+ // SIM record numbers are 1 based
+ emails[0] = readEmailRecord(recNum - 1);
+ AdnRecord rec = mPhoneBookRecords.get(i);
+ if (rec != null) {
+ rec.setEmails(emails);
+ } else {
+ // might be a record with only email
+ rec = new AdnRecord("", "", emails);
+ }
+ mPhoneBookRecords.set(i, rec);
+ }
+ }
+ }
+
+ // ICC cards can be made such that they have an IAP file but all
+ // records are empty. So we read both type 1 and type 2 file
+ // email records, just to be sure.
+
+ int len = mPhoneBookRecords.size();
+ // Type 1 file, the number of records is the same as the number of
+ // records in the ADN file.
+ if (mEmailsForAdnRec == null) {
+ parseType1EmailFile(len);
+ }
+ for (int i = 0; i < numAdnRecs; i++) {
+ ArrayList<String> emailList = null;
+ try {
+ emailList = mEmailsForAdnRec.get(i);
+ } catch (IndexOutOfBoundsException e) {
+ break;
+ }
+ if (emailList == null) continue;
+
+ AdnRecord rec = mPhoneBookRecords.get(i);
+
+ String[] emails = new String[emailList.size()];
+ System.arraycopy(emailList.toArray(), 0, emails, 0, emailList.size());
+ rec.setEmails(emails);
+ mPhoneBookRecords.set(i, rec);
+ }
+ }
+
+ void parseType1EmailFile(int numRecs) {
+ mEmailsForAdnRec = new HashMap<Integer, ArrayList<String>>();
+ byte[] emailRec = null;
+ for (int i = 0; i < numRecs; i++) {
+ try {
+ emailRec = mEmailFileRecord.get(i);
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(LOG_TAG, "Error: Improper ICC card: No email record for ADN, continuing");
+ break;
+ }
+ int adnRecNum = emailRec[emailRec.length - 1];
+
+ if (adnRecNum == -1) {
+ continue;
+ }
+
+ String email = readEmailRecord(i);
+
+ if (email == null || email.equals("")) {
+ continue;
+ }
+
+ // SIM record numbers are 1 based.
+ ArrayList<String> val = mEmailsForAdnRec.get(adnRecNum - 1);
+ if (val == null) {
+ val = new ArrayList<String>();
+ }
+ val.add(email);
+ // SIM record numbers are 1 based.
+ mEmailsForAdnRec.put(adnRecNum - 1, val);
+ }
+ }
+
+ private String readEmailRecord(int recNum) {
+ byte[] emailRec = null;
+ try {
+ emailRec = mEmailFileRecord.get(recNum);
+ } catch (IndexOutOfBoundsException e) {
+ return null;
+ }
+
+ // The length of the record is X+2 byte, where X bytes is the email address
+ String email = IccUtils.adnStringFieldToString(emailRec, 0, emailRec.length - 2);
+ return email;
+ }
+
+ private void readAdnFileAndWait(int recNum) {
+ Map <Integer,Integer> fileIds;
+ fileIds = mPbrFile.mFileIds.get(recNum);
+ if (fileIds == null || fileIds.isEmpty()) return;
+
+
+ int extEf = 0;
+ // Only call fileIds.get while EFEXT1_TAG is available
+ if (fileIds.containsKey(USIM_EFEXT1_TAG)) {
+ extEf = fileIds.get(USIM_EFEXT1_TAG);
+ }
+
+ mAdnCache.requestLoadAllAdnLike(fileIds.get(USIM_EFADN_TAG),
+ extEf, obtainMessage(EVENT_USIM_ADN_LOAD_DONE));
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait");
+ }
+ }
+
+ private void createPbrFile(ArrayList<byte[]> records) {
+ if (records == null) {
+ mPbrFile = null;
+ mIsPbrPresent = false;
+ return;
+ }
+ mPbrFile = new PbrFile(records);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+
+ switch(msg.what) {
+ case EVENT_PBR_LOAD_DONE:
+ ar = (AsyncResult) msg.obj;
+ if (ar.exception == null) {
+ createPbrFile((ArrayList<byte[]>)ar.result);
+ }
+ synchronized (mLock) {
+ mLock.notify();
+ }
+ break;
+ case EVENT_USIM_ADN_LOAD_DONE:
+ log("Loading USIM ADN records done");
+ ar = (AsyncResult) msg.obj;
+ if (ar.exception == null) {
+ mPhoneBookRecords.addAll((ArrayList<AdnRecord>)ar.result);
+ }
+ synchronized (mLock) {
+ mLock.notify();
+ }
+ break;
+ case EVENT_IAP_LOAD_DONE:
+ log("Loading USIM IAP records done");
+ ar = (AsyncResult) msg.obj;
+ if (ar.exception == null) {
+ mIapFileRecord = ((ArrayList<byte[]>)ar.result);
+ }
+ synchronized (mLock) {
+ mLock.notify();
+ }
+ break;
+ case EVENT_EMAIL_LOAD_DONE:
+ log("Loading USIM Email records done");
+ ar = (AsyncResult) msg.obj;
+ if (ar.exception == null) {
+ mEmailFileRecord = ((ArrayList<byte[]>)ar.result);
+ }
+
+ synchronized (mLock) {
+ mLock.notify();
+ }
+ break;
+ }
+ }
+
+ private class PbrFile {
+ // RecNum <EF Tag, efid>
+ HashMap<Integer,Map<Integer,Integer>> mFileIds;
+
+ PbrFile(ArrayList<byte[]> records) {
+ mFileIds = new HashMap<Integer, Map<Integer, Integer>>();
+ SimTlv recTlv;
+ int recNum = 0;
+ for (byte[] record: records) {
+ recTlv = new SimTlv(record, 0, record.length);
+ parseTag(recTlv, recNum);
+ recNum ++;
+ }
+ }
+
+ void parseTag(SimTlv tlv, int recNum) {
+ SimTlv tlvEf;
+ int tag;
+ byte[] data;
+ Map<Integer, Integer> val = new HashMap<Integer, Integer>();
+ do {
+ tag = tlv.getTag();
+ switch(tag) {
+ case USIM_TYPE1_TAG: // A8
+ case USIM_TYPE3_TAG: // AA
+ case USIM_TYPE2_TAG: // A9
+ data = tlv.getData();
+ tlvEf = new SimTlv(data, 0, data.length);
+ parseEf(tlvEf, val, tag);
+ break;
+ }
+ } while (tlv.nextObject());
+ mFileIds.put(recNum, val);
+ }
+
+ void parseEf(SimTlv tlv, Map<Integer, Integer> val, int parentTag) {
+ int tag;
+ byte[] data;
+ int tagNumberWithinParentTag = 0;
+ do {
+ tag = tlv.getTag();
+ if (parentTag == USIM_TYPE2_TAG && tag == USIM_EFEMAIL_TAG) {
+ mEmailPresentInIap = true;
+ mEmailTagNumberInIap = tagNumberWithinParentTag;
+ }
+ switch(tag) {
+ case USIM_EFEMAIL_TAG:
+ case USIM_EFADN_TAG:
+ case USIM_EFEXT1_TAG:
+ case USIM_EFANR_TAG:
+ case USIM_EFPBC_TAG:
+ case USIM_EFGRP_TAG:
+ case USIM_EFAAS_TAG:
+ case USIM_EFGSD_TAG:
+ case USIM_EFUID_TAG:
+ case USIM_EFCCP1_TAG:
+ case USIM_EFIAP_TAG:
+ case USIM_EFSNE_TAG:
+ data = tlv.getData();
+ int efid = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
+ val.put(tag, efid);
+ break;
+ }
+ tagNumberWithinParentTag ++;
+ } while(tlv.nextObject());
+ }
+ }
+
+ private void log(String msg) {
+ if(DBG) Log.d(LOG_TAG, msg);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/UsimServiceTable.java b/src/java/com/android/internal/telephony/gsm/UsimServiceTable.java
new file mode 100644
index 0000000..3fe200b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/UsimServiceTable.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2011 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.IccServiceTable;
+
+/**
+ * Wrapper class for the USIM Service Table EF.
+ * See 3GPP TS 31.102 Release 10 section 4.2.8
+ */
+public final class UsimServiceTable extends IccServiceTable {
+ public enum UsimService {
+ PHONEBOOK,
+ FDN, // Fixed Dialing Numbers
+ FDN_EXTENSION, // FDN extension data in EF_EXT2
+ SDN, // Service Dialing Numbers
+ SDN_EXTENSION, // SDN extension data in EF_EXT3
+ BDN, // Barred Dialing Numbers
+ BDN_EXTENSION, // BDN extension data in EF_EXT4
+ OUTGOING_CALL_INFO,
+ INCOMING_CALL_INFO,
+ SM_STORAGE,
+ SM_STATUS_REPORTS,
+ SM_SERVICE_PARAMS,
+ ADVICE_OF_CHARGE,
+ CAP_CONFIG_PARAMS_2,
+ CB_MESSAGE_ID,
+ CB_MESSAGE_ID_RANGES,
+ GROUP_ID_LEVEL_1,
+ GROUP_ID_LEVEL_2,
+ SPN, // Service Provider Name
+ USER_PLMN_SELECT,
+ MSISDN,
+ IMAGE,
+ LOCALISED_SERVICE_AREAS,
+ EMLPP, // Enhanced Multi-Level Precedence and Preemption
+ EMLPP_AUTO_ANSWER,
+ RFU,
+ GSM_ACCESS,
+ DATA_DL_VIA_SMS_PP,
+ DATA_DL_VIA_SMS_CB,
+ CALL_CONTROL_BY_USIM,
+ MO_SMS_CONTROL_BY_USIM,
+ RUN_AT_COMMAND,
+ IGNORED_1,
+ ENABLED_SERVICES_TABLE,
+ APN_CONTROL_LIST,
+ DEPERSONALISATION_CONTROL_KEYS,
+ COOPERATIVE_NETWORK_LIST,
+ GSM_SECURITY_CONTEXT,
+ CPBCCH_INFO,
+ INVESTIGATION_SCAN,
+ MEXE,
+ OPERATOR_PLMN_SELECT,
+ HPLMN_SELECT,
+ EXTENSION_5, // Extension data for ICI, OCI, MSISDN in EF_EXT5
+ PLMN_NETWORK_NAME,
+ OPERATOR_PLMN_LIST,
+ MBDN, // Mailbox Dialing Numbers
+ MWI_STATUS, // Message Waiting Indication status
+ CFI_STATUS, // Call Forwarding Indication status
+ IGNORED_2,
+ SERVICE_PROVIDER_DISPLAY_INFO,
+ MMS_NOTIFICATION,
+ MMS_NOTIFICATION_EXTENSION, // MMS Notification extension data in EF_EXT8
+ GPRS_CALL_CONTROL_BY_USIM,
+ MMS_CONNECTIVITY_PARAMS,
+ NETWORK_INDICATION_OF_ALERTING,
+ VGCS_GROUP_ID_LIST,
+ VBS_GROUP_ID_LIST,
+ PSEUDONYM,
+ IWLAN_USER_PLMN_SELECT,
+ IWLAN_OPERATOR_PLMN_SELECT,
+ USER_WSID_LIST,
+ OPERATOR_WSID_LIST,
+ VGCS_SECURITY,
+ VBS_SECURITY,
+ WLAN_REAUTH_IDENTITY,
+ MM_STORAGE,
+ GBA, // Generic Bootstrapping Architecture
+ MBMS_SECURITY,
+ DATA_DL_VIA_USSD,
+ EQUIVALENT_HPLMN,
+ TERMINAL_PROFILE_AFTER_UICC_ACTIVATION,
+ EQUIVALENT_HPLMN_PRESENTATION,
+ LAST_RPLMN_SELECTION_INDICATION,
+ OMA_BCAST_PROFILE,
+ GBA_LOCAL_KEY_ESTABLISHMENT,
+ TERMINAL_APPLICATIONS,
+ SPN_ICON,
+ PLMN_NETWORK_NAME_ICON,
+ USIM_IP_CONNECTION_PARAMS,
+ IWLAN_HOME_ID_LIST,
+ IWLAN_EQUIVALENT_HPLMN_PRESENTATION,
+ IWLAN_HPLMN_PRIORITY_INDICATION,
+ IWLAN_LAST_REGISTERED_PLMN,
+ EPS_MOBILITY_MANAGEMENT_INFO,
+ ALLOWED_CSG_LISTS_AND_INDICATIONS,
+ CALL_CONTROL_ON_EPS_PDN_CONNECTION_BY_USIM,
+ HPLMN_DIRECT_ACCESS,
+ ECALL_DATA,
+ OPERATOR_CSG_LISTS_AND_INDICATIONS,
+ SM_OVER_IP,
+ CSG_DISPLAY_CONTROL,
+ IMS_COMMUNICATION_CONTROL_BY_USIM,
+ EXTENDED_TERMINAL_APPLICATIONS,
+ UICC_ACCESS_TO_IMS,
+ NAS_CONFIG_BY_USIM
+ }
+
+ public UsimServiceTable(byte[] table) {
+ super(table);
+ }
+
+ public boolean isAvailable(UsimService service) {
+ return super.isAvailable(service.ordinal());
+ }
+
+ @Override
+ protected String getTag() {
+ return "UsimServiceTable";
+ }
+
+ @Override
+ protected Object[] getValues() {
+ return UsimService.values();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/VoiceMailConstants.java b/src/java/com/android/internal/telephony/gsm/VoiceMailConstants.java
new file mode 100644
index 0000000..0e49e35
--- /dev/null
+++ b/src/java/com/android/internal/telephony/gsm/VoiceMailConstants.java
@@ -0,0 +1,115 @@
+/*
+ * 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/src/java/com/android/internal/telephony/gsm/package.html b/src/java/com/android/internal/telephony/gsm/package.html
new file mode 100755
index 0000000..fed8399
--- /dev/null
+++ b/src/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/src/java/com/android/internal/telephony/ims/IsimRecords.java b/src/java/com/android/internal/telephony/ims/IsimRecords.java
new file mode 100644
index 0000000..b8b98b9
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ims/IsimRecords.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 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.ims;
+
+/**
+ * {@hide}
+ */
+public interface IsimRecords {
+
+ /**
+ * Return the IMS private user identity (IMPI).
+ * Returns null if the IMPI hasn't been loaded or isn't present on the ISIM.
+ * @return the IMS private user identity string, or null if not available
+ */
+ String getIsimImpi();
+
+ /**
+ * Return the IMS home network domain name.
+ * Returns null if the IMS domain hasn't been loaded or isn't present on the ISIM.
+ * @return the IMS home network domain name, or null if not available
+ */
+ String getIsimDomain();
+
+ /**
+ * Return an array of IMS public user identities (IMPU).
+ * Returns null if the IMPU hasn't been loaded or isn't present on the ISIM.
+ * @return an array of IMS public user identity strings, or null if not available
+ */
+ String[] getIsimImpu();
+}
diff --git a/src/java/com/android/internal/telephony/ims/IsimUiccRecords.java b/src/java/com/android/internal/telephony/ims/IsimUiccRecords.java
new file mode 100644
index 0000000..ee1a42d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ims/IsimUiccRecords.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 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.ims;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccRecords;
+import com.android.internal.telephony.gsm.SimTlv;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+
+import static com.android.internal.telephony.IccConstants.EF_DOMAIN;
+import static com.android.internal.telephony.IccConstants.EF_IMPI;
+import static com.android.internal.telephony.IccConstants.EF_IMPU;
+
+/**
+ * {@hide}
+ */
+public final class IsimUiccRecords implements IsimRecords {
+ protected static final String LOG_TAG = "GSM";
+
+ private static final boolean DBG = true;
+ private static final boolean DUMP_RECORDS = false; // Note: PII is logged when this is true
+
+ // ISIM EF records (see 3GPP TS 31.103)
+ private String mIsimImpi; // IMS private user identity
+ private String mIsimDomain; // IMS home network domain name
+ private String[] mIsimImpu; // IMS public user identity(s)
+
+ private static final int TAG_ISIM_VALUE = 0x80; // From 3GPP TS 31.103
+
+ private class EfIsimImpiLoaded implements IccRecords.IccRecordLoaded {
+ public String getEfName() {
+ return "EF_ISIM_IMPI";
+ }
+ public void onRecordLoaded(AsyncResult ar) {
+ byte[] data = (byte[]) ar.result;
+ mIsimImpi = isimTlvToString(data);
+ if (DUMP_RECORDS) log("EF_IMPI=" + mIsimImpi);
+ }
+ }
+
+ private class EfIsimImpuLoaded implements IccRecords.IccRecordLoaded {
+ public String getEfName() {
+ return "EF_ISIM_IMPU";
+ }
+ public void onRecordLoaded(AsyncResult ar) {
+ ArrayList<byte[]> impuList = (ArrayList<byte[]>) ar.result;
+ if (DBG) log("EF_IMPU record count: " + impuList.size());
+ mIsimImpu = new String[impuList.size()];
+ int i = 0;
+ for (byte[] identity : impuList) {
+ String impu = isimTlvToString(identity);
+ if (DUMP_RECORDS) log("EF_IMPU[" + i + "]=" + impu);
+ mIsimImpu[i++] = impu;
+ }
+ }
+ }
+
+ private class EfIsimDomainLoaded implements IccRecords.IccRecordLoaded {
+ public String getEfName() {
+ return "EF_ISIM_DOMAIN";
+ }
+ public void onRecordLoaded(AsyncResult ar) {
+ byte[] data = (byte[]) ar.result;
+ mIsimDomain = isimTlvToString(data);
+ if (DUMP_RECORDS) log("EF_DOMAIN=" + mIsimDomain);
+ }
+ }
+
+ /**
+ * Request the ISIM records to load.
+ * @param iccFh the IccFileHandler to load the records from
+ * @param h the Handler to which the response message will be sent
+ * @return the number of EF record requests that were added
+ */
+ public int fetchIsimRecords(IccFileHandler iccFh, Handler h) {
+ iccFh.loadEFTransparent(EF_IMPI, h.obtainMessage(
+ IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpiLoaded()));
+ iccFh.loadEFLinearFixedAll(EF_IMPU, h.obtainMessage(
+ IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpuLoaded()));
+ iccFh.loadEFTransparent(EF_DOMAIN, h.obtainMessage(
+ IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimDomainLoaded()));
+ return 3; // number of EF record load requests
+ }
+
+ /**
+ * ISIM records for IMS are stored inside a Tag-Length-Value record as a UTF-8 string
+ * with tag value 0x80.
+ * @param record the byte array containing the IMS data string
+ * @return the decoded String value, or null if the record can't be decoded
+ */
+ private static String isimTlvToString(byte[] record) {
+ SimTlv tlv = new SimTlv(record, 0, record.length);
+ do {
+ if (tlv.getTag() == TAG_ISIM_VALUE) {
+ return new String(tlv.getData(), Charset.forName("UTF-8"));
+ }
+ } while (tlv.nextObject());
+
+ Log.e(LOG_TAG, "[ISIM] can't find TLV tag in ISIM record, returning null");
+ return null;
+ }
+
+ void log(String s) {
+ if (DBG) Log.d(LOG_TAG, "[ISIM] " + s);
+ }
+
+ void loge(String s) {
+ if (DBG) Log.e(LOG_TAG, "[ISIM] " + s);
+ }
+
+ /**
+ * Return the IMS private user identity (IMPI).
+ * Returns null if the IMPI hasn't been loaded or isn't present on the ISIM.
+ * @return the IMS private user identity string, or null if not available
+ */
+ public String getIsimImpi() {
+ return mIsimImpi;
+ }
+
+ /**
+ * Return the IMS home network domain name.
+ * Returns null if the IMS domain hasn't been loaded or isn't present on the ISIM.
+ * @return the IMS home network domain name, or null if not available
+ */
+ public String getIsimDomain() {
+ return mIsimDomain;
+ }
+
+ /**
+ * Return an array of IMS public user identities (IMPU).
+ * Returns null if the IMPU hasn't been loaded or isn't present on the ISIM.
+ * @return an array of IMS public user identity strings, or null if not available
+ */
+ public String[] getIsimImpu() {
+ return (mIsimImpu != null) ? mIsimImpu.clone() : null;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/package.html b/src/java/com/android/internal/telephony/package.html
new file mode 100644
index 0000000..6377c18
--- /dev/null
+++ b/src/java/com/android/internal/telephony/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+{@hide}
+</body>
+</html>
diff --git a/src/java/com/android/internal/telephony/sip/SipCallBase.java b/src/java/com/android/internal/telephony/sip/SipCallBase.java
new file mode 100644
index 0000000..9050a43
--- /dev/null
+++ b/src/java/com/android/internal/telephony/sip/SipCallBase.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+
+import android.net.sip.SipManager;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+abstract class SipCallBase extends Call {
+ protected List<Connection> connections = new ArrayList<Connection>();
+
+ protected abstract void setState(State newState);
+
+ public List<Connection> getConnections() {
+ // FIXME should return Collections.unmodifiableList();
+ return connections;
+ }
+
+ public boolean isMultiparty() {
+ return connections.size() > 1;
+ }
+
+ public String toString() {
+ return state.toString() + ":" + super.toString();
+ }
+
+ void clearDisconnected() {
+ for (Iterator<Connection> it = connections.iterator(); it.hasNext(); ) {
+ Connection c = it.next();
+ if (c.getState() == State.DISCONNECTED) it.remove();
+ }
+
+ if (connections.isEmpty()) setState(State.IDLE);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
new file mode 100644
index 0000000..99f4e0f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.telephony.BaseCommands;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+
+/**
+ * SIP doesn't need CommandsInterface. The class does nothing but made to work
+ * with PhoneBase's constructor.
+ */
+class SipCommandInterface extends BaseCommands implements CommandsInterface {
+ SipCommandInterface(Context context) {
+ super(context);
+ }
+
+ @Override public void setOnNITZTime(Handler h, int what, Object obj) {
+ }
+
+ public void getIccCardStatus(Message result) {
+ }
+
+ public void supplyIccPin(String pin, Message result) {
+ }
+
+ public void supplyIccPuk(String puk, String newPin, Message result) {
+ }
+
+ public void supplyIccPin2(String pin, Message result) {
+ }
+
+ public void supplyIccPuk2(String puk, String newPin2, Message result) {
+ }
+
+ public void changeIccPin(String oldPin, String newPin, Message result) {
+ }
+
+ public void changeIccPin2(String oldPin2, String newPin2, Message result) {
+ }
+
+ public void changeBarringPassword(String facility, String oldPwd,
+ String newPwd, Message result) {
+ }
+
+ public void supplyNetworkDepersonalization(String netpin, Message result) {
+ }
+
+ public void getCurrentCalls(Message result) {
+ }
+
+ @Deprecated public void getPDPContextList(Message result) {
+ }
+
+ public void getDataCallList(Message result) {
+ }
+
+ public void dial(String address, int clirMode, Message result) {
+ }
+
+ public void dial(String address, int clirMode, UUSInfo uusInfo,
+ Message result) {
+ }
+
+ public void getIMSI(Message result) {
+ }
+
+ public void getIMSIForApp(String aid, Message result) {
+ }
+
+ public void getIMEI(Message result) {
+ }
+
+ public void getIMEISV(Message result) {
+ }
+
+
+ public void hangupConnection (int gsmIndex, Message result) {
+ }
+
+ public void hangupWaitingOrBackground (Message result) {
+ }
+
+ public void hangupForegroundResumeBackground (Message result) {
+ }
+
+ public void switchWaitingOrHoldingAndActive (Message result) {
+ }
+
+ public void conference (Message result) {
+ }
+
+
+ public void setPreferredVoicePrivacy(boolean enable, Message result) {
+ }
+
+ public void getPreferredVoicePrivacy(Message result) {
+ }
+
+ public void separateConnection (int gsmIndex, Message result) {
+ }
+
+ public void acceptCall (Message result) {
+ }
+
+ public void rejectCall (Message result) {
+ }
+
+ public void explicitCallTransfer (Message result) {
+ }
+
+ public void getLastCallFailCause (Message result) {
+ }
+
+ /** @deprecated */
+ public void getLastPdpFailCause (Message result) {
+ }
+
+ public void getLastDataCallFailCause (Message result) {
+ }
+
+ public void setMute (boolean enableMute, Message response) {
+ }
+
+ public void getMute (Message response) {
+ }
+
+ public void getSignalStrength (Message result) {
+ }
+
+ public void getVoiceRegistrationState (Message result) {
+ }
+
+ public void getDataRegistrationState (Message result) {
+ }
+
+ public void getOperator(Message result) {
+ }
+
+ public void sendDtmf(char c, Message result) {
+ }
+
+ public void startDtmf(char c, Message result) {
+ }
+
+ public void stopDtmf(Message result) {
+ }
+
+ public void sendBurstDtmf(String dtmfString, int on, int off,
+ Message result) {
+ }
+
+ public void sendSMS (String smscPDU, String pdu, Message result) {
+ }
+
+ public void sendCdmaSms(byte[] pdu, Message result) {
+ }
+
+ public void deleteSmsOnSim(int index, Message response) {
+ }
+
+ public void deleteSmsOnRuim(int index, Message response) {
+ }
+
+ public void writeSmsToSim(int status, String smsc, String pdu, Message response) {
+ }
+
+ public void writeSmsToRuim(int status, String pdu, Message response) {
+ }
+
+ public void setupDataCall(String radioTechnology, String profile,
+ String apn, String user, String password, String authType,
+ String protocol, Message result) {
+ }
+
+ public void deactivateDataCall(int cid, int reason, Message result) {
+ }
+
+ public void setRadioPower(boolean on, Message result) {
+ }
+
+ public void setSuppServiceNotifications(boolean enable, Message result) {
+ }
+
+ public void acknowledgeLastIncomingGsmSms(boolean success, int cause,
+ Message result) {
+ }
+
+ public void acknowledgeLastIncomingCdmaSms(boolean success, int cause,
+ Message result) {
+ }
+
+ public void acknowledgeIncomingGsmSmsWithPdu(boolean success, String ackPdu,
+ Message result) {
+ }
+
+ public void iccIO (int command, int fileid, String path, int p1, int p2,
+ int p3, String data, String pin2, Message result) {
+ }
+ public void iccIOForApp (int command, int fileid, String path, int p1, int p2,
+ int p3, String data, String pin2, String aid, Message result) {
+ }
+
+ public void getCLIR(Message result) {
+ }
+
+ public void setCLIR(int clirMode, Message result) {
+ }
+
+ public void queryCallWaiting(int serviceClass, Message response) {
+ }
+
+ public void setCallWaiting(boolean enable, int serviceClass,
+ Message response) {
+ }
+
+ public void setNetworkSelectionModeAutomatic(Message response) {
+ }
+
+ public void setNetworkSelectionModeManual(
+ String operatorNumeric, Message response) {
+ }
+
+ public void getNetworkSelectionMode(Message response) {
+ }
+
+ public void getAvailableNetworks(Message response) {
+ }
+
+ public void setCallForward(int action, int cfReason, int serviceClass,
+ String number, int timeSeconds, Message response) {
+ }
+
+ public void queryCallForwardStatus(int cfReason, int serviceClass,
+ String number, Message response) {
+ }
+
+ public void queryCLIP(Message response) {
+ }
+
+ public void getBasebandVersion (Message response) {
+ }
+
+ @Override
+ public void queryFacilityLock(String facility, String password,
+ int serviceClass, Message response) {
+ }
+
+ @Override
+ public void queryFacilityLockForApp(String facility, String password,
+ int serviceClass, String appId, Message response) {
+ }
+
+ @Override
+ public void setFacilityLock(String facility, boolean lockState,
+ String password, int serviceClass, Message response) {
+ }
+
+ @Override
+ public void setFacilityLockForApp(String facility, boolean lockState,
+ String password, int serviceClass, String appId, Message response) {
+ }
+
+ public void sendUSSD (String ussdString, Message response) {
+ }
+
+ public void cancelPendingUssd (Message response) {
+ }
+
+ public void resetRadio(Message result) {
+ }
+
+ public void invokeOemRilRequestRaw(byte[] data, Message response) {
+ }
+
+ public void invokeOemRilRequestStrings(String[] strings, Message response) {
+ }
+
+ public void setBandMode (int bandMode, Message response) {
+ }
+
+ public void queryAvailableBandMode (Message response) {
+ }
+
+ public void sendTerminalResponse(String contents, Message response) {
+ }
+
+ public void sendEnvelope(String contents, Message response) {
+ }
+
+ public void sendEnvelopeWithStatus(String contents, Message response) {
+ }
+
+ public void handleCallSetupRequestFromSim(
+ boolean accept, Message response) {
+ }
+
+ public void setPreferredNetworkType(int networkType , Message response) {
+ }
+
+ public void getPreferredNetworkType(Message response) {
+ }
+
+ public void getNeighboringCids(Message response) {
+ }
+
+ public void setLocationUpdates(boolean enable, Message response) {
+ }
+
+ public void getSmscAddress(Message result) {
+ }
+
+ public void setSmscAddress(String address, Message result) {
+ }
+
+ public void reportSmsMemoryStatus(boolean available, Message result) {
+ }
+
+ public void reportStkServiceIsRunning(Message result) {
+ }
+
+ @Override
+ public void getCdmaSubscriptionSource(Message response) {
+ }
+
+ public void getGsmBroadcastConfig(Message response) {
+ }
+
+ public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) {
+ }
+
+ public void setGsmBroadcastActivation(boolean activate, Message response) {
+ }
+
+ // ***** Methods for CDMA support
+ public void getDeviceIdentity(Message response) {
+ }
+
+ public void getCDMASubscription(Message response) {
+ }
+
+ public void setPhoneType(int phoneType) { //Set by CDMAPhone and GSMPhone constructor
+ }
+
+ public void queryCdmaRoamingPreference(Message response) {
+ }
+
+ public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+ }
+
+ public void setCdmaSubscriptionSource(int cdmaSubscription , Message response) {
+ }
+
+ public void queryTTYMode(Message response) {
+ }
+
+ public void setTTYMode(int ttyMode, Message response) {
+ }
+
+ public void sendCDMAFeatureCode(String FeatureCode, Message response) {
+ }
+
+ public void getCdmaBroadcastConfig(Message response) {
+ }
+
+ public void setCdmaBroadcastConfig(int[] configValuesArray, Message response) {
+ }
+
+ public void setCdmaBroadcastActivation(boolean activate, Message response) {
+ }
+
+ public void exitEmergencyCallbackMode(Message response) {
+ }
+
+ @Override
+ public void supplyIccPinForApp(String pin, String aid, Message response) {
+ }
+
+ @Override
+ public void supplyIccPukForApp(String puk, String newPin, String aid, Message response) {
+ }
+
+ @Override
+ public void supplyIccPin2ForApp(String pin2, String aid, Message response) {
+ }
+
+ @Override
+ public void supplyIccPuk2ForApp(String puk2, String newPin2, String aid, Message response) {
+ }
+
+ @Override
+ public void changeIccPinForApp(String oldPin, String newPin, String aidPtr, Message response) {
+ }
+
+ @Override
+ public void changeIccPin2ForApp(String oldPin2, String newPin2, String aidPtr,
+ Message response) {
+ }
+
+ public void requestIsimAuthentication(String nonce, Message response) {
+ }
+
+ public void getVoiceRadioTechnology(Message result) {
+ }
+}
diff --git a/src/java/com/android/internal/telephony/sip/SipConnectionBase.java b/src/java/com/android/internal/telephony/sip/SipConnectionBase.java
new file mode 100644
index 0000000..eaba2c4
--- /dev/null
+++ b/src/java/com/android/internal/telephony/sip/SipConnectionBase.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.UUSInfo;
+
+import android.net.sip.SipAudioCall;
+import android.os.SystemClock;
+import android.util.Log;
+import android.telephony.PhoneNumberUtils;
+
+abstract class SipConnectionBase extends Connection {
+ private static final String LOG_TAG = "SIP_CONN";
+
+ private SipAudioCall mSipAudioCall;
+
+ private String dialString; // outgoing calls only
+ private String postDialString; // outgoing calls only
+ private int nextPostDialChar; // index into postDialString
+ private boolean isIncoming;
+
+ /*
+ * These time/timespan values are based on System.currentTimeMillis(),
+ * i.e., "wall clock" time.
+ */
+ private long createTime;
+ private long connectTime;
+ private long disconnectTime;
+
+ /*
+ * These time/timespan values are based on SystemClock.elapsedRealTime(),
+ * i.e., time since boot. They are appropriate for comparison and
+ * calculating deltas.
+ */
+ private long connectTimeReal;
+ private long duration = -1L;
+ private long holdingStartTime; // The time when the Connection last transitioned
+ // into HOLDING
+
+ private DisconnectCause mCause = DisconnectCause.NOT_DISCONNECTED;
+ private PostDialState postDialState = PostDialState.NOT_STARTED;
+
+ SipConnectionBase(String dialString) {
+ this.dialString = dialString;
+
+ postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
+
+ isIncoming = false;
+ createTime = System.currentTimeMillis();
+ }
+
+ protected void setState(Call.State state) {
+ switch (state) {
+ case ACTIVE:
+ if (connectTime == 0) {
+ connectTimeReal = SystemClock.elapsedRealtime();
+ connectTime = System.currentTimeMillis();
+ }
+ break;
+ case DISCONNECTED:
+ duration = getDurationMillis();
+ disconnectTime = System.currentTimeMillis();
+ break;
+ case HOLDING:
+ holdingStartTime = SystemClock.elapsedRealtime();
+ break;
+ }
+ }
+
+ @Override
+ public long getCreateTime() {
+ return createTime;
+ }
+
+ @Override
+ public long getConnectTime() {
+ return connectTime;
+ }
+
+ @Override
+ public long getDisconnectTime() {
+ return disconnectTime;
+ }
+
+ @Override
+ public long getDurationMillis() {
+ if (connectTimeReal == 0) {
+ return 0;
+ } else if (duration < 0) {
+ return SystemClock.elapsedRealtime() - connectTimeReal;
+ } else {
+ return duration;
+ }
+ }
+
+ @Override
+ public long getHoldDurationMillis() {
+ if (getState() != Call.State.HOLDING) {
+ // If not holding, return 0
+ return 0;
+ } else {
+ return SystemClock.elapsedRealtime() - holdingStartTime;
+ }
+ }
+
+ @Override
+ public DisconnectCause getDisconnectCause() {
+ return mCause;
+ }
+
+ void setDisconnectCause(DisconnectCause cause) {
+ mCause = cause;
+ }
+
+ @Override
+ public PostDialState getPostDialState() {
+ return postDialState;
+ }
+
+ @Override
+ public void proceedAfterWaitChar() {
+ // TODO
+ }
+
+ @Override
+ public void proceedAfterWildChar(String str) {
+ // TODO
+ }
+
+ @Override
+ public void cancelPostDial() {
+ // TODO
+ }
+
+ protected abstract Phone getPhone();
+
+ @Override
+ public String getRemainingPostDialString() {
+ if (postDialState == PostDialState.CANCELLED
+ || postDialState == PostDialState.COMPLETE
+ || postDialString == null
+ || postDialString.length() <= nextPostDialChar) {
+ return "";
+ }
+
+ return postDialString.substring(nextPostDialChar);
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[SipConn] " + msg);
+ }
+
+ @Override
+ public int getNumberPresentation() {
+ // TODO: add PRESENTATION_URL
+ return PhoneConstants.PRESENTATION_ALLOWED;
+ }
+
+ @Override
+ public UUSInfo getUUSInfo() {
+ // FIXME: what's this for SIP?
+ return null;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/sip/SipPhone.java b/src/java/com/android/internal/telephony/sip/SipPhone.java
new file mode 100644
index 0000000..346b126
--- /dev/null
+++ b/src/java/com/android/internal/telephony/sip/SipPhone.java
@@ -0,0 +1,939 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.net.rtp.AudioGroup;
+import android.net.sip.SipAudioCall;
+import android.net.sip.SipErrorCode;
+import android.net.sip.SipException;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.net.sip.SipSession;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneNotifier;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * {@hide}
+ */
+public class SipPhone extends SipPhoneBase {
+ private static final String LOG_TAG = "SipPhone";
+ private static final boolean DEBUG = true;
+ private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
+ private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
+ private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
+
+ // A call that is ringing or (call) waiting
+ private SipCall ringingCall = new SipCall();
+ private SipCall foregroundCall = new SipCall();
+ private SipCall backgroundCall = new SipCall();
+
+ private SipManager mSipManager;
+ private SipProfile mProfile;
+
+ SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
+ super(context, notifier);
+
+ if (DEBUG) Log.d(LOG_TAG, "new SipPhone: " + profile.getUriString());
+ ringingCall = new SipCall();
+ foregroundCall = new SipCall();
+ backgroundCall = new SipCall();
+ mProfile = profile;
+ mSipManager = SipManager.newInstance(context);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof SipPhone)) return false;
+ SipPhone that = (SipPhone) o;
+ return mProfile.getUriString().equals(that.mProfile.getUriString());
+ }
+
+ public String getPhoneName() {
+ return "SIP:" + getUriString(mProfile);
+ }
+
+ public String getSipUri() {
+ return mProfile.getUriString();
+ }
+
+ public boolean equals(SipPhone phone) {
+ return getSipUri().equals(phone.getSipUri());
+ }
+
+ public boolean canTake(Object incomingCall) {
+ synchronized (SipPhone.class) {
+ if (!(incomingCall instanceof SipAudioCall)) return false;
+ if (ringingCall.getState().isAlive()) return false;
+
+ // FIXME: is it true that we cannot take any incoming call if
+ // both foreground and background are active
+ if (foregroundCall.getState().isAlive()
+ && backgroundCall.getState().isAlive()) {
+ return false;
+ }
+
+ try {
+ SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
+ if (DEBUG) Log.d(LOG_TAG, "+++ taking call from: "
+ + sipAudioCall.getPeerProfile().getUriString());
+ String localUri = sipAudioCall.getLocalProfile().getUriString();
+ if (localUri.equals(mProfile.getUriString())) {
+ boolean makeCallWait = foregroundCall.getState().isAlive();
+ ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
+ if (sipAudioCall.getState()
+ != SipSession.State.INCOMING_CALL) {
+ // Peer cancelled the call!
+ if (DEBUG) Log.d(LOG_TAG, " call cancelled !!");
+ ringingCall.reset();
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ // Peer may cancel the call at any time during the time we hook
+ // up ringingCall with sipAudioCall. Clean up ringingCall when
+ // that happens.
+ ringingCall.reset();
+ }
+ return false;
+ }
+ }
+
+ public void acceptCall() throws CallStateException {
+ synchronized (SipPhone.class) {
+ if ((ringingCall.getState() == Call.State.INCOMING) ||
+ (ringingCall.getState() == Call.State.WAITING)) {
+ if (DEBUG) Log.d(LOG_TAG, "acceptCall");
+ // Always unmute when answering a new call
+ ringingCall.setMute(false);
+ ringingCall.acceptCall();
+ } else {
+ throw new CallStateException("phone not ringing");
+ }
+ }
+ }
+
+ public void rejectCall() throws CallStateException {
+ synchronized (SipPhone.class) {
+ if (ringingCall.getState().isRinging()) {
+ if (DEBUG) Log.d(LOG_TAG, "rejectCall");
+ ringingCall.rejectCall();
+ } else {
+ throw new CallStateException("phone not ringing");
+ }
+ }
+ }
+
+ public Connection dial(String dialString) throws CallStateException {
+ synchronized (SipPhone.class) {
+ return dialInternal(dialString);
+ }
+ }
+
+ private Connection dialInternal(String dialString)
+ throws CallStateException {
+ clearDisconnected();
+
+ if (!canDial()) {
+ throw new CallStateException("cannot dial in current state");
+ }
+ if (foregroundCall.getState() == SipCall.State.ACTIVE) {
+ switchHoldingAndActive();
+ }
+ if (foregroundCall.getState() != SipCall.State.IDLE) {
+ //we should have failed in !canDial() above before we get here
+ throw new CallStateException("cannot dial in current state");
+ }
+
+ foregroundCall.setMute(false);
+ try {
+ Connection c = foregroundCall.dial(dialString);
+ return c;
+ } catch (SipException e) {
+ Log.e(LOG_TAG, "dial()", e);
+ throw new CallStateException("dial error: " + e);
+ }
+ }
+
+ public void switchHoldingAndActive() throws CallStateException {
+ if (DEBUG) Log.d(LOG_TAG, " ~~~~~~ switch fg and bg");
+ synchronized (SipPhone.class) {
+ foregroundCall.switchWith(backgroundCall);
+ if (backgroundCall.getState().isAlive()) backgroundCall.hold();
+ if (foregroundCall.getState().isAlive()) foregroundCall.unhold();
+ }
+ }
+
+ public boolean canConference() {
+ return true;
+ }
+
+ public void conference() throws CallStateException {
+ synchronized (SipPhone.class) {
+ if ((foregroundCall.getState() != SipCall.State.ACTIVE)
+ || (foregroundCall.getState() != SipCall.State.ACTIVE)) {
+ throw new CallStateException("wrong state to merge calls: fg="
+ + foregroundCall.getState() + ", bg="
+ + backgroundCall.getState());
+ }
+ foregroundCall.merge(backgroundCall);
+ }
+ }
+
+ public void conference(Call that) throws CallStateException {
+ synchronized (SipPhone.class) {
+ if (!(that instanceof SipCall)) {
+ throw new CallStateException("expect " + SipCall.class
+ + ", cannot merge with " + that.getClass());
+ }
+ foregroundCall.merge((SipCall) that);
+ }
+ }
+
+ public boolean canTransfer() {
+ return false;
+ }
+
+ public void explicitCallTransfer() throws CallStateException {
+ //mCT.explicitCallTransfer();
+ }
+
+ public void clearDisconnected() {
+ synchronized (SipPhone.class) {
+ ringingCall.clearDisconnected();
+ foregroundCall.clearDisconnected();
+ backgroundCall.clearDisconnected();
+
+ updatePhoneState();
+ notifyPreciseCallStateChanged();
+ }
+ }
+
+ public void sendDtmf(char c) {
+ if (!PhoneNumberUtils.is12Key(c)) {
+ Log.e(LOG_TAG,
+ "sendDtmf called with invalid character '" + c + "'");
+ } else if (foregroundCall.getState().isAlive()) {
+ synchronized (SipPhone.class) {
+ foregroundCall.sendDtmf(c);
+ }
+ }
+ }
+
+ public void startDtmf(char c) {
+ if (!PhoneNumberUtils.is12Key(c)) {
+ Log.e(LOG_TAG,
+ "startDtmf called with invalid character '" + c + "'");
+ } else {
+ sendDtmf(c);
+ }
+ }
+
+ public void stopDtmf() {
+ // no op
+ }
+
+ public void sendBurstDtmf(String dtmfString) {
+ Log.e(LOG_TAG, "[SipPhone] sendBurstDtmf() is a CDMA method");
+ }
+
+ public void getOutgoingCallerIdDisplay(Message onComplete) {
+ // FIXME: what to reply?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
+ Message onComplete) {
+ // FIXME: what's this for SIP?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void getCallWaiting(Message onComplete) {
+ // FIXME: what to reply?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setCallWaiting(boolean enable, Message onComplete) {
+ // FIXME: what to reply?
+ Log.e(LOG_TAG, "call waiting not supported");
+ }
+
+ @Override
+ public void setEchoSuppressionEnabled(boolean enabled) {
+ // TODO: Remove the enabled argument. We should check the speakerphone
+ // state with AudioManager instead of keeping a state here so the
+ // method with a state argument is redundant. Also rename the method
+ // to something like onSpeaerphoneStateChanged(). Echo suppression may
+ // not be available on every device.
+ synchronized (SipPhone.class) {
+ foregroundCall.setAudioGroupMode();
+ }
+ }
+
+ public void setMute(boolean muted) {
+ synchronized (SipPhone.class) {
+ foregroundCall.setMute(muted);
+ }
+ }
+
+ public boolean getMute() {
+ return (foregroundCall.getState().isAlive()
+ ? foregroundCall.getMute()
+ : backgroundCall.getMute());
+ }
+
+ public Call getForegroundCall() {
+ return foregroundCall;
+ }
+
+ public Call getBackgroundCall() {
+ return backgroundCall;
+ }
+
+ public Call getRingingCall() {
+ return ringingCall;
+ }
+
+ public ServiceState getServiceState() {
+ // FIXME: we may need to provide this when data connectivity is lost
+ // or when server is down
+ return super.getServiceState();
+ }
+
+ private String getUriString(SipProfile p) {
+ // SipProfile.getUriString() may contain "SIP:" and port
+ return p.getUserName() + "@" + getSipDomain(p);
+ }
+
+ private String getSipDomain(SipProfile p) {
+ String domain = p.getSipDomain();
+ // TODO: move this to SipProfile
+ if (domain.endsWith(":5060")) {
+ return domain.substring(0, domain.length() - 5);
+ } else {
+ return domain;
+ }
+ }
+
+ private class SipCall extends SipCallBase {
+ void reset() {
+ connections.clear();
+ setState(Call.State.IDLE);
+ }
+
+ void switchWith(SipCall that) {
+ synchronized (SipPhone.class) {
+ SipCall tmp = new SipCall();
+ tmp.takeOver(this);
+ this.takeOver(that);
+ that.takeOver(tmp);
+ }
+ }
+
+ private void takeOver(SipCall that) {
+ connections = that.connections;
+ state = that.state;
+ for (Connection c : connections) {
+ ((SipConnection) c).changeOwner(this);
+ }
+ }
+
+ @Override
+ public Phone getPhone() {
+ return SipPhone.this;
+ }
+
+ @Override
+ public List<Connection> getConnections() {
+ synchronized (SipPhone.class) {
+ // FIXME should return Collections.unmodifiableList();
+ return connections;
+ }
+ }
+
+ Connection dial(String originalNumber) throws SipException {
+ String calleeSipUri = originalNumber;
+ if (!calleeSipUri.contains("@")) {
+ String replaceStr = Pattern.quote(mProfile.getUserName() + "@");
+ calleeSipUri = mProfile.getUriString().replaceFirst(replaceStr,
+ calleeSipUri + "@");
+ }
+ try {
+ SipProfile callee =
+ new SipProfile.Builder(calleeSipUri).build();
+ SipConnection c = new SipConnection(this, callee,
+ originalNumber);
+ c.dial();
+ connections.add(c);
+ setState(Call.State.DIALING);
+ return c;
+ } catch (ParseException e) {
+ throw new SipException("dial", e);
+ }
+ }
+
+ @Override
+ public void hangup() throws CallStateException {
+ synchronized (SipPhone.class) {
+ if (state.isAlive()) {
+ if (DEBUG) Log.d(LOG_TAG, "hang up call: " + getState()
+ + ": " + this + " on phone " + getPhone());
+ setState(State.DISCONNECTING);
+ CallStateException excp = null;
+ for (Connection c : connections) {
+ try {
+ c.hangup();
+ } catch (CallStateException e) {
+ excp = e;
+ }
+ }
+ if (excp != null) throw excp;
+ } else {
+ if (DEBUG) Log.d(LOG_TAG, "hang up dead call: " + getState()
+ + ": " + this + " on phone " + getPhone());
+ }
+ }
+ }
+
+ void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
+ SipProfile callee = sipAudioCall.getPeerProfile();
+ SipConnection c = new SipConnection(this, callee);
+ connections.add(c);
+
+ Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
+ c.initIncomingCall(sipAudioCall, newState);
+
+ setState(newState);
+ notifyNewRingingConnectionP(c);
+ }
+
+ void rejectCall() throws CallStateException {
+ hangup();
+ }
+
+ void acceptCall() throws CallStateException {
+ if (this != ringingCall) {
+ throw new CallStateException("acceptCall() in a non-ringing call");
+ }
+ if (connections.size() != 1) {
+ throw new CallStateException("acceptCall() in a conf call");
+ }
+ ((SipConnection) connections.get(0)).acceptCall();
+ }
+
+ private boolean isSpeakerOn() {
+ return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
+ .isSpeakerphoneOn();
+ }
+
+ void setAudioGroupMode() {
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup == null) return;
+ int mode = audioGroup.getMode();
+ if (state == State.HOLDING) {
+ audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+ } else if (getMute()) {
+ audioGroup.setMode(AudioGroup.MODE_MUTED);
+ } else if (isSpeakerOn()) {
+ audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
+ } else {
+ audioGroup.setMode(AudioGroup.MODE_NORMAL);
+ }
+ if (DEBUG) Log.d(LOG_TAG, String.format(
+ "audioGroup mode change: %d --> %d", mode,
+ audioGroup.getMode()));
+ }
+
+ void hold() throws CallStateException {
+ setState(State.HOLDING);
+ for (Connection c : connections) ((SipConnection) c).hold();
+ setAudioGroupMode();
+ }
+
+ void unhold() throws CallStateException {
+ setState(State.ACTIVE);
+ AudioGroup audioGroup = new AudioGroup();
+ for (Connection c : connections) {
+ ((SipConnection) c).unhold(audioGroup);
+ }
+ setAudioGroupMode();
+ }
+
+ void setMute(boolean muted) {
+ for (Connection c : connections) {
+ ((SipConnection) c).setMute(muted);
+ }
+ }
+
+ boolean getMute() {
+ return connections.isEmpty()
+ ? false
+ : ((SipConnection) connections.get(0)).getMute();
+ }
+
+ void merge(SipCall that) throws CallStateException {
+ AudioGroup audioGroup = getAudioGroup();
+
+ // copy to an array to avoid concurrent modification as connections
+ // in that.connections will be removed in add(SipConnection).
+ Connection[] cc = that.connections.toArray(
+ new Connection[that.connections.size()]);
+ for (Connection c : cc) {
+ SipConnection conn = (SipConnection) c;
+ add(conn);
+ if (conn.getState() == Call.State.HOLDING) {
+ conn.unhold(audioGroup);
+ }
+ }
+ that.setState(Call.State.IDLE);
+ }
+
+ private void add(SipConnection conn) {
+ SipCall call = conn.getCall();
+ if (call == this) return;
+ if (call != null) call.connections.remove(conn);
+
+ connections.add(conn);
+ conn.changeOwner(this);
+ }
+
+ void sendDtmf(char c) {
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup == null) return;
+ audioGroup.sendDtmf(convertDtmf(c));
+ }
+
+ private int convertDtmf(char c) {
+ int code = c - '0';
+ if ((code < 0) || (code > 9)) {
+ switch (c) {
+ case '*': return 10;
+ case '#': return 11;
+ case 'A': return 12;
+ case 'B': return 13;
+ case 'C': return 14;
+ case 'D': return 15;
+ default:
+ throw new IllegalArgumentException(
+ "invalid DTMF char: " + (int) c);
+ }
+ }
+ return code;
+ }
+
+ @Override
+ protected void setState(State newState) {
+ if (state != newState) {
+ if (DEBUG) Log.v(LOG_TAG, "+***+ call state changed: " + state
+ + " --> " + newState + ": " + this + ": on phone "
+ + getPhone() + " " + connections.size());
+
+ if (newState == Call.State.ALERTING) {
+ state = newState; // need in ALERTING to enable ringback
+ SipPhone.this.startRingbackTone();
+ } else if (state == Call.State.ALERTING) {
+ SipPhone.this.stopRingbackTone();
+ }
+ state = newState;
+ updatePhoneState();
+ notifyPreciseCallStateChanged();
+ }
+ }
+
+ void onConnectionStateChanged(SipConnection conn) {
+ // this can be called back when a conf call is formed
+ if (state != State.ACTIVE) {
+ setState(conn.getState());
+ }
+ }
+
+ void onConnectionEnded(SipConnection conn) {
+ // set state to DISCONNECTED only when all conns are disconnected
+ if (state != State.DISCONNECTED) {
+ boolean allConnectionsDisconnected = true;
+ if (DEBUG) Log.d(LOG_TAG, "---check connections: "
+ + connections.size());
+ for (Connection c : connections) {
+ if (DEBUG) Log.d(LOG_TAG, " state=" + c.getState() + ": "
+ + c);
+ if (c.getState() != State.DISCONNECTED) {
+ allConnectionsDisconnected = false;
+ break;
+ }
+ }
+ if (allConnectionsDisconnected) setState(State.DISCONNECTED);
+ }
+ notifyDisconnectP(conn);
+ }
+
+ private AudioGroup getAudioGroup() {
+ if (connections.isEmpty()) return null;
+ return ((SipConnection) connections.get(0)).getAudioGroup();
+ }
+ }
+
+ private class SipConnection extends SipConnectionBase {
+ private SipCall mOwner;
+ private SipAudioCall mSipAudioCall;
+ private Call.State mState = Call.State.IDLE;
+ private SipProfile mPeer;
+ private String mOriginalNumber; // may be a PSTN number
+ private boolean mIncoming = false;
+
+ private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
+ @Override
+ protected void onCallEnded(DisconnectCause cause) {
+ if (getDisconnectCause() != DisconnectCause.LOCAL) {
+ setDisconnectCause(cause);
+ }
+ synchronized (SipPhone.class) {
+ setState(Call.State.DISCONNECTED);
+ SipAudioCall sipAudioCall = mSipAudioCall;
+ mSipAudioCall = null;
+ String sessionState = (sipAudioCall == null)
+ ? ""
+ : (sipAudioCall.getState() + ", ");
+ if (DEBUG) Log.d(LOG_TAG, "--- connection ended: "
+ + mPeer.getUriString() + ": " + sessionState
+ + "cause: " + getDisconnectCause() + ", on phone "
+ + getPhone());
+ if (sipAudioCall != null) {
+ sipAudioCall.setListener(null);
+ sipAudioCall.close();
+ }
+ mOwner.onConnectionEnded(SipConnection.this);
+ }
+ }
+
+ @Override
+ public void onCallEstablished(SipAudioCall call) {
+ onChanged(call);
+ if (mState == Call.State.ACTIVE) call.startAudio();
+ }
+
+ @Override
+ public void onCallHeld(SipAudioCall call) {
+ onChanged(call);
+ if (mState == Call.State.HOLDING) call.startAudio();
+ }
+
+ @Override
+ public void onChanged(SipAudioCall call) {
+ synchronized (SipPhone.class) {
+ Call.State newState = getCallStateFrom(call);
+ if (mState == newState) return;
+ if (newState == Call.State.INCOMING) {
+ setState(mOwner.getState()); // INCOMING or WAITING
+ } else {
+ if (mOwner == ringingCall) {
+ if (ringingCall.getState() == Call.State.WAITING) {
+ try {
+ switchHoldingAndActive();
+ } catch (CallStateException e) {
+ // disconnect the call.
+ onCallEnded(DisconnectCause.LOCAL);
+ return;
+ }
+ }
+ foregroundCall.switchWith(ringingCall);
+ }
+ setState(newState);
+ }
+ mOwner.onConnectionStateChanged(SipConnection.this);
+ if (DEBUG) Log.v(LOG_TAG, "+***+ connection state changed: "
+ + mPeer.getUriString() + ": " + mState
+ + " on phone " + getPhone());
+ }
+ }
+
+ @Override
+ protected void onError(DisconnectCause cause) {
+ if (DEBUG) Log.d(LOG_TAG, "SIP error: " + cause);
+ onCallEnded(cause);
+ }
+ };
+
+ public SipConnection(SipCall owner, SipProfile callee,
+ String originalNumber) {
+ super(originalNumber);
+ mOwner = owner;
+ mPeer = callee;
+ mOriginalNumber = originalNumber;
+ }
+
+ public SipConnection(SipCall owner, SipProfile callee) {
+ this(owner, callee, getUriString(callee));
+ }
+
+ @Override
+ public String getCnapName() {
+ String displayName = mPeer.getDisplayName();
+ return TextUtils.isEmpty(displayName) ? null
+ : displayName;
+ }
+
+ @Override
+ public int getNumberPresentation() {
+ return PhoneConstants.PRESENTATION_ALLOWED;
+ }
+
+ void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
+ setState(newState);
+ mSipAudioCall = sipAudioCall;
+ sipAudioCall.setListener(mAdapter); // call back to set state
+ mIncoming = true;
+ }
+
+ void acceptCall() throws CallStateException {
+ try {
+ mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL);
+ } catch (SipException e) {
+ throw new CallStateException("acceptCall(): " + e);
+ }
+ }
+
+ void changeOwner(SipCall owner) {
+ mOwner = owner;
+ }
+
+ AudioGroup getAudioGroup() {
+ if (mSipAudioCall == null) return null;
+ return mSipAudioCall.getAudioGroup();
+ }
+
+ void dial() throws SipException {
+ setState(Call.State.DIALING);
+ mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
+ TIMEOUT_MAKE_CALL);
+ mSipAudioCall.setListener(mAdapter);
+ }
+
+ void hold() throws CallStateException {
+ setState(Call.State.HOLDING);
+ try {
+ mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL);
+ } catch (SipException e) {
+ throw new CallStateException("hold(): " + e);
+ }
+ }
+
+ void unhold(AudioGroup audioGroup) throws CallStateException {
+ mSipAudioCall.setAudioGroup(audioGroup);
+ setState(Call.State.ACTIVE);
+ try {
+ mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL);
+ } catch (SipException e) {
+ throw new CallStateException("unhold(): " + e);
+ }
+ }
+
+ void setMute(boolean muted) {
+ if ((mSipAudioCall != null) && (muted != mSipAudioCall.isMuted())) {
+ mSipAudioCall.toggleMute();
+ }
+ }
+
+ boolean getMute() {
+ return (mSipAudioCall == null) ? false
+ : mSipAudioCall.isMuted();
+ }
+
+ @Override
+ protected void setState(Call.State state) {
+ if (state == mState) return;
+ super.setState(state);
+ mState = state;
+ }
+
+ @Override
+ public Call.State getState() {
+ return mState;
+ }
+
+ @Override
+ public boolean isIncoming() {
+ return mIncoming;
+ }
+
+ @Override
+ public String getAddress() {
+ // Phone app uses this to query caller ID. Return the original dial
+ // number (which may be a PSTN number) instead of the peer's SIP
+ // URI.
+ return mOriginalNumber;
+ }
+
+ @Override
+ public SipCall getCall() {
+ return mOwner;
+ }
+
+ @Override
+ protected Phone getPhone() {
+ return mOwner.getPhone();
+ }
+
+ @Override
+ public void hangup() throws CallStateException {
+ synchronized (SipPhone.class) {
+ if (DEBUG) Log.d(LOG_TAG, "hangup conn: " + mPeer.getUriString()
+ + ": " + mState + ": on phone "
+ + getPhone().getPhoneName());
+ if (!mState.isAlive()) return;
+ try {
+ SipAudioCall sipAudioCall = mSipAudioCall;
+ if (sipAudioCall != null) {
+ sipAudioCall.setListener(null);
+ sipAudioCall.endCall();
+ }
+ } catch (SipException e) {
+ throw new CallStateException("hangup(): " + e);
+ } finally {
+ mAdapter.onCallEnded(((mState == Call.State.INCOMING)
+ || (mState == Call.State.WAITING))
+ ? DisconnectCause.INCOMING_REJECTED
+ : DisconnectCause.LOCAL);
+ }
+ }
+ }
+
+ @Override
+ public void separate() throws CallStateException {
+ synchronized (SipPhone.class) {
+ SipCall call = (getPhone() == SipPhone.this)
+ ? (SipCall) SipPhone.this.getBackgroundCall()
+ : (SipCall) SipPhone.this.getForegroundCall();
+ if (call.getState() != Call.State.IDLE) {
+ throw new CallStateException(
+ "cannot put conn back to a call in non-idle state: "
+ + call.getState());
+ }
+ if (DEBUG) Log.d(LOG_TAG, "separate conn: "
+ + mPeer.getUriString() + " from " + mOwner + " back to "
+ + call);
+
+ // separate the AudioGroup and connection from the original call
+ Phone originalPhone = getPhone();
+ AudioGroup audioGroup = call.getAudioGroup(); // may be null
+ call.add(this);
+ mSipAudioCall.setAudioGroup(audioGroup);
+
+ // put the original call to bg; and the separated call becomes
+ // fg if it was in bg
+ originalPhone.switchHoldingAndActive();
+
+ // start audio and notify the phone app of the state change
+ call = (SipCall) SipPhone.this.getForegroundCall();
+ mSipAudioCall.startAudio();
+ call.onConnectionStateChanged(this);
+ }
+ }
+
+ }
+
+ private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
+ if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
+ int sessionState = sipAudioCall.getState();
+ switch (sessionState) {
+ case SipSession.State.READY_TO_CALL: return Call.State.IDLE;
+ case SipSession.State.INCOMING_CALL:
+ case SipSession.State.INCOMING_CALL_ANSWERING: return Call.State.INCOMING;
+ case SipSession.State.OUTGOING_CALL: return Call.State.DIALING;
+ case SipSession.State.OUTGOING_CALL_RING_BACK: return Call.State.ALERTING;
+ case SipSession.State.OUTGOING_CALL_CANCELING: return Call.State.DISCONNECTING;
+ case SipSession.State.IN_CALL: return Call.State.ACTIVE;
+ default:
+ Log.w(LOG_TAG, "illegal connection state: " + sessionState);
+ return Call.State.DISCONNECTED;
+ }
+ }
+
+ private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
+ protected abstract void onCallEnded(Connection.DisconnectCause cause);
+ protected abstract void onError(Connection.DisconnectCause cause);
+
+ @Override
+ public void onCallEnded(SipAudioCall call) {
+ onCallEnded(call.isInCall()
+ ? Connection.DisconnectCause.NORMAL
+ : Connection.DisconnectCause.INCOMING_MISSED);
+ }
+
+ @Override
+ public void onCallBusy(SipAudioCall call) {
+ onCallEnded(Connection.DisconnectCause.BUSY);
+ }
+
+ @Override
+ public void onError(SipAudioCall call, int errorCode,
+ String errorMessage) {
+ switch (errorCode) {
+ case SipErrorCode.SERVER_UNREACHABLE:
+ onError(Connection.DisconnectCause.SERVER_UNREACHABLE);
+ break;
+ case SipErrorCode.PEER_NOT_REACHABLE:
+ onError(Connection.DisconnectCause.NUMBER_UNREACHABLE);
+ break;
+ case SipErrorCode.INVALID_REMOTE_URI:
+ onError(Connection.DisconnectCause.INVALID_NUMBER);
+ break;
+ case SipErrorCode.TIME_OUT:
+ case SipErrorCode.TRANSACTION_TERMINTED:
+ onError(Connection.DisconnectCause.TIMED_OUT);
+ break;
+ case SipErrorCode.DATA_CONNECTION_LOST:
+ onError(Connection.DisconnectCause.LOST_SIGNAL);
+ break;
+ case SipErrorCode.INVALID_CREDENTIALS:
+ onError(Connection.DisconnectCause.INVALID_CREDENTIALS);
+ break;
+ case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION:
+ onError(Connection.DisconnectCause.OUT_OF_NETWORK);
+ break;
+ case SipErrorCode.SERVER_ERROR:
+ onError(Connection.DisconnectCause.SERVER_ERROR);
+ break;
+ case SipErrorCode.SOCKET_ERROR:
+ case SipErrorCode.CLIENT_ERROR:
+ default:
+ Log.w(LOG_TAG, "error: " + SipErrorCode.toString(errorCode)
+ + ": " + errorMessage);
+ onError(Connection.DisconnectCause.ERROR_UNSPECIFIED);
+ }
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/sip/SipPhoneBase.java b/src/java/com/android/internal/telephony/sip/SipPhoneBase.java
new file mode 100755
index 0000000..ff64486
--- /dev/null
+++ b/src/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.telephony.CellLocation;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.util.Log;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccPhoneBookInterfaceManager;
+import com.android.internal.telephony.IccSmsInterfaceManager;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.OperatorInfo;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.PhoneSubInfo;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.UUSInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+abstract class SipPhoneBase extends PhoneBase {
+ private static final String LOG_TAG = "SipPhone";
+
+ private RegistrantList mRingbackRegistrants = new RegistrantList();
+ private PhoneConstants.State state = PhoneConstants.State.IDLE;
+
+ public SipPhoneBase(Context context, PhoneNotifier notifier) {
+ super(notifier, context, new SipCommandInterface(context), false);
+ }
+
+ public abstract Call getForegroundCall();
+
+ public abstract Call getBackgroundCall();
+
+ public abstract Call getRingingCall();
+
+ public Connection dial(String dialString, UUSInfo uusInfo)
+ throws CallStateException {
+ // ignore UUSInfo
+ return dial(dialString);
+ }
+
+ void migrateFrom(SipPhoneBase from) {
+ migrate(mRingbackRegistrants, from.mRingbackRegistrants);
+ migrate(mPreciseCallStateRegistrants, from.mPreciseCallStateRegistrants);
+ migrate(mNewRingingConnectionRegistrants, from.mNewRingingConnectionRegistrants);
+ migrate(mIncomingRingRegistrants, from.mIncomingRingRegistrants);
+ migrate(mDisconnectRegistrants, from.mDisconnectRegistrants);
+ migrate(mServiceStateRegistrants, from.mServiceStateRegistrants);
+ migrate(mMmiCompleteRegistrants, from.mMmiCompleteRegistrants);
+ migrate(mMmiRegistrants, from.mMmiRegistrants);
+ migrate(mUnknownConnectionRegistrants, from.mUnknownConnectionRegistrants);
+ migrate(mSuppServiceFailedRegistrants, from.mSuppServiceFailedRegistrants);
+ }
+
+ static void migrate(RegistrantList to, RegistrantList from) {
+ from.removeCleared();
+ for (int i = 0, n = from.size(); i < n; i++) {
+ to.add((Registrant) from.get(i));
+ }
+ }
+
+ @Override
+ public void registerForRingbackTone(Handler h, int what, Object obj) {
+ mRingbackRegistrants.addUnique(h, what, obj);
+ }
+
+ @Override
+ public void unregisterForRingbackTone(Handler h) {
+ mRingbackRegistrants.remove(h);
+ }
+
+ protected void startRingbackTone() {
+ AsyncResult result = new AsyncResult(null, Boolean.TRUE, null);
+ mRingbackRegistrants.notifyRegistrants(result);
+ }
+
+ protected void stopRingbackTone() {
+ AsyncResult result = new AsyncResult(null, Boolean.FALSE, null);
+ mRingbackRegistrants.notifyRegistrants(result);
+ }
+
+ public ServiceState getServiceState() {
+ // FIXME: we may need to provide this when data connectivity is lost
+ // or when server is down
+ ServiceState s = new ServiceState();
+ s.setState(ServiceState.STATE_IN_SERVICE);
+ return s;
+ }
+
+ public CellLocation getCellLocation() {
+ return null;
+ }
+
+ public PhoneConstants.State getState() {
+ return state;
+ }
+
+ public int getPhoneType() {
+ return PhoneConstants.PHONE_TYPE_SIP;
+ }
+
+ public SignalStrength getSignalStrength() {
+ return new SignalStrength();
+ }
+
+ public boolean getMessageWaitingIndicator() {
+ return false;
+ }
+
+ public boolean getCallForwardingIndicator() {
+ return false;
+ }
+
+ public List<? extends MmiCode> getPendingMmiCodes() {
+ return new ArrayList<MmiCode>(0);
+ }
+
+ public PhoneConstants.DataState getDataConnectionState() {
+ return PhoneConstants.DataState.DISCONNECTED;
+ }
+
+ public PhoneConstants.DataState getDataConnectionState(String apnType) {
+ return PhoneConstants.DataState.DISCONNECTED;
+ }
+
+ public DataActivityState getDataActivityState() {
+ return DataActivityState.NONE;
+ }
+
+ /**
+ * Notify any interested party of a Phone state change {@link Phone.State}
+ */
+ void notifyPhoneStateChanged() {
+ mNotifier.notifyPhoneState(this);
+ }
+
+ /**
+ * Notify registrants of a change in the call state. This notifies changes in {@link Call.State}
+ * Use this when changes in the precise call state are needed, else use notifyPhoneStateChanged.
+ */
+ void notifyPreciseCallStateChanged() {
+ /* we'd love it if this was package-scoped*/
+ super.notifyPreciseCallStateChangedP();
+ }
+
+ void notifyNewRingingConnection(Connection c) {
+ super.notifyNewRingingConnectionP(c);
+ }
+
+ void notifyDisconnect(Connection cn) {
+ mDisconnectRegistrants.notifyResult(cn);
+ }
+
+ void notifyUnknownConnection() {
+ mUnknownConnectionRegistrants.notifyResult(this);
+ }
+
+ void notifySuppServiceFailed(SuppService code) {
+ mSuppServiceFailedRegistrants.notifyResult(code);
+ }
+
+ void notifyServiceStateChanged(ServiceState ss) {
+ super.notifyServiceStateChangedP(ss);
+ }
+
+ public void notifyCallForwardingIndicator() {
+ mNotifier.notifyCallForwardingChanged(this);
+ }
+
+ public boolean canDial() {
+ int serviceState = getServiceState().getState();
+ Log.v(LOG_TAG, "canDial(): serviceState = " + serviceState);
+ if (serviceState == ServiceState.STATE_POWER_OFF) return false;
+
+ String disableCall = SystemProperties.get(
+ TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
+ Log.v(LOG_TAG, "canDial(): disableCall = " + disableCall);
+ if (disableCall.equals("true")) return false;
+
+ Log.v(LOG_TAG, "canDial(): ringingCall: " + getRingingCall().getState());
+ Log.v(LOG_TAG, "canDial(): foregndCall: " + getForegroundCall().getState());
+ Log.v(LOG_TAG, "canDial(): backgndCall: " + getBackgroundCall().getState());
+ return !getRingingCall().isRinging()
+ && (!getForegroundCall().getState().isAlive()
+ || !getBackgroundCall().getState().isAlive());
+ }
+
+ public boolean handleInCallMmiCommands(String dialString)
+ throws CallStateException {
+ return false;
+ }
+
+ 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 boolean handlePinMmi(String dialString) {
+ return false;
+ }
+
+ public void sendUssdResponse(String ussdMessge) {
+ }
+
+ public void registerForSuppServiceNotification(
+ Handler h, int what, Object obj) {
+ }
+
+ public void unregisterForSuppServiceNotification(Handler h) {
+ }
+
+ public void setRadioPower(boolean power) {
+ }
+
+ public String getVoiceMailNumber() {
+ return null;
+ }
+
+ public String getVoiceMailAlphaTag() {
+ return null;
+ }
+
+ public String getDeviceId() {
+ return null;
+ }
+
+ public String getDeviceSvn() {
+ return null;
+ }
+
+ public String getImei() {
+ return null;
+ }
+
+ public String getEsn() {
+ Log.e(LOG_TAG, "[SipPhone] getEsn() is a CDMA method");
+ return "0";
+ }
+
+ public String getMeid() {
+ Log.e(LOG_TAG, "[SipPhone] getMeid() is a CDMA method");
+ return "0";
+ }
+
+ public String getSubscriberId() {
+ return null;
+ }
+
+ public String getIccSerialNumber() {
+ return null;
+ }
+
+ public String getLine1Number() {
+ return null;
+ }
+
+ public String getLine1AlphaTag() {
+ return null;
+ }
+
+ public void setLine1Number(String alphaTag, String number, Message onComplete) {
+ // FIXME: what to reply for SIP?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setVoiceMailNumber(String alphaTag, String voiceMailNumber,
+ Message onComplete) {
+ // FIXME: what to reply for SIP?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
+ }
+
+ public void setCallForwardingOption(int commandInterfaceCFAction,
+ int commandInterfaceCFReason, String dialingNumber,
+ int timerSeconds, Message onComplete) {
+ }
+
+ public void getOutgoingCallerIdDisplay(Message onComplete) {
+ // FIXME: what to reply?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
+ Message onComplete) {
+ // FIXME: what's this for SIP?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void getCallWaiting(Message onComplete) {
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setCallWaiting(boolean enable, Message onComplete) {
+ Log.e(LOG_TAG, "call waiting not supported");
+ }
+
+ public boolean getIccRecordsLoaded() {
+ return false;
+ }
+
+ public IccCard getIccCard() {
+ return null;
+ }
+
+ public void getAvailableNetworks(Message response) {
+ }
+
+ public void setNetworkSelectionModeAutomatic(Message response) {
+ }
+
+ public void selectNetworkManually(
+ OperatorInfo network,
+ Message response) {
+ }
+
+ public void getNeighboringCids(Message response) {
+ }
+
+ public void setOnPostDialCharacter(Handler h, int what, Object obj) {
+ }
+
+ public void getDataCallList(Message response) {
+ }
+
+ public List<DataConnection> getCurrentDataConnectionList () {
+ return null;
+ }
+
+ public void updateServiceLocation() {
+ }
+
+ public void enableLocationUpdates() {
+ }
+
+ public void disableLocationUpdates() {
+ }
+
+ public boolean getDataRoamingEnabled() {
+ return false;
+ }
+
+ public void setDataRoamingEnabled(boolean enable) {
+ }
+
+ public boolean enableDataConnectivity() {
+ return false;
+ }
+
+ public boolean disableDataConnectivity() {
+ return false;
+ }
+
+ public boolean isDataConnectivityPossible() {
+ return false;
+ }
+
+ boolean updateCurrentCarrierInProvider() {
+ return false;
+ }
+
+ public void saveClirSetting(int commandInterfaceCLIRMode) {
+ }
+
+ public PhoneSubInfo getPhoneSubInfo(){
+ return null;
+ }
+
+ public IccSmsInterfaceManager getIccSmsInterfaceManager(){
+ return null;
+ }
+
+ public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
+ return null;
+ }
+
+ public IccFileHandler getIccFileHandler(){
+ return null;
+ }
+
+ public void activateCellBroadcastSms(int activate, Message response) {
+ Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP.");
+ }
+
+ public void getCellBroadcastSmsConfig(Message response) {
+ Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP.");
+ }
+
+ public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response){
+ Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP.");
+ }
+
+ //@Override
+ public boolean needsOtaServiceProvisioning() {
+ // FIXME: what's this for SIP?
+ return false;
+ }
+
+ //@Override
+ public LinkProperties getLinkProperties(String apnType) {
+ // FIXME: what's this for SIP?
+ return null;
+ }
+
+ void updatePhoneState() {
+ PhoneConstants.State oldState = state;
+
+ if (getRingingCall().isRinging()) {
+ state = PhoneConstants.State.RINGING;
+ } else if (getForegroundCall().isIdle()
+ && getBackgroundCall().isIdle()) {
+ state = PhoneConstants.State.IDLE;
+ } else {
+ state = PhoneConstants.State.OFFHOOK;
+ }
+
+ if (state != oldState) {
+ Log.d(LOG_TAG, " ^^^ new phone state: " + state);
+ notifyPhoneStateChanged();
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/sip/SipPhoneFactory.java b/src/java/com/android/internal/telephony/sip/SipPhoneFactory.java
new file mode 100644
index 0000000..611e3ea
--- /dev/null
+++ b/src/java/com/android/internal/telephony/sip/SipPhoneFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import com.android.internal.telephony.PhoneNotifier;
+
+import android.content.Context;
+import android.net.sip.SipProfile;
+import android.util.Log;
+
+import java.text.ParseException;
+
+/**
+ * {@hide}
+ */
+public class SipPhoneFactory {
+ /**
+ * Makes a {@link SipPhone} object.
+ * @param sipUri the local SIP URI the phone runs on
+ * @param context {@code Context} needed to create a Phone object
+ * @param phoneNotifier {@code PhoneNotifier} needed to create a Phone
+ * object
+ * @return the {@code SipPhone} object or null if the SIP URI is not valid
+ */
+ public static SipPhone makePhone(String sipUri, Context context,
+ PhoneNotifier phoneNotifier) {
+ try {
+ SipProfile profile = new SipProfile.Builder(sipUri).build();
+ return new SipPhone(context, phoneNotifier, profile);
+ } catch (ParseException e) {
+ Log.w("SipPhoneFactory", "makePhone", e);
+ return null;
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/test/ModelInterpreter.java b/src/java/com/android/internal/telephony/test/ModelInterpreter.java
new file mode 100644
index 0000000..b116c35
--- /dev/null
+++ b/src/java/com/android/internal/telephony/test/ModelInterpreter.java
@@ -0,0 +1,741 @@
+/*
+ * 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.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+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, 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 mHandlerThread;
+
+ 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();
+ mHandlerThread = new HandlerThread("ModelInterpreter");
+ mHandlerThread.start();
+ Looper looper = mHandlerThread.getLooper();
+ simulatedCallState = new SimulatedGsmCallState(looper);
+ }
+
+ //***** 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;
+ }
+ }
+ }
+
+
+ //***** 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()
+ {
+ Looper looper = mHandlerThread.getLooper();
+ if (looper != null) {
+ looper.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/src/java/com/android/internal/telephony/test/SimulatedCommands.java b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
new file mode 100644
index 0000000..4f61509
--- /dev/null
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -0,0 +1,1525 @@
+/*
+ * 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.AsyncResult;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.BaseCommands;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.DataCallState;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.gsm.CallFailCause;
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+import com.android.internal.telephony.gsm.SuppServiceNotification;
+
+import java.util.ArrayList;
+
+public final class SimulatedCommands extends BaseCommands
+ implements CommandsInterface, 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 mHandlerThread;
+ 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
+ mHandlerThread = new HandlerThread("SimulatedCommands");
+ mHandlerThread.start();
+ Looper looper = mHandlerThread.getLooper();
+
+ simulatedCallState = new SimulatedGsmCallState(looper);
+
+ 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 getIccCardStatus(Message result) {
+ unimplemented(result);
+ }
+
+ public void supplyIccPin(String pin, Message result) {
+ if (mSimLockedState != SimLockState.REQUIRE_PIN) {
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPin: 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] supplyIccPin: success!");
+ mPinUnlockAttempts = 0;
+ mSimLockedState = SimLockState.NONE;
+ mIccStatusChangedRegistrants.notifyRegistrants();
+
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ mPinUnlockAttempts ++;
+
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPin: failed! attempt=" +
+ mPinUnlockAttempts);
+ if (mPinUnlockAttempts >= 3) {
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPin: 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 supplyIccPuk(String puk, String newPin, Message result) {
+ if (mSimLockedState != SimLockState.REQUIRE_PUK) {
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPuk: 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] supplyIccPuk: success!");
+ mSimLockedState = SimLockState.NONE;
+ mPukUnlockAttempts = 0;
+ mIccStatusChangedRegistrants.notifyRegistrants();
+
+ if (result != null) {
+ AsyncResult.forMessage(result, null, null);
+ result.sendToTarget();
+ }
+
+ return;
+ }
+
+ if (result != null) {
+ mPukUnlockAttempts ++;
+
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPuk: failed! attempt=" +
+ mPukUnlockAttempts);
+ if (mPukUnlockAttempts >= 10) {
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPuk: 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 supplyIccPin2(String pin2, Message result) {
+ if (mSimFdnEnabledState != SimFdnState.REQUIRE_PIN2) {
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPin2: 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] supplyIccPin2: 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] supplyIccPin2: failed! attempt=" +
+ mPin2UnlockAttempts);
+ if (mPin2UnlockAttempts >= 3) {
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPin2: 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 supplyIccPuk2(String puk2, String newPin2, Message result) {
+ if (mSimFdnEnabledState != SimFdnState.REQUIRE_PUK2) {
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPuk2: 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] supplyIccPuk2: 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] supplyIccPuk2: failed! attempt=" +
+ mPuk2UnlockAttempts);
+ if (mPuk2UnlockAttempts >= 10) {
+ Log.i(LOG_TAG, "[SimCmd] supplyIccPuk2: 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 changeIccPin(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] changeIccPin: pin failed!");
+
+ CommandException ex = new CommandException(
+ CommandException.Error.PASSWORD_INCORRECT);
+ AsyncResult.forMessage(result, null, ex);
+ result.sendToTarget();
+ }
+ }
+
+ public void changeIccPin2(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] changeIccPin2: 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;
+ }
+
+ @Override
+ public void queryFacilityLock(String facility, String pin,
+ int serviceClass, Message result) {
+ queryFacilityLockForApp(facility, pin, serviceClass, null, result);
+ }
+
+ @Override
+ public void queryFacilityLockForApp(String facility, String pin, int serviceClass,
+ String appId, 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);
+ }
+
+ @Override
+ public void setFacilityLock(String facility, boolean lockEnabled, String pin, int serviceClass,
+ Message result) {
+ setFacilityLockForApp(facility, lockEnabled, pin, serviceClass, null, result);
+ }
+
+ @Override
+ public void setFacilityLockForApp(String facility, boolean lockEnabled,
+ String pin, int serviceClass, String appId,
+ 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 original 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.RADIO_ON) && !isSimLocked()) {
+ //Log.i("GSM", "[SimCmds] getCurrentCalls");
+ resultSuccess(result, simulatedCallState.getDriverCalls());
+ } else {
+ //Log.i("GSM", "[SimCmds] getCurrentCalls: RADIO_OFF or SIM not ready!");
+ resultFail(result,
+ new CommandException(
+ CommandException.Error.RADIO_NOT_AVAILABLE));
+ }
+ }
+
+ /**
+ * @deprecated
+ */
+ public void getPDPContextList(Message result) {
+ getDataCallList(result);
+ }
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the original value of result.obj
+ * ar.result contains a List of DataCallState
+ */
+ public void getDataCallList(Message result) {
+ resultSuccess(result, new ArrayList<DataCallState>(0));
+ }
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the original 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 original 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, UUSInfo uusInfo, Message result) {
+ simulatedCallState.onDial(address);
+
+ resultSuccess(result, null);
+ }
+
+ public void getIMSI(Message result) {
+ getIMSIForApp(null, result);
+ }
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the original value of result.obj
+ * ar.result is String containing IMSI on success
+ */
+ public void getIMSIForApp(String aid, Message result) {
+ resultSuccess(result, "012345678901234");
+ }
+
+ /**
+ * returned message
+ * retMsg.obj = AsyncResult ar
+ * ar.exception carries exception on failure
+ * ar.userObject contains the original 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 original 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 original 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 original 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 original 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 original 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 original 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 original 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 original 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 original 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);
+ }
+
+ /**
+ * @deprecated
+ */
+ public void getLastPdpFailCause (Message result) {
+ unimplemented(result);
+ }
+
+ public void getLastDataCallFailCause(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 available 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 sendEnvelopeWithStatus(String contents, Message response) {
+ resultSuccess(response, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleCallSetupRequestFromSim(
+ boolean accept, Message response) {
+ resultSuccess(response, null);
+ }
+
+ /**
+ * response.obj.result is an String[14]
+ * See ril.h for details
+ *
+ * Please note that registration state 4 ("unknown") is treated
+ * as "out of service" above
+ */
+ public void getVoiceRegistrationState (Message result) {
+ String ret[] = new String[14];
+
+ ret[0] = "5"; // registered roam
+ ret[1] = null;
+ ret[2] = null;
+ ret[3] = null;
+ ret[4] = null;
+ ret[5] = null;
+ ret[6] = null;
+ ret[7] = null;
+ ret[8] = null;
+ ret[9] = null;
+ ret[10] = null;
+ ret[11] = null;
+ ret[12] = null;
+ ret[13] = 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 getDataRegistrationState (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 original 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 original 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 original value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void stopDtmf(Message result) {
+ resultSuccess(result, null);
+ }
+
+ /**
+ * ar.exception carries exception on failure
+ * ar.userObject contains the original value of result.obj
+ * ar.result is null on success and failure
+ */
+ public void sendBurstDtmf(String dtmfString, int on, int off, 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 deleteSmsOnRuim(int index, Message response) {
+ Log.d(LOG_TAG, "Delete RUIM 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 writeSmsToRuim(int status, String pdu, Message response) {
+ Log.d(LOG_TAG, "Write SMS to RUIM with status " + status);
+ unimplemented(response);
+ }
+
+ public void setupDataCall(String radioTechnology, String profile,
+ String apn, String user, String password, String authType,
+ String protocol, Message result) {
+ unimplemented(result);
+ }
+
+ public void deactivateDataCall(int cid, int reason, 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);
+ }
+
+ public void getSmscAddress(Message result) {
+ unimplemented(result);
+ }
+
+ public void setSmscAddress(String address, Message result) {
+ unimplemented(result);
+ }
+
+ public void reportSmsMemoryStatus(boolean available, Message result) {
+ unimplemented(result);
+ }
+
+ public void reportStkServiceIsRunning(Message result) {
+ resultSuccess(result, null);
+ }
+
+ @Override
+ public void getCdmaSubscriptionSource(Message result) {
+ unimplemented(result);
+ }
+
+ private boolean isSimLocked() {
+ if (mSimLockedState != SimLockState.NONE) {
+ return true;
+ }
+ return false;
+ }
+
+ public void setRadioPower(boolean on, Message result) {
+ if(on) {
+ setRadioState(RadioState.RADIO_ON);
+ } else {
+ setRadioState(RadioState.RADIO_OFF);
+ }
+ }
+
+
+ public void acknowledgeLastIncomingGsmSms(boolean success, int cause, Message result) {
+ unimplemented(result);
+ }
+
+ public void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message result) {
+ unimplemented(result);
+ }
+
+ public void acknowledgeIncomingGsmSmsWithPdu(boolean success, String ackPdu,
+ Message result) {
+ unimplemented(result);
+ }
+
+ public void iccIO(int command, int fileid, String path, int p1, int p2, int p3, String data,
+ String pin2, Message response) {
+ iccIOForApp(command, fileid, path, p1, p2, p3, data,pin2, null, response);
+ }
+
+ /**
+ * parameters equivalent to 27.007 AT+CRSM command
+ * response.obj will be an AsyncResult
+ * response.obj.userObj will be a SimIoResult on success
+ */
+ public void iccIOForApp (int command, int fileid, String path, int p1, int p2,
+ int p3, String data, String pin2, String aid, 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 exitEmergencyCallbackMode(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);
+ Looper looper = mHandlerThread.getLooper();
+ if (looper != null) {
+ looper.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");
+ }
+ }
+
+ //***** 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();
+ }
+ }
+ }
+
+ // ***** Methods for CDMA support
+ public void
+ getDeviceIdentity(Message response) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ unimplemented(response);
+ }
+
+ public void
+ getCDMASubscription(Message response) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ unimplemented(response);
+ }
+
+ public void
+ setCdmaSubscriptionSource(int cdmaSubscriptionType, Message response) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ unimplemented(response);
+ }
+
+ public void queryCdmaRoamingPreference(Message response) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ unimplemented(response);
+ }
+
+ public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ unimplemented(response);
+ }
+
+ public void
+ setPhoneType(int phoneType) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ }
+
+ public void getPreferredVoicePrivacy(Message result) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ unimplemented(result);
+ }
+
+ public void setPreferredVoicePrivacy(boolean enable, Message result) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ unimplemented(result);
+ }
+
+ /**
+ * Set the TTY mode
+ *
+ * @param ttyMode is one of the following:
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_OFF}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_FULL}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_HCO}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
+ * @param response is callback message
+ */
+ public void setTTYMode(int ttyMode, Message response) {
+ Log.w(LOG_TAG, "Not implemented in SimulatedCommands");
+ unimplemented(response);
+ }
+
+ /**
+ * Query the TTY mode
+ * (AsyncResult)response.obj).result is an int[] with element [0] set to
+ * tty mode:
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_OFF}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_FULL}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_HCO}
+ * - {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
+ * @param response is callback message
+ */
+ public void queryTTYMode(Message response) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ unimplemented(response);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendCDMAFeatureCode(String FeatureCode, Message response) {
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ unimplemented(response);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendCdmaSms(byte[] pdu, Message response){
+ Log.w(LOG_TAG, "CDMA not implemented in SimulatedCommands");
+ }
+
+ public void setCdmaBroadcastActivation(boolean activate, Message response) {
+ unimplemented(response);
+
+ }
+
+ public void getCdmaBroadcastConfig(Message response) {
+ unimplemented(response);
+
+ }
+
+ public void setCdmaBroadcastConfig(int[] configValuesArray, Message response) {
+ unimplemented(response);
+
+ }
+
+ public void forceDataDormancy(Message response) {
+ unimplemented(response);
+ }
+
+
+ public void setGsmBroadcastActivation(boolean activate, Message response) {
+ unimplemented(response);
+ }
+
+
+ public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) {
+ unimplemented(response);
+ }
+
+ public void getGsmBroadcastConfig(Message response) {
+ unimplemented(response);
+ }
+
+ @Override
+ public void supplyIccPinForApp(String pin, String aid, Message response) {
+ unimplemented(response);
+ }
+
+ @Override
+ public void supplyIccPukForApp(String puk, String newPin, String aid, Message response) {
+ unimplemented(response);
+ }
+
+ @Override
+ public void supplyIccPin2ForApp(String pin2, String aid, Message response) {
+ unimplemented(response);
+ }
+
+ @Override
+ public void supplyIccPuk2ForApp(String puk2, String newPin2, String aid, Message response) {
+ unimplemented(response);
+ }
+
+ @Override
+ public void changeIccPinForApp(String oldPin, String newPin, String aidPtr, Message response) {
+ unimplemented(response);
+ }
+
+ @Override
+ public void changeIccPin2ForApp(String oldPin2, String newPin2, String aidPtr,
+ Message response) {
+ unimplemented(response);
+ }
+
+ public void requestIsimAuthentication(String nonce, Message response) {
+ unimplemented(response);
+ }
+
+ public void getVoiceRadioTechnology(Message response) {
+ unimplemented(response);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java b/src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java
new file mode 100644
index 0000000..c6c301d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java
@@ -0,0 +1,809 @@
+/*
+ * 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.Looper;
+import android.os.Message;
+import android.os.Handler;
+import android.telephony.PhoneNumberUtils;
+import com.android.internal.telephony.ATParseEx;
+import com.android.internal.telephony.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 SimulatedGsmCallState(Looper looper) {
+ super(looper);
+ }
+
+ 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/src/java/com/android/internal/telephony/test/SimulatedRadioControl.java b/src/java/com/android/internal/telephony/test/SimulatedRadioControl.java
new file mode 100644
index 0000000..054d370
--- /dev/null
+++ b/src/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/src/java/com/android/internal/telephony/test/package.html b/src/java/com/android/internal/telephony/test/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/test/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
new file mode 100644
index 0000000..5961efd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 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.uicc;
+
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.cdma.CDMALTEPhone;
+import com.android.internal.telephony.cdma.CDMAPhone;
+import com.android.internal.telephony.gsm.GSMPhone;
+
+import android.util.Log;
+
+/* This class is responsible for keeping all knowledge about
+ * ICCs in the system. It is also used as API to get appropriate
+ * applications to pass them to phone and service trackers.
+ */
+public class UiccController {
+ private static final boolean DBG = true;
+ private static final String LOG_TAG = "RIL_UiccController";
+
+ private static UiccController mInstance;
+
+ private PhoneBase mCurrentPhone;
+ private boolean mIsCurrentCard3gpp;
+ private IccCard mIccCard;
+
+ public static synchronized UiccController getInstance(PhoneBase phone) {
+ if (mInstance == null) {
+ mInstance = new UiccController(phone);
+ } else {
+ mInstance.setNewPhone(phone);
+ }
+ return mInstance;
+ }
+
+ public IccCard getIccCard() {
+ return mIccCard;
+ }
+
+ private UiccController(PhoneBase phone) {
+ if (DBG) log("Creating UiccController");
+ setNewPhone(phone);
+ }
+
+ private void setNewPhone(PhoneBase phone) {
+ mCurrentPhone = phone;
+ if (phone instanceof GSMPhone) {
+ if (DBG) log("New phone is GSMPhone");
+ updateCurrentCard(IccCard.CARD_IS_3GPP);
+ } else if (phone instanceof CDMALTEPhone){
+ if (DBG) log("New phone type is CDMALTEPhone");
+ updateCurrentCard(IccCard.CARD_IS_3GPP);
+ } else if (phone instanceof CDMAPhone){
+ if (DBG) log("New phone type is CDMAPhone");
+ updateCurrentCard(IccCard.CARD_IS_NOT_3GPP);
+ } else {
+ Log.e(LOG_TAG, "Unhandled phone type. Critical error!");
+ }
+ }
+
+ private void updateCurrentCard(boolean isNewCard3gpp) {
+ if (mIsCurrentCard3gpp == isNewCard3gpp && mIccCard != null) {
+ return;
+ }
+
+ if (mIccCard != null) {
+ mIccCard.dispose();
+ mIccCard = null;
+ }
+
+ mIsCurrentCard3gpp = isNewCard3gpp;
+ mIccCard = new IccCard(mCurrentPhone, mCurrentPhone.getPhoneName(),
+ isNewCard3gpp, DBG);
+ }
+
+ private void log(String string) {
+ Log.d(LOG_TAG, string);
+ }
+} \ No newline at end of file
diff --git a/tests/telephonymockriltests/Android.mk b/tests/telephonymockriltests/Android.mk
new file mode 100644
index 0000000..9731d0d
--- /dev/null
+++ b/tests/telephonymockriltests/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_STATIC_JAVA_LIBRARIES := mockrilcontroller
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_PACKAGE_NAME := TelephonyMockRilTests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/telephonymockriltests/AndroidManifest.xml b/tests/telephonymockriltests/AndroidManifest.xml
new file mode 100644
index 0000000..63f44a2
--- /dev/null
+++ b/tests/telephonymockriltests/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2009 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.telephonymockriltests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:label="TelephonyMockRilTest"
+ android:name="TelephonyMockRilTest">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name=".TelephonyMockTestRunner"
+ android:targetPackage="com.android.telephonymockriltests"
+ android:label="Test runner for Telephony Tests Using Mock RIL"
+ />
+
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
diff --git a/tests/telephonymockriltests/src/com/android/telephonymockriltests/TelephonyMockTestRunner.java b/tests/telephonymockriltests/src/com/android/telephonymockriltests/TelephonyMockTestRunner.java
new file mode 100644
index 0000000..78ee738
--- /dev/null
+++ b/tests/telephonymockriltests/src/com/android/telephonymockriltests/TelephonyMockTestRunner.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010, 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.telephonymockriltests;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import com.android.internal.telephony.mockril.MockRilController;
+import android.util.Log;
+
+import com.android.telephonymockriltests.functional.SimpleTestUsingMockRil;
+
+import java.io.IOException;
+import junit.framework.TestSuite;
+import junit.framework.TestCase;
+
+/**
+ * Test runner for telephony tests that using Mock RIL
+ *
+ */
+public class TelephonyMockTestRunner extends InstrumentationTestRunner {
+ private static final String TAG="TelephonyMockTestRunner";
+ public MockRilController mController;
+
+ @Override
+ public TestSuite getAllTests() {
+ TestSuite suite = new InstrumentationTestSuite(this);
+ suite.addTestSuite(SimpleTestUsingMockRil.class);
+ return suite;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ try {
+ mController = new MockRilController();
+ } catch (IOException e) {
+ e.printStackTrace();
+ TestCase.assertTrue("Create Mock RIl Controller failed", false);
+ }
+ TestCase.assertNotNull(mController);
+ super.onCreate(icicle);
+ }
+
+ @Override
+ public void finish(int resultCode, Bundle results) {
+ if (mController != null)
+ mController.closeChannel();
+ super.finish(resultCode, results);
+ }
+}
diff --git a/tests/telephonymockriltests/src/com/android/telephonymockriltests/functional/SimpleTestUsingMockRil.java b/tests/telephonymockriltests/src/com/android/telephonymockriltests/functional/SimpleTestUsingMockRil.java
new file mode 100644
index 0000000..3ea1cf2
--- /dev/null
+++ b/tests/telephonymockriltests/src/com/android/telephonymockriltests/functional/SimpleTestUsingMockRil.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 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.telephonymockriltests.functional;
+
+import com.android.internal.telephony.mockril.MockRilController;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.telephonymockriltests.TelephonyMockTestRunner;
+
+/**
+ * A simple test that using Mock RIL Controller
+ */
+public class SimpleTestUsingMockRil extends InstrumentationTestCase {
+ private static final String TAG = "SimpleTestUsingMockRil";
+ private MockRilController mMockRilCtrl = null;
+ private TelephonyMockTestRunner mRunner;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mRunner = (TelephonyMockTestRunner)getInstrumentation();
+ mMockRilCtrl = mRunner.mController;
+ assertNotNull(mMockRilCtrl);
+ }
+
+ /**
+ * Get the current radio state of RIL
+ */
+ public void testGetRadioState() {
+ int state = mMockRilCtrl.getRadioState();
+ Log.v(TAG, "testGetRadioState: " + state);
+ assertTrue(state >= 0 && state <= 9);
+ }
+
+ /**
+ * Set the current radio state of RIL
+ * and verify the radio state is set correctly
+ */
+ public void testSetRadioState() {
+ for (int state = 0; state <= 9; state++) {
+ Log.v(TAG, "set radio state to be " + state);
+ assertTrue("set radio state: " + state + " failed.",
+ mMockRilCtrl.setRadioState(state));
+ }
+ assertFalse("use an invalid radio state", mMockRilCtrl.setRadioState(-1));
+ assertFalse("the radio state doesn't exist", mMockRilCtrl.setRadioState(10));
+ }
+}
diff --git a/tests/telephonytests/Android.mk b/tests/telephonytests/Android.mk
new file mode 100644
index 0000000..197cedf
--- /dev/null
+++ b/tests/telephonytests/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
+
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
+
+LOCAL_PACKAGE_NAME := FrameworksTelephonyTests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/telephonytests/AndroidManifest.xml b/tests/telephonytests/AndroidManifest.xml
new file mode 100644
index 0000000..ba1d957
--- /dev/null
+++ b/tests/telephonytests/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2009 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.telephonytests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:label="TelephonyTest"
+ android:name="TelephonyTest">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.frameworks.telephonytests"
+ android:label="Frameworks Telephony Tests">
+ </instrumentation>
+
+ <instrumentation android:name=".TelephonyMockRilTestRunner"
+ android:targetPackage="com.android.frameworks.telephonytests"
+ android:label="Test Runner for Mock Ril Tests"
+ />
+
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
diff --git a/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java b/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java
new file mode 100644
index 0000000..9192f57
--- /dev/null
+++ b/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010, 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.frameworks.telephonytests;
+
+import android.os.Bundle;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import android.util.Log;
+
+import java.io.IOException;
+
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.mockril.MockRilTest;
+
+import junit.framework.TestSuite;
+
+public class TelephonyMockRilTestRunner extends InstrumentationTestRunner {
+
+ public RilChannel mMockRilChannel;
+
+ @Override
+ public TestSuite getAllTests() {
+ log("getAllTests E");
+ TestSuite suite = new InstrumentationTestSuite(this);
+ suite.addTestSuite(MockRilTest.class);
+ log("getAllTests X");
+ return suite;
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ log("getLoader EX");
+ return TelephonyMockRilTestRunner.class.getClassLoader();
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ log("onCreate E");
+ try {
+ mMockRilChannel = RilChannel.makeRilChannel();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ log("onCreate X");
+
+ super.onCreate(icicle);
+ }
+
+ @Override
+ public void onDestroy() {
+ // I've not seen this called
+ log("onDestroy EX");
+ super.onDestroy();
+ }
+
+ @Override
+ public void onStart() {
+ // Called when the instrumentation thread is started.
+ // At the moment we don't need the thread so return
+ // which will shut down this unused thread.
+ log("onStart EX");
+ super.onStart();
+ }
+
+ @Override
+ public void finish(int resultCode, Bundle results) {
+ // Called when complete so I ask the mMockRilChannel to quit.
+ log("finish E");
+ mMockRilChannel.close();
+ log("finish X");
+ super.finish(resultCode, results);
+ }
+
+ private void log(String s) {
+ Log.e("TelephonyMockRilTestRunner", s);
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ATResponseParserTest.java b/tests/telephonytests/src/com/android/internal/telephony/ATResponseParserTest.java
new file mode 100644
index 0000000..81727e4
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ATResponseParserTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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 junit.framework.TestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class ATResponseParserTest extends TestCase {
+ @SmallTest
+ public void testBasic() throws Exception {
+ ATResponseParser p = new ATResponseParser("+CREG: 0");
+
+ assertEquals(0, p.nextInt());
+
+ assertFalse(p.hasMore());
+
+ try {
+ p.nextInt();
+ fail("exception expected");
+ } catch (ATParseEx ex) {
+ //test pass
+ }
+
+ p = new ATResponseParser("+CREG: 0,1");
+ assertEquals(0, p.nextInt());
+ assertEquals(1, p.nextInt());
+ assertFalse(p.hasMore());
+
+ p = new ATResponseParser("+CREG: 0, 1");
+ assertEquals(0, p.nextInt());
+ assertEquals(1, p.nextInt());
+ assertFalse(p.hasMore());
+
+ p = new ATResponseParser("+CREG: 0, 1,");
+ assertEquals(0, p.nextInt());
+ assertEquals(1, p.nextInt());
+ // this seems odd but is probably OK
+ assertFalse(p.hasMore());
+ try {
+ p.nextInt();
+ fail("exception expected");
+ } catch (ATParseEx ex) {
+ //test pass
+ }
+
+ p = new ATResponseParser("+CREG: 0, 1 ");
+ assertEquals(0, p.nextInt());
+ assertEquals(1, p.nextInt());
+ assertFalse(p.hasMore());
+
+ p = new ATResponseParser("0, 1 ");
+ // no prefix -> exception
+ try {
+ p.nextInt();
+ fail("exception expected");
+ } catch (ATParseEx ex) {
+ //test pass
+ }
+
+ p = new ATResponseParser("+CREG: 0, 1, 5");
+ assertFalse(p.nextBoolean());
+ assertTrue(p.nextBoolean());
+ try {
+ // is this over-constraining?
+ p.nextBoolean();
+ fail("exception expected");
+ } catch (ATParseEx ex) {
+ //test pass
+ }
+
+ p = new ATResponseParser("+CLCC: 1,0,2,0,0,\"+18005551212\",145");
+
+ assertEquals(1, p.nextInt());
+ assertFalse(p.nextBoolean());
+ assertEquals(2, p.nextInt());
+ assertEquals(0, p.nextInt());
+ assertEquals(0, p.nextInt());
+ assertEquals("+18005551212", p.nextString());
+ assertEquals(145, p.nextInt());
+ assertFalse(p.hasMore());
+
+ p = new ATResponseParser("+CLCC: 1,0,2,0,0,\"+18005551212,145");
+
+ assertEquals(1, p.nextInt());
+ assertFalse(p.nextBoolean());
+ assertEquals(2, p.nextInt());
+ assertEquals(0, p.nextInt());
+ assertEquals(0, p.nextInt());
+ try {
+ p.nextString();
+ fail("expected ex");
+ } catch (ATParseEx ex) {
+ //test pass
+ }
+
+ p = new ATResponseParser("+FOO: \"\"");
+ assertEquals("", p.nextString());
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java b/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java
new file mode 100644
index 0000000..8a4a285
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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 junit.framework.TestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * {@hide}
+ */
+public class AdnRecordTest extends TestCase {
+
+ @SmallTest
+ public void testBasic() throws Exception {
+ AdnRecord adn;
+
+ //
+ // Typical record
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes("566F696365204D61696C07918150367742F3FFFFFFFFFFFF"));
+
+ assertEquals("Voice Mail", adn.getAlphaTag());
+ assertEquals("+18056377243", adn.getNumber());
+ assertFalse(adn.isEmpty());
+
+ //
+ // Empty records, empty strings
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"));
+
+ assertEquals("", adn.getAlphaTag());
+ assertEquals("", adn.getNumber());
+ assertTrue(adn.isEmpty());
+
+ //
+ // Record too short
+ //
+ adn = new AdnRecord(IccUtils.hexStringToBytes( "FF"));
+
+ assertEquals("", adn.getAlphaTag());
+ assertEquals("", adn.getNumber());
+ assertTrue(adn.isEmpty());
+
+ //
+ // TOA = 0xff ("control string")
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes("566F696365204D61696C07FF8150367742F3FFFFFFFFFFFF"));
+
+ assertEquals("Voice Mail", adn.getAlphaTag());
+ assertEquals("18056377243", adn.getNumber());
+ assertFalse(adn.isEmpty());
+
+ //
+ // TOA = 0x81 (unknown)
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes("566F696365204D61696C07818150367742F3FFFFFFFFFFFF"));
+
+ assertEquals("Voice Mail", adn.getAlphaTag());
+ assertEquals("18056377243", adn.getNumber());
+ assertFalse(adn.isEmpty());
+
+ //
+ // Number Length is too long
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes("566F696365204D61696C0F918150367742F3FFFFFFFFFFFF"));
+
+ assertEquals("Voice Mail", adn.getAlphaTag());
+ assertEquals("", adn.getNumber());
+ assertFalse(adn.isEmpty());
+
+ //
+ // Number Length is zero (invalid)
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes("566F696365204D61696C00918150367742F3FFFFFFFFFFFF"));
+
+ assertEquals("Voice Mail", adn.getAlphaTag());
+ assertEquals("", adn.getNumber());
+ assertFalse(adn.isEmpty());
+
+ //
+ // Number Length is 2, first number byte is FF, TOA is international
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes("566F696365204D61696C0291FF50367742F3FFFFFFFFFFFF"));
+
+ assertEquals("Voice Mail", adn.getAlphaTag());
+ assertEquals("", adn.getNumber());
+ assertFalse(adn.isEmpty());
+
+ //
+ // Number Length is 2, first number digit is valid, TOA is international
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes("566F696365204D61696C0291F150367742F3FFFFFFFFFFFF"));
+
+ assertEquals("Voice Mail", adn.getAlphaTag());
+ assertEquals("+1", adn.getNumber());
+ assertFalse(adn.isEmpty());
+
+ //
+ // An extended record
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes(
+ "4164676A6DFFFFFFFFFFFFFFFFFFFFFF0B918188551512C221436587FF01"));
+
+ assertEquals("Adgjm", adn.getAlphaTag());
+ assertEquals("+18885551212,12345678", adn.getNumber());
+ assertFalse(adn.isEmpty());
+ assertTrue(adn.hasExtendedRecord());
+
+ adn.appendExtRecord(IccUtils.hexStringToBytes("0206092143658709ffffffffff"));
+
+ assertEquals("Adgjm", adn.getAlphaTag());
+ assertEquals("+18885551212,12345678901234567890", adn.getNumber());
+ assertFalse(adn.isEmpty());
+
+ //
+ // An extended record with an invalid extension
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes(
+ "4164676A6DFFFFFFFFFFFFFFFFFFFFFF0B918188551512C221436587FF01"));
+
+ assertEquals("Adgjm", adn.getAlphaTag());
+ assertEquals("+18885551212,12345678", adn.getNumber());
+ assertFalse(adn.isEmpty());
+ assertTrue(adn.hasExtendedRecord());
+
+ adn.appendExtRecord(IccUtils.hexStringToBytes("0106092143658709ffffffffff"));
+
+ assertEquals("Adgjm", adn.getAlphaTag());
+ assertEquals("+18885551212,12345678", adn.getNumber());
+ assertFalse(adn.isEmpty());
+
+ //
+ // An extended record with an invalid extension
+ //
+ adn = new AdnRecord(
+ IccUtils.hexStringToBytes(
+ "4164676A6DFFFFFFFFFFFFFFFFFFFFFF0B918188551512C221436587FF01"));
+
+ assertEquals("Adgjm", adn.getAlphaTag());
+ assertEquals("+18885551212,12345678", adn.getNumber());
+ assertFalse(adn.isEmpty());
+ assertTrue(adn.hasExtendedRecord());
+
+ adn.appendExtRecord(IccUtils.hexStringToBytes("020B092143658709ffffffffff"));
+
+ assertEquals("Adgjm", adn.getAlphaTag());
+ assertEquals("+18885551212,12345678", adn.getNumber());
+ assertFalse(adn.isEmpty());
+ }
+}
+
+
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ApnSettingTest.java b/tests/telephonytests/src/com/android/internal/telephony/ApnSettingTest.java
new file mode 100755
index 0000000..ac8c4c1
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ApnSettingTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 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 junit.framework.TestCase;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class ApnSettingTest extends TestCase {
+
+ public static final String[] TYPES = {"default", "*"};
+
+ public static void assertApnSettingEqual(ApnSetting a1, ApnSetting a2) {
+ assertEquals(a1.carrier, a2.carrier);
+ assertEquals(a1.apn, a2.apn);
+ assertEquals(a1.proxy, a2.proxy);
+ assertEquals(a1.port, a2.port);
+ assertEquals(a1.mmsc, a2.mmsc);
+ assertEquals(a1.mmsProxy, a2.mmsProxy);
+ assertEquals(a1.mmsPort, a2.mmsPort);
+ assertEquals(a1.user, a2.user);
+ assertEquals(a1.password, a2.password);
+ assertEquals(a1.authType, a2.authType);
+ assertEquals(a1.id, a2.id);
+ assertEquals(a1.numeric, a2.numeric);
+ assertEquals(a1.protocol, a2.protocol);
+ assertEquals(a1.roamingProtocol, a2.roamingProtocol);
+ assertEquals(a1.types.length, a2.types.length);
+ int i;
+ for (i = 0; i < a1.types.length; i++) {
+ assertEquals(a1.types[i], a2.types[i]);
+ }
+ assertEquals(a1.carrierEnabled, a2.carrierEnabled);
+ assertEquals(a1.bearer, a2.bearer);
+ }
+
+ @SmallTest
+ public void testFromString() throws Exception {
+ String[] dunTypes = {"DUN"};
+ String[] mmsTypes = {"mms", "*"};
+
+ ApnSetting expected_apn;
+ String testString;
+
+ // A real-world v1 example string.
+ testString = "Vodafone IT,web.omnitel.it,,,,,,,,,222,10,,DUN";
+ expected_apn = new ApnSetting(
+ -1, "22210", "Vodafone IT", "web.omnitel.it", "", "",
+ "", "", "", "", "", 0, dunTypes, "IP", "IP",true,0);
+ assertApnSettingEqual(expected_apn, ApnSetting.fromString(testString));
+
+ // A v2 string.
+ testString = "[ApnSettingV2] Name,apn,,,,,,,,,123,45,,mms|*,IPV6,IP,true,14";
+ expected_apn = new ApnSetting(
+ -1, "12345", "Name", "apn", "", "",
+ "", "", "", "", "", 0, mmsTypes, "IPV6", "IP",true,14);
+ assertApnSettingEqual(expected_apn, ApnSetting.fromString(testString));
+
+ // A v2 string with spaces.
+ testString = "[ApnSettingV2] Name,apn, ,,,,,,,,123,45,,mms|*,IPV4V6, IP,true,14";
+ expected_apn = new ApnSetting(
+ -1, "12345", "Name", "apn", "", "",
+ "", "", "", "", "", 0, mmsTypes, "IPV4V6", "IP",true,14);
+ assertApnSettingEqual(expected_apn, ApnSetting.fromString(testString));
+
+ // Return null if insufficient fields given.
+ testString = "[ApnSettingV2] Name,apn,,,,,,,,,123, 45,,mms|*";
+ assertEquals(null, ApnSetting.fromString(testString));
+
+ testString = "Name,apn,,,,,,,,,123, 45,";
+ assertEquals(null, ApnSetting.fromString(testString));
+
+ // Parse (incorrect) V2 format without the tag as V1.
+ testString = "Name,apn,,,,,,,,,123, 45,,mms|*,IPV6,true,14";
+ String[] incorrectTypes = {"mms|*", "IPV6"};
+ expected_apn = new ApnSetting(
+ -1, "12345", "Name", "apn", "", "",
+ "", "", "", "", "", 0, incorrectTypes, "IP", "IP",true,14);
+ assertApnSettingEqual(expected_apn, ApnSetting.fromString(testString));
+ }
+
+
+ @SmallTest
+ public void testToString() throws Exception {
+ String[] types = {"default", "*"};
+ ApnSetting apn = new ApnSetting(
+ 99, "12345", "Name", "apn", "proxy", "port",
+ "mmsc", "mmsproxy", "mmsport", "user", "password", 0,
+ types, "IPV4V6", "IP", true, 14);
+ String expected = "[ApnSettingV2] Name, 99, 12345, apn, proxy, " +
+ "mmsc, mmsproxy, mmsport, port, 0, default | *, " +
+ "IPV4V6, IP, true, 14";
+ assertEquals(expected, apn.toString());
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallerInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallerInfoTest.java
new file mode 100644
index 0000000..1e5dafb
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallerInfoTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2009 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+import android.util.Log;
+import android.os.Looper;
+import android.test.ActivityInstrumentationTestCase;
+import android.util.StringBuilderPrinter;
+
+/*
+ * Check the CallerInfo utility class works as expected.
+ *
+ */
+
+public class CallerInfoTest extends AndroidTestCase {
+ private CallerInfo mInfo;
+ private Context mContext;
+
+ private static final String kEmergencyNumber = "Emergency Number";
+ private static final int kToken = 0xdeadbeef;
+ private static final String TAG = "CallerInfoUnitTest";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = new MockContext();
+ mInfo = new CallerInfo();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Checks the caller info instance is flagged as an emergency if
+ * the number is an emergency one. There is no test for the
+ * contact based constructors because emergency number are not in
+ * the contact DB.
+ */
+ @SmallTest
+ public void testEmergencyIsProperlySet() throws Exception {
+ assertFalse(mInfo.isEmergencyNumber());
+
+ mInfo = CallerInfo.getCallerInfo(mContext, "911");
+ assertIsValidEmergencyCallerInfo();
+
+ mInfo = CallerInfo.getCallerInfo(mContext, "tel:911");
+ assertIsValidEmergencyCallerInfo();
+
+
+ // This one hits the content resolver.
+ mInfo = CallerInfo.getCallerInfo(mContext, "18001234567");
+ assertFalse(mInfo.isEmergencyNumber());
+ }
+
+ /**
+ * Same as testEmergencyIsProperlySet but uses the async query api.
+ */
+ @SmallTest
+ public void testEmergencyIsProperlySetUsingAsyncQuery() throws Exception {
+ QueryRunner query;
+
+ query = new QueryRunner("911");
+ query.runAndCheckCompletion();
+ assertIsValidEmergencyCallerInfo();
+
+ query = new QueryRunner("tel:911");
+ query.runAndCheckCompletion();
+ assertIsValidEmergencyCallerInfo();
+
+ query = new QueryRunner("18001234567");
+ query.runAndCheckCompletion();
+ assertFalse(mInfo.isEmergencyNumber());
+ }
+
+ /**
+ * For emergency caller info, phoneNumber should be set to the
+ * string emergency_call_dialog_number_for_display and the
+ * photoResource should be set to the picture_emergency drawable.
+ */
+ @SmallTest
+ public void testEmergencyNumberAndPhotoAreSet() throws Exception {
+ mInfo = CallerInfo.getCallerInfo(mContext, "911");
+
+ assertIsValidEmergencyCallerInfo();
+ }
+
+ // TODO: Add more tests:
+ /**
+ * Check if the voice mail number cannot be retrieved that the
+ * original phone number is preserved.
+ */
+ /**
+ * Check the markAs* methods work.
+ */
+
+
+ //
+ // Helpers
+ //
+
+ // Partial implementation of MockResources.
+ public class MockResources extends android.test.mock.MockResources
+ {
+ @Override
+ public String getString(int resId) throws Resources.NotFoundException {
+ switch (resId) {
+ case com.android.internal.R.string.emergency_call_dialog_number_for_display:
+ return kEmergencyNumber;
+ default:
+ throw new UnsupportedOperationException("Missing handling for resid " + resId);
+ }
+ }
+ }
+
+ // Partial implementation of MockContext.
+ public class MockContext extends android.test.mock.MockContext {
+ private ContentResolver mResolver;
+ private Resources mResources;
+
+ public MockContext() {
+ mResolver = new android.test.mock.MockContentResolver();
+ mResources = new MockResources();
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+
+ @Override
+ public Resources getResources() {
+ return mResources;
+ }
+ }
+
+ /**
+ * Class to run a CallerInfoAsyncQuery in a separate thread, with
+ * its own Looper. We cannot use the main Looper because on the
+ * 1st quit the thread is maked dead, ie no further test can use
+ * it. Also there is not way to inject a Looper instance in the
+ * query, so we have to use a thread with its own looper.
+ */
+ private class QueryRunner extends Thread
+ implements CallerInfoAsyncQuery.OnQueryCompleteListener {
+ private Looper mLooper;
+ private String mNumber;
+ private boolean mAsyncCompleted;
+
+ public QueryRunner(String number) {
+ super();
+ mNumber = number;
+ }
+
+ // Run the query in the thread, wait for completion.
+ public void runAndCheckCompletion() throws InterruptedException {
+ start();
+ join();
+ assertTrue(mAsyncCompleted);
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ mLooper = Looper.myLooper();
+ mAsyncCompleted = false;
+ // The query will pick the thread local looper we've just prepared.
+ CallerInfoAsyncQuery.startQuery(kToken, mContext, mNumber, this, null);
+ mLooper.loop();
+ }
+
+ // Quit the Looper on the 1st callback
+ // (EVENT_EMERGENCY_NUMBER). There is another message
+ // (EVENT_END_OF_QUEUE) that will never be delivered because
+ // the test has exited. The corresponding stack trace
+ // "Handler{xxxxx} sending message to a Handler on a dead
+ // thread" can be ignored.
+ public void onQueryComplete(int token, Object cookie, CallerInfo info) {
+ mAsyncCompleted = true;
+ mInfo = info;
+ mLooper.quit();
+ }
+ }
+
+ /**
+ * Fail if mInfo does not contain a valid emergency CallerInfo instance.
+ */
+ private void assertIsValidEmergencyCallerInfo() throws Exception {
+ assertTrue(mInfo.isEmergencyNumber());
+
+ // For emergency caller info, phoneNumber should be set to the
+ // string emergency_call_dialog_number_for_display and the
+ // photoResource should be set to the picture_emergency drawable.
+ assertEquals(kEmergencyNumber, mInfo.phoneNumber);
+ assertEquals(com.android.internal.R.drawable.picture_emergency, mInfo.photoResource);
+
+ // The name should be null
+ assertNull(mInfo.name);
+ assertEquals(0, mInfo.namePresentation);
+ assertNull(mInfo.cnapName);
+ assertEquals(0, mInfo.numberPresentation);
+
+ assertFalse(mInfo.contactExists);
+ assertEquals(0, mInfo.person_id);
+ assertFalse(mInfo.needUpdate);
+ assertNull(mInfo.contactRefUri);
+
+ assertNull(mInfo.phoneLabel);
+ assertEquals(0, mInfo.numberType);
+ assertNull(mInfo.numberLabel);
+
+ assertNull(mInfo.contactRingtoneUri);
+ assertFalse(mInfo.shouldSendToVoicemail);
+
+ assertNull(mInfo.cachedPhoto);
+ assertFalse(mInfo.isCachedPhotoCurrent);
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
new file mode 100644
index 0000000..f9dc3a9
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
@@ -0,0 +1,361 @@
+/*
+ * 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 com.android.internal.telephony.GsmAlphabet;
+
+import junit.framework.TestCase;
+
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class GsmAlphabetTest extends TestCase {
+
+ private static final String sGsmExtendedChars = "{|}\\[~]\f\u20ac";
+
+ @SmallTest
+ public void test7bitWithHeader() throws Exception {
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = 1;
+ concatRef.seqNumber = 2;
+ concatRef.msgCount = 2;
+ concatRef.isEightBits = true;
+ SmsHeader header = new SmsHeader();
+ header.concatRef = concatRef;
+
+ String message = "aaaaaaaaaabbbbbbbbbbcccccccccc";
+ byte[] userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message,
+ SmsHeader.toByteArray(header), 0, 0);
+ int septetCount = GsmAlphabet.countGsmSeptetsUsingTables(message, true, 0, 0);
+ String parsedMessage = GsmAlphabet.gsm7BitPackedToString(
+ userData, SmsHeader.toByteArray(header).length+2, septetCount, 1, 0, 0);
+ assertEquals(message, parsedMessage);
+ }
+
+ // TODO: This method should *really* be a series of individual test methods.
+ // However, it's a SmallTest because it executes quickly.
+ @SmallTest
+ public void testBasic() throws Exception {
+ // '@' maps to char 0
+ assertEquals(0, GsmAlphabet.charToGsm('@'));
+
+ // `a (a with grave accent) maps to last GSM character
+ assertEquals(0x7f, GsmAlphabet.charToGsm('\u00e0'));
+
+ //
+ // These are the extended chars
+ // They should all return GsmAlphabet.GSM_EXTENDED_ESCAPE
+ //
+
+ for (int i = 0, s = sGsmExtendedChars.length(); i < s; i++) {
+ assertEquals(GsmAlphabet.GSM_EXTENDED_ESCAPE,
+ GsmAlphabet.charToGsm(sGsmExtendedChars.charAt(i)));
+
+ }
+
+ // euro symbol
+ assertEquals(GsmAlphabet.GSM_EXTENDED_ESCAPE,
+ GsmAlphabet.charToGsm('\u20ac'));
+
+ // An unmappable char (the 'cent' char) maps to a space
+ assertEquals(GsmAlphabet.charToGsm(' '),
+ GsmAlphabet.charToGsm('\u00a2'));
+
+ // unmappable = space = 1 septet
+ assertEquals(1, GsmAlphabet.countGsmSeptets('\u00a2'));
+
+ //
+ // Test extended table
+ //
+
+ for (int i = 0, s = sGsmExtendedChars.length(); i < s; i++) {
+ assertEquals(sGsmExtendedChars.charAt(i),
+ GsmAlphabet.gsmExtendedToChar(
+ GsmAlphabet.charToGsmExtended(sGsmExtendedChars.charAt(i))));
+
+ }
+
+ // Unmappable extended char
+ assertEquals(GsmAlphabet.charToGsm(' '),
+ GsmAlphabet.charToGsmExtended('@'));
+
+ //
+ // gsmToChar()
+ //
+
+ assertEquals('@', GsmAlphabet.gsmToChar(0));
+
+ // `a (a with grave accent) maps to last GSM character
+ assertEquals('\u00e0', GsmAlphabet.gsmToChar(0x7f));
+
+ assertEquals('\uffff',
+ GsmAlphabet.gsmToChar(GsmAlphabet.GSM_EXTENDED_ESCAPE));
+
+ // Out-of-range/unmappable value
+ assertEquals(' ', GsmAlphabet.gsmToChar(0x80));
+
+ //
+ // gsmExtendedToChar()
+ //
+
+ assertEquals('{', GsmAlphabet.gsmExtendedToChar(0x28));
+
+ // No double-escapes
+ assertEquals(' ', GsmAlphabet.gsmExtendedToChar(
+ GsmAlphabet.GSM_EXTENDED_ESCAPE));
+
+ // Reserved for extension to extension table (mapped to space)
+ assertEquals(' ', GsmAlphabet.gsmExtendedToChar(GsmAlphabet.GSM_EXTENDED_ESCAPE));
+
+ // Unmappable (mapped to character in default or national locking shift table)
+ assertEquals('@', GsmAlphabet.gsmExtendedToChar(0));
+ assertEquals('\u00e0', GsmAlphabet.gsmExtendedToChar(0x7f));
+
+ //
+ // stringTo7BitPacked, gsm7BitPackedToString
+ //
+
+ byte[] packed;
+ StringBuilder testString = new StringBuilder(300);
+
+ // Check all alignment cases
+ for (int i = 0; i < 9; i++, testString.append('@')) {
+ packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
+ assertEquals(testString.toString(),
+ GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
+ }
+
+ // Check full non-extended alphabet
+ for (int i = 0; i < 0x80; i++) {
+ char c;
+
+ if (i == GsmAlphabet.GSM_EXTENDED_ESCAPE) {
+ continue;
+ }
+
+ c = GsmAlphabet.gsmToChar(i);
+ testString.append(c);
+
+ // These are all non-extended chars, so it should be
+ // one septet per char
+ assertEquals(1, GsmAlphabet.countGsmSeptets(c));
+ }
+
+ packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
+ assertEquals(testString.toString(),
+ GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
+
+ // Test extended chars too
+
+ testString.append(sGsmExtendedChars);
+
+ for (int i = 0, s = sGsmExtendedChars.length(); i < s; i++) {
+ // These are all extended chars, so it should be
+ // two septets per char
+ assertEquals(2, GsmAlphabet.countGsmSeptets(sGsmExtendedChars.charAt(i)));
+
+ }
+
+ packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
+ assertEquals(testString.toString(),
+ GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
+
+ // stringTo7BitPacked handles up to 255 septets
+
+ testString.setLength(0);
+ for (int i = 0; i < 255; i++) {
+ testString.append('@');
+ }
+
+ packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
+ assertEquals(testString.toString(),
+ GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
+
+ // > 255 septets throws runtime exception
+ testString.append('@');
+
+ try {
+ GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
+ fail("expected exception");
+ } catch (EncodeException ex) {
+ // exception expected
+ }
+
+ // Try 254 septets with 127 extended chars
+
+ testString.setLength(0);
+ for (int i = 0; i < (255 / 2); i++) {
+ testString.append('{');
+ }
+
+ packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
+ assertEquals(testString.toString(),
+ GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
+
+ // > 255 septets throws runtime exception
+ testString.append('{');
+
+ try {
+ GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
+ fail("expected exception");
+ } catch (EncodeException ex) {
+ // exception expected
+ }
+
+ // Reserved for extension to extension table (mapped to space)
+ packed = new byte[]{(byte)(0x1b | 0x80), 0x1b >> 1};
+ assertEquals(" ", GsmAlphabet.gsm7BitPackedToString(packed, 0, 2));
+
+ // Unmappable (mapped to character in default alphabet table)
+ packed[0] = 0x1b;
+ packed[1] = 0x00;
+ assertEquals("@", GsmAlphabet.gsm7BitPackedToString(packed, 0, 2));
+ packed[0] = (byte)(0x1b | 0x80);
+ packed[1] = (byte)(0x7f >> 1);
+ assertEquals("\u00e0", GsmAlphabet.gsm7BitPackedToString(packed, 0, 2));
+
+ //
+ // 8 bit unpacked format
+ //
+ // Note: we compare hex strings here
+ // because Assert doesn't have array comparisons
+
+ byte unpacked[];
+
+ unpacked = IccUtils.hexStringToBytes("566F696365204D61696C");
+ assertEquals("Voice Mail",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));
+
+ assertEquals(IccUtils.bytesToHexString(unpacked),
+ IccUtils.bytesToHexString(
+ GsmAlphabet.stringToGsm8BitPacked("Voice Mail")));
+
+ unpacked = GsmAlphabet.stringToGsm8BitPacked(sGsmExtendedChars);
+ // two bytes for every extended char
+ assertEquals(2 * sGsmExtendedChars.length(), unpacked.length);
+ assertEquals(sGsmExtendedChars,
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));
+
+ // should be two bytes per extended char
+ assertEquals(2 * sGsmExtendedChars.length(), unpacked.length);
+
+ // Test truncation of unaligned extended chars
+ unpacked = new byte[3];
+ GsmAlphabet.stringToGsm8BitUnpackedField(sGsmExtendedChars, unpacked,
+ 0, unpacked.length);
+
+ // Should be one extended char and an 0xff at the end
+
+ assertEquals(0xff, 0xff & unpacked[2]);
+ assertEquals(sGsmExtendedChars.substring(0, 1),
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));
+
+ // Test truncation of normal chars
+ unpacked = new byte[3];
+ GsmAlphabet.stringToGsm8BitUnpackedField("abcd", unpacked,
+ 0, unpacked.length);
+
+ assertEquals("abc",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));
+
+ // Test truncation of mixed normal and extended chars
+ unpacked = new byte[3];
+ GsmAlphabet.stringToGsm8BitUnpackedField("a{cd", unpacked,
+ 0, unpacked.length);
+
+ assertEquals("a{",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));
+
+ // Test padding after normal char
+ unpacked = new byte[3];
+ GsmAlphabet.stringToGsm8BitUnpackedField("a", unpacked,
+ 0, unpacked.length);
+
+ assertEquals("a",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));
+
+ assertEquals(0xff, 0xff & unpacked[1]);
+ assertEquals(0xff, 0xff & unpacked[2]);
+
+ // Test malformed input -- escape char followed by end of field
+ unpacked[0] = 0;
+ unpacked[1] = 0;
+ unpacked[2] = GsmAlphabet.GSM_EXTENDED_ESCAPE;
+
+ assertEquals("@@",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));
+
+ // non-zero offset
+ assertEquals("@",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));
+
+ // test non-zero offset
+ unpacked[0] = 0;
+ GsmAlphabet.stringToGsm8BitUnpackedField("abcd", unpacked,
+ 1, unpacked.length - 1);
+
+
+ assertEquals(0, unpacked[0]);
+
+ assertEquals("ab",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));
+
+ // test non-zero offset with truncated extended char
+ unpacked[0] = 0;
+
+ GsmAlphabet.stringToGsm8BitUnpackedField("a{", unpacked,
+ 1, unpacked.length - 1);
+
+ assertEquals(0, unpacked[0]);
+
+ assertEquals("a",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));
+
+ // Reserved for extension to extension table (mapped to space)
+ unpacked[0] = 0x1b;
+ unpacked[1] = 0x1b;
+ assertEquals(" ", GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, 2));
+
+ // Unmappable (mapped to character in default or national locking shift table)
+ unpacked[1] = 0x00;
+ assertEquals("@", GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, 2));
+ unpacked[1] = 0x7f;
+ assertEquals("\u00e0", GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, 2));
+ }
+
+ @SmallTest
+ public void testGsm8BitUpackedWithEuckr() throws Exception {
+ // Some feature phones in Korea store contacts as euc-kr.
+ // Test this situations.
+ byte unpacked[];
+
+ // Test general alphabet strings.
+ unpacked = IccUtils.hexStringToBytes("61626320646566FF");
+ assertEquals("abc def",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+
+ // Test korean strings.
+ unpacked = IccUtils.hexStringToBytes("C5D7BDBAC6AEFF");
+ assertEquals("\uD14C\uC2A4\uD2B8",
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+
+ // Test gsm Extented Characters.
+ unpacked = GsmAlphabet.stringToGsm8BitPacked(sGsmExtendedChars);
+ assertEquals(sGsmExtendedChars,
+ GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
new file mode 100644
index 0000000..f2e5da1
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
@@ -0,0 +1,538 @@
+/*
+ * 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.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.gsm.SmsMessage;
+import com.android.internal.util.HexDump;
+
+import java.util.ArrayList;
+
+public class GsmSmsTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testAddressing() throws Exception {
+ String pdu = "07914151551512f2040B916105551511f100006060605130308A04D4F29C0E";
+ SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+ assertEquals("+14155551212", sms.getServiceCenterAddress());
+ assertEquals("+16505551111", sms.getOriginatingAddress());
+ assertEquals("Test", sms.getMessageBody());
+
+ pdu = "07914151551512f2040B916105551511f100036060924180008A0DA"
+ + "8695DAC2E8FE9296A794E07";
+ sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+ assertEquals("+14155551212", sms.getServiceCenterAddress());
+ assertEquals("+16505551111", sms.getOriginatingAddress());
+ assertEquals("(Subject)Test", sms.getMessageBody());
+ }
+
+ @SmallTest
+ public void testUdh() throws Exception {
+ String pdu = "07914140279510F6440A8111110301003BF56080207130138A8C0B05040B8423F"
+ + "000032A02010106276170706C69636174696F6E2F766E642E7761702E6D6D732D"
+ + "6D65737361676500AF848D0185B4848C8298524E453955304A6D7135514141426"
+ + "66C414141414D7741414236514141414141008D908918802B3135313232393737"
+ + "3638332F545950453D504C4D4E008A808E022B918805810306977F83687474703"
+ + "A2F2F36";
+ SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+ SmsHeader header = sms.getUserDataHeader();
+ assertNotNull(header);
+ assertNotNull(header.concatRef);
+ assertEquals(header.concatRef.refNumber, 42);
+ assertEquals(header.concatRef.msgCount, 2);
+ assertEquals(header.concatRef.seqNumber, 1);
+ assertEquals(header.concatRef.isEightBits, true);
+ assertNotNull(header.portAddrs);
+ assertEquals(header.portAddrs.destPort, 2948);
+ assertEquals(header.portAddrs.origPort, 9200);
+ assertEquals(header.portAddrs.areEightBits, false);
+
+ pdu = "07914140279510F6440A8111110301003BF56080207130238A3B0B05040B8423F"
+ + "000032A0202362E3130322E3137312E3135302F524E453955304A6D7135514141"
+ + "42666C414141414D774141423651414141414100";
+ sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+ header = sms.getUserDataHeader();
+ assertNotNull(header);
+ assertNotNull(header.concatRef);
+ assertEquals(header.concatRef.refNumber, 42);
+ assertEquals(header.concatRef.msgCount, 2);
+ assertEquals(header.concatRef.seqNumber, 2);
+ assertEquals(header.concatRef.isEightBits, true);
+ assertNotNull(header.portAddrs);
+ assertEquals(header.portAddrs.destPort, 2948);
+ assertEquals(header.portAddrs.origPort, 9200);
+ assertEquals(header.portAddrs.areEightBits, false);
+ }
+
+ @SmallTest
+ public void testUcs2() throws Exception {
+ String pdu = "07912160130300F4040B914151245584F600087010807121352B1021220"
+ + "0A900AE00680065006C006C006F";
+ SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+ assertEquals("\u2122\u00a9\u00aehello", sms.getMessageBody());
+ }
+
+ @SmallTest
+ public void testMultipart() throws Exception {
+ /*
+ * Multi-part text SMS with septet data.
+ */
+ String pdu = "07916163838408F6440B816105224431F700007060217175830AA0050003"
+ + "00020162B1582C168BC562B1582C168BC562B1582C168BC562B1582C"
+ + "168BC562B1582C168BC562B1582C168BC562B1582C168BC562B1582C"
+ + "168BC562B1582C168BC562B1582C168BC562B1582C168BC562B1582C"
+ + "168BC562B1582C168BC562B1582C168BC562B1582C168BC562B1582C"
+ + "168BC562B1582C168BC562B1582C168BC562B1582C168BC562";
+ SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+ assertEquals(sms.getMessageBody(),
+ "1111111111111111111111111111111111111111"
+ + "1111111111111111111111111111111111111111"
+ + "1111111111111111111111111111111111111111"
+ + "111111111111111111111111111111111");
+
+ pdu = "07916163838408F6440B816105224431F700007060217185000A23050003"
+ + "00020262B1582C168BC96432994C2693C96432994C2693C96432990C";
+ sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+ assertEquals("1111111222222222222222222222", sms.getMessageBody());
+ }
+
+ @SmallTest
+ public void testCPHSVoiceMail() throws Exception {
+ // "set MWI flag"
+
+ String pdu = "07912160130310F20404D0110041006060627171118A0120";
+
+ SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+
+ assertTrue(sms.isReplace());
+ assertEquals("_@", sms.getOriginatingAddress());
+ assertEquals(" ", sms.getMessageBody());
+ assertTrue(sms.isMWISetMessage());
+
+ // "clear mwi flag"
+
+ pdu = "07912160130310F20404D0100041006021924193352B0120";
+
+ sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+
+ assertTrue(sms.isMWIClearMessage());
+
+ // "clear MWI flag"
+
+ pdu = "07912160130310F20404D0100041006060627161058A0120";
+
+ sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+
+ assertTrue(sms.isReplace());
+ assertEquals("\u0394@", sms.getOriginatingAddress());
+ assertEquals(" ", sms.getMessageBody());
+ assertTrue(sms.isMWIClearMessage());
+ }
+
+ @SmallTest
+ public void testCingularVoiceMail() throws Exception {
+ // "set MWI flag"
+
+ String pdu = "07912180958750F84401800500C87020026195702B06040102000200";
+ SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+
+ assertTrue(sms.isMWISetMessage());
+ assertTrue(sms.isMwiDontStore());
+
+ // "clear mwi flag"
+
+ pdu = "07912180958750F84401800500C07020027160112B06040102000000";
+ sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+
+ assertTrue(sms.isMWIClearMessage());
+ assertTrue(sms.isMwiDontStore());
+ }
+
+ @SmallTest
+ public void testEmailGateway() throws Exception {
+ String pdu = "07914151551512f204038105f300007011103164638a28e6f71b50c687db" +
+ "7076d9357eb7412f7a794e07cdeb6275794c07bde8e5391d247e93f3";
+
+ SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+
+ assertEquals("+14155551212", sms.getServiceCenterAddress());
+ assertTrue(sms.isEmail());
+ assertEquals("foo@example.com", sms.getEmailFrom());
+ assertEquals("foo@example.com", sms.getDisplayOriginatingAddress());
+ // As of https://android-git.corp.google.com/g/#change,9324
+ // getPseudoSubject will always be empty, and any subject is not extracted.
+ assertEquals("", sms.getPseudoSubject());
+ assertEquals("test subject /test body", sms.getDisplayMessageBody());
+ assertEquals("test subject /test body", sms.getEmailBody());
+
+ // email gateway sms test, including gsm extended character set.
+ pdu = "07914151551512f204038105f400007011103105458a29e6f71b50c687db" +
+ "7076d9357eb741af0d0a442fcfe9c23739bfe16d289bdee6b5f1813629";
+
+ sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+
+ assertEquals("+14155551212", sms.getServiceCenterAddress());
+ assertTrue(sms.isEmail());
+ assertEquals("foo@example.com", sms.getDisplayOriginatingAddress());
+ assertEquals("foo@example.com", sms.getEmailFrom());
+ assertEquals("{ testBody[^~\\] }", sms.getDisplayMessageBody());
+ assertEquals("{ testBody[^~\\] }", sms.getEmailBody());
+ }
+
+ @SmallTest
+ public void testExtendedCharacterTable() throws Exception {
+ String pdu = "07914151551512f2040B916105551511f100006080615131728A44D4F29C0E2" +
+ "AE3E96537B94C068DD16179784C2FCB41F4B0985D06B958ADD00FB0E94536AF9749" +
+ "74DA6D281BA00E95E26D509B946FC3DBF87A25D56A04";
+
+ SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+
+ assertEquals("+14155551212", sms.getServiceCenterAddress());
+ assertEquals("+16505551111", sms.getOriginatingAddress());
+ assertEquals("Test extended character table .,-!?@~_\\/&\"';^|:()<{}>[]=%*+#",
+ sms.getMessageBody());
+ }
+
+ // GSM 7 bit tables in String form, Escape (0x1B) replaced with '@'
+ private static final String[] sBasicTables = {
+ // GSM 7 bit default alphabet
+ "@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\n\u00d8\u00f8\r\u00c5\u00e5\u0394_"
+ + "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e@\u00c6\u00e6\u00df\u00c9"
+ + " !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6"
+ + "\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0",
+
+ // Turkish locking shift table
+ "@\u00a3$\u00a5\u20ac\u00e9\u00f9\u0131\u00f2\u00c7\n\u011e\u011f\r\u00c5\u00e5\u0394_"
+ + "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e@\u015e\u015f\u00df\u00c9"
+ + " !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u0130ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6"
+ + "\u00d1\u00dc\u00a7\u00e7abcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0",
+
+ // no locking shift table defined for Spanish
+ "",
+
+ // Portuguese locking shift table
+ "@\u00a3$\u00a5\u00ea\u00e9\u00fa\u00ed\u00f3\u00e7\n\u00d4\u00f4\r\u00c1\u00e1\u0394_"
+ + "\u00aa\u00c7\u00c0\u221e^\\\u20ac\u00d3|@\u00c2\u00e2\u00ca\u00c9 !\"#\u00ba%&'()"
+ + "*+,-./0123456789:;<=>?\u00cdABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c3\u00d5\u00da\u00dc"
+ + "\u00a7~abcdefghijklmnopqrstuvwxyz\u00e3\u00f5`\u00fc\u00e0"
+ };
+
+ @SmallTest
+ public void testFragmentText() throws Exception {
+ boolean isGsmPhone = (TelephonyManager.getDefault().getPhoneType() ==
+ TelephonyManager.PHONE_TYPE_GSM);
+
+ // Valid 160 character 7-bit text.
+ String text = "123456789012345678901234567890123456789012345678901234567890" +
+ "1234567890123456789012345678901234567890123456789012345678901234567890" +
+ "123456789012345678901234567890";
+ GsmAlphabet.TextEncodingDetails ted = SmsMessage.calculateLength(text, false);
+ assertEquals(1, ted.msgCount);
+ assertEquals(160, ted.codeUnitCount);
+ assertEquals(1, ted.codeUnitSize);
+ assertEquals(0, ted.languageTable);
+ assertEquals(0, ted.languageShiftTable);
+ if (isGsmPhone) {
+ ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
+ assertEquals(1, fragments.size());
+ }
+
+ // Valid 161 character 7-bit text.
+ text = "123456789012345678901234567890123456789012345678901234567890" +
+ "1234567890123456789012345678901234567890123456789012345678901234567890" +
+ "1234567890123456789012345678901";
+ ted = SmsMessage.calculateLength(text, false);
+ assertEquals(2, ted.msgCount);
+ assertEquals(161, ted.codeUnitCount);
+ assertEquals(1, ted.codeUnitSize);
+ assertEquals(0, ted.languageTable);
+ assertEquals(0, ted.languageShiftTable);
+ if (isGsmPhone) {
+ ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
+ assertEquals(2, fragments.size());
+ assertEquals(text, fragments.get(0) + fragments.get(1));
+ assertEquals(153, fragments.get(0).length());
+ assertEquals(8, fragments.get(1).length());
+ }
+ }
+
+ @SmallTest
+ public void testFragmentTurkishText() throws Exception {
+ boolean isGsmPhone = (TelephonyManager.getDefault().getPhoneType() ==
+ TelephonyManager.PHONE_TYPE_GSM);
+
+ int[] oldTables = GsmAlphabet.getEnabledSingleShiftTables();
+ int[] turkishTable = { 1 };
+ GsmAlphabet.setEnabledSingleShiftTables(turkishTable);
+
+ // Valid 77 character text with Turkish characters.
+ String text = "ĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşı" +
+ "ĞŞİğşıĞŞİğşıĞŞİğş";
+ GsmAlphabet.TextEncodingDetails ted = SmsMessage.calculateLength(text, false);
+ assertEquals(1, ted.msgCount);
+ assertEquals(154, ted.codeUnitCount);
+ assertEquals(1, ted.codeUnitSize);
+ assertEquals(0, ted.languageTable);
+ assertEquals(1, ted.languageShiftTable);
+ if (isGsmPhone) {
+ ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
+ assertEquals(1, fragments.size());
+ assertEquals(text, fragments.get(0));
+ assertEquals(77, fragments.get(0).length());
+ }
+
+ // Valid 78 character text with Turkish characters.
+ text = "ĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşı" +
+ "ĞŞİğşıĞŞİğşıĞŞİğşı";
+ ted = SmsMessage.calculateLength(text, false);
+ assertEquals(2, ted.msgCount);
+ assertEquals(156, ted.codeUnitCount);
+ assertEquals(1, ted.codeUnitSize);
+ assertEquals(0, ted.languageTable);
+ assertEquals(1, ted.languageShiftTable);
+ if (isGsmPhone) {
+ ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
+ assertEquals(2, fragments.size());
+ assertEquals(text, fragments.get(0) + fragments.get(1));
+ assertEquals(74, fragments.get(0).length());
+ assertEquals(4, fragments.get(1).length());
+ }
+
+ // Valid 160 character text with Turkish characters.
+ text = "ĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşı" +
+ "ĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğ" +
+ "ĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşıĞŞİğşı";
+ ted = SmsMessage.calculateLength(text, false);
+ assertEquals(3, ted.msgCount);
+ assertEquals(320, ted.codeUnitCount);
+ assertEquals(1, ted.codeUnitSize);
+ assertEquals(0, ted.languageTable);
+ assertEquals(1, ted.languageShiftTable);
+ if (isGsmPhone) {
+ ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
+ assertEquals(3, fragments.size());
+ assertEquals(text, fragments.get(0) + fragments.get(1) + fragments.get(2));
+ assertEquals(74, fragments.get(0).length());
+ assertEquals(74, fragments.get(1).length());
+ assertEquals(12, fragments.get(2).length());
+ }
+
+ GsmAlphabet.setEnabledSingleShiftTables(oldTables);
+ }
+
+
+ @SmallTest
+ public void testDecode() throws Exception {
+ decodeSingle(0); // default table
+ decodeSingle(1); // Turkish locking shift table
+ decodeSingle(3); // Portuguese locking shift table
+ }
+
+ private void decodeSingle(int language) throws Exception {
+ byte[] septets = new byte[(7 * 128 + 7) / 8];
+
+ int bitOffset = 0;
+
+ for (int i = 0; i < 128; i++) {
+ int v;
+ if (i == 0x1b) {
+ // extended escape char
+ v = 0;
+ } else {
+ v = i;
+ }
+
+ int byteOffset = bitOffset / 8;
+ int shift = bitOffset % 8;
+
+ septets[byteOffset] |= v << shift;
+
+ if (shift > 1) {
+ septets[byteOffset + 1] = (byte) (v >> (8 - shift));
+ }
+
+ bitOffset += 7;
+ }
+
+ String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, 128, 0, language, 0);
+ byte[] reEncoded = GsmAlphabet.stringToGsm7BitPacked(decoded, language, 0);
+
+ assertEquals(sBasicTables[language], decoded);
+
+ // reEncoded has the count septets byte at the front
+ assertEquals(septets.length + 1, reEncoded.length);
+
+ for (int i = 0; i < septets.length; i++) {
+ assertEquals(septets[i], reEncoded[i + 1]);
+ }
+ }
+
+ private static final int GSM_ESCAPE_CHARACTER = 0x1b;
+
+ private static final String[] sExtendedTables = {
+ // GSM 7 bit default alphabet extension table
+ "\f^{}\\[~]|\u20ac",
+
+ // Turkish single shift extension table
+ "\f^{}\\[~]|\u011e\u0130\u015e\u00e7\u20ac\u011f\u0131\u015f",
+
+ // Spanish single shift extension table
+ "\u00e7\f^{}\\[~]|\u00c1\u00cd\u00d3\u00da\u00e1\u20ac\u00ed\u00f3\u00fa",
+
+ // Portuguese single shift extension table
+ "\u00ea\u00e7\f\u00d4\u00f4\u00c1\u00e1\u03a6\u0393^\u03a9\u03a0\u03a8\u03a3\u0398\u00ca"
+ + "{}\\[~]|\u00c0\u00cd\u00d3\u00da\u00c3\u00d5\u00c2\u20ac\u00ed\u00f3\u00fa\u00e3"
+ + "\u00f5\u00e2"
+ };
+
+ private static final int[][] sExtendedTableIndexes = {
+ {0x0a, 0x14, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x65},
+ {0x0a, 0x14, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x47, 0x49, 0x53, 0x63,
+ 0x65, 0x67, 0x69, 0x73},
+ {0x09, 0x0a, 0x14, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x41, 0x49, 0x4f,
+ 0x55, 0x61, 0x65, 0x69, 0x6f, 0x75},
+ {0x05, 0x09, 0x0a, 0x0b, 0x0c, 0x0e, 0x0f, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1f, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x41, 0x49,
+ 0x4f, 0x55, 0x5b, 0x5c, 0x61, 0x65, 0x69, 0x6f, 0x75, 0x7b, 0x7c, 0x7f}
+ };
+
+ @SmallTest
+ public void testDecodeExtended() throws Exception {
+ for (int language = 0; language < 3; language++) {
+ int[] tableIndex = sExtendedTableIndexes[language];
+ int numSeptets = tableIndex.length * 2; // two septets per extended char
+ byte[] septets = new byte[(7 * numSeptets + 7) / 8];
+
+ int bitOffset = 0;
+
+ for (int v : tableIndex) {
+ // escape character
+ int byteOffset = bitOffset / 8;
+ int shift = bitOffset % 8;
+
+ septets[byteOffset] |= GSM_ESCAPE_CHARACTER << shift;
+
+ if (shift > 1) {
+ septets[byteOffset + 1] = (byte) (GSM_ESCAPE_CHARACTER >> (8 - shift));
+ }
+
+ bitOffset += 7;
+
+ // extended table index
+ byteOffset = bitOffset / 8;
+ shift = bitOffset % 8;
+
+ septets[byteOffset] |= v << shift;
+
+ if (shift > 1) {
+ septets[byteOffset + 1] = (byte) (v >> (8 - shift));
+ }
+
+ bitOffset += 7;
+ }
+
+ String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0,
+ 0, language);
+ byte[] reEncoded = GsmAlphabet.stringToGsm7BitPacked(decoded, 0, language);
+
+ assertEquals(sExtendedTables[language], decoded);
+
+ // reEncoded has the count septets byte at the front
+ assertEquals(septets.length + 1, reEncoded.length);
+
+ for (int i = 0; i < septets.length; i++) {
+ assertEquals(septets[i], reEncoded[i + 1]);
+ }
+ }
+ }
+
+ @SmallTest
+ public void testDecodeExtendedFallback() throws Exception {
+ // verify that unmapped characters in extension table fall back to locking shift table
+ for (int language = 0; language < 3; language++) {
+ int[] tableIndex = sExtendedTableIndexes[language];
+ int numChars = 128 - tableIndex.length;
+ int numSeptets = numChars * 2; // two septets per extended char
+ byte[] septets = new byte[(7 * numSeptets + 7) / 8];
+
+ int tableOffset = 0;
+ int bitOffset = 0;
+
+ StringBuilder defaultTable = new StringBuilder(128);
+ StringBuilder turkishTable = new StringBuilder(128);
+ StringBuilder portugueseTable = new StringBuilder(128);
+
+ for (char c = 0; c < 128; c++) {
+ // skip characters that are present in the current extension table
+ if (tableOffset < tableIndex.length && tableIndex[tableOffset] == c) {
+ tableOffset++;
+ continue;
+ }
+
+ // escape character
+ int byteOffset = bitOffset / 8;
+ int shift = bitOffset % 8;
+
+ septets[byteOffset] |= GSM_ESCAPE_CHARACTER << shift;
+
+ if (shift > 1) {
+ septets[byteOffset + 1] = (byte) (GSM_ESCAPE_CHARACTER >> (8 - shift));
+ }
+
+ bitOffset += 7;
+
+ // extended table index
+ byteOffset = bitOffset / 8;
+ shift = bitOffset % 8;
+
+ septets[byteOffset] |= c << shift;
+
+ if (shift > 1) {
+ septets[byteOffset + 1] = (byte) (c >> (8 - shift));
+ }
+
+ bitOffset += 7;
+
+ if (c == GsmAlphabet.GSM_EXTENDED_ESCAPE) {
+ // double Escape maps to space character
+ defaultTable.append(' ');
+ turkishTable.append(' ');
+ portugueseTable.append(' ');
+ } else {
+ // other unmapped chars map to the default or locking shift table
+ defaultTable.append(sBasicTables[0].charAt(c));
+ turkishTable.append(sBasicTables[1].charAt(c));
+ portugueseTable.append(sBasicTables[3].charAt(c));
+ }
+ }
+
+ String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0,
+ 0, language);
+
+ assertEquals(defaultTable.toString(), decoded);
+
+ decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0, 1, language);
+ assertEquals(turkishTable.toString(), decoded);
+
+ decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0, 3, language);
+ assertEquals(portugueseTable.toString(), decoded);
+ }
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/IccServiceTableTest.java b/tests/telephonytests/src/com/android/internal/telephony/IccServiceTableTest.java
new file mode 100644
index 0000000..c89f33a
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/IccServiceTableTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Test IccServiceTable class.
+ */
+public class IccServiceTableTest extends AndroidTestCase {
+
+ static class TestIccServiceTable extends IccServiceTable {
+ public enum TestIccService {
+ SERVICE1,
+ SERVICE2,
+ SERVICE3,
+ SERVICE4
+ }
+
+ public TestIccServiceTable(byte[] table) {
+ super(table);
+ }
+
+ public boolean isAvailable(TestIccService service) {
+ return super.isAvailable(service.ordinal());
+ }
+
+ @Override
+ protected String getTag() {
+ return "TestIccServiceTable";
+ }
+
+ @Override
+ protected Object[] getValues() {
+ return TestIccService.values();
+ }
+ }
+
+ @SmallTest
+ public void testIccServiceTable() {
+ byte[] noServices = {0x00};
+ byte[] service1 = {0x01};
+ byte[] service4 = {0x08};
+ byte[] allServices = {0x0f};
+
+ TestIccServiceTable testTable1 = new TestIccServiceTable(noServices);
+ assertFalse(testTable1.isAvailable(TestIccServiceTable.TestIccService.SERVICE1));
+ assertFalse(testTable1.isAvailable(TestIccServiceTable.TestIccService.SERVICE2));
+ assertFalse(testTable1.isAvailable(TestIccServiceTable.TestIccService.SERVICE3));
+ assertFalse(testTable1.isAvailable(TestIccServiceTable.TestIccService.SERVICE4));
+
+ TestIccServiceTable testTable2 = new TestIccServiceTable(service1);
+ assertTrue(testTable2.isAvailable(TestIccServiceTable.TestIccService.SERVICE1));
+ assertFalse(testTable2.isAvailable(TestIccServiceTable.TestIccService.SERVICE2));
+ assertFalse(testTable2.isAvailable(TestIccServiceTable.TestIccService.SERVICE3));
+ assertFalse(testTable2.isAvailable(TestIccServiceTable.TestIccService.SERVICE4));
+
+ TestIccServiceTable testTable3 = new TestIccServiceTable(service4);
+ assertFalse(testTable3.isAvailable(TestIccServiceTable.TestIccService.SERVICE1));
+ assertFalse(testTable3.isAvailable(TestIccServiceTable.TestIccService.SERVICE2));
+ assertFalse(testTable3.isAvailable(TestIccServiceTable.TestIccService.SERVICE3));
+ assertTrue(testTable3.isAvailable(TestIccServiceTable.TestIccService.SERVICE4));
+
+ TestIccServiceTable testTable4 = new TestIccServiceTable(allServices);
+ assertTrue(testTable4.isAvailable(TestIccServiceTable.TestIccService.SERVICE1));
+ assertTrue(testTable4.isAvailable(TestIccServiceTable.TestIccService.SERVICE2));
+ assertTrue(testTable4.isAvailable(TestIccServiceTable.TestIccService.SERVICE3));
+ assertTrue(testTable4.isAvailable(TestIccServiceTable.TestIccService.SERVICE4));
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java
new file mode 100644
index 0000000..79dca39
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2011 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.test.AndroidTestCase;
+
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Test cases for the IntRangeManager class.
+ */
+public class IntRangeManagerTest extends AndroidTestCase {
+
+ private static final int SMS_CB_CODE_SCHEME_MIN = 0;
+ private static final int SMS_CB_CODE_SCHEME_MAX = 255;
+
+ private static final int FLAG_START_UPDATE_CALLED = 0x01;
+ private static final int FLAG_ADD_RANGE_CALLED = 0x02;
+ private static final int FLAG_FINISH_UPDATE_CALLED = 0x04;
+
+ private static final int ALL_FLAGS_SET = FLAG_START_UPDATE_CALLED | FLAG_ADD_RANGE_CALLED |
+ FLAG_FINISH_UPDATE_CALLED;
+
+ /** Dummy IntRangeManager for testing. */
+ class TestIntRangeManager extends IntRangeManager {
+ ArrayList<SmsBroadcastConfigInfo> mConfigList =
+ new ArrayList<SmsBroadcastConfigInfo>();
+
+ int flags;
+ boolean finishUpdateReturnValue = true;
+
+ /**
+ * Called when the list of enabled ranges has changed. This will be
+ * followed by zero or more calls to {@link #addRange} followed by
+ * a call to {@link #finishUpdate}.
+ */
+ protected void startUpdate() {
+ mConfigList.clear();
+ flags |= FLAG_START_UPDATE_CALLED;
+ }
+
+ /**
+ * Called after {@link #startUpdate} to indicate a range of enabled
+ * values.
+ * @param startId the first id included in the range
+ * @param endId the last id included in the range
+ */
+ protected void addRange(int startId, int endId, boolean selected) {
+ mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
+ SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
+ flags |= FLAG_ADD_RANGE_CALLED;
+ }
+
+ /**
+ * Called to indicate the end of a range update started by the
+ * previous call to {@link #startUpdate}.
+ */
+ protected boolean finishUpdate() {
+ flags |= FLAG_FINISH_UPDATE_CALLED;
+ return finishUpdateReturnValue;
+ }
+
+ /** Reset the object for the next test case. */
+ void reset() {
+ flags = 0;
+ mConfigList.clear();
+ }
+ }
+
+ public void testEmptyRangeManager() {
+ TestIntRangeManager testManager = new TestIntRangeManager();
+ assertEquals("expecting empty configlist", 0, testManager.mConfigList.size());
+ }
+
+ private void checkConfigInfo(SmsBroadcastConfigInfo info, int fromServiceId,
+ int toServiceId, int fromCodeScheme, int toCodeScheme, boolean selected) {
+ assertEquals("fromServiceId", fromServiceId, info.getFromServiceId());
+ assertEquals("toServiceId", toServiceId, info.getToServiceId());
+ assertEquals("fromCodeScheme", fromCodeScheme, info.getFromCodeScheme());
+ assertEquals("toCodeScheme", toCodeScheme, info.getToCodeScheme());
+ assertEquals("selected", selected, info.isSelected());
+ }
+
+ public void testAddSingleChannel() {
+ TestIntRangeManager testManager = new TestIntRangeManager();
+ assertEquals("flags before test", 0, testManager.flags);
+ assertTrue("enabling range", testManager.enableRange(123, 123, "client1"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 123, 123, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 123, 123, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ }
+
+ public void testRemoveSingleChannel() {
+ TestIntRangeManager testManager = new TestIntRangeManager();
+ assertTrue("enabling range", testManager.enableRange(123, 123, "client1"));
+ assertEquals("flags after enable", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ testManager.reset();
+ assertTrue("disabling range", testManager.disableRange(123, 123, "client1"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 123, 123, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", FLAG_START_UPDATE_CALLED | FLAG_FINISH_UPDATE_CALLED,
+ testManager.flags);
+ assertEquals("configlist size", 0, testManager.mConfigList.size());
+ }
+
+ public void testRemoveBadChannel() {
+ TestIntRangeManager testManager = new TestIntRangeManager();
+ assertFalse("disabling missing range", testManager.disableRange(123, 123, "client1"));
+ assertEquals("flags after test", 0, testManager.flags);
+ assertEquals("configlist size", 0, testManager.mConfigList.size());
+ }
+
+ public void testAddTwoChannels() {
+ TestIntRangeManager testManager = new TestIntRangeManager();
+ assertEquals("flags before test", 0, testManager.flags);
+ assertTrue("enabling range 1", testManager.enableRange(100, 120, "client1"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 100, 120, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("enabling range 2", testManager.enableRange(200, 250, "client2"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 200, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 2, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 100, 120, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(1), 200, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ }
+
+ public void testOverlappingChannels() {
+ TestIntRangeManager testManager = new TestIntRangeManager();
+ assertEquals("flags before test", 0, testManager.flags);
+ assertTrue("enabling range 1", testManager.enableRange(100, 200, "client1"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("enabling range 2", testManager.enableRange(150, 250, "client2"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 201, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 100, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("disabling range 1", testManager.disableRange(100, 200, "client1"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 100, 149, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("disabling range 2", testManager.disableRange(150, 250, "client2"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", FLAG_START_UPDATE_CALLED | FLAG_FINISH_UPDATE_CALLED,
+ testManager.flags);
+ assertEquals("configlist size", 0, testManager.mConfigList.size());
+ }
+
+ public void testOverlappingChannels2() {
+ TestIntRangeManager testManager = new TestIntRangeManager();
+ assertEquals("flags before test", 0, testManager.flags);
+ assertTrue("enabling range 1", testManager.enableRange(100, 200, "client1"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("enabling range 2", testManager.enableRange(150, 250, "client2"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 201, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("disabling range 2", testManager.disableRange(150, 250, "client2"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 201, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("disabling range 1", testManager.disableRange(100, 200, "client1"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ }
+
+ public void testMultipleOverlappingChannels() {
+ TestIntRangeManager testManager = new TestIntRangeManager();
+ assertEquals("flags before test", 0, testManager.flags);
+ assertTrue("enabling range 1", testManager.enableRange(67, 9999, "client1"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 67, 9999, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("enabling range 2", testManager.enableRange(150, 250, "client2"));
+ assertEquals("flags after test", 0, testManager.flags);
+ assertEquals("configlist size", 0, testManager.mConfigList.size());
+ testManager.reset();
+ assertTrue("enabling range 3", testManager.enableRange(25, 75, "client3"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 25, 66, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("enabling range 4", testManager.enableRange(12, 500, "client4"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 12, 24, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("enabling range 5", testManager.enableRange(8000, 9998, "client5"));
+ assertEquals("flags after test", 0, testManager.flags);
+ assertEquals("configlist size", 0, testManager.mConfigList.size());
+ testManager.reset();
+ assertTrue("enabling range 6", testManager.enableRange(50000, 65535, "client6"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 2, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 12, 9999, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(1), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("disabling range 1", testManager.disableRange(67, 9999, "client1"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 2, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 501, 7999, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ checkConfigInfo(testManager.mConfigList.get(1), 9999, 9999, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 3, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 12, 500, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(1), 8000, 9998, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(2), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("disabling range 4", testManager.disableRange(12, 500, "client4"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 3, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 12, 24, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ checkConfigInfo(testManager.mConfigList.get(1), 76, 149, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ checkConfigInfo(testManager.mConfigList.get(2), 251, 500, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 4, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(1), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(2), 8000, 9998, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(3), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("disabling range 5", testManager.disableRange(8000, 9998, "client5"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 8000, 9998, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 3, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(1), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(2), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("disabling range 6", testManager.disableRange(50000, 65535, "client6"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 2, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ checkConfigInfo(testManager.mConfigList.get(1), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("disabling range 2", testManager.disableRange(150, 250, "client2"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, true);
+ testManager.reset();
+ assertTrue("disabling range 3", testManager.disableRange(25, 75, "client3"));
+ assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+ assertEquals("configlist size", 1, testManager.mConfigList.size());
+ checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+ SMS_CB_CODE_SCHEME_MAX, false);
+ testManager.reset();
+ assertTrue("updating ranges", testManager.updateRanges());
+ assertEquals("flags after test", FLAG_START_UPDATE_CALLED | FLAG_FINISH_UPDATE_CALLED,
+ testManager.flags);
+ assertEquals("configlist size", 0, testManager.mConfigList.size());
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java b/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
new file mode 100644
index 0000000..868c76d
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.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;
+
+import com.android.internal.telephony.MccTable;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import android.util.Log;
+
+public class MccTableTest extends AndroidTestCase {
+ private final static String LOG_TAG = "GSM";
+
+ @SmallTest
+ public void testTimeZone() throws Exception {
+ assertEquals(MccTable.defaultTimeZoneForMcc(208), "ECT");
+ assertEquals(MccTable.defaultTimeZoneForMcc(232), "Europe/Vienna");
+ assertEquals(MccTable.defaultTimeZoneForMcc(655), "Africa/Johannesburg");
+ assertEquals(MccTable.defaultTimeZoneForMcc(440), "Asia/Tokyo");
+ assertEquals(MccTable.defaultTimeZoneForMcc(441), "Asia/Tokyo");
+ assertEquals(MccTable.defaultTimeZoneForMcc(525), "Asia/Singapore");
+ assertEquals(MccTable.defaultTimeZoneForMcc(240), null); // tz not defined, hence default
+ assertEquals(MccTable.defaultTimeZoneForMcc(0), null); // mcc not defined, hence default
+ assertEquals(MccTable.defaultTimeZoneForMcc(2000), null); // mcc not defined, hence default
+ }
+
+ @SmallTest
+ public void testCountryCode() throws Exception {
+ assertEquals(MccTable.countryCodeForMcc(270), "lu");
+ assertEquals(MccTable.countryCodeForMcc(202), "gr");
+ assertEquals(MccTable.countryCodeForMcc(750), "fk");
+ assertEquals(MccTable.countryCodeForMcc(646), "mg");
+ assertEquals(MccTable.countryCodeForMcc(314), "us");
+ assertEquals(MccTable.countryCodeForMcc(300), ""); // mcc not defined, hence default
+ assertEquals(MccTable.countryCodeForMcc(0), ""); // mcc not defined, hence default
+ assertEquals(MccTable.countryCodeForMcc(2000), ""); // mcc not defined, hence default
+ }
+
+ @SmallTest
+ public void testLang() throws Exception {
+ assertEquals(MccTable.defaultLanguageForMcc(311), "en");
+ assertEquals(MccTable.defaultLanguageForMcc(232), "de");
+ assertEquals(MccTable.defaultLanguageForMcc(230), "cs");
+ assertEquals(MccTable.defaultLanguageForMcc(204), "nl");
+ assertEquals(MccTable.defaultLanguageForMcc(274), null); // lang not defined, hence default
+ assertEquals(MccTable.defaultLanguageForMcc(0), null); // mcc not defined, hence default
+ assertEquals(MccTable.defaultLanguageForMcc(2000), null); // mcc not defined, hence default
+ }
+
+ @SmallTest
+ public void testSmDigits() throws Exception {
+ assertEquals(MccTable.smallestDigitsMccForMnc(312), 3);
+ assertEquals(MccTable.smallestDigitsMccForMnc(430), 2);
+ assertEquals(MccTable.smallestDigitsMccForMnc(365), 3);
+ assertEquals(MccTable.smallestDigitsMccForMnc(536), 2);
+ assertEquals(MccTable.smallestDigitsMccForMnc(352), 2); // sd not defined, hence default
+ assertEquals(MccTable.smallestDigitsMccForMnc(0), 2); // mcc not defined, hence default
+ assertEquals(MccTable.smallestDigitsMccForMnc(2000), 2); // mcc not defined, hence default
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NeighboringCellInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/NeighboringCellInfoTest.java
new file mode 100644
index 0000000..b63dc71
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/NeighboringCellInfoTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009 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.Parcel;
+import android.test.AndroidTestCase;
+import android.telephony.NeighboringCellInfo;
+import android.test. suitebuilder.annotation.SmallTest;
+
+import static android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_EDGE;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_GPRS;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_UMTS;
+
+public class NeighboringCellInfoTest extends AndroidTestCase {
+ @SmallTest
+ public void testConstructor() {
+ int rssi = 31;
+ NeighboringCellInfo nc;
+
+ nc = new NeighboringCellInfo(rssi, "FFFFFFF", NETWORK_TYPE_EDGE);
+ assertEquals(NETWORK_TYPE_EDGE, nc.getNetworkType());
+ assertEquals(rssi, nc.getRssi());
+ assertEquals(0xfff, nc.getLac());
+ assertEquals(0xffff, nc.getCid());
+ assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getPsc());
+
+ nc = new NeighboringCellInfo(rssi, "1FF", NETWORK_TYPE_UMTS);
+ assertEquals(NETWORK_TYPE_UMTS, nc.getNetworkType());
+ assertEquals(rssi, nc.getRssi());
+ assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getCid());
+ assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getLac());
+ assertEquals(0x1ff, nc.getPsc());
+
+ nc = new NeighboringCellInfo(rssi, "1FF", NETWORK_TYPE_UNKNOWN);
+ assertEquals(NETWORK_TYPE_UNKNOWN, nc.getNetworkType());
+ assertEquals(rssi, nc.getRssi());
+ assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getCid());
+ assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getLac());
+ assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getPsc());
+ }
+
+ @SmallTest
+ public void testParcel() {
+ int rssi = 20;
+
+ NeighboringCellInfo nc = new NeighboringCellInfo(rssi, "12345678", NETWORK_TYPE_GPRS);
+ assertEquals(NETWORK_TYPE_GPRS, nc.getNetworkType());
+ assertEquals(rssi, nc.getRssi());
+ assertEquals(0x1234, nc.getLac());
+ assertEquals(0x5678, nc.getCid());
+ assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getPsc());
+
+ Parcel p = Parcel.obtain();
+ p.setDataPosition(0);
+ nc.writeToParcel(p, 0);
+
+ p.setDataPosition(0);
+ NeighboringCellInfo nw = new NeighboringCellInfo(p);
+ assertEquals(NETWORK_TYPE_GPRS, nw.getNetworkType());
+ assertEquals(rssi, nw.getRssi());
+ assertEquals(0x1234, nw.getLac());
+ assertEquals(0x5678, nw.getCid());
+ assertEquals(NeighboringCellInfo.UNKNOWN_CID, nw.getPsc());
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
new file mode 100644
index 0000000..db670f8
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -0,0 +1,657 @@
+/*
+ * 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.SpannableStringBuilder;
+import android.telephony.PhoneNumberUtils;
+
+public class PhoneNumberUtilsTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testExtractNetworkPortion() throws Exception {
+ assertEquals(
+ "+17005554141",
+ PhoneNumberUtils.extractNetworkPortion("+17005554141")
+ );
+
+ assertEquals(
+ "+17005554141",
+ PhoneNumberUtils.extractNetworkPortion("+1 (700).555-4141")
+ );
+
+ assertEquals(
+ "17005554141",
+ PhoneNumberUtils.extractNetworkPortion("1 (700).555-4141")
+ );
+
+ // This may seem wrong, but it's probably ok
+ assertEquals(
+ "17005554141*#",
+ PhoneNumberUtils.extractNetworkPortion("1 (700).555-4141*#")
+ );
+
+ assertEquals(
+ "170055541NN",
+ PhoneNumberUtils.extractNetworkPortion("1 (700).555-41NN")
+ );
+
+ assertEquals(
+ "170055541NN",
+ PhoneNumberUtils.extractNetworkPortion("1 (700).555-41NN,1234")
+ );
+
+ assertEquals(
+ "170055541NN",
+ PhoneNumberUtils.extractNetworkPortion("1 (700).555-41NN;1234")
+ );
+
+ // An MMI string is unperterbed, even though it contains a
+ // (valid in this case) embedded +
+ assertEquals(
+ "**21**17005554141#",
+ PhoneNumberUtils.extractNetworkPortion("**21**+17005554141#")
+ //TODO this is the correct result, although the above
+ //result has been returned since change 31776
+ //"**21**+17005554141#"
+ );
+
+ assertEquals("", PhoneNumberUtils.extractNetworkPortion(""));
+
+ assertEquals("", PhoneNumberUtils.extractNetworkPortion(",1234"));
+
+ byte [] b = new byte[20];
+ b[0] = (byte) 0x81; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+ b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
+ assertEquals("17005550020",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+ b[0] = (byte) 0x80; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+ b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
+ assertEquals("17005550020",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+ b[0] = (byte) 0x90; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+ b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
+ assertEquals("+17005550020",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+ b[0] = (byte) 0x91; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+ b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
+ assertEquals("+17005550020",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+ byte[] bRet = PhoneNumberUtils.networkPortionToCalledPartyBCD("+17005550020");
+ assertEquals(7, bRet.length);
+ for (int i = 0; i < 7; i++) {
+ assertEquals(b[i], bRet[i]);
+ }
+
+ bRet = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength("+17005550020");
+ assertEquals(8, bRet.length);
+ assertEquals(bRet[0], 7);
+ for (int i = 1; i < 8; i++) {
+ assertEquals(b[i - 1], bRet[i]);
+ }
+
+ bRet = PhoneNumberUtils.networkPortionToCalledPartyBCD("7005550020");
+ assertEquals("7005550020",
+ PhoneNumberUtils.calledPartyBCDToString(bRet, 0, bRet.length));
+
+ b[0] = (byte) 0x81; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+ b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xB0;
+ assertEquals("17005550020#",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+ b[0] = (byte) 0x91; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+ b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xB0;
+ assertEquals("+17005550020#",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+ b[0] = (byte) 0x81; b[1] = (byte) 0x2A; b[2] = (byte) 0xB1;
+ assertEquals("*21#",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 3));
+
+ b[0] = (byte) 0x81; b[1] = (byte) 0x2B; b[2] = (byte) 0xB1;
+ assertEquals("#21#",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 3));
+
+ b[0] = (byte) 0x91; b[1] = (byte) 0x2A; b[2] = (byte) 0xB1;
+ assertEquals("*21#+",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 3));
+
+ b[0] = (byte) 0x81; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0xFB;
+ assertEquals("**21#",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 4));
+
+ b[0] = (byte) 0x91; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0xFB;
+ assertEquals("**21#+",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 4));
+
+ b[0] = (byte) 0x81; b[1] = (byte) 0x9A; b[2] = (byte) 0xA9; b[3] = (byte) 0x71;
+ b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
+ b[8] = (byte) 0xB0;
+ assertEquals("*99*17005550020#",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
+
+ b[0] = (byte) 0x91; b[1] = (byte) 0x9A; b[2] = (byte) 0xA9; b[3] = (byte) 0x71;
+ b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
+ b[8] = (byte) 0xB0;
+ assertEquals("*99*+17005550020#",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
+
+ b[0] = (byte) 0x81; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0x1A;
+ b[4] = (byte) 0x07; b[5] = (byte) 0x50; b[6] = (byte) 0x55; b[7] = (byte) 0x00;
+ b[8] = (byte) 0x02; b[9] = (byte) 0xFB;
+ assertEquals("**21*17005550020#",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 10));
+
+ b[0] = (byte) 0x91; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0x1A;
+ b[4] = (byte) 0x07; b[5] = (byte) 0x50; b[6] = (byte) 0x55; b[7] = (byte) 0x00;
+ b[8] = (byte) 0x02; b[9] = (byte) 0xFB;
+ assertEquals("**21*+17005550020#",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 10));
+
+ b[0] = (byte) 0x81; b[1] = (byte) 0x2A; b[2] = (byte) 0xA1; b[3] = (byte) 0x71;
+ b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
+ b[8] = (byte) 0xF0;
+ assertEquals("*21*17005550020",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
+
+ b[0] = (byte) 0x91; b[1] = (byte) 0x2A; b[2] = (byte) 0xB1; b[3] = (byte) 0x71;
+ b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
+ b[8] = (byte) 0xF0;
+ assertEquals("*21#+17005550020",
+ PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
+
+ assertNull(PhoneNumberUtils.extractNetworkPortion(null));
+ assertNull(PhoneNumberUtils.extractPostDialPortion(null));
+ assertTrue(PhoneNumberUtils.compare(null, null));
+ assertFalse(PhoneNumberUtils.compare(null, "123"));
+ assertFalse(PhoneNumberUtils.compare("123", null));
+ assertNull(PhoneNumberUtils.toCallerIDMinMatch(null));
+ assertNull(PhoneNumberUtils.getStrippedReversed(null));
+ assertNull(PhoneNumberUtils.stringFromStringAndTOA(null, 1));
+ }
+
+ @SmallTest
+ public void testExtractNetworkPortionAlt() throws Exception {
+ assertEquals(
+ "+17005554141",
+ PhoneNumberUtils.extractNetworkPortionAlt("+17005554141")
+ );
+
+ assertEquals(
+ "+17005554141",
+ PhoneNumberUtils.extractNetworkPortionAlt("+1 (700).555-4141")
+ );
+
+ assertEquals(
+ "17005554141",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141")
+ );
+
+ // This may seem wrong, but it's probably ok
+ assertEquals(
+ "17005554141*#",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141*#")
+ );
+
+ assertEquals(
+ "170055541NN",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN")
+ );
+
+ assertEquals(
+ "170055541NN",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN,1234")
+ );
+
+ assertEquals(
+ "170055541NN",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN;1234")
+ );
+
+ // An MMI string is unperterbed, even though it contains a
+ // (valid in this case) embedded +
+ assertEquals(
+ "**21**+17005554141#",
+ PhoneNumberUtils.extractNetworkPortionAlt("**21**+17005554141#")
+ );
+
+ assertEquals(
+ "*31#+447966164208",
+ PhoneNumberUtils.extractNetworkPortionAlt("*31#+447966164208")
+ );
+
+ assertEquals(
+ "*31#+447966164208",
+ PhoneNumberUtils.extractNetworkPortionAlt("*31# (+44) 79 6616 4208")
+ );
+
+ assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(""));
+
+ assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(",1234"));
+
+ assertNull(PhoneNumberUtils.extractNetworkPortionAlt(null));
+ }
+
+ @SmallTest
+ public void testB() throws Exception {
+ assertEquals("", PhoneNumberUtils.extractPostDialPortion("+17005554141"));
+ assertEquals("", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-4141"));
+ assertEquals("", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN"));
+ assertEquals(",1234", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN,1234"));
+ assertEquals(";1234", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN;1234"));
+ assertEquals(";1234,;N",
+ PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN;1-2.34 ,;N"));
+ }
+
+ @SmallTest
+ public void testCompare() throws Exception {
+ // this is odd
+ assertFalse(PhoneNumberUtils.compare("", ""));
+
+ assertTrue(PhoneNumberUtils.compare("911", "911"));
+ assertFalse(PhoneNumberUtils.compare("911", "18005550911"));
+ assertTrue(PhoneNumberUtils.compare("5555", "5555"));
+ assertFalse(PhoneNumberUtils.compare("5555", "180055555555"));
+
+ assertTrue(PhoneNumberUtils.compare("+17005554141", "+17005554141"));
+ assertTrue(PhoneNumberUtils.compare("+17005554141", "+1 (700).555-4141"));
+ assertTrue(PhoneNumberUtils.compare("+17005554141", "+1 (700).555-4141,1234"));
+ assertTrue(PhoneNumberUtils.compare("+17005554141", "17005554141"));
+ assertTrue(PhoneNumberUtils.compare("+17005554141", "7005554141"));
+ assertTrue(PhoneNumberUtils.compare("+17005554141", "5554141"));
+ assertTrue(PhoneNumberUtils.compare("17005554141", "5554141"));
+ assertTrue(PhoneNumberUtils.compare("+17005554141", "01117005554141"));
+ assertTrue(PhoneNumberUtils.compare("+17005554141", "0017005554141"));
+ assertTrue(PhoneNumberUtils.compare("17005554141", "0017005554141"));
+
+
+ assertTrue(PhoneNumberUtils.compare("+17005554141", "**31#+17005554141"));
+
+ assertFalse(PhoneNumberUtils.compare("+1 999 7005554141", "+1 7005554141"));
+ assertTrue(PhoneNumberUtils.compare("011 1 7005554141", "7005554141"));
+
+ assertFalse(PhoneNumberUtils.compare("011 11 7005554141", "+17005554141"));
+
+ assertFalse(PhoneNumberUtils.compare("+17005554141", "7085882300"));
+
+ assertTrue(PhoneNumberUtils.compare("+44 207 792 3490", "0 207 792 3490"));
+
+ assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "00 207 792 3490"));
+ assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "011 207 792 3490"));
+
+ /***** FIXME's ******/
+ //
+ // MMI header should be ignored
+ assertFalse(PhoneNumberUtils.compare("+17005554141", "**31#17005554141"));
+
+ // It's too bad this is false
+ // +44 (0) 207 792 3490 is not a dialable number
+ // but it is commonly how European phone numbers are written
+ assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "+44 (0) 207 792 3490"));
+
+ // The japanese international prefix, for example, messes us up
+ // But who uses a GSM phone in Japan?
+ assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "010 44 207 792 3490"));
+
+ // The Australian one messes us up too
+ assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "0011 44 207 792 3490"));
+
+ // The Russian trunk prefix messes us up, as does current
+ // Russian area codes (which bein with 0)
+
+ assertFalse(PhoneNumberUtils.compare("+7(095)9100766", "8(095)9100766"));
+
+ // 444 is not a valid country code, but
+ // matchIntlPrefixAndCC doesnt know this
+ assertTrue(PhoneNumberUtils.compare("+444 207 792 3490", "0 207 792 3490"));
+
+ // compare SMS short code
+ assertTrue(PhoneNumberUtils.compare("404-04", "40404"));
+ }
+
+
+ @SmallTest
+ public void testToCallerIDIndexable() throws Exception {
+ assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("17005554141"));
+ assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-4141"));
+ assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-4141,1234"));
+ assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-4141;1234"));
+
+ //this seems wrong, or at least useless
+ assertEquals("NN14555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-41NN"));
+
+ //<shrug> -- these are all not useful, but not terribly wrong
+ assertEquals("", PhoneNumberUtils.toCallerIDMinMatch(""));
+ assertEquals("0032", PhoneNumberUtils.toCallerIDMinMatch("2300"));
+ assertEquals("0032+", PhoneNumberUtils.toCallerIDMinMatch("+2300"));
+ assertEquals("#130#*", PhoneNumberUtils.toCallerIDMinMatch("*#031#"));
+ }
+
+ @SmallTest
+ public void testGetIndexable() throws Exception {
+ assertEquals("14145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-4141"));
+ assertEquals("14145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-4141,1234"));
+ assertEquals("14145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-4141;1234"));
+
+ //this seems wrong, or at least useless
+ assertEquals("NN145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-41NN"));
+
+ //<shrug> -- these are all not useful, but not terribly wrong
+ assertEquals("", PhoneNumberUtils.getStrippedReversed(""));
+ assertEquals("0032", PhoneNumberUtils.getStrippedReversed("2300"));
+ assertEquals("0032+", PhoneNumberUtils.getStrippedReversed("+2300"));
+ assertEquals("#130#*", PhoneNumberUtils.getStrippedReversed("*#031#"));
+ }
+
+ @SmallTest
+ public void testNanpFormatting() {
+ SpannableStringBuilder number = new SpannableStringBuilder();
+ number.append("8005551212");
+ PhoneNumberUtils.formatNanpNumber(number);
+ assertEquals("800-555-1212", number.toString());
+
+ number.clear();
+ number.append("800555121");
+ PhoneNumberUtils.formatNanpNumber(number);
+ assertEquals("800-555-121", number.toString());
+
+ number.clear();
+ number.append("555-1212");
+ PhoneNumberUtils.formatNanpNumber(number);
+ assertEquals("555-1212", number.toString());
+
+ number.clear();
+ number.append("800-55512");
+ PhoneNumberUtils.formatNanpNumber(number);
+ assertEquals("800-555-12", number.toString());
+
+ number.clear();
+ number.append("46645");
+ PhoneNumberUtils.formatNanpNumber(number);
+ assertEquals("46645", number.toString());
+ }
+
+ @SmallTest
+ public void testConvertKeypadLettersToDigits() {
+ assertEquals("1-800-4664-411",
+ PhoneNumberUtils.convertKeypadLettersToDigits("1-800-GOOG-411"));
+ assertEquals("18004664411",
+ PhoneNumberUtils.convertKeypadLettersToDigits("1800GOOG411"));
+ assertEquals("1-800-466-4411",
+ PhoneNumberUtils.convertKeypadLettersToDigits("1-800-466-4411"));
+ assertEquals("18004664411",
+ PhoneNumberUtils.convertKeypadLettersToDigits("18004664411"));
+ assertEquals("222-333-444-555-666-7777-888-9999",
+ PhoneNumberUtils.convertKeypadLettersToDigits(
+ "ABC-DEF-GHI-JKL-MNO-PQRS-TUV-WXYZ"));
+ assertEquals("222-333-444-555-666-7777-888-9999",
+ PhoneNumberUtils.convertKeypadLettersToDigits(
+ "abc-def-ghi-jkl-mno-pqrs-tuv-wxyz"));
+ assertEquals("(800) 222-3334",
+ PhoneNumberUtils.convertKeypadLettersToDigits("(800) ABC-DEFG"));
+ }
+
+ // To run this test, the device has to be registered with network
+ public void testCheckAndProcessPlusCode() {
+ assertEquals("0118475797000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+8475797000"));
+ assertEquals("18475797000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+18475797000"));
+ assertEquals("0111234567",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+1234567"));
+ assertEquals("01123456700000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+23456700000"));
+ assertEquals("01111875767800",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+11875767800"));
+ assertEquals("8475797000,18475231753",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,+18475231753"));
+ assertEquals("0118475797000,18475231753",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+8475797000,+18475231753"));
+ assertEquals("8475797000;0118469312345",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;+8469312345"));
+ assertEquals("8475797000,0111234567",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,+1234567"));
+ assertEquals("847597000;01111875767000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("847597000;+11875767000"));
+ assertEquals("8475797000,,0118469312345",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,,+8469312345"));
+ assertEquals("8475797000;,0118469312345",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;,+8469312345"));
+ assertEquals("8475797000,;18475231753",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,;+18475231753"));
+ assertEquals("8475797000;,01111875767000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;,+11875767000"));
+ assertEquals("8475797000,;01111875767000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,;+11875767000"));
+ assertEquals("8475797000,,,01111875767000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,,,+11875767000"));
+ assertEquals("8475797000;,,01111875767000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;,,+11875767000"));
+ assertEquals("+;,8475797000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+;,8475797000"));
+ assertEquals("8475797000,",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,"));
+ assertEquals("847+579-7000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("847+579-7000"));
+ assertEquals(",8475797000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode(",8475797000"));
+ assertEquals(";;8475797000,,",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode(";;8475797000,,"));
+ assertEquals("+this+is$weird;,+",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+this+is$weird;,+"));
+ assertEquals("",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCode(""));
+ assertNull(PhoneNumberUtils.cdmaCheckAndProcessPlusCode(null));
+ }
+
+ @SmallTest
+ public void testCheckAndProcessPlusCodeByNumberFormat() {
+ assertEquals("18475797000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+ PhoneNumberUtils.FORMAT_NANP,PhoneNumberUtils.FORMAT_NANP));
+ assertEquals("+18475797000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+ PhoneNumberUtils.FORMAT_NANP,PhoneNumberUtils.FORMAT_JAPAN));
+ assertEquals("+18475797000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+ PhoneNumberUtils.FORMAT_NANP,PhoneNumberUtils.FORMAT_UNKNOWN));
+ assertEquals("+18475797000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+ PhoneNumberUtils.FORMAT_JAPAN,PhoneNumberUtils.FORMAT_JAPAN));
+ assertEquals("+18475797000",
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+ PhoneNumberUtils.FORMAT_UNKNOWN,PhoneNumberUtils.FORMAT_UNKNOWN));
+ }
+
+ /**
+ * Basic checks for the VoiceMail number.
+ */
+ @SmallTest
+ public void testWithNumberNotEqualToVoiceMail() throws Exception {
+ assertFalse(PhoneNumberUtils.isVoiceMailNumber("911"));
+ assertFalse(PhoneNumberUtils.isVoiceMailNumber("tel:911"));
+ assertFalse(PhoneNumberUtils.isVoiceMailNumber("+18001234567"));
+ assertFalse(PhoneNumberUtils.isVoiceMailNumber(""));
+ assertFalse(PhoneNumberUtils.isVoiceMailNumber(null));
+ // This test fails on a device without a sim card
+ /*TelephonyManager mTelephonyManager =
+ (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE);
+ String mVoiceMailNumber = mTelephonyManager.getDefault().getVoiceMailNumber();
+ assertTrue(PhoneNumberUtils.isVoiceMailNumber(mVoiceMailNumber));
+ */
+ }
+
+ @SmallTest
+ public void testFormatNumberToE164() {
+ // Note: ISO 3166-1 only allows upper case country codes.
+ assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "US"));
+ assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "US"));
+ assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "US"));
+ }
+
+ @SmallTest
+ public void testFormatNumber() {
+ assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("650 2910000", "US"));
+ assertEquals("223-4567", PhoneNumberUtils.formatNumber("2234567", "US"));
+ assertEquals("011 86 10 8888 0000",
+ PhoneNumberUtils.formatNumber("011861088880000", "US"));
+ assertEquals("010 8888 0000", PhoneNumberUtils.formatNumber("01088880000", "CN"));
+ // formatNumber doesn't format alpha numbers, but keep them as they are.
+ assertEquals("800-GOOG-114", PhoneNumberUtils.formatNumber("800-GOOG-114", "US"));
+ }
+
+ @SmallTest
+ public void testFormatNumber_LeadingStarAndHash() {
+ // Numbers with a leading '*' or '#' should be left unchanged.
+ assertEquals("*650 2910000", PhoneNumberUtils.formatNumber("*650 2910000", "US"));
+ assertEquals("#650 2910000", PhoneNumberUtils.formatNumber("#650 2910000", "US"));
+ assertEquals("*#650 2910000", PhoneNumberUtils.formatNumber("*#650 2910000", "US"));
+ assertEquals("#*650 2910000", PhoneNumberUtils.formatNumber("#*650 2910000", "US"));
+ assertEquals("#650*2910000", PhoneNumberUtils.formatNumber("#650*2910000", "US"));
+ assertEquals("#650*2910000", PhoneNumberUtils.formatNumber("#650*2910000", "US"));
+ assertEquals("##650 2910000", PhoneNumberUtils.formatNumber("##650 2910000", "US"));
+ assertEquals("**650 2910000", PhoneNumberUtils.formatNumber("**650 2910000", "US"));
+ }
+
+ @SmallTest
+ public void testNormalizeNumber() {
+ assertEquals("6502910000", PhoneNumberUtils.normalizeNumber("650 2910000"));
+ assertEquals("1234567", PhoneNumberUtils.normalizeNumber("12,3#4*567"));
+ assertEquals("8004664114", PhoneNumberUtils.normalizeNumber("800-GOOG-114"));
+ assertEquals("+16502910000", PhoneNumberUtils.normalizeNumber("+1 650 2910000"));
+ }
+
+ @SmallTest
+ public void testFormatDailabeNumber() {
+ // Using the phoneNumberE164's country code
+ assertEquals("(650) 291-0000",
+ PhoneNumberUtils.formatNumber("6502910000", "+16502910000", "CN"));
+ // Using the default country code for a phone number containing the IDD
+ assertEquals("011 86 10 8888 0000",
+ PhoneNumberUtils.formatNumber("011861088880000", "+861088880000", "US"));
+ assertEquals("00 86 10 8888 0000",
+ PhoneNumberUtils.formatNumber("00861088880000", "+861088880000", "GB"));
+ assertEquals("+86 10 8888 0000",
+ PhoneNumberUtils.formatNumber("+861088880000", "+861088880000", "GB"));
+ // Wrong default country, so no formatting is done
+ assertEquals("011861088880000",
+ PhoneNumberUtils.formatNumber("011861088880000", "+861088880000", "GB"));
+ // The phoneNumberE164 is null
+ assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("6502910000", null, "US"));
+ // The given number has a country code.
+ assertEquals("+1 650-291-0000", PhoneNumberUtils.formatNumber("+16502910000", null, "CN"));
+ // The given number was formatted.
+ assertEquals("650-291-0000", PhoneNumberUtils.formatNumber("650-291-0000", null, "US"));
+ // A valid Polish number should be formatted.
+ assertEquals("506 128 687", PhoneNumberUtils.formatNumber("506128687", null, "PL"));
+ // An invalid Polish number should be left as it is. Note Poland doesn't use '0' as a
+ // national prefix; therefore, the leading '0' makes the number invalid.
+ assertEquals("0506128687", PhoneNumberUtils.formatNumber("0506128687", null, "PL"));
+ // Wrong default country, so no formatting is done
+ assertEquals("011861088880000",
+ PhoneNumberUtils.formatNumber("011861088880000", "", "GB"));
+ }
+
+ @SmallTest
+ public void testIsEmergencyNumber() {
+ // There are two parallel sets of tests here: one for the
+ // regular isEmergencyNumber() method, and the other for
+ // isPotentialEmergencyNumber().
+ //
+ // (The difference is that isEmergencyNumber() will return true
+ // only if the specified number exactly matches an actual
+ // emergency number, but isPotentialEmergencyNumber() will
+ // return true if the specified number simply starts with the
+ // same digits as any actual emergency number.)
+
+ // Tests for isEmergencyNumber():
+ assertTrue(PhoneNumberUtils.isEmergencyNumber("911", "US"));
+ assertTrue(PhoneNumberUtils.isEmergencyNumber("112", "US"));
+ // The next two numbers are not valid phone numbers in the US,
+ // so do not count as emergency numbers (but they *are* "potential"
+ // emergency numbers; see below.)
+ assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345", "US"));
+ assertFalse(PhoneNumberUtils.isEmergencyNumber("11212345", "US"));
+ // A valid mobile phone number from Singapore shouldn't be classified as an emergency number
+ // in Singapore, as 911 is not an emergency number there.
+ assertFalse(PhoneNumberUtils.isEmergencyNumber("91121234", "SG"));
+ // A valid fixed-line phone number from Brazil shouldn't be classified as an emergency number
+ // in Brazil, as 112 is not an emergency number there.
+ assertFalse(PhoneNumberUtils.isEmergencyNumber("1121234567", "BR"));
+ // A valid local phone number from Brazil shouldn't be classified as an emergency number in
+ // Brazil.
+ assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345", "BR"));
+
+ // Tests for isPotentialEmergencyNumber():
+ // These first two are obviously emergency numbers:
+ assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("911", "US"));
+ assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("112", "US"));
+ // The next two numbers are not valid phone numbers in the US, but can be used to trick the
+ // system to dial 911 and 112, which are emergency numbers in the US. For the purpose of
+ // addressing that, they are also classified as "potential" emergency numbers in the US.
+ assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("91112345", "US"));
+ assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("11212345", "US"));
+
+ // A valid mobile phone number from Singapore shouldn't be classified as an emergency number
+ // in Singapore, as 911 is not an emergency number there.
+ // This test fails on devices that have ecclist property preloaded with 911.
+ // assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91121234", "SG"));
+
+ // A valid fixed-line phone number from Brazil shouldn't be classified as an emergency number
+ // in Brazil, as 112 is not an emergency number there.
+ assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("1121234567", "BR"));
+ // A valid local phone number from Brazil shouldn't be classified as an emergency number in
+ // Brazil.
+ assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91112345", "BR"));
+ }
+
+ @SmallTest
+ public void testStripSeparators() {
+ // Smoke tests which should never fail.
+ assertEquals("1234567890", PhoneNumberUtils.stripSeparators("1234567890"));
+ assertEquals("911", PhoneNumberUtils.stripSeparators("911"));
+ assertEquals("112", PhoneNumberUtils.stripSeparators("112"));
+
+ // Separators should be removed, while '+' or any other digits should not.
+ assertEquals("+16502910000", PhoneNumberUtils.stripSeparators("+1 (650) 291-0000"));
+
+ // WAIT, PAUSE should *not* be stripped
+ assertEquals("+16502910000,300;",
+ PhoneNumberUtils.stripSeparators("+1 (650) 291-0000, 300;"));
+ }
+
+ @SmallTest
+ public void testConvertAndStrip() {
+ // Smoke tests which should never fail.
+ assertEquals("1234567890", PhoneNumberUtils.convertAndStrip("1234567890"));
+ assertEquals("911", PhoneNumberUtils.convertAndStrip("911"));
+ assertEquals("112", PhoneNumberUtils.convertAndStrip("112"));
+
+ // It should convert keypad characters into digits, and strip separators
+ assertEquals("22233344455566677778889999",
+ PhoneNumberUtils.convertAndStrip("ABC DEF GHI JKL MNO PQR STUV WXYZ"));
+
+ // Test real cases.
+ assertEquals("18004664411", PhoneNumberUtils.convertAndStrip("1-800-GOOG-411"));
+ assertEquals("8002223334", PhoneNumberUtils.convertAndStrip("(800) ABC-DEFG"));
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
new file mode 100644
index 0000000..a6a0fce
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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;
+
+import android.telephony.PhoneNumberFormattingTextWatcher;
+import android.test.AndroidTestCase;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.TextWatcher;
+
+public class PhoneNumberWatcherTest extends AndroidTestCase {
+ public void testAppendChars() {
+ final String multiChars = "65012345";
+ final String formatted1 = "(650) 123-45";
+ TextWatcher textWatcher = getTextWatcher();
+ SpannableStringBuilder number = new SpannableStringBuilder();
+ // Append more than one chars
+ textWatcher.beforeTextChanged(number, 0, 0, multiChars.length());
+ number.append(multiChars);
+ Selection.setSelection(number, number.length());
+ textWatcher.onTextChanged(number, 0, 0, number.length());
+ textWatcher.afterTextChanged(number);
+ assertEquals(formatted1, number.toString());
+ assertEquals(formatted1.length(), Selection.getSelectionEnd(number));
+ // Append one chars
+ final char appendChar = '6';
+ final String formatted2 = "(650) 123-456";
+ int len = number.length();
+ textWatcher.beforeTextChanged(number, number.length(), 0, 1);
+ number.append(appendChar);
+ Selection.setSelection(number, number.length());
+ textWatcher.onTextChanged(number, len, 0, 1);
+ textWatcher.afterTextChanged(number);
+ assertEquals(formatted2, number.toString());
+ assertEquals(formatted2.length(), Selection.getSelectionEnd(number));
+ }
+
+ public void testRemoveLastChars() {
+ final String init = "65012345678";
+ final String result1 = "(650) 123-4567";
+ TextWatcher textWatcher = getTextWatcher();
+ // Remove the last char.
+ SpannableStringBuilder number = new SpannableStringBuilder(init);
+ int len = number.length();
+ textWatcher.beforeTextChanged(number, len - 1, 1, 0);
+ number.delete(len - 1, len);
+ Selection.setSelection(number, number.length());
+ textWatcher.onTextChanged(number, number.length() - 1, 1, 0);
+ textWatcher.afterTextChanged(number);
+ assertEquals(result1, number.toString());
+ assertEquals(result1.length(), Selection.getSelectionEnd(number));
+ // Remove last 5 chars
+ final String result2 = "650-123";
+ textWatcher.beforeTextChanged(number, number.length() - 4, 4, 0);
+ number.delete(number.length() - 5, number.length());
+ Selection.setSelection(number, number.length());
+ textWatcher.onTextChanged(number, number.length(), 4, 0);
+ textWatcher.afterTextChanged(number);
+ assertEquals(result2, number.toString());
+ assertEquals(result2.length(), Selection.getSelectionEnd(number));
+ }
+
+ public void testInsertChars() {
+ final String init = "650-23";
+ final String expected1 = "650-123";
+ TextWatcher textWatcher = getTextWatcher();
+
+ // Insert one char
+ SpannableStringBuilder number = new SpannableStringBuilder(init);
+ textWatcher.beforeTextChanged(number, 3, 0, 1);
+ number.insert(3, "1"); // 6501-23
+ Selection.setSelection(number, 4); // make the cursor at right of 1
+ textWatcher.onTextChanged(number, 3, 0, 1);
+ textWatcher.afterTextChanged(number);
+ assertEquals(expected1, number.toString());
+ // the cursor should still at the right of '1'
+ assertEquals(5, Selection.getSelectionEnd(number));
+
+ // Insert multiple chars
+ final String expected2 = "(650) 145-6723";
+ textWatcher.beforeTextChanged(number, 5, 0, 4);
+ number.insert(5, "4567"); // change to 650-1456723
+ Selection.setSelection(number, 9); // the cursor is at the right of '7'.
+ textWatcher.onTextChanged(number, 7, 0, 4);
+ textWatcher.afterTextChanged(number);
+ assertEquals(expected2, number.toString());
+ // the cursor should be still at the right of '7'
+ assertEquals(12, Selection.getSelectionEnd(number));
+ }
+
+ public void testStopFormatting() {
+ final String init = "(650) 123";
+ final String expected1 = "(650) 123 4";
+ TextWatcher textWatcher = getTextWatcher();
+
+ // Append space
+ SpannableStringBuilder number = new SpannableStringBuilder(init);
+ textWatcher.beforeTextChanged(number, 9, 0, 2);
+ number.insert(9, " 4"); // (6501) 23 4
+ Selection.setSelection(number, number.length()); // make the cursor at right of 4
+ textWatcher.onTextChanged(number, 9, 0, 2);
+ textWatcher.afterTextChanged(number);
+ assertEquals(expected1, number.toString());
+ // the cursor should still at the right of '1'
+ assertEquals(expected1.length(), Selection.getSelectionEnd(number));
+
+ // Delete a ')'
+ final String expected2 ="(650 123";
+ textWatcher = getTextWatcher();
+ number = new SpannableStringBuilder(init);
+ textWatcher.beforeTextChanged(number, 4, 1, 0);
+ number.delete(4, 5); // (6501 23 4
+ Selection.setSelection(number, 5); // make the cursor at right of 1
+ textWatcher.onTextChanged(number, 4, 1, 0);
+ textWatcher.afterTextChanged(number);
+ assertEquals(expected2, number.toString());
+ // the cursor should still at the right of '1'
+ assertEquals(5, Selection.getSelectionEnd(number));
+
+ // Insert a hyphen
+ final String expected3 ="(650) 12-3";
+ textWatcher = getTextWatcher();
+ number = new SpannableStringBuilder(init);
+ textWatcher.beforeTextChanged(number, 8, 0, 1);
+ number.insert(8, "-"); // (650) 12-3
+ Selection.setSelection(number, 9); // make the cursor at right of -
+ textWatcher.onTextChanged(number, 8, 0, 1);
+ textWatcher.afterTextChanged(number);
+ assertEquals(expected3, number.toString());
+ // the cursor should still at the right of '-'
+ assertEquals(9, Selection.getSelectionEnd(number));
+ }
+
+ public void testRestartFormatting() {
+ final String init = "(650) 123";
+ final String expected1 = "(650) 123 4";
+ TextWatcher textWatcher = getTextWatcher();
+
+ // Append space
+ SpannableStringBuilder number = new SpannableStringBuilder(init);
+ textWatcher.beforeTextChanged(number, 9, 0, 2);
+ number.insert(9, " 4"); // (650) 123 4
+ Selection.setSelection(number, number.length()); // make the cursor at right of 4
+ textWatcher.onTextChanged(number, 9, 0, 2);
+ textWatcher.afterTextChanged(number);
+ assertEquals(expected1, number.toString());
+ // the cursor should still at the right of '4'
+ assertEquals(expected1.length(), Selection.getSelectionEnd(number));
+
+ // Clear the current string, and start formatting again.
+ int len = number.length();
+ textWatcher.beforeTextChanged(number, 0, len, 0);
+ number.delete(0, len);
+ textWatcher.onTextChanged(number, 0, len, 0);
+ textWatcher.afterTextChanged(number);
+
+ final String expected2 = "650-1234";
+ number = new SpannableStringBuilder(init);
+ textWatcher.beforeTextChanged(number, 9, 0, 1);
+ number.insert(9, "4"); // (650) 1234
+ Selection.setSelection(number, number.length()); // make the cursor at right of 4
+ textWatcher.onTextChanged(number, 9, 0, 1);
+ textWatcher.afterTextChanged(number);
+ assertEquals(expected2, number.toString());
+ // the cursor should still at the right of '4'
+ assertEquals(expected2.length(), Selection.getSelectionEnd(number));
+ }
+
+ public void testTextChangedByOtherTextWatcher() {
+ final TextWatcher cleanupTextWatcher = new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable s) {
+ s.clear();
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count,
+ int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before,
+ int count) {
+ }
+ };
+ final String init = "(650) 123";
+ final String expected1 = "";
+ TextWatcher textWatcher = getTextWatcher();
+
+ SpannableStringBuilder number = new SpannableStringBuilder(init);
+ textWatcher.beforeTextChanged(number, 5, 0, 1);
+ number.insert(5, "4"); // (6504) 123
+ Selection.setSelection(number, 5); // make the cursor at right of 4
+ textWatcher.onTextChanged(number, 5, 0, 1);
+ number.setSpan(cleanupTextWatcher, 0, number.length(), 0);
+ textWatcher.afterTextChanged(number);
+ assertEquals(expected1, number.toString());
+ }
+
+ /**
+ * Test the case where some other component is auto-completing what the user is typing
+ */
+ public void testAutoCompleteWithFormattedNumber() {
+ String init = "650-1";
+ String expected = "+1-650-123-4567"; // Different formatting than ours
+ testReplacement(init, expected, expected);
+ }
+
+ /**
+ * Test the case where some other component is auto-completing what the user is typing
+ */
+ public void testAutoCompleteWithFormattedNameAndNumber() {
+ String init = "650-1";
+ String expected = "Test User <650-123-4567>";
+ testReplacement(init, expected, expected);
+ }
+
+ /**
+ * Test the case where some other component is auto-completing what the user is typing
+ */
+ public void testAutoCompleteWithNumericNameAndNumber() {
+ String init = "650";
+ String expected = "2nd Test User <650-123-4567>";
+ testReplacement(init, expected, expected);
+ }
+
+ /**
+ * Test the case where some other component is auto-completing what the user is typing
+ */
+ public void testAutoCompleteWithUnformattedNumber() {
+ String init = "650-1";
+ String expected = "6501234567";
+ testReplacement(init, expected, expected);
+ }
+
+ /**
+ * Test the case where some other component is auto-completing what the user is typing, where
+ * the deleted text doesn't have any formatting and neither does the replacement text: in this
+ * case the replacement text should be formatted by the PhoneNumberFormattingTextWatcher.
+ */
+ public void testAutoCompleteUnformattedWithUnformattedNumber() {
+ String init = "650";
+ String replacement = "6501234567";
+ String expected = "(650) 123-4567";
+ testReplacement(init, replacement, expected);
+
+ String init2 = "650";
+ String replacement2 = "16501234567";
+ String expected2 = "1 650-123-4567";
+ testReplacement(init2, replacement2, expected2);
+ }
+
+ /**
+ * Helper method for testing replacing the entire string with another string
+ * @param init The initial string
+ * @param expected
+ */
+ private void testReplacement(String init, String replacement, String expected) {
+ TextWatcher textWatcher = getTextWatcher();
+
+ SpannableStringBuilder number = new SpannableStringBuilder(init);
+
+ // Replace entire text with the given values
+ textWatcher.beforeTextChanged(number, 0, init.length(), replacement.length());
+ number.replace(0, init.length(), replacement, 0, replacement.length());
+ Selection.setSelection(number, replacement.length()); // move the cursor to the end
+ textWatcher.onTextChanged(number, 0, init.length(), replacement.length());
+ textWatcher.afterTextChanged(number);
+
+ assertEquals(expected, number.toString());
+ // the cursor should be still at the end
+ assertEquals(expected.length(), Selection.getSelectionEnd(number));
+ }
+
+ private TextWatcher getTextWatcher() {
+ return new PhoneNumberFormattingTextWatcher("US");
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SMSDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/SMSDispatcherTest.java
new file mode 100644
index 0000000..8a66614
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/SMSDispatcherTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.test.suitebuilder.annotation.MediumTest;
+import com.android.internal.telephony.TestPhoneNotifier;
+import com.android.internal.telephony.gsm.SmsMessage;
+import com.android.internal.telephony.test.SimulatedCommands;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.Suppress;
+
+import java.util.Iterator;
+
+/**
+ * {@hide}
+ */
+public class SMSDispatcherTest extends AndroidTestCase {
+ @MediumTest
+ public void testCMT1() throws Exception {
+ SmsMessage sms;
+ SmsHeader header;
+
+ String[] lines = new String[2];
+
+ lines[0] = "+CMT: ,158";
+ lines[1] = "07914140279510F6440A8111110301003BF56080426101748A8C0B05040B"
+ + "8423F000035502010106276170706C69636174696F6E2F766E642E776170"
+ + "2E6D6D732D6D65737361676500AF848D0185B4848C8298524F347839776F"
+ + "7547514D4141424C3641414141536741415A4B554141414141008D908918"
+ + "802B31363530323438363137392F545950453D504C4D4E008A808E028000"
+ + "88058103093A8083687474703A2F2F36";
+
+ sms = SmsMessage.newFromCMT(lines);
+ header = sms.getUserDataHeader();
+ assertNotNull(header);
+ assertNotNull(sms.getUserData());
+ assertNotNull(header.concatRef);
+ assertEquals(header.concatRef.refNumber, 85);
+ assertEquals(header.concatRef.msgCount, 2);
+ assertEquals(header.concatRef.seqNumber, 1);
+ assertEquals(header.concatRef.isEightBits, true);
+ assertNotNull(header.portAddrs);
+ assertEquals(header.portAddrs.destPort, 2948);
+ assertEquals(header.portAddrs.origPort, 9200);
+ assertEquals(header.portAddrs.areEightBits, false);
+ }
+
+ @MediumTest
+ public void testCMT2() throws Exception {
+ SmsMessage sms;
+ SmsHeader header;
+
+ String[] lines = new String[2];
+
+ lines[0] = "+CMT: ,77";
+ lines[1] = "07914140279510F6440A8111110301003BF56080426101848A3B0B05040B8423F"
+ + "00003550202362E3130322E3137312E3135302F524F347839776F7547514D4141"
+ + "424C3641414141536741415A4B55414141414100";
+
+ sms = SmsMessage.newFromCMT(lines);
+ header = sms.getUserDataHeader();
+ assertNotNull(header);
+ assertNotNull(sms.getUserData());
+ assertNotNull(header.concatRef);
+ assertEquals(header.concatRef.refNumber, 85);
+ assertEquals(header.concatRef.msgCount, 2);
+ assertEquals(header.concatRef.seqNumber, 2);
+ assertEquals(header.concatRef.isEightBits, true);
+ assertNotNull(header.portAddrs);
+ assertEquals(header.portAddrs.destPort, 2948);
+ assertEquals(header.portAddrs.origPort, 9200);
+ assertEquals(header.portAddrs.areEightBits, false);
+ }
+
+ @MediumTest
+ public void testEfRecord() throws Exception {
+ SmsMessage sms;
+
+ String s = "03029111000c9194981492631000f269206190022000a053e4534a05358bd3"
+ + "69f05804259da0219418a40641536a110a0aea408080604028180e888462c1"
+ + "50341c0f484432a1542c174c46b3e1743c9f9068442a994ea8946ac56ab95e"
+ + "b0986c46abd96eb89c6ec7ebf97ec0a070482c1a8fc8a472c96c3a9fd0a874"
+ + "4aad5aafd8ac76cbed7abfe0b0784c2e9bcfe8b47acd6ebbdff0b87c4eafdb"
+ + "eff8bc7ecfeffbffffffffffffffffffffffffffff";
+ byte[] data = IccUtils.hexStringToBytes(s);
+
+ sms = SmsMessage.createFromEfRecord(1, data);
+ assertNotNull(sms.getMessageBody());
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
new file mode 100644
index 0000000..609e768
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.ServiceManager;
+import android.test.suitebuilder.annotation.Suppress;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@Suppress
+public class SimPhoneBookTest extends TestCase {
+
+ public void testBasic() throws Exception {
+ IIccPhoneBook simPhoneBook =
+ IIccPhoneBook.Stub.asInterface(ServiceManager.getService("simphonebook"));
+ assertNotNull(simPhoneBook);
+
+ int size[] = simPhoneBook.getAdnRecordsSize(IccConstants.EF_ADN);
+ assertNotNull(size);
+ assertEquals(3, size.length);
+ assertEquals(size[0] * size[2], size[1]);
+ assertTrue(size[2] >= 100);
+
+ List<AdnRecord> adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+ // do it twice cause the second time shall read from cache only
+ adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+ assertNotNull(adnRecordList);
+
+ // Test for phone book update
+ int adnIndex, listIndex = 0;
+ AdnRecord originalAdn = null;
+ // We need to maintain the state of the SIM before and after the test.
+ // Since this test doesn't mock the SIM we try to get a valid ADN record,
+ // for 3 tries and if this fails, we bail out.
+ for (adnIndex = 3 ; adnIndex >= 1; adnIndex--) {
+ listIndex = adnIndex - 1; // listIndex is zero based.
+ originalAdn = adnRecordList.get(listIndex);
+ assertNotNull("Original Adn is Null.", originalAdn);
+ assertNotNull("Original Adn alpha tag is null.", originalAdn.getAlphaTag());
+ assertNotNull("Original Adn number is null.", originalAdn.getNumber());
+
+ if (originalAdn.getNumber().length() > 0 &&
+ originalAdn.getAlphaTag().length() > 0) {
+ break;
+ }
+ }
+ if (adnIndex == 0) return;
+
+ AdnRecord emptyAdn = new AdnRecord("", "");
+ AdnRecord firstAdn = new AdnRecord("John", "4085550101");
+ AdnRecord secondAdn = new AdnRecord("Andy", "6505550102");
+ String pin2 = null;
+
+ // udpate by index
+ boolean success = simPhoneBook.updateAdnRecordsInEfByIndex(IccConstants.EF_ADN,
+ firstAdn.getAlphaTag(), firstAdn.getNumber(), adnIndex, pin2);
+ adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+ AdnRecord tmpAdn = adnRecordList.get(listIndex);
+ assertTrue(success);
+ assertTrue(firstAdn.isEqual(tmpAdn));
+
+ // replace by search
+ success = simPhoneBook.updateAdnRecordsInEfBySearch(IccConstants.EF_ADN,
+ firstAdn.getAlphaTag(), firstAdn.getNumber(),
+ secondAdn.getAlphaTag(), secondAdn.getNumber(), pin2);
+ adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+ tmpAdn = adnRecordList.get(listIndex);
+ assertTrue(success);
+ assertFalse(firstAdn.isEqual(tmpAdn));
+ assertTrue(secondAdn.isEqual(tmpAdn));
+
+ // erase be search
+ success = simPhoneBook.updateAdnRecordsInEfBySearch(IccConstants.EF_ADN,
+ secondAdn.getAlphaTag(), secondAdn.getNumber(),
+ emptyAdn.getAlphaTag(), emptyAdn.getNumber(), pin2);
+ adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+ tmpAdn = adnRecordList.get(listIndex);
+ assertTrue(success);
+ assertTrue(tmpAdn.isEmpty());
+
+ // restore the orginial adn
+ success = simPhoneBook.updateAdnRecordsInEfByIndex(IccConstants.EF_ADN,
+ originalAdn.getAlphaTag(), originalAdn.getNumber(), adnIndex,
+ pin2);
+ adnRecordList = simPhoneBook.getAdnRecordsInEf(IccConstants.EF_ADN);
+ tmpAdn = adnRecordList.get(listIndex);
+ assertTrue(success);
+ assertTrue(originalAdn.isEqual(tmpAdn));
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java
new file mode 100644
index 0000000..1609680
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.ServiceManager;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.Suppress;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+public class SimSmsTest extends TestCase {
+
+ @MediumTest
+ @Suppress // TODO: suppress this test for now since it doesn't work on the emulator
+ public void testBasic() throws Exception {
+
+ ISms sms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ assertNotNull(sms);
+
+ List<SmsRawData> records = sms.getAllMessagesFromIccEf();
+ assertNotNull(records);
+ assertTrue(records.size() >= 0);
+
+ int firstNullIndex = -1;
+ int firstValidIndex = -1;
+ byte[] pdu = null;
+ for (int i = 0; i < records.size(); i++) {
+ SmsRawData data = records.get(i);
+ if (data != null && firstValidIndex == -1) {
+ firstValidIndex = i;
+ pdu = data.getBytes();
+ }
+ if (data == null && firstNullIndex == -1) {
+ firstNullIndex = i;
+ }
+ if (firstNullIndex != -1 && firstValidIndex != -1) {
+ break;
+ }
+ }
+ if (firstNullIndex == -1 || firstValidIndex == -1)
+ return;
+ assertNotNull(pdu);
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
new file mode 100644
index 0000000..ef62d85
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.android.internal.telephony.gsm.SimTlv;
+import com.android.internal.telephony.IccUtils;
+import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+
+public class SimUtilsTest extends TestCase {
+
+ @SmallTest
+ public void testBasic() throws Exception {
+ byte[] data, data2;
+
+ /*
+ * bcdToString()
+ */
+
+ // An EF[ICCID] record
+ data = IccUtils.hexStringToBytes("981062400510444868f2");
+ assertEquals("8901260450014484862", IccUtils.bcdToString(data, 0, data.length));
+
+ // skip the first and last bytes
+ assertEquals("0126045001448486", IccUtils.bcdToString(data, 1, data.length - 2));
+
+ // Stops on invalid BCD value
+ data = IccUtils.hexStringToBytes("98E062400510444868f2");
+ assertEquals("890", IccUtils.bcdToString(data, 0, data.length));
+
+ // skip the high nibble 'F' since some PLMNs have it
+ data = IccUtils.hexStringToBytes("98F062400510444868f2");
+ assertEquals("890260450014484862", IccUtils.bcdToString(data, 0, data.length));
+
+ /*
+ * gsmBcdByteToInt()
+ */
+
+ assertEquals(98, IccUtils.gsmBcdByteToInt((byte) 0x89));
+
+ // Out of range is treated as 0
+ assertEquals(8, IccUtils.gsmBcdByteToInt((byte) 0x8c));
+
+ /*
+ * cdmaBcdByteToInt()
+ */
+
+ assertEquals(89, IccUtils.cdmaBcdByteToInt((byte) 0x89));
+
+ // Out of range is treated as 0
+ assertEquals(80, IccUtils.cdmaBcdByteToInt((byte) 0x8c));
+
+ /*
+ * adnStringFieldToString()
+ */
+
+
+ data = IccUtils.hexStringToBytes("00566f696365204d61696c07918150367742f3ffffffffffff");
+ // Again, skip prepended 0
+ // (this is an EF[ADN] record)
+ assertEquals("Voice Mail", IccUtils.adnStringFieldToString(data, 1, data.length - 15));
+
+ data = IccUtils.hexStringToBytes("809673539A5764002F004DFFFFFFFFFF");
+ // (this is from an EF[ADN] record)
+ assertEquals("\u9673\u539A\u5764/M", IccUtils.adnStringFieldToString(data, 0, data.length));
+
+ data = IccUtils.hexStringToBytes("810A01566fec6365204de0696cFFFFFF");
+ // (this is made up to test since I don't have a real one)
+ assertEquals("Vo\u00ECce M\u00E0il", IccUtils.adnStringFieldToString(data, 0, data.length));
+
+ data = IccUtils.hexStringToBytes("820505302D82d32d31");
+ // Example from 3GPP TS 11.11 V18.1.3.0 annex B
+ assertEquals("-\u0532\u0583-1", IccUtils.adnStringFieldToString(data, 0, data.length));
+ }
+
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
new file mode 100644
index 0000000..b848657
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2010 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.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import com.android.internal.telephony.SmsConstants;
+
+import java.util.Random;
+
+/**
+ * Test cases to verify selection of the optimal 7 bit encoding tables
+ * (for all combinations of enabled national language tables) for messages
+ * containing Turkish, Spanish, Portuguese, Greek, and other symbols
+ * present in the GSM default and national language tables defined in
+ * 3GPP TS 23.038. Also verifies correct SMS encoding for CDMA, which only
+ * supports the GSM 7 bit default alphabet, ASCII 8 bit, and UCS-2.
+ * Tests both encoding variations: unsupported characters mapped to space,
+ * and unsupported characters force entire message to UCS-2.
+ */
+public class SmsMessageBodyTest extends AndroidTestCase {
+ private static final String TAG = "SmsMessageBodyTest";
+
+ // ASCII chars in the GSM 7 bit default alphabet
+ private static final String sAsciiChars = "@$_ !\"#%&'()*+,-./0123456789" +
+ ":;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n\r";
+
+ // Unicode chars in the GSM 7 bit default alphabet and both locking shift tables
+ private static final String sGsmDefaultChars = "\u00a3\u00a5\u00e9\u00c7\u0394\u00c9" +
+ "\u00dc\u00a7\u00fc\u00e0";
+
+ // Unicode chars in the GSM 7 bit default table and Turkish locking shift tables
+ private static final String sGsmDefaultAndTurkishTables = "\u00f9\u00f2\u00c5\u00e5\u00df" +
+ "\u00a4\u00c4\u00d6\u00d1\u00e4\u00f6\u00f1";
+
+ // Unicode chars in the GSM 7 bit default table but not the locking shift tables
+ private static final String sGsmDefaultTableOnly = "\u00e8\u00ec\u00d8\u00f8\u00c6\u00e6" +
+ "\u00a1\u00bf";
+
+ // ASCII chars in the GSM default extension table
+ private static final String sGsmExtendedAsciiChars = "{}[]\f";
+
+ // chars in GSM default extension table and Portuguese locking shift table
+ private static final String sGsmExtendedPortugueseLocking = "^\\|~";
+
+ // Euro currency symbol
+ private static final String sGsmExtendedEuroSymbol = "\u20ac";
+
+ // CJK ideographs, Hiragana, Katakana, full width letters, Cyrillic, etc.
+ private static final String sUnicodeChars = "\u4e00\u4e01\u4e02\u4e03" +
+ "\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d" +
+ "\u4e0e\u4e0f\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048" +
+ "\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8" +
+ "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18" +
+ "\uff70\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78" +
+ "\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408" +
+ "\u00a2\u00a9\u00ae\u2122";
+
+ // chars in Turkish single shift and locking shift tables
+ private static final String sTurkishChars = "\u0131\u011e\u011f\u015e\u015f\u0130";
+
+ // chars in Spanish single shift table and Portuguese single and locking shift tables
+ private static final String sPortugueseAndSpanishChars = "\u00c1\u00e1\u00cd\u00ed"
+ + "\u00d3\u00f3\u00da\u00fa";
+
+ // chars in all national language tables but not in the standard GSM alphabets
+ private static final String sNationalLanguageTablesOnly = "\u00e7";
+
+ // chars in Portuguese single shift and locking shift tables
+ private static final String sPortugueseChars = "\u00ea\u00d4\u00f4\u00c0\u00c2\u00e2"
+ + "\u00ca\u00c3\u00d5\u00e3\u00f5";
+
+ // chars in Portuguese locking shift table only
+ private static final String sPortugueseLockingShiftChars = "\u00aa\u221e\u00ba`";
+
+ // Greek letters in GSM alphabet missing from Portuguese locking and single shift tables
+ private static final String sGreekLettersNotInPortugueseTables = "\u039b\u039e";
+
+ // Greek letters in GSM alphabet and Portuguese single shift (but not locking shift) table
+ private static final String sGreekLettersInPortugueseShiftTable =
+ "\u03a6\u0393\u03a9\u03a0\u03a8\u03a3\u0398";
+
+ // List of classes of characters in SMS tables
+ private static final String[] sCharacterClasses = {
+ sGsmExtendedAsciiChars,
+ sGsmExtendedPortugueseLocking,
+ sGsmDefaultChars,
+ sGsmDefaultAndTurkishTables,
+ sGsmDefaultTableOnly,
+ sGsmExtendedEuroSymbol,
+ sUnicodeChars,
+ sTurkishChars,
+ sPortugueseChars,
+ sPortugueseLockingShiftChars,
+ sPortugueseAndSpanishChars,
+ sGreekLettersNotInPortugueseTables,
+ sGreekLettersInPortugueseShiftTable,
+ sNationalLanguageTablesOnly,
+ sAsciiChars
+ };
+
+ private static final int sNumCharacterClasses = sCharacterClasses.length;
+
+ // For each character class, whether it is present in a particular char table.
+ // First three entries are locking shift tables, followed by four single shift tables
+ private static final boolean[][] sCharClassPresenceInTables = {
+ // ASCII chars in all GSM extension tables
+ {false, false, false, true, true, true, true},
+ // ASCII chars in all GSM extension tables and Portuguese locking shift table
+ {false, false, true, true, true, true, true},
+ // non-ASCII chars in GSM default alphabet and all locking tables
+ {true, true, true, false, false, false, false},
+ // non-ASCII chars in GSM default alphabet and Turkish locking shift table
+ {true, true, false, false, false, false, false},
+ // non-ASCII chars in GSM default alphabet table only
+ {true, false, false, false, false, false, false},
+ // Euro symbol is present in several tables
+ {false, true, true, true, true, true, true},
+ // Unicode characters not present in any 7 bit tables
+ {false, false, false, false, false, false, false},
+ // Characters specific to Turkish language
+ {false, true, false, false, true, false, false},
+ // Characters in Portuguese single shift and locking shift tables
+ {false, false, true, false, false, false, true},
+ // Characters in Portuguese locking shift table only
+ {false, false, true, false, false, false, false},
+ // Chars in Spanish single shift and Portuguese single and locking shift tables
+ {false, false, true, false, false, true, true},
+ // Greek letters in GSM default alphabet missing from Portuguese tables
+ {true, true, false, false, false, false, false},
+ // Greek letters in GSM alphabet and Portuguese single shift table
+ {true, true, false, false, false, false, true},
+ // Chars in all national language tables but not the standard GSM tables
+ {false, true, true, false, true, true, true},
+ // ASCII chars in GSM default alphabet
+ {true, true, true, false, false, false, false}
+ };
+
+ private static final int sTestLengthCount = 12;
+
+ private static final int[] sSeptetTestLengths =
+ { 0, 1, 2, 80, 159, 160, 161, 240, 305, 306, 307, 320};
+
+ private static final int[] sUnicodeTestLengths =
+ { 0, 1, 2, 35, 69, 70, 71, 100, 133, 134, 135, 160};
+
+ private static final int[] sTestMsgCounts =
+ { 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3};
+
+ private static final int[] sSeptetUnitsRemaining =
+ {160, 159, 158, 80, 1, 0, 145, 66, 1, 0, 152, 139};
+
+ private static final int[] sUnicodeUnitsRemaining =
+ { 70, 69, 68, 35, 1, 0, 63, 34, 1, 0, 66, 41};
+
+ // Combinations of enabled GSM national language single shift tables
+ private static final int[][] sEnabledSingleShiftTables = {
+ {}, // GSM default alphabet only
+ {1}, // Turkish (single shift only)
+ {1}, // Turkish (single and locking shift)
+ {2}, // Spanish
+ {3}, // Portuguese (single shift only)
+ {3}, // Portuguese (single and locking shift)
+ {1, 2}, // Turkish + Spanish (single shift only)
+ {1, 2}, // Turkish + Spanish (single and locking shift)
+ {1, 3}, // Turkish + Portuguese (single shift only)
+ {1, 3}, // Turkish + Portuguese (single and locking shift)
+ {2, 3}, // Spanish + Portuguese (single shift only)
+ {2, 3}, // Spanish + Portuguese (single and locking shift)
+ {1, 2, 3}, // Turkish, Spanish, Portuguese (single shift only)
+ {1, 2, 3}, // Turkish, Spanish, Portuguese (single and locking shift)
+ {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} // all language tables
+ };
+
+ // Combinations of enabled GSM national language locking shift tables
+ private static final int[][] sEnabledLockingShiftTables = {
+ {}, // GSM default alphabet only
+ {}, // Turkish (single shift only)
+ {1}, // Turkish (single and locking shift)
+ {}, // Spanish (no locking shift table)
+ {}, // Portuguese (single shift only)
+ {3}, // Portuguese (single and locking shift)
+ {}, // Turkish + Spanish (single shift only)
+ {1}, // Turkish + Spanish (single and locking shift)
+ {}, // Turkish + Portuguese (single shift only)
+ {1, 3}, // Turkish + Portuguese (single and locking shift)
+ {}, // Spanish + Portuguese (single shift only)
+ {3}, // Spanish + Portuguese (single and locking shift)
+ {}, // Turkish, Spanish, Portuguese (single shift only)
+ {1, 3}, // Turkish, Spanish, Portuguese (single and locking shift)
+ {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} // all language tables
+ };
+
+ // LanguagePair counter indexes to check for each entry above
+ private static final int[][] sLanguagePairIndexesByEnabledIndex = {
+ {0}, // default tables only
+ {0, 1}, // Turkish (single shift only)
+ {0, 1, 4, 5}, // Turkish (single and locking shift)
+ {0, 2}, // Spanish
+ {0, 3}, // Portuguese (single shift only)
+ {0, 3, 8, 11}, // Portuguese (single and locking shift)
+ {0, 1, 2}, // Turkish + Spanish (single shift only)
+ {0, 1, 2, 4, 5, 6}, // Turkish + Spanish (single and locking shift)
+ {0, 1, 3}, // Turkish + Portuguese (single shift only)
+ {0, 1, 3, 4, 5, 7, 8, 9, 11}, // Turkish + Portuguese (single and locking shift)
+ {0, 2, 3}, // Spanish + Portuguese (single shift only)
+ {0, 2, 3, 8, 10, 11}, // Spanish + Portuguese (single and locking shift)
+ {0, 1, 2, 3}, // all languages (single shift only)
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, // all languages (single and locking shift)
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} // all languages (no Indic chars in test)
+ };
+
+ /**
+ * User data header requires one octet for length. Count as one septet, because
+ * all combinations of header elements below will have at least one free bit
+ * when padding to the nearest septet boundary.
+ */
+ private static final int UDH_SEPTET_COST_LENGTH = 1;
+
+ /**
+ * Using a non-default language locking shift table OR single shift table
+ * requires a user data header of 3 octets, or 4 septets, plus UDH length.
+ */
+ private static final int UDH_SEPTET_COST_ONE_SHIFT_TABLE = 4;
+
+ /**
+ * Using a non-default language locking shift table AND single shift table
+ * requires a user data header of 6 octets, or 7 septets, plus UDH length.
+ */
+ private static final int UDH_SEPTET_COST_TWO_SHIFT_TABLES = 7;
+
+ /**
+ * Multi-part messages require a user data header of 5 octets, or 6 septets,
+ * plus UDH length.
+ */
+ private static final int UDH_SEPTET_COST_CONCATENATED_MESSAGE = 6;
+
+ @SmallTest
+ public void testCalcLengthAscii() throws Exception {
+ StringBuilder sb = new StringBuilder(320);
+ int[] values = {0, 0, 0, SmsConstants.ENCODING_7BIT, 0, 0};
+ int startPos = 0;
+ int asciiCharsLen = sAsciiChars.length();
+
+ for (int i = 0; i < sTestLengthCount; i++) {
+ int len = sSeptetTestLengths[i];
+ assertTrue(sb.length() <= len);
+
+ while (sb.length() < len) {
+ int addCount = len - sb.length();
+ int endPos = (asciiCharsLen - startPos > addCount) ?
+ (startPos + addCount) : asciiCharsLen;
+ sb.append(sAsciiChars, startPos, endPos);
+ startPos = (endPos == asciiCharsLen) ? 0 : endPos;
+ }
+ assertEquals(len, sb.length());
+
+ String testStr = sb.toString();
+ values[0] = sTestMsgCounts[i];
+ values[1] = len;
+ values[2] = sSeptetUnitsRemaining[i];
+
+ callGsmLengthMethods(testStr, false, values);
+ callGsmLengthMethods(testStr, true, values);
+ callCdmaLengthMethods(testStr, false, values);
+ callCdmaLengthMethods(testStr, true, values);
+ }
+ }
+
+ @SmallTest
+ public void testCalcLengthUnicode() throws Exception {
+ StringBuilder sb = new StringBuilder(160);
+ int[] values = {0, 0, 0, SmsConstants.ENCODING_16BIT, 0, 0};
+ int[] values7bit = {1, 0, 0, SmsConstants.ENCODING_7BIT, 0, 0};
+ int startPos = 0;
+ int unicodeCharsLen = sUnicodeChars.length();
+
+ // start with length 1: empty string uses ENCODING_7BIT
+ for (int i = 1; i < sTestLengthCount; i++) {
+ int len = sUnicodeTestLengths[i];
+ assertTrue(sb.length() <= len);
+
+ while (sb.length() < len) {
+ int addCount = len - sb.length();
+ int endPos = (unicodeCharsLen - startPos > addCount) ?
+ (startPos + addCount) : unicodeCharsLen;
+ sb.append(sUnicodeChars, startPos, endPos);
+ startPos = (endPos == unicodeCharsLen) ? 0 : endPos;
+ }
+ assertEquals(len, sb.length());
+
+ String testStr = sb.toString();
+ values[0] = sTestMsgCounts[i];
+ values[1] = len;
+ values[2] = sUnicodeUnitsRemaining[i];
+ values7bit[1] = len;
+ values7bit[2] = SmsConstants.MAX_USER_DATA_SEPTETS - len;
+
+ callGsmLengthMethods(testStr, false, values);
+ callCdmaLengthMethods(testStr, false, values);
+ callGsmLengthMethods(testStr, true, values7bit);
+ callCdmaLengthMethods(testStr, true, values7bit);
+ }
+ }
+
+ private static class LanguagePair {
+ // index is 2 for Portuguese locking shift because there is no Spanish locking shift table
+ private final int langTableIndex;
+ private final int langShiftTableIndex;
+ int length;
+ int missingChars7bit;
+
+ LanguagePair(int langTable, int langShiftTable) {
+ langTableIndex = langTable;
+ langShiftTableIndex = langShiftTable;
+ }
+
+ void clear() {
+ length = 0;
+ missingChars7bit = 0;
+ }
+
+ void addChar(boolean[] charClassTableRow) {
+ if (charClassTableRow[langTableIndex]) {
+ length++;
+ } else if (charClassTableRow[3 + langShiftTableIndex]) {
+ length += 2;
+ } else {
+ length++; // use ' ' for unmapped char in 7 bit only mode
+ missingChars7bit++;
+ }
+ }
+ }
+
+ private static class CounterHelper {
+ LanguagePair[] mCounters;
+ int[] mStatsCounters;
+ int mUnicodeCounter;
+
+ CounterHelper() {
+ mCounters = new LanguagePair[12];
+ mStatsCounters = new int[12];
+ for (int i = 0; i < 12; i++) {
+ mCounters[i] = new LanguagePair(i/4, i%4);
+ }
+ }
+
+ void clear() {
+ // Note: don't clear stats counters
+ for (int i = 0; i < 12; i++) {
+ mCounters[i].clear();
+ }
+ }
+
+ void addChar(int charClass) {
+ boolean[] charClassTableRow = sCharClassPresenceInTables[charClass];
+ for (int i = 0; i < 12; i++) {
+ mCounters[i].addChar(charClassTableRow);
+ }
+ }
+
+ void fillData(int enabledLangsIndex, boolean use7bitOnly, int[] values, int length) {
+ int[] languagePairs = sLanguagePairIndexesByEnabledIndex[enabledLangsIndex];
+ int minNumSeptets = Integer.MAX_VALUE;
+ int minNumSeptetsWithHeader = Integer.MAX_VALUE;
+ int minNumMissingChars = Integer.MAX_VALUE;
+ int langIndex = -1;
+ int langShiftIndex = -1;
+ for (int i : languagePairs) {
+ LanguagePair pair = mCounters[i];
+ int udhLength = 0;
+ if (i != 0) {
+ udhLength = UDH_SEPTET_COST_LENGTH;
+ if (i < 4 || i % 4 == 0) {
+ udhLength += UDH_SEPTET_COST_ONE_SHIFT_TABLE;
+ } else {
+ udhLength += UDH_SEPTET_COST_TWO_SHIFT_TABLES;
+ }
+ }
+ int numSeptetsWithHeader;
+ if (pair.length > (SmsConstants.MAX_USER_DATA_SEPTETS - udhLength)) {
+ if (udhLength == 0) {
+ udhLength = UDH_SEPTET_COST_LENGTH;
+ }
+ udhLength += UDH_SEPTET_COST_CONCATENATED_MESSAGE;
+ int septetsPerPart = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength;
+ int msgCount = (pair.length + septetsPerPart - 1) / septetsPerPart;
+ numSeptetsWithHeader = udhLength * msgCount + pair.length;
+ } else {
+ numSeptetsWithHeader = udhLength + pair.length;
+ }
+
+ if (use7bitOnly) {
+ if (pair.missingChars7bit < minNumMissingChars || (pair.missingChars7bit ==
+ minNumMissingChars && numSeptetsWithHeader < minNumSeptetsWithHeader)) {
+ minNumSeptets = pair.length;
+ minNumSeptetsWithHeader = numSeptetsWithHeader;
+ minNumMissingChars = pair.missingChars7bit;
+ langIndex = pair.langTableIndex;
+ langShiftIndex = pair.langShiftTableIndex;
+ }
+ } else {
+ if (pair.missingChars7bit == 0 && numSeptetsWithHeader < minNumSeptetsWithHeader) {
+ minNumSeptets = pair.length;
+ minNumSeptetsWithHeader = numSeptetsWithHeader;
+ langIndex = pair.langTableIndex;
+ langShiftIndex = pair.langShiftTableIndex;
+ }
+ }
+ }
+ if (langIndex == -1) {
+ // nothing matches, use values for Unicode
+ int byteCount = length * 2;
+ if (byteCount > SmsConstants.MAX_USER_DATA_BYTES) {
+ values[0] = (byteCount + SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER - 1) /
+ SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+ values[2] = ((values[0] * SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER) -
+ byteCount) / 2;
+ } else {
+ values[0] = 1;
+ values[2] = (SmsConstants.MAX_USER_DATA_BYTES - byteCount) / 2;
+ }
+ values[1] = length;
+ values[3] = SmsConstants.ENCODING_16BIT;
+ values[4] = 0;
+ values[5] = 0;
+ mUnicodeCounter++;
+ } else {
+ int udhLength = 0;
+ if (langIndex != 0 || langShiftIndex != 0) {
+ udhLength = UDH_SEPTET_COST_LENGTH;
+ if (langIndex == 0 || langShiftIndex == 0) {
+ udhLength += UDH_SEPTET_COST_ONE_SHIFT_TABLE;
+ } else {
+ udhLength += UDH_SEPTET_COST_TWO_SHIFT_TABLES;
+ }
+ }
+ int msgCount;
+ if (minNumSeptets > (SmsConstants.MAX_USER_DATA_SEPTETS - udhLength)) {
+ if (udhLength == 0) {
+ udhLength = UDH_SEPTET_COST_LENGTH;
+ }
+ udhLength += UDH_SEPTET_COST_CONCATENATED_MESSAGE;
+ int septetsPerPart = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength;
+ msgCount = (minNumSeptets + septetsPerPart - 1) / septetsPerPart;
+ } else {
+ msgCount = 1;
+ }
+ values[0] = msgCount;
+ values[1] = minNumSeptets;
+ values[2] = (values[0] * (SmsConstants.MAX_USER_DATA_SEPTETS - udhLength)) -
+ minNumSeptets;
+ values[3] = SmsConstants.ENCODING_7BIT;
+ values[4] = (langIndex == 2 ? 3 : langIndex); // Portuguese is code 3, index 2
+ values[5] = langShiftIndex;
+ assertEquals("minNumSeptetsWithHeader", minNumSeptetsWithHeader,
+ udhLength * msgCount + minNumSeptets);
+ mStatsCounters[langIndex * 4 + langShiftIndex]++;
+ }
+ }
+
+ void printStats() {
+ Log.d(TAG, "Unicode selection count: " + mUnicodeCounter);
+ for (int i = 0; i < 12; i++) {
+ Log.d(TAG, "Language pair index " + i + " count: " + mStatsCounters[i]);
+ }
+ }
+ }
+
+ @LargeTest
+ public void testCalcLengthMixed7bit() throws Exception {
+ StringBuilder sb = new StringBuilder(320);
+ CounterHelper ch = new CounterHelper();
+ Random r = new Random(0x4321); // use the same seed for reproducibility
+ int[] expectedValues = new int[6];
+ int[] origLockingShiftTables = GsmAlphabet.getEnabledLockingShiftTables();
+ int[] origSingleShiftTables = GsmAlphabet.getEnabledSingleShiftTables();
+ int enabledLanguagesTestCases = sEnabledSingleShiftTables.length;
+ long startTime = System.currentTimeMillis();
+
+ // Repeat for 10 test runs
+ for (int run = 0; run < 10; run++) {
+ sb.setLength(0);
+ ch.clear();
+ int unicodeOnlyCount = 0;
+
+ // Test incrementally from 1 to 320 character random messages
+ for (int i = 1; i < 320; i++) {
+ // 1% chance to add from each special character class, else add an ASCII char
+ int charClass = r.nextInt(100);
+ if (charClass >= sNumCharacterClasses) {
+ charClass = sNumCharacterClasses - 1; // last class is ASCII
+ }
+ int classLength = sCharacterClasses[charClass].length();
+ char nextChar = sCharacterClasses[charClass].charAt(r.nextInt(classLength));
+ sb.append(nextChar);
+ ch.addChar(charClass);
+
+// if (i % 20 == 0) {
+// Log.d(TAG, "test string: " + sb);
+// }
+
+ // Test string against all combinations of enabled languages
+ boolean unicodeOnly = true;
+ for (int j = 0; j < enabledLanguagesTestCases; j++) {
+ GsmAlphabet.setEnabledSingleShiftTables(sEnabledSingleShiftTables[j]);
+ GsmAlphabet.setEnabledLockingShiftTables(sEnabledLockingShiftTables[j]);
+ ch.fillData(j, false, expectedValues, i);
+ if (expectedValues[3] == SmsConstants.ENCODING_7BIT) {
+ unicodeOnly = false;
+ }
+ callGsmLengthMethods(sb, false, expectedValues);
+ // test 7 bit only mode
+ ch.fillData(j, true, expectedValues, i);
+ callGsmLengthMethods(sb, true, expectedValues);
+ }
+ // after 10 iterations with a Unicode-only string, skip to next test string
+ // so we can spend more time testing strings that do encode into 7 bits.
+ if (unicodeOnly && ++unicodeOnlyCount == 10) {
+// Log.d(TAG, "Unicode only: skipping to next test string");
+ break;
+ }
+ }
+ }
+ ch.printStats();
+ Log.d(TAG, "Completed in " + (System.currentTimeMillis() - startTime) + " ms");
+ GsmAlphabet.setEnabledLockingShiftTables(origLockingShiftTables);
+ GsmAlphabet.setEnabledSingleShiftTables(origSingleShiftTables);
+ }
+
+ private void callGsmLengthMethods(CharSequence msgBody, boolean use7bitOnly,
+ int[] expectedValues)
+ {
+ // deprecated GSM-specific method
+ int[] values = android.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly);
+ assertEquals("msgCount", expectedValues[0], values[0]);
+ assertEquals("codeUnitCount", expectedValues[1], values[1]);
+ assertEquals("codeUnitsRemaining", expectedValues[2], values[2]);
+ assertEquals("codeUnitSize", expectedValues[3], values[3]);
+
+ int activePhone = TelephonyManager.getDefault().getPhoneType();
+ if (TelephonyManager.PHONE_TYPE_GSM == activePhone) {
+ values = android.telephony.SmsMessage.calculateLength(msgBody, use7bitOnly);
+ assertEquals("msgCount", expectedValues[0], values[0]);
+ assertEquals("codeUnitCount", expectedValues[1], values[1]);
+ assertEquals("codeUnitsRemaining", expectedValues[2], values[2]);
+ assertEquals("codeUnitSize", expectedValues[3], values[3]);
+ }
+
+ GsmAlphabet.TextEncodingDetails ted =
+ com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly);
+ assertEquals("msgCount", expectedValues[0], ted.msgCount);
+ assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount);
+ assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining);
+ assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize);
+ assertEquals("languageTable", expectedValues[4], ted.languageTable);
+ assertEquals("languageShiftTable", expectedValues[5], ted.languageShiftTable);
+ }
+
+ private void callCdmaLengthMethods(CharSequence msgBody, boolean use7bitOnly,
+ int[] expectedValues)
+ {
+ int activePhone = TelephonyManager.getDefault().getPhoneType();
+ if (TelephonyManager.PHONE_TYPE_CDMA == activePhone) {
+ int[] values = android.telephony.SmsMessage.calculateLength(msgBody, use7bitOnly);
+ assertEquals("msgCount", expectedValues[0], values[0]);
+ assertEquals("codeUnitCount", expectedValues[1], values[1]);
+ assertEquals("codeUnitsRemaining", expectedValues[2], values[2]);
+ assertEquals("codeUnitSize", expectedValues[3], values[3]);
+ }
+
+ GsmAlphabet.TextEncodingDetails ted =
+ com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly);
+ assertEquals("msgCount", expectedValues[0], ted.msgCount);
+ assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount);
+ assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining);
+ assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize);
+
+ ted = com.android.internal.telephony.cdma.sms.BearerData.calcTextEncodingDetails(msgBody, use7bitOnly);
+ assertEquals("msgCount", expectedValues[0], ted.msgCount);
+ assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount);
+ assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining);
+ assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize);
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyUtilsTest.java
new file mode 100644
index 0000000..3757017
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyUtilsTest.java
@@ -0,0 +1,219 @@
+/**
+ * Copyright (C) 2009 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 com.android.internal.telephony.RetryManager;
+import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class TelephonyUtilsTest extends TestCase {
+
+ /**
+ * After first creating the RetryManager
+ * isRetryNeeded should be false and the time 0
+ */
+ @SmallTest
+ public void testRetryManagerEmpty() throws Exception {
+ RetryManager rm = new RetryManager();
+
+ assertEquals(0, rm.getRetryCount());
+ assertFalse(rm.isRetryForever());
+ assertFalse(rm.isRetryNeeded());
+ assertEquals(0, rm.getRetryCount());
+ assertEquals(0, rm.getRetryTimer());
+
+ rm.increaseRetryCount();
+ assertFalse(rm.isRetryForever());
+ assertFalse(rm.isRetryNeeded());
+ assertEquals(0, rm.getRetryCount());
+ assertEquals(0, rm.getRetryTimer());
+
+ rm.setRetryCount(123);
+ assertFalse(rm.isRetryForever());
+ assertFalse(rm.isRetryNeeded());
+ assertEquals(0, rm.getRetryCount());
+ assertEquals(0, rm.getRetryTimer());
+
+ rm.retryForeverUsingLastTimeout();
+ assertTrue(rm.isRetryForever());
+ assertTrue(rm.isRetryNeeded());
+ assertEquals(0, rm.getRetryCount());
+ assertEquals(0, rm.getRetryTimer());
+
+ rm.setRetryCount(2);
+ assertFalse(rm.isRetryForever());
+ assertFalse(rm.isRetryNeeded());
+ assertEquals(0, rm.getRetryCount());
+ assertEquals(0, rm.getRetryTimer());
+ }
+
+ /**
+ * A simple test and that randomization is doing something.
+ */
+ @SmallTest
+ public void testRetryManagerSimplest() throws Exception {
+ RetryManager rm = new RetryManager();
+
+ assertTrue(rm.configure(1, 500, 10));
+ int loops = 10;
+ int count = 0;
+ for (int i = 0; i < loops; i++) {
+ assertTrue(rm.isRetryNeeded());
+ int time = rm.getRetryTimer();
+ assertTrue((time >= 500) && (time < 600));
+ if (time == 500) {
+ count++;
+ }
+ }
+ assertFalse(count == loops);
+ rm.increaseRetryCount();
+ assertFalse(rm.isRetryNeeded());
+ rm.setRetryCount(0);
+ assertTrue(rm.isRetryNeeded());
+ }
+
+ /**
+ * Test multiple values using simple configuration.
+ */
+ @SmallTest
+ public void testRetryManagerSimple() throws Exception {
+ RetryManager rm = new RetryManager();
+
+ assertTrue(rm.configure(3, 1000, 0));
+ assertTrue(rm.isRetryNeeded());
+ assertEquals(1000, rm.getRetryTimer());
+ assertEquals(rm.getRetryTimer(), 1000);
+ rm.increaseRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ assertEquals(1000, rm.getRetryTimer());
+ rm.increaseRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ assertEquals(1000, rm.getRetryTimer());
+ rm.increaseRetryCount();
+ assertFalse(rm.isRetryNeeded());
+ assertEquals(1000, rm.getRetryTimer());
+ }
+
+ /**
+ * Test string configuration, simplest
+ */
+ @SmallTest
+ public void testRetryManageSimpleString() throws Exception {
+ RetryManager rm = new RetryManager();
+
+ assertTrue(rm.configure("101"));
+ assertTrue(rm.isRetryNeeded());
+ assertEquals(101, rm.getRetryTimer());
+ rm.increaseRetryCount();
+ assertFalse(rm.isRetryNeeded());
+ }
+
+ /**
+ * Test infinite retires
+ */
+ @SmallTest
+ public void testRetryManageInfinite() throws Exception {
+ RetryManager rm = new RetryManager();
+
+ assertTrue(rm.configure("1000,2000,3000,max_retries=infinite"));
+ assertTrue(rm.isRetryNeeded());
+ assertEquals(1000, rm.getRetryTimer());
+ rm.increaseRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ assertEquals(2000, rm.getRetryTimer());
+ rm.increaseRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ // All others are 3000 and isRetryNeeded is always true
+ for (int i=0; i < 100; i++) {
+ assertEquals(3000, rm.getRetryTimer());
+ rm.increaseRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ }
+ }
+
+ /**
+ * Test string configuration using all options and with quotes.
+ */
+ @SmallTest
+ public void testRetryManageString() throws Exception {
+ RetryManager rm = new RetryManager();
+ int time;
+
+ assertTrue(rm.configure(
+ "\"max_retries=4, default_randomization=100,1000, 2000 :200 , 3000\""));
+ assertTrue(rm.isRetryNeeded());
+ time = rm.getRetryTimer();
+ assertTrue((time >= 1000) && (time < 1100));
+
+ rm.increaseRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ time = rm.getRetryTimer();
+ assertTrue((time >= 2000) && (time < 2200));
+
+ rm.increaseRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ time = rm.getRetryTimer();
+ assertTrue((time >= 3000) && (time < 3100));
+
+ rm.increaseRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ time = rm.getRetryTimer();
+ assertTrue((time >= 3000) && (time < 3100));
+
+ rm.increaseRetryCount();
+ assertFalse(rm.isRetryNeeded());
+ }
+
+ /**
+ * Test string configuration using all options.
+ */
+ @SmallTest
+ public void testRetryManageForever() throws Exception {
+ RetryManager rm = new RetryManager();
+ int time;
+
+ assertTrue(rm.configure("1000, 2000, 3000"));
+ assertTrue(rm.isRetryNeeded());
+ assertFalse(rm.isRetryForever());
+ assertEquals(0, rm.getRetryCount());
+ assertEquals(1000, rm.getRetryTimer());
+
+ rm.retryForeverUsingLastTimeout();
+ rm.increaseRetryCount();
+ rm.increaseRetryCount();
+ rm.increaseRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ assertTrue(rm.isRetryForever());
+ assertEquals(3, rm.getRetryCount());
+ assertEquals(3000, rm.getRetryTimer());
+
+ rm.setRetryCount(1);
+ assertTrue(rm.isRetryNeeded());
+ assertFalse(rm.isRetryForever());
+ assertEquals(1, rm.getRetryCount());
+ assertEquals(2000, rm.getRetryTimer());
+
+ rm.retryForeverUsingLastTimeout();
+ assertTrue(rm.isRetryNeeded());
+ assertTrue(rm.isRetryForever());
+ rm.resetRetryCount();
+ assertTrue(rm.isRetryNeeded());
+ assertFalse(rm.isRetryForever());
+ assertEquals(0, rm.getRetryCount());
+ assertEquals(1000, rm.getRetryTimer());
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TestPhoneNotifier.java b/tests/telephonytests/src/com/android/internal/telephony/TestPhoneNotifier.java
new file mode 100644
index 0000000..b8f0568
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/TestPhoneNotifier.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import android.telephony.CellInfo;
+
+/**
+ * Stub class used for unit tests
+ */
+
+public class TestPhoneNotifier implements PhoneNotifier {
+ public TestPhoneNotifier() {
+ }
+
+ 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, String apnType) {
+ }
+
+ public void notifyDataConnection(Phone sender, String reason, String apnType,
+ PhoneConstants.DataState state) {
+ }
+
+ public void notifyDataConnectionFailed(Phone sender, String reason, String apnType) {
+ }
+
+ public void notifyDataActivity(Phone sender) {
+ }
+
+ public void notifyOtaspChanged(Phone sender, int otaspMode) {
+ }
+
+ public void notifyCellInfo(Phone sender, CellInfo cellInfo) {
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/Wap230WspContentTypeTest.java b/tests/telephonytests/src/com/android/internal/telephony/Wap230WspContentTypeTest.java
new file mode 100644
index 0000000..d31b294
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/Wap230WspContentTypeTest.java
@@ -0,0 +1,853 @@
+/*
+ * Copyright (C) 2010 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 com.android.internal.telephony.WspTypeDecoder;
+import com.android.internal.util.HexDump;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+public class Wap230WspContentTypeTest extends TestCase {
+
+ public static final Map<Integer, String> WELL_KNOWN_SHORT_MIME_TYPES
+ = new HashMap<Integer, String>();
+ public static final Map<Integer, String> WELL_KNOWN_LONG_MIME_TYPES
+ = new HashMap<Integer, String>();
+ public static final Map<Integer, String> WELL_KNOWN_PARAMETERS
+ = new HashMap<Integer, String>();
+
+ static {
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x00, "*/*");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x01, "text/*");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x02, "text/html");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x03, "text/plain");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x04, "text/x-hdml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x05, "text/x-ttml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x06, "text/x-vCalendar");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x07, "text/x-vCard");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x08, "text/vnd.wap.wml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x09, "text/vnd.wap.wmlscript");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x0A, "text/vnd.wap.wta-event");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x0B, "multipart/*");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x0C, "multipart/mixed");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x0D, "multipart/form-data");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x0E, "multipart/byterantes");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x0F, "multipart/alternative");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x10, "application/*");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x11, "application/java-vm");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x12, "application/x-www-form-urlencoded");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x13, "application/x-hdmlc");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x14, "application/vnd.wap.wmlc");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x15, "application/vnd.wap.wmlscriptc");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x16, "application/vnd.wap.wta-eventc");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x17, "application/vnd.wap.uaprof");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x18, "application/vnd.wap.wtls-ca-certificate");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x19, "application/vnd.wap.wtls-user-certificate");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x1A, "application/x-x509-ca-cert");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x1B, "application/x-x509-user-cert");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x1C, "image/*");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x1D, "image/gif");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x1E, "image/jpeg");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x1F, "image/tiff");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x20, "image/png");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x21, "image/vnd.wap.wbmp");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x22, "application/vnd.wap.multipart.*");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x23, "application/vnd.wap.multipart.mixed");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x24, "application/vnd.wap.multipart.form-data");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x25, "application/vnd.wap.multipart.byteranges");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x26, "application/vnd.wap.multipart.alternative");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x27, "application/xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x28, "text/xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x29, "application/vnd.wap.wbxml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x2A, "application/x-x968-cross-cert");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x2B, "application/x-x968-ca-cert");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x2C, "application/x-x968-user-cert");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x2D, "text/vnd.wap.si");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x2E, "application/vnd.wap.sic");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x2F, "text/vnd.wap.sl");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x30, "application/vnd.wap.slc");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x31, "text/vnd.wap.co");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x32, "application/vnd.wap.coc");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x33, "application/vnd.wap.multipart.related");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x34, "application/vnd.wap.sia");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x35, "text/vnd.wap.connectivity-xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x36, "application/vnd.wap.connectivity-wbxml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x37, "application/pkcs7-mime");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x38, "application/vnd.wap.hashed-certificate");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x39, "application/vnd.wap.signed-certificate");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x3A, "application/vnd.wap.cert-response");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x3B, "application/xhtml+xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x3C, "application/wml+xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x3D, "text/css");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x3E, "application/vnd.wap.mms-message");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x3F, "application/vnd.wap.rollover-certificate");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x40, "application/vnd.wap.locc+wbxml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x41, "application/vnd.wap.loc+xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x42, "application/vnd.syncml.dm+wbxml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x43, "application/vnd.syncml.dm+xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x44, "application/vnd.syncml.notification");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x45, "application/vnd.wap.xhtml+xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x46, "application/vnd.wv.csp.cir");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x47, "application/vnd.oma.dd+xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x48, "application/vnd.oma.drm.message");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x49, "application/vnd.oma.drm.content");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x4A, "application/vnd.oma.drm.rights+xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x4B, "application/vnd.oma.drm.rights+wbxml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x4C, "application/vnd.wv.csp+xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x4D, "application/vnd.wv.csp+wbxml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x4E, "application/vnd.syncml.ds.notification");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x4F, "audio/*");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x50, "video/*");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x51, "application/vnd.oma.dd2+xml");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x52, "application/mikey");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x53, "application/vnd.oma.dcd");
+ WELL_KNOWN_SHORT_MIME_TYPES.put(0x54, "application/vnd.oma.dcdc");
+
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0201, "application/vnd.uplanet.cacheop-wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0202, "application/vnd.uplanet.signal");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0203, "application/vnd.uplanet.alert-wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0204, "application/vnd.uplanet.list-wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0205, "application/vnd.uplanet.listcmd-wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0206, "application/vnd.uplanet.channel-wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0207, "application/vnd.uplanet.provisioning-status-uri");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0208, "x-wap.multipart/vnd.uplanet.header-set");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0209, "application/vnd.uplanet.bearer-choice-wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x020A, "application/vnd.phonecom.mmc-wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x020B, "application/vnd.nokia.syncset+wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x020C, "image/x-up-wpng");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0300, "application/iota.mmc-wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0301, "application/iota.mmc-xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0302, "application/vnd.syncml+xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0303, "application/vnd.syncml+wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0304, "text/vnd.wap.emn+xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0305, "text/calendar");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0306, "application/vnd.omads-email+xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0307, "application/vnd.omads-file+xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0308, "application/vnd.omads-folder+xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0309, "text/directory;profile=vCard");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x030A, "application/vnd.wap.emn+wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x030B, "application/vnd.nokia.ipdc-purchase-response");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x030C, "application/vnd.motorola.screen3+xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x030D, "application/vnd.motorola.screen3+gzip");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x030E, "application/vnd.cmcc.setting+wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x030F, "application/vnd.cmcc.bombing+wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0310, "application/vnd.docomo.pf");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0311, "application/vnd.docomo.ub");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0312, "application/vnd.omaloc-supl-init");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0313, "application/vnd.oma.group-usage-list+xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0314, "application/oma-directory+xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0315, "application/vnd.docomo.pf2");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0316, "application/vnd.oma.drm.roap-trigger+wbxml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0317, "application/vnd.sbm.mid2");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0318, "application/vnd.wmf.bootstrap");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x0319, "application/vnc.cmcc.dcd+xml");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x031A, "application/vnd.sbm.cid");
+ WELL_KNOWN_LONG_MIME_TYPES.put(0x031B, "application/vnd.oma.bcast.provisioningtrigger");
+
+ WELL_KNOWN_PARAMETERS.put(0x00, "Q");
+ WELL_KNOWN_PARAMETERS.put(0x01, "Charset");
+ WELL_KNOWN_PARAMETERS.put(0x02, "Level");
+ WELL_KNOWN_PARAMETERS.put(0x03, "Type");
+ WELL_KNOWN_PARAMETERS.put(0x07, "Differences");
+ WELL_KNOWN_PARAMETERS.put(0x08, "Padding");
+ WELL_KNOWN_PARAMETERS.put(0x09, "Type");
+ WELL_KNOWN_PARAMETERS.put(0x0E, "Max-Age");
+ WELL_KNOWN_PARAMETERS.put(0x10, "Secure");
+ WELL_KNOWN_PARAMETERS.put(0x11, "SEC");
+ WELL_KNOWN_PARAMETERS.put(0x12, "MAC");
+ WELL_KNOWN_PARAMETERS.put(0x13, "Creation-date");
+ WELL_KNOWN_PARAMETERS.put(0x14, "Modification-date");
+ WELL_KNOWN_PARAMETERS.put(0x15, "Read-date");
+ WELL_KNOWN_PARAMETERS.put(0x16, "Size");
+ WELL_KNOWN_PARAMETERS.put(0x17, "Name");
+ WELL_KNOWN_PARAMETERS.put(0x18, "Filename");
+ WELL_KNOWN_PARAMETERS.put(0x19, "Start");
+ WELL_KNOWN_PARAMETERS.put(0x1A, "Start-info");
+ WELL_KNOWN_PARAMETERS.put(0x1B, "Comment");
+ WELL_KNOWN_PARAMETERS.put(0x1C, "Domain");
+ WELL_KNOWN_PARAMETERS.put(0x1D, "Path");
+
+ }
+
+ final int WSP_DEFINED_SHORT_MIME_TYPE_COUNT = 85;
+ final int WSP_DEFINED_LONG_MIME_TYPE_COUNT = 85;
+
+ private static final byte WSP_STRING_TERMINATOR = 0x00;
+ private static final byte WSP_SHORT_INTEGER_MASK = (byte) 0x80;
+ private static final byte WSP_LENGTH_QUOTE = 0x1F;
+ private static final byte WSP_QUOTE = 0x22;
+
+ private static final short LONG_MIME_TYPE_OMA_DIRECTORY_XML = 0x0314;
+ private static final short LONG_MIME_TYPE_UNASSIGNED = 0x052C;
+
+ private static final byte SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE = 0x3F;
+ private static final byte SHORT_MIME_TYPE_UNASSIGNED = 0x60;
+
+ private static final String STRING_MIME_TYPE_ROLLOVER_CERTIFICATE
+ = "application/vnd.wap.rollover-certificate";
+
+ private static final byte TYPED_PARAM_Q = 0x00;
+ private static final byte TYPED_PARAM_DOMAIN = 0x1C;
+ private static final byte PARAM_UNASSIGNED = 0x42;
+ private static final byte PARAM_NO_VALUE = 0x00;
+ private static final byte TYPED_PARAM_SEC = 0x11;
+ private static final byte TYPED_PARAM_MAC = 0x12;
+
+ public void testHasExpectedNumberOfShortMimeTypes() {
+ assertEquals(WSP_DEFINED_SHORT_MIME_TYPE_COUNT, WELL_KNOWN_SHORT_MIME_TYPES.size());
+ }
+
+ public void testHasExpectedNumberOfLongMimeTypes() {
+ assertEquals(WSP_DEFINED_LONG_MIME_TYPE_COUNT, WELL_KNOWN_LONG_MIME_TYPES.size());
+ }
+
+ public void testWellKnownShortIntegerMimeTypeValues() {
+
+ for (int value : Wap230WspContentTypeTest.WELL_KNOWN_SHORT_MIME_TYPES.keySet()) {
+ WspTypeDecoder unit = new WspTypeDecoder(
+ HexDump.toByteArray((byte) (value | WSP_SHORT_INTEGER_MASK)));
+ assertTrue(unit.decodeContentType(0));
+ String mimeType = unit.getValueString();
+ int wellKnownValue = (int) unit.getValue32();
+ assertEquals(Wap230WspContentTypeTest.WELL_KNOWN_SHORT_MIME_TYPES.get(value), mimeType);
+ assertEquals(value, wellKnownValue);
+ assertEquals(1, unit.getDecodedDataLength());
+ }
+ }
+
+ public void testWellKnownLongIntegerMimeTypeValues() {
+ byte headerLength = 3;
+ byte typeLength = 2;
+ for (int value : Wap230WspContentTypeTest.WELL_KNOWN_SHORT_MIME_TYPES.keySet()) {
+ byte[] data = new byte[10];
+ data[0] = headerLength;
+ data[1] = typeLength;
+ data[2] = (byte) (value >> 8);
+ data[3] = (byte) (value & 0xFF);
+ WspTypeDecoder unit = new WspTypeDecoder(data);
+ assertTrue(unit.decodeContentType(0));
+ String mimeType = unit.getValueString();
+ int wellKnownValue = (int) unit.getValue32();
+ assertEquals(Wap230WspContentTypeTest.WELL_KNOWN_SHORT_MIME_TYPES.get(value), mimeType);
+ assertEquals(value, wellKnownValue);
+ assertEquals(4, unit.getDecodedDataLength());
+ }
+ }
+
+ public void testDecodeReturnsFalse_WhenOnlyAZeroBytePresent() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x00);
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertFalse(unit.decodeContentType(0));
+ }
+
+ public void testConstrainedMediaExtensionMedia() throws Exception {
+
+ String testType = "application/wibble";
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(testType.getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+ String mimeType = unit.getValueString();
+ assertEquals(testType, mimeType);
+ assertEquals(-1, unit.getValue32());
+ assertEquals(19, unit.getDecodedDataLength());
+ }
+
+ public void testGeneralFormShortLengthExtensionMedia() throws Exception {
+
+ String testType = "12345678901234567890123456789";
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(testType.length() + 1);
+ out.write(testType.getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+ assertEquals(testType, mimeType);
+ assertEquals(-1, unit.getValue32());
+ assertEquals(31, unit.getDecodedDataLength());
+ }
+
+ public void testGeneralFormShortLengthWellKnownShortInteger() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x01);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+ assertEquals(2, unit.getDecodedDataLength());
+
+ }
+
+ public void testGeneralFormShortLengthWellKnownShortIntegerWithUnknownValue() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x01);
+ out.write(SHORT_MIME_TYPE_UNASSIGNED | WSP_SHORT_INTEGER_MASK);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+ assertNull(mimeType);
+ assertEquals(SHORT_MIME_TYPE_UNASSIGNED, unit.getValue32());
+ assertEquals(2, unit.getDecodedDataLength());
+
+ }
+
+ public void testGeneralFormShortLengthWellKnownLongInteger() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(0x03); // header length
+ out.write(0x02); // type length (2 octets)
+ out.write(LONG_MIME_TYPE_OMA_DIRECTORY_XML >> 8);
+ out.write(LONG_MIME_TYPE_OMA_DIRECTORY_XML & 0xFF);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals("application/oma-directory+xml", mimeType);
+ assertEquals(LONG_MIME_TYPE_OMA_DIRECTORY_XML, unit.getValue32());
+ assertEquals(4, unit.getDecodedDataLength());
+ }
+
+ public void testGeneralFormShortLengthWellKnownLongIntegerWithUnknownValue() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(0x03); // Value-length, short-length
+ out.write(0x02); // long-integer length (2 octets)
+ out.write(LONG_MIME_TYPE_UNASSIGNED >> 8);
+ out.write(LONG_MIME_TYPE_UNASSIGNED & 0xFF);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertNull(mimeType);
+ assertEquals(LONG_MIME_TYPE_UNASSIGNED, unit.getValue32());
+ assertEquals(4, unit.getDecodedDataLength());
+
+ }
+
+ public void testGeneralFormLengthQuoteWellKnownShortInteger() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(WSP_LENGTH_QUOTE);
+ out.write(0x01); // Length as UINTVAR
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+ assertEquals(3, unit.getDecodedDataLength());
+
+ }
+
+ public void testGeneralFormLengthQuoteWellKnownShortIntegerWithUnknownValue() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(WSP_LENGTH_QUOTE);
+ out.write(0x01); // Length as UINTVAR
+ out.write(SHORT_MIME_TYPE_UNASSIGNED | WSP_SHORT_INTEGER_MASK);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+ assertNull(mimeType);
+ assertEquals(SHORT_MIME_TYPE_UNASSIGNED, unit.getValue32());
+ assertEquals(3, unit.getDecodedDataLength());
+ }
+
+ public void testGeneralFormLengthQuoteWellKnownLongInteger() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(WSP_LENGTH_QUOTE);
+ out.write(0x03); // Length as UINTVAR
+ out.write(0x02); // long-integer length (2 octets)
+ out.write(LONG_MIME_TYPE_OMA_DIRECTORY_XML >> 8);
+ out.write(LONG_MIME_TYPE_OMA_DIRECTORY_XML & 0xFF);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals("application/oma-directory+xml", mimeType);
+ assertEquals(LONG_MIME_TYPE_OMA_DIRECTORY_XML, unit.getValue32());
+ assertEquals(5, unit.getDecodedDataLength());
+
+ }
+
+ public void testGeneralFormLengthQuoteWellKnownLongIntegerWithUnknownValue() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(WSP_LENGTH_QUOTE);
+ out.write(0x03); // Length as UINTVAR
+ out.write(0x02); // long-integer length (2 octets)
+ out.write(LONG_MIME_TYPE_UNASSIGNED >> 8);
+ out.write(LONG_MIME_TYPE_UNASSIGNED & 0xFF);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertNull(mimeType);
+ assertEquals(LONG_MIME_TYPE_UNASSIGNED, unit.getValue32());
+ assertEquals(5, unit.getDecodedDataLength());
+
+ }
+
+ public void testGeneralFormLengthQuoteExtensionMedia() throws Exception {
+
+ String testType = "application/wibble";
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(WSP_LENGTH_QUOTE);
+ out.write(testType.length() + 1); // Length as UINTVAR
+
+ out.write(testType.getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(testType, mimeType);
+ assertEquals(-1, unit.getValue32());
+ assertEquals(21, unit.getDecodedDataLength());
+
+ }
+
+ public void testGeneralFormLengthQuoteExtensionMediaWithNiceLongMimeType() throws Exception {
+
+ String testType =
+ "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
+ +"01234567890123456789012345678901234567890123456789012345678901234567890123456789";
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ out.write(WSP_LENGTH_QUOTE);
+ out.write(0x81); // Length as UINTVAR (161 decimal, 0xA1), 2 bytes
+ out.write(0x21);
+
+ out.write(testType.getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(testType, mimeType);
+ assertEquals(-1, unit.getValue32());
+ assertEquals(164, unit.getDecodedDataLength());
+
+ }
+
+ public void testConstrainedMediaExtensionMediaWithSpace() throws Exception {
+
+ String testType = " application/wibble";
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(testType.getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(testType, mimeType);
+ assertEquals(-1, unit.getValue32());
+ assertEquals(20, unit.getDecodedDataLength());
+
+ }
+
+ public void testTypedParamWellKnownShortIntegerNoValue() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x03); // Value-length, short-length
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(TYPED_PARAM_DOMAIN | WSP_SHORT_INTEGER_MASK);
+ out.write(PARAM_NO_VALUE);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+
+ assertEquals(4, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals(null, params.get("Domain"));
+
+ }
+
+ public void testTypedParamWellKnownShortIntegerTokenText() throws Exception {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x14); // Value-length, short-length
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(TYPED_PARAM_DOMAIN | WSP_SHORT_INTEGER_MASK);
+ out.write("wdstechnology.com".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+
+ assertEquals(out.toByteArray().length, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("wdstechnology.com", params.get("Domain"));
+
+ }
+
+ public void testTypedParamWellKnownLongIntegerTokenText() throws Exception {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x15);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(0x01);
+ out.write(TYPED_PARAM_DOMAIN);
+ out.write("wdstechnology.com".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+
+ assertEquals(22, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("wdstechnology.com", params.get("Domain"));
+
+ }
+
+ public void testTypedParamWellKnownShortIntegerQuotedText() throws Exception {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x15);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(TYPED_PARAM_DOMAIN | WSP_SHORT_INTEGER_MASK);
+ out.write(WSP_QUOTE);
+ out.write("wdstechnology.com".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(0x3F, unit.getValue32());
+ assertEquals(22, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("wdstechnology.com", params.get("Domain"));
+
+ }
+
+ public void testTypedParamWellKnownShortIntegerCompactIntegerValue() {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x3);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(TYPED_PARAM_SEC | WSP_SHORT_INTEGER_MASK);
+ out.write(0x01 | WSP_SHORT_INTEGER_MASK);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(0x3F, unit.getValue32());
+ assertEquals(4, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("1", params.get("SEC"));
+
+ }
+
+ public void testTypedParamWellKnownShortIntegerMultipleParameters() throws Exception {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x0B);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(TYPED_PARAM_SEC | WSP_SHORT_INTEGER_MASK);
+ out.write(0x01 | WSP_SHORT_INTEGER_MASK);
+ out.write(TYPED_PARAM_MAC | WSP_SHORT_INTEGER_MASK);
+ out.write(WSP_QUOTE);
+ out.write("imapc".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+ assertEquals(12, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("1", params.get("SEC"));
+ assertEquals("imapc", params.get("MAC"));
+ }
+
+ public void testUntypedParamIntegerValueShortInteger() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x0A);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write("MYPARAM".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR); // EOS
+ out.write(0x45 | WSP_SHORT_INTEGER_MASK);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+ assertEquals(11, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("69", params.get("MYPARAM"));
+ }
+
+ public void testUntypedParamIntegerValueLongInteger() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x0C);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write("MYPARAM".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+ out.write(0x02); // Short Length
+ out.write(0x42); // Long Integer byte 1
+ out.write(0x69); // Long Integer byte 2
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(0x3F, unit.getValue32());
+ assertEquals(13, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("17001", params.get("MYPARAM"));
+ }
+
+ public void testUntypedParamTextNoValue() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x0A);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write("MYPARAM".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+ out.write(PARAM_NO_VALUE);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+ assertEquals(11, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals(null, params.get("MYPARAM"));
+
+ }
+
+ public void testUntypedParamTextTokenText() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x11);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write("MYPARAM".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+ out.write("myvalue".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+ assertEquals(18, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("myvalue", params.get("MYPARAM"));
+ }
+
+ public void testUntypedParamTextQuotedString() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x11);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write("MYPARAM".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+ out.write(WSP_QUOTE);
+ out.write("myvalue".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+ assertEquals(19, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("myvalue", params.get("MYPARAM"));
+
+ }
+
+ public void testDecodesReturnsFalse_ForParamWithMissingValue() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x09);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write("MYPARAM".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertFalse(unit.decodeContentType(0));
+ }
+
+ public void testTypedParamTextQValue() {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x04);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(TYPED_PARAM_Q);
+ out.write(0x83); // Q value byte 1
+ out.write(0x31); // Q value byte 2
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(0x3F, unit.getValue32());
+ assertEquals(5, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("433", params.get("Q"));
+
+ }
+
+ public void testTypedParamUnassignedWellKnownShortIntegerTokenText() throws Exception {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x14);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(PARAM_UNASSIGNED | WSP_SHORT_INTEGER_MASK);
+ out.write("wdstechnology.com".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+
+ assertEquals(21, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("wdstechnology.com", params.get("unassigned/0x42"));
+
+ }
+
+ public void testTypedParamUnassignedWellKnownLongIntegerTokenText() throws Exception {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x15);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(0x01); // Short-length of well-known parameter token
+ out.write(PARAM_UNASSIGNED);
+ out.write("wdstechnology.com".getBytes("US-ASCII"));
+ out.write(WSP_STRING_TERMINATOR);
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertTrue(unit.decodeContentType(0));
+
+ String mimeType = unit.getValueString();
+
+ assertEquals(STRING_MIME_TYPE_ROLLOVER_CERTIFICATE, mimeType);
+ assertEquals(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE, unit.getValue32());
+
+ assertEquals(22, unit.getDecodedDataLength());
+
+ Map<String, String> params = unit.getContentParameters();
+ assertEquals("wdstechnology.com", params.get("unassigned/0x42"));
+ }
+
+ public void testDecodesReturnsFalse_WhenParamValueNotTerminated() throws Exception {
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(0x15);
+ out.write(SHORT_MIME_TYPE_ROLLOVER_CERTIFICATE | WSP_SHORT_INTEGER_MASK);
+ out.write(0x01);
+ out.write(PARAM_UNASSIGNED);
+ out.write("wdstechnology.com".getBytes("US-ASCII"));
+
+ WspTypeDecoder unit = new WspTypeDecoder(out.toByteArray());
+ assertFalse(unit.decodeContentType(0));
+ }
+} \ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
new file mode 100644
index 0000000..d2faceb
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
@@ -0,0 +1,746 @@
+/*
+ * Copyright (C) 2012 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.cdma;
+
+import android.os.Parcel;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbMessage;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.util.BitwiseOutputStream;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Test cases for basic SmsCbMessage operation for CDMA.
+ */
+public class CdmaSmsCbTest extends AndroidTestCase {
+
+ /* Copy of private subparameter identifier constants from BearerData class. */
+ private static final byte SUBPARAM_MESSAGE_IDENTIFIER = (byte) 0x00;
+ private static final byte SUBPARAM_USER_DATA = (byte) 0x01;
+ private static final byte SUBPARAM_PRIORITY_INDICATOR = (byte) 0x08;
+ private static final byte SUBPARAM_LANGUAGE_INDICATOR = (byte) 0x0D;
+ private static final byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12;
+
+ /**
+ * Initialize a Parcel for an incoming CDMA cell broadcast. The caller will write the
+ * bearer data and then convert it to an SmsMessage.
+ * @param serviceCategory the CDMA service category
+ * @return the initialized Parcel
+ */
+ private static Parcel createBroadcastParcel(int serviceCategory) {
+ Parcel p = Parcel.obtain();
+
+ p.writeInt(SmsEnvelope.TELESERVICE_NOT_SET);
+ p.writeByte((byte) 1); // non-zero for MESSAGE_TYPE_BROADCAST
+ p.writeInt(serviceCategory);
+
+ // dummy address (RIL may generate a different dummy address for broadcasts)
+ p.writeInt(CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF); // sAddress.digit_mode
+ p.writeInt(CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK); // sAddress.number_mode
+ p.writeInt(CdmaSmsAddress.TON_UNKNOWN); // sAddress.number_type
+ p.writeInt(CdmaSmsAddress.NUMBERING_PLAN_ISDN_TELEPHONY); // sAddress.number_plan
+ p.writeByte((byte) 0); // sAddress.number_of_digits
+ p.writeInt((byte) 0); // sSubAddress.subaddressType
+ p.writeByte((byte) 0); // sSubAddress.odd
+ p.writeByte((byte) 0); // sSubAddress.number_of_digits
+ return p;
+ }
+
+ /**
+ * Initialize a BitwiseOutputStream with the CDMA bearer data subparameters except for
+ * user data. The caller will append the user data and add it to the parcel.
+ * @param messageId the 16-bit message identifier
+ * @param priority message priority
+ * @param language message language code
+ * @return the initialized BitwiseOutputStream
+ */
+ private static BitwiseOutputStream createBearerDataStream(int messageId, int priority,
+ int language) throws BitwiseOutputStream.AccessException {
+ BitwiseOutputStream bos = new BitwiseOutputStream(10);
+ bos.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
+ bos.write(8, 3); // length: 3 bytes
+ bos.write(4, BearerData.MESSAGE_TYPE_DELIVER);
+ bos.write(8, ((messageId >>> 8) & 0xff));
+ bos.write(8, (messageId & 0xff));
+ bos.write(1, 0); // no User Data Header
+ bos.write(3, 0); // reserved
+
+ if (priority != -1) {
+ bos.write(8, SUBPARAM_PRIORITY_INDICATOR);
+ bos.write(8, 1); // length: 1 byte
+ bos.write(2, (priority & 0x03));
+ bos.write(6, 0); // reserved
+ }
+
+ if (language != -1) {
+ bos.write(8, SUBPARAM_LANGUAGE_INDICATOR);
+ bos.write(8, 1); // length: 1 byte
+ bos.write(8, (language & 0xff));
+ }
+
+ return bos;
+ }
+
+ /**
+ * Write the bearer data array to the parcel, then return a new SmsMessage from the parcel.
+ * @param p the parcel containing the CDMA SMS headers
+ * @param bearerData the bearer data byte array to append to the parcel
+ * @return the new SmsMessage created from the parcel
+ */
+ private static SmsMessage createMessageFromParcel(Parcel p, byte[] bearerData) {
+ p.writeInt(bearerData.length);
+ for (byte b : bearerData) {
+ p.writeByte(b);
+ }
+ p.setDataPosition(0); // reset position for reading
+ SmsMessage message = SmsMessage.newFromParcel(p);
+ p.recycle();
+ return message;
+ }
+
+ /**
+ * Create a parcel for an incoming CMAS broadcast, then return a new SmsMessage created
+ * from the parcel.
+ * @param serviceCategory the CDMA service category
+ * @param messageId the 16-bit message identifier
+ * @param priority message priority
+ * @param language message language code
+ * @param body message body
+ * @param cmasCategory CMAS category (or -1 to skip adding CMAS type 1 elements record)
+ * @param responseType CMAS response type
+ * @param severity CMAS severity
+ * @param urgency CMAS urgency
+ * @param certainty CMAS certainty
+ * @return the newly created SmsMessage object
+ */
+ private static SmsMessage createCmasSmsMessage(int serviceCategory, int messageId, int priority,
+ int language, int encoding, String body, int cmasCategory, int responseType,
+ int severity, int urgency, int certainty) throws Exception {
+ BitwiseOutputStream cmasBos = new BitwiseOutputStream(10);
+ cmasBos.write(8, 0); // CMAE protocol version 0
+
+ if (body != null) {
+ cmasBos.write(8, 0); // Type 0 elements (alert text)
+ encodeBody(encoding, body, true, cmasBos);
+ }
+
+ if (cmasCategory != SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN) {
+ cmasBos.write(8, 1); // Type 1 elements
+ cmasBos.write(8, 4); // length: 4 bytes
+ cmasBos.write(8, (cmasCategory & 0xff));
+ cmasBos.write(8, (responseType & 0xff));
+ cmasBos.write(4, (severity & 0x0f));
+ cmasBos.write(4, (urgency & 0x0f));
+ cmasBos.write(4, (certainty & 0x0f));
+ cmasBos.write(4, 0); // pad to octet boundary
+ }
+
+ byte[] cmasUserData = cmasBos.toByteArray();
+
+ Parcel p = createBroadcastParcel(serviceCategory);
+ BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language);
+
+ bos.write(8, SUBPARAM_USER_DATA);
+ bos.write(8, cmasUserData.length + 2); // add 2 bytes for msg_encoding and num_fields
+ bos.write(5, UserData.ENCODING_OCTET);
+ bos.write(8, cmasUserData.length);
+ bos.writeByteArray(cmasUserData.length * 8, cmasUserData);
+ bos.write(3, 0); // pad to byte boundary
+
+ return createMessageFromParcel(p, bos.toByteArray());
+ }
+
+ /**
+ * Create a parcel for an incoming CDMA cell broadcast, then return a new SmsMessage created
+ * from the parcel.
+ * @param serviceCategory the CDMA service category
+ * @param messageId the 16-bit message identifier
+ * @param priority message priority
+ * @param language message language code
+ * @param encoding user data encoding method
+ * @param body the message body
+ * @return the newly created SmsMessage object
+ */
+ private static SmsMessage createBroadcastSmsMessage(int serviceCategory, int messageId,
+ int priority, int language, int encoding, String body) throws Exception {
+ Parcel p = createBroadcastParcel(serviceCategory);
+ BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language);
+
+ bos.write(8, SUBPARAM_USER_DATA);
+ encodeBody(encoding, body, false, bos);
+
+ return createMessageFromParcel(p, bos.toByteArray());
+ }
+
+ /**
+ * Append the message length, encoding, and body to the BearerData output stream.
+ * This is used for writing the User Data subparameter for non-CMAS broadcasts and for
+ * writing the alert text for CMAS broadcasts.
+ * @param encoding one of the CDMA UserData encoding values
+ * @param body the message body
+ * @param isCmasRecord true if this is a CMAS type 0 elements record; false for user data
+ * @param bos the BitwiseOutputStream to write to
+ * @throws Exception on any encoding error
+ */
+ private static void encodeBody(int encoding, String body, boolean isCmasRecord,
+ BitwiseOutputStream bos) throws Exception {
+ if (encoding == UserData.ENCODING_7BIT_ASCII || encoding == UserData.ENCODING_IA5) {
+ int charCount = body.length();
+ int recordBits = (charCount * 7) + 5; // add 5 bits for char set field
+ int recordOctets = (recordBits + 7) / 8; // round up to octet boundary
+ int padBits = (recordOctets * 8) - recordBits;
+
+ if (!isCmasRecord) {
+ recordOctets++; // add 8 bits for num_fields
+ }
+
+ bos.write(8, recordOctets);
+ bos.write(5, (encoding & 0x1f));
+
+ if (!isCmasRecord) {
+ bos.write(8, charCount);
+ }
+
+ for (int i = 0; i < charCount; i++) {
+ bos.write(7, body.charAt(i));
+ }
+
+ bos.write(padBits, 0); // pad to octet boundary
+ } else if (encoding == UserData.ENCODING_GSM_7BIT_ALPHABET
+ || encoding == UserData.ENCODING_GSM_DCS) {
+ // convert to 7-bit packed encoding with septet count in index 0 of byte array
+ byte[] encodedBody = GsmAlphabet.stringToGsm7BitPacked(body);
+
+ int charCount = encodedBody[0]; // septet count
+ int recordBits = (charCount * 7) + 5; // add 5 bits for char set field
+ int recordOctets = (recordBits + 7) / 8; // round up to octet boundary
+ int padBits = (recordOctets * 8) - recordBits;
+
+ if (!isCmasRecord) {
+ recordOctets++; // add 8 bits for num_fields
+ if (encoding == UserData.ENCODING_GSM_DCS) {
+ recordOctets++; // add 8 bits for DCS (message type)
+ }
+ }
+
+ bos.write(8, recordOctets);
+ bos.write(5, (encoding & 0x1f));
+
+ if (!isCmasRecord && encoding == UserData.ENCODING_GSM_DCS) {
+ bos.write(8, 0); // GSM DCS: 7 bit default alphabet, no msg class
+ }
+
+ if (!isCmasRecord) {
+ bos.write(8, charCount);
+ }
+ byte[] bodySeptets = Arrays.copyOfRange(encodedBody, 1, encodedBody.length);
+ bos.writeByteArray(charCount * 7, bodySeptets);
+ bos.write(padBits, 0); // pad to octet boundary
+ } else if (encoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
+ // 6 bit packed encoding with 0x20 offset (ASCII 0x20 - 0x60)
+ int charCount = body.length();
+ int recordBits = (charCount * 6) + 21; // add 21 bits for header fields
+ int recordOctets = (recordBits + 7) / 8; // round up to octet boundary
+ int padBits = (recordOctets * 8) - recordBits;
+
+ bos.write(8, recordOctets);
+
+ bos.write(5, (encoding & 0x1f));
+ bos.write(8, UserData.IS91_MSG_TYPE_SHORT_MESSAGE);
+ bos.write(8, charCount);
+
+ for (int i = 0; i < charCount; i++) {
+ bos.write(6, ((int) body.charAt(i) - 0x20));
+ }
+
+ bos.write(padBits, 0); // pad to octet boundary
+ } else {
+ byte[] encodedBody;
+ switch (encoding) {
+ case UserData.ENCODING_UNICODE_16:
+ encodedBody = body.getBytes("UTF-16BE");
+ break;
+
+ case UserData.ENCODING_SHIFT_JIS:
+ encodedBody = body.getBytes("Shift_JIS");
+ break;
+
+ case UserData.ENCODING_KOREAN:
+ encodedBody = body.getBytes("KSC5601");
+ break;
+
+ case UserData.ENCODING_LATIN_HEBREW:
+ encodedBody = body.getBytes("ISO-8859-8");
+ break;
+
+ case UserData.ENCODING_LATIN:
+ default:
+ encodedBody = body.getBytes("ISO-8859-1");
+ break;
+ }
+ int charCount = body.length(); // use actual char count for num fields
+ int recordOctets = encodedBody.length + 1; // add 1 byte for encoding and pad bits
+ if (!isCmasRecord) {
+ recordOctets++; // add 8 bits for num_fields
+ }
+ bos.write(8, recordOctets);
+ bos.write(5, (encoding & 0x1f));
+ if (!isCmasRecord) {
+ bos.write(8, charCount);
+ }
+ bos.writeByteArray(encodedBody.length * 8, encodedBody);
+ bos.write(3, 0); // pad to octet boundary
+ }
+ }
+
+ private static final String TEST_TEXT = "This is a test CDMA cell broadcast message..."
+ + "678901234567890123456789012345678901234567890";
+
+ private static final String PRES_ALERT =
+ "THE PRESIDENT HAS ISSUED AN EMERGENCY ALERT. CHECK LOCAL MEDIA FOR MORE DETAILS";
+
+ private static final String EXTREME_ALERT = "FLASH FLOOD WARNING FOR SOUTH COCONINO COUNTY"
+ + " - NORTH CENTRAL ARIZONA UNTIL 415 PM MST";
+
+ private static final String SEVERE_ALERT = "SEVERE WEATHER WARNING FOR SOMERSET COUNTY"
+ + " - NEW JERSEY UNTIL 415 PM MST";
+
+ private static final String AMBER_ALERT =
+ "AMBER ALERT:Mountain View,CA VEH'07 Blue Honda Civic CA LIC 5ABC123";
+
+ private static final String MONTHLY_TEST_ALERT = "This is a test of the emergency alert system."
+ + " This is only a test. 89012345678901234567890";
+
+ private static final String IS91_TEXT = "IS91 SHORT MSG"; // max length 14 chars
+
+ /**
+ * Verify that the SmsCbMessage has the correct values for CDMA.
+ * @param cbMessage the message to test
+ */
+ private static void verifyCbValues(SmsCbMessage cbMessage) {
+ assertEquals(SmsCbMessage.MESSAGE_FORMAT_3GPP2, cbMessage.getMessageFormat());
+ assertEquals(SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, cbMessage.getGeographicalScope());
+ assertEquals(false, cbMessage.isEtwsMessage()); // ETWS on CDMA not currently supported
+ }
+
+ private static void doTestNonEmergencyBroadcast(int encoding) throws Exception {
+ SmsMessage msg = createBroadcastSmsMessage(123, 456, BearerData.PRIORITY_NORMAL,
+ BearerData.LANGUAGE_ENGLISH, encoding, TEST_TEXT);
+
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ verifyCbValues(cbMessage);
+ assertEquals(123, cbMessage.getServiceCategory());
+ assertEquals(456, cbMessage.getSerialNumber());
+ assertEquals(SmsCbMessage.MESSAGE_PRIORITY_NORMAL, cbMessage.getMessagePriority());
+ assertEquals("en", cbMessage.getLanguageCode());
+ assertEquals(TEST_TEXT, cbMessage.getMessageBody());
+ assertEquals(false, cbMessage.isEmergencyMessage());
+ assertEquals(false, cbMessage.isCmasMessage());
+ }
+
+ public void testNonEmergencyBroadcast7bitAscii() throws Exception {
+ doTestNonEmergencyBroadcast(UserData.ENCODING_7BIT_ASCII);
+ }
+
+ public void testNonEmergencyBroadcast7bitGsm() throws Exception {
+ doTestNonEmergencyBroadcast(UserData.ENCODING_GSM_7BIT_ALPHABET);
+ }
+
+ public void testNonEmergencyBroadcast16bitUnicode() throws Exception {
+ doTestNonEmergencyBroadcast(UserData.ENCODING_UNICODE_16);
+ }
+
+ public void testNonEmergencyBroadcastIs91Extended() throws Exception {
+ // IS-91 doesn't support language or priority subparameters, max 14 chars text
+ SmsMessage msg = createBroadcastSmsMessage(987, 654, -1, -1,
+ UserData.ENCODING_IS91_EXTENDED_PROTOCOL, IS91_TEXT);
+
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ verifyCbValues(cbMessage);
+ assertEquals(987, cbMessage.getServiceCategory());
+ assertEquals(654, cbMessage.getSerialNumber());
+ assertEquals(SmsCbMessage.MESSAGE_PRIORITY_NORMAL, cbMessage.getMessagePriority());
+ assertEquals(null, cbMessage.getLanguageCode());
+ assertEquals(IS91_TEXT, cbMessage.getMessageBody());
+ assertEquals(false, cbMessage.isEmergencyMessage());
+ assertEquals(false, cbMessage.isCmasMessage());
+ }
+
+ private static void doTestCmasBroadcast(int serviceCategory, int messageClass, String body)
+ throws Exception {
+ SmsMessage msg = createCmasSmsMessage(
+ serviceCategory, 1234, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+ UserData.ENCODING_7BIT_ASCII, body, -1, -1, -1, -1, -1);
+
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ verifyCbValues(cbMessage);
+ assertEquals(serviceCategory, cbMessage.getServiceCategory());
+ assertEquals(1234, cbMessage.getSerialNumber());
+ assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority());
+ assertEquals("en", cbMessage.getLanguageCode());
+ assertEquals(body, cbMessage.getMessageBody());
+ assertEquals(true, cbMessage.isEmergencyMessage());
+ assertEquals(true, cbMessage.isCmasMessage());
+ SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
+ assertEquals(messageClass, cmasInfo.getMessageClass());
+ assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory());
+ assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType());
+ assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity());
+ assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency());
+ assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty());
+ }
+
+ public void testCmasPresidentialAlert() throws Exception {
+ doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
+ SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, PRES_ALERT);
+ }
+
+ public void testCmasExtremeAlert() throws Exception {
+ doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+ SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, EXTREME_ALERT);
+ }
+
+ public void testCmasSevereAlert() throws Exception {
+ doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT,
+ SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT, SEVERE_ALERT);
+ }
+
+ public void testCmasAmberAlert() throws Exception {
+ doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY,
+ SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY, AMBER_ALERT);
+ }
+
+ public void testCmasTestMessage() throws Exception {
+ doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE,
+ SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST, MONTHLY_TEST_ALERT);
+ }
+
+ public void testCmasExtremeAlertType1Elements() throws Exception {
+ SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+ 5678, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+ UserData.ENCODING_7BIT_ASCII, EXTREME_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_ENV,
+ SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, SmsCbCmasInfo.CMAS_SEVERITY_SEVERE,
+ SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
+
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ verifyCbValues(cbMessage);
+ assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+ cbMessage.getServiceCategory());
+ assertEquals(5678, cbMessage.getSerialNumber());
+ assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority());
+ assertEquals("en", cbMessage.getLanguageCode());
+ assertEquals(EXTREME_ALERT, cbMessage.getMessageBody());
+ assertEquals(true, cbMessage.isEmergencyMessage());
+ assertEquals(true, cbMessage.isCmasMessage());
+ SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
+ assertEquals(SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, cmasInfo.getMessageClass());
+ assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_ENV, cmasInfo.getCategory());
+ assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, cmasInfo.getResponseType());
+ assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_SEVERE, cmasInfo.getSeverity());
+ assertEquals(SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, cmasInfo.getUrgency());
+ assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY, cmasInfo.getCertainty());
+ }
+
+ // VZW requirement is to discard message with unsupported charset. Verify that we return null
+ // for this unsupported character set.
+ public void testCmasUnsupportedCharSet() throws Exception {
+ SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+ 12345, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+ UserData.ENCODING_GSM_DCS, EXTREME_ALERT, -1, -1, -1, -1, -1);
+
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ assertNull("expected null for unsupported charset", cbMessage);
+ }
+
+ // VZW requirement is to discard message with unsupported charset. Verify that we return null
+ // for this unsupported character set.
+ public void testCmasUnsupportedCharSet2() throws Exception {
+ SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+ 67890, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+ UserData.ENCODING_KOREAN, EXTREME_ALERT, -1, -1, -1, -1, -1);
+
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ assertNull("expected null for unsupported charset", cbMessage);
+ }
+
+ // VZW requirement is to discard message without record type 0. The framework will decode it
+ // and the app will discard it.
+ public void testCmasNoRecordType0() throws Exception {
+ SmsMessage msg = createCmasSmsMessage(
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, 1234,
+ BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+ UserData.ENCODING_7BIT_ASCII, null, -1, -1, -1, -1, -1);
+
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ verifyCbValues(cbMessage);
+ assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
+ cbMessage.getServiceCategory());
+ assertEquals(1234, cbMessage.getSerialNumber());
+ assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority());
+ assertEquals("en", cbMessage.getLanguageCode());
+ assertEquals(null, cbMessage.getMessageBody());
+ assertEquals(true, cbMessage.isEmergencyMessage());
+ assertEquals(true, cbMessage.isCmasMessage());
+ SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
+ assertEquals(SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, cmasInfo.getMessageClass());
+ assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory());
+ assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType());
+ assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity());
+ assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency());
+ assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty());
+ }
+
+ // Make sure we don't throw an exception if we feed completely random data to BearerStream.
+ public void testRandomBearerStreamData() {
+ Random r = new Random(54321);
+ for (int run = 0; run < 1000; run++) {
+ int len = r.nextInt(140);
+ byte[] data = new byte[len];
+ for (int i = 0; i < len; i++) {
+ data[i] = (byte) r.nextInt(256);
+ }
+ // Log.d("CdmaSmsCbTest", "trying random bearer data run " + run + " length " + len);
+ try {
+ int category = 0x0ff0 + r.nextInt(32); // half CMAS, half non-CMAS
+ Parcel p = createBroadcastParcel(category);
+ SmsMessage msg = createMessageFromParcel(p, data);
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ // with random input, cbMessage will almost always be null (log when it isn't)
+ if (cbMessage != null) {
+ Log.d("CdmaSmsCbTest", "success: " + cbMessage);
+ }
+ } catch (Exception e) {
+ Log.d("CdmaSmsCbTest", "exception thrown", e);
+ fail("Exception in decoder at run " + run + " length " + len + ": " + e);
+ }
+ }
+ }
+
+ // Make sure we don't throw an exception if we put random data in the UserData subparam.
+ public void testRandomUserData() {
+ Random r = new Random(94040);
+ for (int run = 0; run < 1000; run++) {
+ int category = 0x0ff0 + r.nextInt(32); // half CMAS, half non-CMAS
+ Parcel p = createBroadcastParcel(category);
+ int len = r.nextInt(140);
+ // Log.d("CdmaSmsCbTest", "trying random user data run " + run + " length " + len);
+
+ try {
+ BitwiseOutputStream bos = createBearerDataStream(r.nextInt(65536), r.nextInt(4),
+ r.nextInt(256));
+
+ bos.write(8, SUBPARAM_USER_DATA);
+ bos.write(8, len);
+
+ for (int i = 0; i < len; i++) {
+ bos.write(8, r.nextInt(256));
+ }
+
+ SmsMessage msg = createMessageFromParcel(p, bos.toByteArray());
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ } catch (Exception e) {
+ Log.d("CdmaSmsCbTest", "exception thrown", e);
+ fail("Exception in decoder at run " + run + " length " + len + ": " + e);
+ }
+ }
+ }
+
+ /**
+ * Initialize a Parcel for incoming Service Category Program Data teleservice. The caller will
+ * write the bearer data and then convert it to an SmsMessage.
+ * @return the initialized Parcel
+ */
+ private static Parcel createServiceCategoryProgramDataParcel() {
+ Parcel p = Parcel.obtain();
+
+ p.writeInt(SmsEnvelope.TELESERVICE_SCPT);
+ p.writeByte((byte) 0); // non-zero for MESSAGE_TYPE_BROADCAST
+ p.writeInt(0);
+
+ // dummy address (RIL may generate a different dummy address for broadcasts)
+ p.writeInt(CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF); // sAddress.digit_mode
+ p.writeInt(CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK); // sAddress.number_mode
+ p.writeInt(CdmaSmsAddress.TON_UNKNOWN); // sAddress.number_type
+ p.writeInt(CdmaSmsAddress.NUMBERING_PLAN_ISDN_TELEPHONY); // sAddress.number_plan
+ p.writeByte((byte) 0); // sAddress.number_of_digits
+ p.writeInt((byte) 0); // sSubAddress.subaddressType
+ p.writeByte((byte) 0); // sSubAddress.odd
+ p.writeByte((byte) 0); // sSubAddress.number_of_digits
+ return p;
+ }
+
+ private static final String CAT_EXTREME_THREAT = "Extreme Threat to Life and Property";
+ private static final String CAT_SEVERE_THREAT = "Severe Threat to Life and Property";
+ private static final String CAT_AMBER_ALERTS = "AMBER Alerts";
+
+ public void testServiceCategoryProgramDataAddCategory() throws Exception {
+ Parcel p = createServiceCategoryProgramDataParcel();
+ BitwiseOutputStream bos = createBearerDataStream(123, -1, -1);
+
+ int categoryNameLength = CAT_EXTREME_THREAT.length();
+ int subparamLengthBits = (53 + (categoryNameLength * 7));
+ int subparamLengthBytes = (subparamLengthBits + 7) / 8;
+ int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits;
+
+ bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA);
+ bos.write(8, subparamLengthBytes);
+ bos.write(5, UserData.ENCODING_7BIT_ASCII);
+
+ bos.write(4, CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY);
+ bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT >>> 8));
+ bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT & 0xff));
+ bos.write(8, BearerData.LANGUAGE_ENGLISH);
+ bos.write(8, 100); // max messages
+ bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT);
+
+ bos.write(8, categoryNameLength);
+ for (int i = 0; i < categoryNameLength; i++) {
+ bos.write(7, CAT_EXTREME_THREAT.charAt(i));
+ }
+ bos.write(subparamPadBits, 0);
+
+ SmsMessage msg = createMessageFromParcel(p, bos.toByteArray());
+ assertNotNull(msg);
+ msg.parseSms();
+ List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData();
+ assertNotNull(programDataList);
+ assertEquals(1, programDataList.size());
+ CdmaSmsCbProgramData programData = programDataList.get(0);
+ assertEquals(CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY, programData.getOperation());
+ assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, programData.getCategory());
+ assertEquals(CAT_EXTREME_THREAT, programData.getCategoryName());
+ assertEquals("en", programData.getLanguageCode());
+ assertEquals(100, programData.getMaxMessages());
+ assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT, programData.getAlertOption());
+ }
+
+ public void testServiceCategoryProgramDataDeleteTwoCategories() throws Exception {
+ Parcel p = createServiceCategoryProgramDataParcel();
+ BitwiseOutputStream bos = createBearerDataStream(456, -1, -1);
+
+ int category1NameLength = CAT_SEVERE_THREAT.length();
+ int category2NameLength = CAT_AMBER_ALERTS.length();
+
+ int subparamLengthBits = (101 + (category1NameLength * 7) + (category2NameLength * 7));
+ int subparamLengthBytes = (subparamLengthBits + 7) / 8;
+ int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits;
+
+ bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA);
+ bos.write(8, subparamLengthBytes);
+ bos.write(5, UserData.ENCODING_7BIT_ASCII);
+
+ bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY);
+ bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT >>> 8));
+ bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT & 0xff));
+ bos.write(8, BearerData.LANGUAGE_ENGLISH);
+ bos.write(8, 0); // max messages
+ bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT);
+
+ bos.write(8, category1NameLength);
+ for (int i = 0; i < category1NameLength; i++) {
+ bos.write(7, CAT_SEVERE_THREAT.charAt(i));
+ }
+
+ bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY);
+ bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY >>> 8));
+ bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY & 0xff));
+ bos.write(8, BearerData.LANGUAGE_ENGLISH);
+ bos.write(8, 0); // max messages
+ bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT);
+
+ bos.write(8, category2NameLength);
+ for (int i = 0; i < category2NameLength; i++) {
+ bos.write(7, CAT_AMBER_ALERTS.charAt(i));
+ }
+
+ bos.write(subparamPadBits, 0);
+
+ SmsMessage msg = createMessageFromParcel(p, bos.toByteArray());
+ assertNotNull(msg);
+ msg.parseSms();
+ List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData();
+ assertNotNull(programDataList);
+ assertEquals(2, programDataList.size());
+
+ CdmaSmsCbProgramData programData = programDataList.get(0);
+ assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation());
+ assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, programData.getCategory());
+ assertEquals(CAT_SEVERE_THREAT, programData.getCategoryName());
+ assertEquals("en", programData.getLanguageCode());
+ assertEquals(0, programData.getMaxMessages());
+ assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption());
+
+ programData = programDataList.get(1);
+ assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation());
+ assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY,
+ programData.getCategory());
+ assertEquals(CAT_AMBER_ALERTS, programData.getCategoryName());
+ assertEquals("en", programData.getLanguageCode());
+ assertEquals(0, programData.getMaxMessages());
+ assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption());
+ }
+
+ private static final byte[] CMAS_TEST_BEARER_DATA = {
+ 0x00, 0x03, 0x1C, 0x78, 0x00, 0x01, 0x59, 0x02, (byte) 0xB8, 0x00, 0x02, 0x10, (byte) 0xAA,
+ 0x68, (byte) 0xD3, (byte) 0xCD, 0x06, (byte) 0x9E, 0x68, 0x30, (byte) 0xA0, (byte) 0xE9,
+ (byte) 0x97, (byte) 0x9F, 0x44, 0x1B, (byte) 0xF3, 0x20, (byte) 0xE9, (byte) 0xA3,
+ 0x2A, 0x08, 0x7B, (byte) 0xF6, (byte) 0xED, (byte) 0xCB, (byte) 0xCB, 0x1E, (byte) 0x9C,
+ 0x3B, 0x10, 0x4D, (byte) 0xDF, (byte) 0x8B, 0x4E,
+ (byte) 0xCC, (byte) 0xA8, 0x20, (byte) 0xEC, (byte) 0xCB, (byte) 0xCB, (byte) 0xA2, 0x0A,
+ 0x7E, 0x79, (byte) 0xF4, (byte) 0xCB, (byte) 0xB5, 0x72, 0x0A, (byte) 0x9A, 0x34,
+ (byte) 0xF3, 0x41, (byte) 0xA7, (byte) 0x9A, 0x0D, (byte) 0xFB, (byte) 0xB6, 0x79, 0x41,
+ (byte) 0x85, 0x07, 0x4C, (byte) 0xBC, (byte) 0xFA, 0x2E, 0x00, 0x08, 0x20, 0x58, 0x38,
+ (byte) 0x88, (byte) 0x80, 0x10, 0x54, 0x06, 0x38, 0x20, 0x60,
+ 0x30, (byte) 0xA8, (byte) 0x81, (byte) 0x90, 0x20, 0x08
+ };
+
+ // Test case for CMAS test message received on the Sprint network.
+ public void testDecodeRawBearerData() throws Exception {
+ Parcel p = createBroadcastParcel(SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE);
+ SmsMessage msg = createMessageFromParcel(p, CMAS_TEST_BEARER_DATA);
+
+ SmsCbMessage cbMessage = msg.parseBroadcastSms();
+ assertNotNull("expected non-null for bearer data", cbMessage);
+ assertEquals("geoScope", cbMessage.getGeographicalScope(), 1);
+ assertEquals("serialNumber", cbMessage.getSerialNumber(), 51072);
+ assertEquals("serviceCategory", cbMessage.getServiceCategory(),
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE);
+ assertEquals("payload", cbMessage.getMessageBody(),
+ "This is a test of the Commercial Mobile Alert System. This is only a test.");
+
+ SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
+ assertNotNull("expected non-null for CMAS info", cmasInfo);
+ assertEquals("category", cmasInfo.getCategory(), SmsCbCmasInfo.CMAS_CATEGORY_OTHER);
+ assertEquals("responseType", cmasInfo.getResponseType(),
+ SmsCbCmasInfo.CMAS_RESPONSE_TYPE_NONE);
+ assertEquals("severity", cmasInfo.getSeverity(), SmsCbCmasInfo.CMAS_SEVERITY_SEVERE);
+ assertEquals("urgency", cmasInfo.getUrgency(), SmsCbCmasInfo.CMAS_URGENCY_EXPECTED);
+ assertEquals("certainty", cmasInfo.getCertainty(), SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
new file mode 100644
index 0000000..bb37b65
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
@@ -0,0 +1,887 @@
+/*
+ * 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.cdma.sms;
+
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.cdma.SmsMessage;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.BitwiseOutputStream;
+import com.android.internal.util.HexDump;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class CdmaSmsTest extends AndroidTestCase {
+ private final static String LOG_TAG = "XXX CdmaSmsTest XXX";
+
+ @SmallTest
+ public void testCdmaSmsAddrParsing() throws Exception {
+ CdmaSmsAddress addr = CdmaSmsAddress.parse("6502531000");
+ assertEquals(addr.ton, CdmaSmsAddress.TON_UNKNOWN);
+ assertEquals(addr.digitMode, CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF);
+ assertEquals(addr.numberMode, CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK);
+ assertEquals(addr.numberOfDigits, 10);
+ assertEquals(addr.origBytes.length, 10);
+ byte[] data = {6, 5, 10, 2, 5, 3, 1, 10, 10, 10};
+ for (int i = 0; i < data.length; i++) {
+ assertEquals(addr.origBytes[i], data[i]);
+ }
+ addr = CdmaSmsAddress.parse("(650) 253-1000");
+ assertEquals(addr.ton, CdmaSmsAddress.TON_UNKNOWN);
+ assertEquals(addr.digitMode, CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF);
+ assertEquals(addr.numberMode, CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK);
+ assertEquals(addr.numberOfDigits, 10);
+ assertEquals(addr.origBytes.length, 10);
+ byte[] data2 = {6, 5, 10, 2, 5, 3, 1, 10, 10, 10};
+ for (int i = 0; i < data2.length; i++) {
+ assertEquals(addr.origBytes[i], data2[i]);
+ }
+ addr = CdmaSmsAddress.parse("650.253.1000");
+ assertEquals(addr.ton, CdmaSmsAddress.TON_UNKNOWN);
+ assertEquals(addr.digitMode, CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF);
+ assertEquals(addr.numberMode, CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK);
+ assertEquals(addr.numberOfDigits, 10);
+ assertEquals(addr.origBytes.length, 10);
+ byte[] data5 = {6, 5, 10, 2, 5, 3, 1, 10, 10, 10};
+ for (int i = 0; i < data2.length; i++) {
+ assertEquals(addr.origBytes[i], data5[i]);
+ }
+ addr = CdmaSmsAddress.parse("(+886) 917 222 555");
+ assertEquals(addr.ton, CdmaSmsAddress.TON_INTERNATIONAL_OR_IP);
+ assertEquals(addr.digitMode, CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF);
+ assertEquals(addr.numberMode, CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK);
+ assertEquals(addr.numberOfDigits, 12);
+ assertEquals(addr.origBytes.length, 12);
+ byte[] data3 = {8, 8, 6, 9, 1, 7, 2, 2, 2, 5, 5, 5};
+ for (int i = 0; i < data3.length; i++) {
+ assertEquals(addr.origBytes[i], data3[i]);
+ }
+ addr = CdmaSmsAddress.parse("(650) *253-1000 #600");
+ byte[] data4 = {6, 5, 10, 11, 2, 5, 3, 1, 10, 10, 10, 12, 6, 10, 10};
+ for (int i = 0; i < data4.length; i++) {
+ assertEquals(addr.origBytes[i], data4[i]);
+ }
+ String input = "x@y.com,a@b.com";
+ addr = CdmaSmsAddress.parse(input);
+ assertEquals(addr.ton, CdmaSmsAddress.TON_NATIONAL_OR_EMAIL);
+ assertEquals(addr.digitMode, CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR);
+ assertEquals(addr.numberMode, CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK);
+ assertEquals(addr.numberOfDigits, 15);
+ assertEquals(addr.origBytes.length, 15);
+ assertEquals(new String(addr.origBytes), input);
+ addr = CdmaSmsAddress.parse("foo bar");
+ assertEquals(addr.ton, CdmaSmsAddress.TON_UNKNOWN);
+ assertEquals(addr.digitMode, CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR);
+ assertEquals(addr.numberMode, CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK);
+ assertEquals(addr.numberOfDigits, 6);
+ assertEquals(addr.origBytes.length, 6);
+ assertEquals(new String(addr.origBytes), "foobar");
+ addr = CdmaSmsAddress.parse("f\noo\tb a\rr");
+ assertEquals(new String(addr.origBytes), "foobar");
+ assertEquals(CdmaSmsAddress.parse("f\u0000oo bar"), null);
+ assertEquals(CdmaSmsAddress.parse("f\u0007oo bar"), null);
+ assertEquals(CdmaSmsAddress.parse("f\u0080oo bar"), null);
+ assertEquals(CdmaSmsAddress.parse("f\u1ECFboo\u001fbar"), null);
+ assertEquals(CdmaSmsAddress.parse("f\u0080oo bar"), null);
+ }
+
+ @SmallTest
+ public void testUserData7bitGsm() throws Exception {
+ String pdu = "00031040900112488ea794e074d69e1b7392c270326cde9e98";
+ BearerData bearerData = BearerData.decode(HexDump.hexStringToByteArray(pdu));
+ assertEquals("Test standard SMS", bearerData.userData.payloadStr);
+ }
+
+ @SmallTest
+ public void testUserData7bitAscii() throws Exception {
+ String pdu = "0003100160010610262d5ab500";
+ BearerData bearerData = BearerData.decode(HexDump.hexStringToByteArray(pdu));
+ assertEquals("bjjj", bearerData.userData.payloadStr);
+ }
+
+ @SmallTest
+ public void testUserData7bitAsciiTwo() throws Exception {
+ String pdu = "00031001d00109104539b4d052ebb3d0";
+ BearerData bearerData = BearerData.decode(HexDump.hexStringToByteArray(pdu));
+ assertEquals("SMS Rulz", bearerData.userData.payloadStr);
+ }
+
+ @SmallTest
+ public void testUserDataIa5() throws Exception {
+ String pdu = "00031002100109184539b4d052ebb3d0";
+ BearerData bearerData = BearerData.decode(HexDump.hexStringToByteArray(pdu));
+ assertEquals("SMS Rulz", bearerData.userData.payloadStr);
+ }
+
+ @SmallTest
+ public void testUserData7bitAsciiFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 0;
+ bearerData.hasUserDataHeader = false;
+ UserData userData = new UserData();
+ userData.payloadStr = "Test standard SMS";
+ userData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
+ userData.msgEncodingSet = true;
+ bearerData.userData = userData;
+ byte []encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ assertEquals(BearerData.MESSAGE_TYPE_DELIVER, revBearerData.messageType);
+ assertEquals(0, revBearerData.messageId);
+ assertEquals(false, revBearerData.hasUserDataHeader);
+ assertEquals(userData.msgEncoding, revBearerData.userData.msgEncoding);
+ assertEquals(userData.payloadStr.length(), revBearerData.userData.numFields);
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ userData.payloadStr = "Test \u007f standard \u0000 SMS";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals("Test standard SMS", revBearerData.userData.payloadStr);
+ userData.payloadStr = "Test \n standard \r SMS";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ userData.payloadStr = "";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ }
+
+ @SmallTest
+ public void testUserData7bitGsmFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 0;
+ bearerData.hasUserDataHeader = false;
+ UserData userData = new UserData();
+ userData.payloadStr = "Test standard SMS";
+ userData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+ userData.msgEncodingSet = true;
+ bearerData.userData = userData;
+ byte []encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ assertEquals(BearerData.MESSAGE_TYPE_DELIVER, revBearerData.messageType);
+ assertEquals(0, revBearerData.messageId);
+ assertEquals(false, revBearerData.hasUserDataHeader);
+ assertEquals(userData.msgEncoding, revBearerData.userData.msgEncoding);
+ assertEquals(userData.payloadStr.length(), revBearerData.userData.numFields);
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ userData.payloadStr = "1234567";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ userData.payloadStr = "";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ userData.payloadStr = "12345678901234567890123456789012345678901234567890" +
+ "12345678901234567890123456789012345678901234567890" +
+ "12345678901234567890123456789012345678901234567890" +
+ "1234567890";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ userData.payloadStr = "Test \u007f illegal \u0000 SMS chars";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals("Test illegal SMS chars", revBearerData.userData.payloadStr);
+ userData.payloadStr = "More @ testing\nis great^|^~woohoo";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = 0xEE;
+ concatRef.msgCount = 2;
+ concatRef.seqNumber = 2;
+ concatRef.isEightBits = true;
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.concatRef = concatRef;
+ byte[] encodedHeader = SmsHeader.toByteArray(smsHeader);
+ userData.userDataHeader = smsHeader;
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ SmsHeader decodedHeader = revBearerData.userData.userDataHeader;
+ assertEquals(decodedHeader.concatRef.refNumber, concatRef.refNumber);
+ assertEquals(decodedHeader.concatRef.msgCount, concatRef.msgCount);
+ assertEquals(decodedHeader.concatRef.seqNumber, concatRef.seqNumber);
+ }
+
+ @SmallTest
+ public void testUserDataUtf16Feedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 0;
+ bearerData.hasUserDataHeader = false;
+ UserData userData = new UserData();
+ userData.payloadStr = "\u0160u\u1E5B\u0301r\u1ECFg\uD835\uDC1At\u00E9\u4E002\u3042";
+ userData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ userData.msgEncodingSet = true;
+ bearerData.userData = userData;
+ byte []encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ assertEquals(BearerData.MESSAGE_TYPE_DELIVER, revBearerData.messageType);
+ assertEquals(0, revBearerData.messageId);
+ assertEquals(false, revBearerData.hasUserDataHeader);
+ assertEquals(userData.msgEncoding, revBearerData.userData.msgEncoding);
+ assertEquals(userData.payloadStr.length(), revBearerData.userData.numFields);
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ userData.msgEncoding = UserData.ENCODING_OCTET;
+ userData.msgEncodingSet = false;
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(BearerData.MESSAGE_TYPE_DELIVER, revBearerData.messageType);
+ assertEquals(0, revBearerData.messageId);
+ assertEquals(false, revBearerData.hasUserDataHeader);
+ assertEquals(userData.msgEncoding, revBearerData.userData.msgEncoding);
+ assertEquals(userData.payloadStr.length(), revBearerData.userData.numFields);
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ userData.payloadStr = "1234567";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ userData.payloadStr = "";
+ revBearerData = BearerData.decode(BearerData.encode(bearerData));
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ }
+
+ @SmallTest
+ public void testMonolithicOne() throws Exception {
+ String pdu = "0003200010010410168d2002010503060812011101590501c706069706180000000701c108" +
+ "01c00901800a01e00b01030c01c00d01070e05039acc13880f018011020566";
+ BearerData bearerData = BearerData.decode(HexDump.hexStringToByteArray(pdu));
+ assertEquals(bearerData.messageType, BearerData.MESSAGE_TYPE_SUBMIT);
+ assertEquals(bearerData.messageId, 1);
+ assertEquals(bearerData.priority, BearerData.PRIORITY_EMERGENCY);
+ assertEquals(bearerData.privacy, BearerData.PRIVACY_CONFIDENTIAL);
+ assertEquals(bearerData.userAckReq, true);
+ assertEquals(bearerData.readAckReq, true);
+ assertEquals(bearerData.deliveryAckReq, true);
+ assertEquals(bearerData.reportReq, false);
+ assertEquals(bearerData.numberOfMessages, 3);
+ assertEquals(bearerData.alert, BearerData.ALERT_HIGH_PRIO);
+ assertEquals(bearerData.language, BearerData.LANGUAGE_HEBREW);
+ assertEquals(bearerData.callbackNumber.digitMode, CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF);
+ assertEquals(bearerData.callbackNumber.numberMode,
+ CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK);
+ assertEquals(bearerData.callbackNumber.ton, CdmaSmsAddress.TON_UNKNOWN);
+ assertEquals(bearerData.callbackNumber.numberPlan, CdmaSmsAddress.NUMBERING_PLAN_UNKNOWN);
+ assertEquals(bearerData.callbackNumber.numberOfDigits, 7);
+ assertEquals(bearerData.callbackNumber.address, "3598271");
+ assertEquals(bearerData.displayMode, BearerData.DISPLAY_MODE_USER);
+ assertEquals(bearerData.depositIndex, 1382);
+ assertEquals(bearerData.userResponseCode, 5);
+ assertEquals(bearerData.msgCenterTimeStamp.year, 2008);
+ assertEquals(bearerData.msgCenterTimeStamp.month, 11);
+ assertEquals(bearerData.msgCenterTimeStamp.monthDay, 1);
+ assertEquals(bearerData.msgCenterTimeStamp.hour, 11);
+ assertEquals(bearerData.msgCenterTimeStamp.minute, 1);
+ assertEquals(bearerData.msgCenterTimeStamp.second, 59);
+ assertEquals(bearerData.validityPeriodAbsolute, null);
+ assertEquals(bearerData.validityPeriodRelative, 193);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.year, 1997);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.month, 5);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.monthDay, 18);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.hour, 0);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.minute, 0);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.second, 0);
+ assertEquals(bearerData.deferredDeliveryTimeRelative, 199);
+ assertEquals(bearerData.hasUserDataHeader, false);
+ assertEquals(bearerData.userData.msgEncoding, UserData.ENCODING_7BIT_ASCII);
+ assertEquals(bearerData.userData.numFields, 2);
+ assertEquals(bearerData.userData.payloadStr, "hi");
+ }
+
+ @SmallTest
+ public void testMonolithicTwo() throws Exception {
+ String pdu = "0003200010010410168d200201050306081201110159050192060697061800000007013d0" +
+ "801c00901800a01e00b01030c01c00d01070e05039acc13880f018011020566";
+ BearerData bearerData = BearerData.decode(HexDump.hexStringToByteArray(pdu));
+ assertEquals(bearerData.messageType, BearerData.MESSAGE_TYPE_SUBMIT);
+ assertEquals(bearerData.messageId, 1);
+ assertEquals(bearerData.priority, BearerData.PRIORITY_EMERGENCY);
+ assertEquals(bearerData.privacy, BearerData.PRIVACY_CONFIDENTIAL);
+ assertEquals(bearerData.userAckReq, true);
+ assertEquals(bearerData.readAckReq, true);
+ assertEquals(bearerData.deliveryAckReq, true);
+ assertEquals(bearerData.reportReq, false);
+ assertEquals(bearerData.numberOfMessages, 3);
+ assertEquals(bearerData.alert, BearerData.ALERT_HIGH_PRIO);
+ assertEquals(bearerData.language, BearerData.LANGUAGE_HEBREW);
+ assertEquals(bearerData.callbackNumber.digitMode, CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF);
+ assertEquals(bearerData.callbackNumber.numberMode,
+ CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK);
+ assertEquals(bearerData.callbackNumber.ton, CdmaSmsAddress.TON_UNKNOWN);
+ assertEquals(bearerData.callbackNumber.numberPlan, CdmaSmsAddress.NUMBERING_PLAN_UNKNOWN);
+ assertEquals(bearerData.callbackNumber.numberOfDigits, 7);
+ assertEquals(bearerData.callbackNumber.address, "3598271");
+ assertEquals(bearerData.displayMode, BearerData.DISPLAY_MODE_USER);
+ assertEquals(bearerData.depositIndex, 1382);
+ assertEquals(bearerData.userResponseCode, 5);
+ assertEquals(bearerData.msgCenterTimeStamp.year, 2008);
+ assertEquals(bearerData.msgCenterTimeStamp.month, 11);
+ assertEquals(bearerData.msgCenterTimeStamp.monthDay, 1);
+ assertEquals(bearerData.msgCenterTimeStamp.hour, 11);
+ assertEquals(bearerData.msgCenterTimeStamp.minute, 1);
+ assertEquals(bearerData.msgCenterTimeStamp.second, 59);
+ assertEquals(bearerData.validityPeriodAbsolute, null);
+ assertEquals(bearerData.validityPeriodRelative, 61);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.year, 1997);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.month, 5);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.monthDay, 18);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.hour, 0);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.minute, 0);
+ assertEquals(bearerData.deferredDeliveryTimeAbsolute.second, 0);
+ assertEquals(bearerData.deferredDeliveryTimeRelative, 146);
+ assertEquals(bearerData.hasUserDataHeader, false);
+ assertEquals(bearerData.userData.msgEncoding, UserData.ENCODING_7BIT_ASCII);
+ assertEquals(bearerData.userData.numFields, 2);
+ assertEquals(bearerData.userData.payloadStr, "hi");
+ }
+
+ @SmallTest
+ public void testUserDataHeaderConcatRefFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 55;
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = 0xEE;
+ concatRef.msgCount = 2;
+ concatRef.seqNumber = 2;
+ concatRef.isEightBits = true;
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.concatRef = concatRef;
+ byte[] encodedHeader = SmsHeader.toByteArray(smsHeader);
+ SmsHeader decodedHeader = SmsHeader.fromByteArray(encodedHeader);
+ assertEquals(decodedHeader.concatRef.refNumber, concatRef.refNumber);
+ assertEquals(decodedHeader.concatRef.msgCount, concatRef.msgCount);
+ assertEquals(decodedHeader.concatRef.seqNumber, concatRef.seqNumber);
+ assertEquals(decodedHeader.concatRef.isEightBits, concatRef.isEightBits);
+ assertEquals(decodedHeader.portAddrs, null);
+ UserData userData = new UserData();
+ userData.payloadStr = "User Data Header (UDH) feedback test";
+ userData.userDataHeader = smsHeader;
+ bearerData.userData = userData;
+ byte[] encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ decodedHeader = revBearerData.userData.userDataHeader;
+ assertEquals(decodedHeader.concatRef.refNumber, concatRef.refNumber);
+ assertEquals(decodedHeader.concatRef.msgCount, concatRef.msgCount);
+ assertEquals(decodedHeader.concatRef.seqNumber, concatRef.seqNumber);
+ assertEquals(decodedHeader.concatRef.isEightBits, concatRef.isEightBits);
+ assertEquals(decodedHeader.portAddrs, null);
+ }
+
+ @SmallTest
+ public void testUserDataHeaderIllegalConcatRef() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 55;
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = 0x10;
+ concatRef.msgCount = 0;
+ concatRef.seqNumber = 2;
+ concatRef.isEightBits = true;
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.concatRef = concatRef;
+ byte[] encodedHeader = SmsHeader.toByteArray(smsHeader);
+ SmsHeader decodedHeader = SmsHeader.fromByteArray(encodedHeader);
+ assertEquals(decodedHeader.concatRef, null);
+ concatRef.isEightBits = false;
+ encodedHeader = SmsHeader.toByteArray(smsHeader);
+ decodedHeader = SmsHeader.fromByteArray(encodedHeader);
+ assertEquals(decodedHeader.concatRef, null);
+ concatRef.msgCount = 1;
+ concatRef.seqNumber = 2;
+ encodedHeader = SmsHeader.toByteArray(smsHeader);
+ decodedHeader = SmsHeader.fromByteArray(encodedHeader);
+ assertEquals(decodedHeader.concatRef, null);
+ concatRef.msgCount = 1;
+ concatRef.seqNumber = 0;
+ encodedHeader = SmsHeader.toByteArray(smsHeader);
+ decodedHeader = SmsHeader.fromByteArray(encodedHeader);
+ assertEquals(decodedHeader.concatRef, null);
+ concatRef.msgCount = 2;
+ concatRef.seqNumber = 1;
+ encodedHeader = SmsHeader.toByteArray(smsHeader);
+ decodedHeader = SmsHeader.fromByteArray(encodedHeader);
+ assertEquals(decodedHeader.concatRef.msgCount, 2);
+ assertEquals(decodedHeader.concatRef.seqNumber, 1);
+ }
+
+ @SmallTest
+ public void testUserDataHeaderMixedFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 42;
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = 0x34;
+ concatRef.msgCount = 5;
+ concatRef.seqNumber = 2;
+ concatRef.isEightBits = false;
+ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+ portAddrs.destPort = 88;
+ portAddrs.origPort = 66;
+ portAddrs.areEightBits = false;
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.concatRef = concatRef;
+ smsHeader.portAddrs = portAddrs;
+ byte[] encodedHeader = SmsHeader.toByteArray(smsHeader);
+ SmsHeader decodedHeader = SmsHeader.fromByteArray(encodedHeader);
+ assertEquals(decodedHeader.concatRef.refNumber, concatRef.refNumber);
+ assertEquals(decodedHeader.concatRef.msgCount, concatRef.msgCount);
+ assertEquals(decodedHeader.concatRef.seqNumber, concatRef.seqNumber);
+ assertEquals(decodedHeader.concatRef.isEightBits, concatRef.isEightBits);
+ assertEquals(decodedHeader.portAddrs.destPort, portAddrs.destPort);
+ assertEquals(decodedHeader.portAddrs.origPort, portAddrs.origPort);
+ assertEquals(decodedHeader.portAddrs.areEightBits, portAddrs.areEightBits);
+ UserData userData = new UserData();
+ userData.payloadStr = "User Data Header (UDH) feedback test";
+ userData.userDataHeader = smsHeader;
+ bearerData.userData = userData;
+ byte[] encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ decodedHeader = revBearerData.userData.userDataHeader;
+ assertEquals(decodedHeader.concatRef.refNumber, concatRef.refNumber);
+ assertEquals(decodedHeader.concatRef.msgCount, concatRef.msgCount);
+ assertEquals(decodedHeader.concatRef.seqNumber, concatRef.seqNumber);
+ assertEquals(decodedHeader.concatRef.isEightBits, concatRef.isEightBits);
+ assertEquals(decodedHeader.portAddrs.destPort, portAddrs.destPort);
+ assertEquals(decodedHeader.portAddrs.origPort, portAddrs.origPort);
+ assertEquals(decodedHeader.portAddrs.areEightBits, portAddrs.areEightBits);
+ }
+
+ @SmallTest
+ public void testReplyOption() throws Exception {
+ String pdu1 = "0003104090011648b6a794e0705476bf77bceae934fe5f6d94d87450080a0180";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals("Test Acknowledgement 1", bd1.userData.payloadStr);
+ assertEquals(true, bd1.userAckReq);
+ assertEquals(false, bd1.deliveryAckReq);
+ assertEquals(false, bd1.readAckReq);
+ assertEquals(false, bd1.reportReq);
+ String pdu2 = "0003104090011648b6a794e0705476bf77bceae934fe5f6d94d87490080a0140";
+ BearerData bd2 = BearerData.decode(HexDump.hexStringToByteArray(pdu2));
+ assertEquals("Test Acknowledgement 2", bd2.userData.payloadStr);
+ assertEquals(false, bd2.userAckReq);
+ assertEquals(true, bd2.deliveryAckReq);
+ assertEquals(false, bd2.readAckReq);
+ assertEquals(false, bd2.reportReq);
+ String pdu3 = "0003104090011648b6a794e0705476bf77bceae934fe5f6d94d874d0080a0120";
+ BearerData bd3 = BearerData.decode(HexDump.hexStringToByteArray(pdu3));
+ assertEquals("Test Acknowledgement 3", bd3.userData.payloadStr);
+ assertEquals(false, bd3.userAckReq);
+ assertEquals(false, bd3.deliveryAckReq);
+ assertEquals(true, bd3.readAckReq);
+ assertEquals(false, bd3.reportReq);
+ String pdu4 = "0003104090011648b6a794e0705476bf77bceae934fe5f6d94d87510080a0110";
+ BearerData bd4 = BearerData.decode(HexDump.hexStringToByteArray(pdu4));
+ assertEquals("Test Acknowledgement 4", bd4.userData.payloadStr);
+ assertEquals(false, bd4.userAckReq);
+ assertEquals(false, bd4.deliveryAckReq);
+ assertEquals(false, bd4.readAckReq);
+ assertEquals(true, bd4.reportReq);
+ }
+
+ @SmallTest
+ public void testReplyOptionFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 0;
+ bearerData.hasUserDataHeader = false;
+ UserData userData = new UserData();
+ userData.payloadStr = "test reply option";
+ bearerData.userData = userData;
+ bearerData.userAckReq = true;
+ byte []encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ assertEquals(true, revBearerData.userAckReq);
+ assertEquals(false, revBearerData.deliveryAckReq);
+ assertEquals(false, revBearerData.readAckReq);
+ assertEquals(false, revBearerData.reportReq);
+ bearerData.userAckReq = false;
+ bearerData.deliveryAckReq = true;
+ encodedSms = BearerData.encode(bearerData);
+ revBearerData = BearerData.decode(encodedSms);
+ assertEquals(false, revBearerData.userAckReq);
+ assertEquals(true, revBearerData.deliveryAckReq);
+ assertEquals(false, revBearerData.readAckReq);
+ assertEquals(false, revBearerData.reportReq);
+ bearerData.deliveryAckReq = false;
+ bearerData.readAckReq = true;
+ encodedSms = BearerData.encode(bearerData);
+ revBearerData = BearerData.decode(encodedSms);
+ assertEquals(false, revBearerData.userAckReq);
+ assertEquals(false, revBearerData.deliveryAckReq);
+ assertEquals(true, revBearerData.readAckReq);
+ assertEquals(false, revBearerData.reportReq);
+ bearerData.readAckReq = false;
+ bearerData.reportReq = true;
+ encodedSms = BearerData.encode(bearerData);
+ revBearerData = BearerData.decode(encodedSms);
+ assertEquals(false, revBearerData.userAckReq);
+ assertEquals(false, revBearerData.deliveryAckReq);
+ assertEquals(false, revBearerData.readAckReq);
+ assertEquals(true, revBearerData.reportReq);
+ }
+
+ @SmallTest
+ public void testNumberOfMessages() throws Exception {
+ // Note that the message text below does not properly reflect
+ // the message count. The author of these messages was
+ // apparently unaware that the values are bcd encoded, and the
+ // values being tested against (not the ones in the message
+ // text) are actually correct.
+ String pdu1 = "000310409001124896a794e07595f69f199540ea759a0dc8e00b0163";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals("Test Voice mail 99", bd1.userData.payloadStr);
+ assertEquals(63, bd1.numberOfMessages);
+ String pdu2 = "00031040900113489ea794e07595f69f199540ea759a0988c0600b0164";
+ BearerData bd2 = BearerData.decode(HexDump.hexStringToByteArray(pdu2));
+ assertEquals("Test Voice mail 100", bd2.userData.payloadStr);
+ assertEquals(64, bd2.numberOfMessages);
+ }
+
+ @SmallTest
+ public void testCallbackNum() throws Exception {
+ String pdu1 = "00031040900112488ea794e070d436cb638bc5e035ce2f97900e06910431323334";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals("Test Callback nbr", bd1.userData.payloadStr);
+ assertEquals(CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR, bd1.callbackNumber.digitMode);
+ assertEquals(CdmaSmsAddress.TON_INTERNATIONAL_OR_IP, bd1.callbackNumber.ton);
+ assertEquals(CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK, bd1.callbackNumber.numberMode);
+ assertEquals(CdmaSmsAddress.NUMBERING_PLAN_ISDN_TELEPHONY, bd1.callbackNumber.numberPlan);
+ assertEquals("1234", bd1.callbackNumber.address);
+ }
+
+ @SmallTest
+ public void testCallbackNumDtmf() throws Exception {
+ String pdu1 = "00031002300109104539b4d052ebb3d00e07052d4c90a55080";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals("SMS Rulz", bd1.userData.payloadStr);
+ assertEquals(CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF, bd1.callbackNumber.digitMode);
+ assertEquals(CdmaSmsAddress.TON_UNKNOWN, bd1.callbackNumber.ton);
+ assertEquals(CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK, bd1.callbackNumber.numberMode);
+ assertEquals(CdmaSmsAddress.NUMBERING_PLAN_UNKNOWN, bd1.callbackNumber.numberPlan);
+ assertEquals("5099214001", bd1.callbackNumber.address);
+ }
+
+ @SmallTest
+ public void testCallbackNumFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 0;
+ bearerData.hasUserDataHeader = false;
+ UserData userData = new UserData();
+ userData.payloadStr = "test callback number";
+ bearerData.userData = userData;
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.digitMode = CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR;
+ addr.ton = CdmaSmsAddress.TON_NATIONAL_OR_EMAIL;
+ addr.numberMode = CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK;
+ addr.numberPlan = CdmaSmsAddress.NUMBERING_PLAN_UNKNOWN;
+ addr.address = "8005551212";
+ addr.numberOfDigits = (byte)addr.address.length();
+ bearerData.callbackNumber = addr;
+ byte []encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ CdmaSmsAddress revAddr = revBearerData.callbackNumber;
+ assertEquals(addr.digitMode, revAddr.digitMode);
+ assertEquals(addr.ton, revAddr.ton);
+ assertEquals(addr.numberMode, revAddr.numberMode);
+ assertEquals(addr.numberPlan, revAddr.numberPlan);
+ assertEquals(addr.numberOfDigits, revAddr.numberOfDigits);
+ assertEquals(addr.address, revAddr.address);
+ addr.address = "8*55#1012";
+ addr.numberOfDigits = (byte)addr.address.length();
+ addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF;
+ encodedSms = BearerData.encode(bearerData);
+ revBearerData = BearerData.decode(encodedSms);
+ revAddr = revBearerData.callbackNumber;
+ assertEquals(addr.digitMode, revAddr.digitMode);
+ assertEquals(addr.numberOfDigits, revAddr.numberOfDigits);
+ assertEquals(addr.address, revAddr.address);
+ }
+
+ @SmallTest
+ public void testPrivacyIndicator() throws Exception {
+ String pdu1 = "0003104090010c485f4194dfea34becf61b840090140";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals(bd1.privacy, BearerData.PRIVACY_RESTRICTED);
+ String pdu2 = "0003104090010c485f4194dfea34becf61b840090180";
+ BearerData bd2 = BearerData.decode(HexDump.hexStringToByteArray(pdu2));
+ assertEquals(bd2.privacy, BearerData.PRIVACY_CONFIDENTIAL);
+ String pdu3 = "0003104090010c485f4194dfea34becf61b8400901c0";
+ BearerData bd3 = BearerData.decode(HexDump.hexStringToByteArray(pdu3));
+ assertEquals(bd3.privacy, BearerData.PRIVACY_SECRET);
+ }
+
+ @SmallTest
+ public void testPrivacyIndicatorFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 0;
+ bearerData.hasUserDataHeader = false;
+ UserData userData = new UserData();
+ userData.payloadStr = "test privacy indicator";
+ bearerData.userData = userData;
+ bearerData.privacy = BearerData.PRIVACY_SECRET;
+ bearerData.privacyIndicatorSet = true;
+ byte []encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ assertEquals(revBearerData.userData.payloadStr, userData.payloadStr);
+ assertEquals(revBearerData.privacyIndicatorSet, true);
+ assertEquals(revBearerData.privacy, BearerData.PRIVACY_SECRET);
+ bearerData.privacy = BearerData.PRIVACY_RESTRICTED;
+ encodedSms = BearerData.encode(bearerData);
+ revBearerData = BearerData.decode(encodedSms);
+ assertEquals(revBearerData.privacy, BearerData.PRIVACY_RESTRICTED);
+ }
+
+ @SmallTest
+ public void testMsgDeliveryAlert() throws Exception {
+ String pdu1 = "0003104090010d4866a794e07055965b91d040300c0100";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals(bd1.alert, 0);
+ assertEquals(bd1.userData.payloadStr, "Test Alert 0");
+ String pdu2 = "0003104090010d4866a794e07055965b91d140300c0140";
+ BearerData bd2 = BearerData.decode(HexDump.hexStringToByteArray(pdu2));
+ assertEquals(bd2.alert, 1);
+ assertEquals(bd2.userData.payloadStr, "Test Alert 1");
+ String pdu3 = "0003104090010d4866a794e07055965b91d240300c0180";
+ BearerData bd3 = BearerData.decode(HexDump.hexStringToByteArray(pdu3));
+ assertEquals(bd3.alert, 2);
+ assertEquals(bd3.userData.payloadStr, "Test Alert 2");
+ String pdu4 = "0003104090010d4866a794e07055965b91d340300c01c0";
+ BearerData bd4 = BearerData.decode(HexDump.hexStringToByteArray(pdu4));
+ assertEquals(bd4.alert, 3);
+ assertEquals(bd4.userData.payloadStr, "Test Alert 3");
+ String pdu5 = "00031000000126114F4CBCFA20DB979F3C39F2A0C9976" +
+ "69ED979794187665E5D1028EFA7A6840E1062D3D39A900C028000";
+ BearerData bd5 = BearerData.decode(HexDump.hexStringToByteArray(pdu5));
+ assertEquals(bd5.alert, BearerData.ALERT_MEDIUM_PRIO);
+ assertEquals(bd5.userData.payloadStr, "test message delivery alert (with 8 bits)");
+ String pdu6 = "00031000000126114F4CBCFA20DB979F3C39F2A0C9976" +
+ "69ED979794187665E5D1028EFA7A6840C1062D3D39A900C00";
+ BearerData bd6 = BearerData.decode(HexDump.hexStringToByteArray(pdu6));
+ assertEquals(bd6.userData.payloadStr, "test message delivery alert (with 0 bits)");
+ assertEquals(bd6.alertIndicatorSet, false);
+ }
+
+ @SmallTest
+ public void testMiscParams() throws Exception {
+ String pdu1 = "00031002400109104539b4d052ebb3d00c0180";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals(bd1.alert, BearerData.ALERT_MEDIUM_PRIO);
+ assertEquals(bd1.userData.payloadStr, "SMS Rulz");
+ String pdu2 = "00031002500109104539b4d052ebb3d00801800901c0";
+ BearerData bd2 = BearerData.decode(HexDump.hexStringToByteArray(pdu2));
+ assertEquals(bd2.priority, BearerData.PRIORITY_URGENT);
+ assertEquals(bd2.privacy, BearerData.PRIVACY_SECRET);
+ assertEquals(bd2.userData.payloadStr, "SMS Rulz");
+ String pdu3 = "00031002600109104539b4d052ebb3d00901400c01c0";
+ BearerData bd3 = BearerData.decode(HexDump.hexStringToByteArray(pdu3));
+ assertEquals(bd3.privacy, BearerData.PRIVACY_RESTRICTED);
+ assertEquals(bd3.alert, BearerData.ALERT_HIGH_PRIO);
+ assertEquals(bd3.userData.payloadStr, "SMS Rulz");
+ String pdu4 = "00031002700109104539b4d052ebb3d00f0105";
+ BearerData bd4 = BearerData.decode(HexDump.hexStringToByteArray(pdu4));
+ assertEquals(bd4.displayMode, BearerData.DISPLAY_MODE_IMMEDIATE);
+ assertEquals(bd4.userData.payloadStr, "SMS Rulz");
+ }
+ @SmallTest
+ public void testMsgDeliveryAlertFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 0;
+ bearerData.hasUserDataHeader = false;
+ UserData userData = new UserData();
+ userData.payloadStr = "test message delivery alert";
+ bearerData.userData = userData;
+ bearerData.alert = BearerData.ALERT_MEDIUM_PRIO;
+ bearerData.alertIndicatorSet = true;
+ byte []encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ assertEquals(revBearerData.userData.payloadStr, userData.payloadStr);
+ assertEquals(revBearerData.alertIndicatorSet, true);
+ assertEquals(revBearerData.alert, bearerData.alert);
+ bearerData.alert = BearerData.ALERT_HIGH_PRIO;
+ encodedSms = BearerData.encode(bearerData);
+ revBearerData = BearerData.decode(encodedSms);
+ assertEquals(revBearerData.userData.payloadStr, userData.payloadStr);
+ assertEquals(revBearerData.alertIndicatorSet, true);
+ assertEquals(revBearerData.alert, bearerData.alert);
+ }
+
+ @SmallTest
+ public void testLanguageIndicator() throws Exception {
+ String pdu1 = "0003104090011748bea794e0731436ef3bd7c2e0352eef27a1c263fe58080d0101";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals(bd1.userData.payloadStr, "Test Language indicator");
+ assertEquals(bd1.language, BearerData.LANGUAGE_ENGLISH);
+ String pdu2 = "0003104090011748bea794e0731436ef3bd7c2e0352eef27a1c263fe58080d0106";
+ BearerData bd2 = BearerData.decode(HexDump.hexStringToByteArray(pdu2));
+ assertEquals(bd2.userData.payloadStr, "Test Language indicator");
+ assertEquals(bd2.language, BearerData.LANGUAGE_CHINESE);
+ }
+
+ @SmallTest
+ public void testLanguageIndicatorFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 0;
+ bearerData.hasUserDataHeader = false;
+ UserData userData = new UserData();
+ userData.payloadStr = "test language indicator";
+ bearerData.userData = userData;
+ bearerData.language = BearerData.LANGUAGE_ENGLISH;
+ bearerData.languageIndicatorSet = true;
+ byte []encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ assertEquals(revBearerData.userData.payloadStr, userData.payloadStr);
+ assertEquals(revBearerData.languageIndicatorSet, true);
+ assertEquals(revBearerData.language, bearerData.language);
+ bearerData.language = BearerData.LANGUAGE_KOREAN;
+ encodedSms = BearerData.encode(bearerData);
+ revBearerData = BearerData.decode(encodedSms);
+ assertEquals(revBearerData.userData.payloadStr, userData.payloadStr);
+ assertEquals(revBearerData.languageIndicatorSet, true);
+ assertEquals(revBearerData.language, bearerData.language);
+ }
+
+ @SmallTest
+ public void testDisplayMode() throws Exception {
+ String pdu1 = "0003104090010c485f4194dfea34becf61b8400f0100";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals(bd1.displayMode, BearerData.DISPLAY_MODE_IMMEDIATE);
+ String pdu2 = "0003104090010c485f4194dfea34becf61b8400f0140";
+ BearerData bd2 = BearerData.decode(HexDump.hexStringToByteArray(pdu2));
+ assertEquals(bd2.displayMode, BearerData.DISPLAY_MODE_DEFAULT);
+ String pdu3 = "0003104090010c485f4194dfea34becf61b8400f0180";
+ BearerData bd3 = BearerData.decode(HexDump.hexStringToByteArray(pdu3));
+ assertEquals(bd3.displayMode, BearerData.DISPLAY_MODE_USER);
+ }
+
+ @SmallTest
+ public void testDisplayModeFeedback() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 0;
+ bearerData.hasUserDataHeader = false;
+ UserData userData = new UserData();
+ userData.payloadStr = "test display mode";
+ bearerData.userData = userData;
+ bearerData.displayMode = BearerData.DISPLAY_MODE_IMMEDIATE;
+ bearerData.displayModeSet = true;
+ byte []encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ assertEquals(revBearerData.userData.payloadStr, userData.payloadStr);
+ assertEquals(revBearerData.displayModeSet, true);
+ assertEquals(revBearerData.displayMode, bearerData.displayMode);
+ bearerData.displayMode = BearerData.DISPLAY_MODE_USER;
+ encodedSms = BearerData.encode(bearerData);
+ revBearerData = BearerData.decode(encodedSms);
+ assertEquals(revBearerData.userData.payloadStr, userData.payloadStr);
+ assertEquals(revBearerData.displayModeSet, true);
+ assertEquals(revBearerData.displayMode, bearerData.displayMode);
+ }
+
+ @SmallTest
+ public void testIs91() throws Exception {
+ String pdu1 = "000320001001070c2039acc13880";
+ BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1));
+ assertEquals(bd1.callbackNumber.address, "3598271");
+ String pdu4 = "000320001001080c283c314724b34e";
+ BearerData bd4 = BearerData.decode(HexDump.hexStringToByteArray(pdu4));
+ assertEquals(bd4.userData.payloadStr, "ABCDEFG");
+ }
+
+ @SmallTest
+ public void testUserDataHeaderWithEightCharMsg() throws Exception {
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+ bearerData.messageId = 55;
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = 0xEE;
+ concatRef.msgCount = 2;
+ concatRef.seqNumber = 2;
+ concatRef.isEightBits = true;
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.concatRef = concatRef;
+ UserData userData = new UserData();
+ userData.payloadStr = "01234567";
+ userData.userDataHeader = smsHeader;
+ bearerData.userData = userData;
+ byte[] encodedSms = BearerData.encode(bearerData);
+ BearerData revBearerData = BearerData.decode(encodedSms);
+ assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
+ }
+
+ @SmallTest
+ public void testFragmentText() throws Exception {
+ boolean isCdmaPhone = (TelephonyManager.getDefault().getPhoneType() ==
+ TelephonyManager.PHONE_TYPE_CDMA);
+ // Valid 160 character ASCII text.
+ String text1 = "123456789012345678901234567890123456789012345678901234567890" +
+ "1234567890123456789012345678901234567890123456789012345678901234567890" +
+ "12345678901234567890123456789[";
+ TextEncodingDetails ted = SmsMessage.calculateLength(text1, false);
+ assertEquals(ted.msgCount, 1);
+ assertEquals(ted.codeUnitCount, 160);
+ assertEquals(ted.codeUnitSize, 1);
+ if (isCdmaPhone) {
+ ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text1);
+ assertEquals(fragments.size(), 1);
+ }
+
+ /*
+ This is not a valid test: we will never encode a single-segment
+ EMS message. Leaving this here, since we may try to support
+ this in the future.
+
+ // Valid 160 character GSM text -- the last character is
+ // non-ASCII, and so this will currently generate a singleton
+ // EMS message, which is not necessarily supported by Verizon.
+ String text2 = "123456789012345678901234567890123456789012345678901234567890" +
+ "1234567890123456789012345678901234567890123456789012345678901234567890" +
+ "12345678901234567890123456789\u00a3"; // Trailing pound-currency sign.
+ ted = SmsMessage.calculateLength(text2, false);
+ assertEquals(ted.msgCount, 1);
+ assertEquals(ted.codeUnitCount, 160);
+ assertEquals(ted.codeUnitSize, 1);
+ if (isCdmaPhone) {
+ ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text2);
+ assertEquals(fragments.size(), 1);
+ }
+ */
+
+ // *IF* we supported single-segment EMS, this text would result in a
+ // single fragment with 7-bit encoding. But we don't, so this text
+ // results in three fragments of 16-bit encoding.
+ String text2 = "123456789012345678901234567890123456789012345678901234567890" +
+ "1234567890123456789012345678901234567890123456789012345678901234567890" +
+ "12345678901234567890123456789\u00a3"; // Trailing pound-currency sign.
+ ted = SmsMessage.calculateLength(text2, false);
+ assertEquals(3, ted.msgCount);
+ assertEquals(160, ted.codeUnitCount);
+ assertEquals(3, ted.codeUnitSize);
+ if (isCdmaPhone) {
+ ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text2);
+ assertEquals(3, fragments.size());
+ }
+
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
new file mode 100644
index 0000000..699f113
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
@@ -0,0 +1,1933 @@
+/*
+ * 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 GSMTestHandler.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.Handler;
+import android.os.Message;
+import android.telephony.ServiceState;
+import android.test.AndroidTestCase;
+import android.test.PerformanceTestCase;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.gsm.CallFailCause;
+import com.android.internal.telephony.gsm.GSMPhone;
+import com.android.internal.telephony.gsm.GSMTestHandler;
+import com.android.internal.telephony.gsm.GsmMmiCode;
+import com.android.internal.telephony.gsm.SuppServiceNotification;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+
+import java.util.List;
+
+
+public class GSMPhoneTest extends AndroidTestCase implements PerformanceTestCase {
+ private SimulatedRadioControl mRadioControl;
+ private GSMPhone mGSMPhone;
+ private GSMTestHandler mGSMTestHandler;
+ private Handler mHandler;
+
+ private static final int EVENT_PHONE_STATE_CHANGED = 1;
+ private static final int EVENT_DISCONNECT = 2;
+ private static final int EVENT_RINGING = 3;
+ private static final int EVENT_CHANNEL_OPENED = 4;
+ private static final int EVENT_POST_DIAL = 5;
+ private static final int EVENT_DONE = 6;
+ private static final int EVENT_SSN = 7;
+ private static final int EVENT_MMI_INITIATE = 8;
+ private static final int EVENT_MMI_COMPLETE = 9;
+ private static final int EVENT_IN_SERVICE = 10;
+ private static final int SUPP_SERVICE_FAILED = 11;
+ private static final int SERVICE_STATE_CHANGED = 12;
+ private static final int EVENT_OEM_RIL_MESSAGE = 13;
+ public static final int ANY_MESSAGE = -1;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mGSMTestHandler = new GSMTestHandler(mContext);
+
+ mGSMTestHandler.start();
+ synchronized (mGSMTestHandler) {
+ do {
+ mGSMTestHandler.wait();
+ } while (mGSMTestHandler.getGSMPhone() == null);
+ }
+
+ mGSMPhone = mGSMTestHandler.getGSMPhone();
+ mRadioControl = mGSMTestHandler.getSimulatedCommands();
+
+ mHandler = mGSMTestHandler.getHandler();
+ mGSMPhone.registerForPreciseCallStateChanged(mHandler, EVENT_PHONE_STATE_CHANGED, null);
+ mGSMPhone.registerForNewRingingConnection(mHandler, EVENT_RINGING, null);
+ mGSMPhone.registerForDisconnect(mHandler, EVENT_DISCONNECT, null);
+
+ mGSMPhone.setOnPostDialCharacter(mHandler, EVENT_POST_DIAL, null);
+
+ mGSMPhone.registerForSuppServiceNotification(mHandler, EVENT_SSN, null);
+ mGSMPhone.registerForMmiInitiate(mHandler, EVENT_MMI_INITIATE, null);
+ mGSMPhone.registerForMmiComplete(mHandler, EVENT_MMI_COMPLETE, null);
+ mGSMPhone.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
+
+ mGSMPhone.registerForServiceStateChanged(mHandler, SERVICE_STATE_CHANGED, null);
+
+ // wait until we get phone in both voice and data service
+ Message msg;
+ ServiceState state;
+
+ do {
+ msg = mGSMTestHandler.waitForMessage(SERVICE_STATE_CHANGED);
+ assertNotNull("Message Time Out", msg);
+ state = (ServiceState) ((AsyncResult) msg.obj).result;
+ } while (state.getState() != ServiceState.STATE_IN_SERVICE);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mRadioControl.shutdown();
+
+ mGSMPhone.unregisterForPreciseCallStateChanged(mHandler);
+ mGSMPhone.unregisterForNewRingingConnection(mHandler);
+ mGSMPhone.unregisterForDisconnect(mHandler);
+ mGSMPhone.setOnPostDialCharacter(mHandler, 0, null);
+ mGSMPhone.unregisterForSuppServiceNotification(mHandler);
+ mGSMPhone.unregisterForMmiInitiate(mHandler);
+ mGSMPhone.unregisterForMmiComplete(mHandler);
+
+ mGSMPhone = null;
+ mRadioControl = null;
+ mHandler = null;
+ mGSMTestHandler.cleanup();
+
+ super.tearDown();
+ }
+
+ // These test can only be run once.
+ public int startPerformance(Intermediates intermediates) {
+ return 1;
+ }
+
+ public boolean isPerformanceOnly() {
+ return false;
+ }
+
+
+ //This test is causing the emulator screen to turn off. I don't understand
+ //why, but I'm removing it until we can figure it out.
+ public void brokenTestGeneral() throws Exception {
+ Connection cn;
+ Message msg;
+ AsyncResult ar;
+
+ // IDLE state
+
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestCreateTime());
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+ assertFalse(mGSMPhone.canConference());
+
+ // One DIALING connection
+
+ mRadioControl.setAutoProgressConnectingCall(false);
+
+ mGSMPhone.dial("+13125551212");
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_PHONE_STATE_CHANGED);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertEquals(Call.State.DIALING, mGSMPhone.getForegroundCall().getState());
+ assertTrue(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+
+ /*do {
+ mGSMTestHandler.waitForMessage(ANY_MESSAGE);
+ } while (mGSMPhone.getForegroundCall().getConnections().size() == 0);*/
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.DIALING,
+ mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ cn = mGSMPhone.getForegroundCall().getConnections().get(0);
+ assertTrue(!cn.isIncoming());
+ assertEquals(Connection.PostDialState.NOT_STARTED, cn.getPostDialState());
+
+ assertEquals(Connection.DisconnectCause.NOT_DISCONNECTED, cn.getDisconnectCause());
+
+ assertFalse(mGSMPhone.canConference());
+
+ // One ALERTING connection
+
+ mRadioControl.progressConnectingCallState();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ }
+ while (mGSMPhone.getForegroundCall().getState() != Call.State.ALERTING);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertTrue(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ALERTING, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ cn = mGSMPhone.getForegroundCall().getConnections().get(0);
+ assertTrue(!cn.isIncoming());
+ assertEquals(Connection.PostDialState.NOT_STARTED, cn.getPostDialState());
+ assertFalse(mGSMPhone.canConference());
+
+ // One ACTIVE connection
+
+ mRadioControl.progressConnectingCallState();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestConnectTime() > 0);
+
+ cn = mGSMPhone.getForegroundCall().getConnections().get(0);
+ assertTrue(!cn.isIncoming());
+ assertEquals(Connection.PostDialState.COMPLETE, cn.getPostDialState());
+ assertFalse(mGSMPhone.canConference());
+
+ // One disconnected connection
+ mGSMPhone.getForegroundCall().hangup();
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_DISCONNECT);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestConnectTime() > 0);
+
+ assertFalse(mGSMPhone.canConference());
+
+ cn = mGSMPhone.getForegroundCall().getEarliestConnection();
+
+ assertEquals(Call.State.DISCONNECTED, cn.getState());
+
+ // Back to idle state
+
+ mGSMPhone.clearDisconnected();
+
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestCreateTime());
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ assertFalse(mGSMPhone.canConference());
+
+ // cn left over from before phone.clearDisconnected();
+
+ assertEquals(Call.State.DISCONNECTED, cn.getState());
+
+ // One ringing (INCOMING) call
+
+ mRadioControl.triggerRing("18005551212");
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_RINGING);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+
+ ar = (AsyncResult) msg.obj;
+ cn = (Connection) ar.result;
+ assertTrue(cn.isRinging());
+ assertEquals(mGSMPhone.getRingingCall(), cn.getCall());
+
+ assertEquals(1, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.INCOMING, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getRingingCall().getEarliestCreateTime() > 0);
+ assertEquals(0, mGSMPhone.getRingingCall().getEarliestConnectTime());
+
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestCreateTime());
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ cn = mGSMPhone.getRingingCall().getConnections().get(0);
+ assertTrue(cn.isIncoming());
+ assertEquals(Connection.PostDialState.NOT_STARTED, cn.getPostDialState());
+
+ assertFalse(mGSMPhone.canConference());
+
+ // One mobile terminated active call
+ mGSMPhone.acceptCall();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getRingingCall().getConnections().size() == 1);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getRingingCall().isRinging());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE,
+ mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestConnectTime() > 0);
+
+ cn = mGSMPhone.getForegroundCall().getConnections().get(0);
+ assertTrue(cn.isIncoming());
+ assertEquals(Connection.PostDialState.NOT_STARTED, cn.getPostDialState());
+
+ assertFalse(mGSMPhone.canConference());
+
+ // One disconnected (local hangup) call
+
+ try {
+ Connection conn;
+ conn = mGSMPhone.getForegroundCall().getConnections().get(0);
+ conn.hangup();
+ } catch (CallStateException ex) {
+ ex.printStackTrace();
+ fail("unexpected ex");
+ }
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_DISCONNECT);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+ assertFalse(mGSMPhone.getRingingCall().isRinging());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.DISCONNECTED,
+ mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestConnectTime() > 0);
+
+ cn = mGSMPhone.getForegroundCall().getEarliestConnection();
+
+ assertEquals(Call.State.DISCONNECTED, cn.getState());
+
+ assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+
+ assertFalse(mGSMPhone.canConference());
+
+ // Back to idle state
+
+ mGSMPhone.clearDisconnected();
+
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+ assertFalse(mGSMPhone.getRingingCall().isRinging());
+
+ assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+ assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestCreateTime());
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ assertFalse(mGSMPhone.canConference());
+
+ // cn left over from before phone.clearDisconnected();
+
+ assertEquals(Call.State.DISCONNECTED, cn.getState());
+
+ // One ringing call
+
+ mRadioControl.triggerRing("18005551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getRingingCall().getConnections().isEmpty());
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+
+ assertEquals(1, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.INCOMING, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getRingingCall().getEarliestCreateTime() > 0);
+ assertEquals(0, mGSMPhone.getRingingCall().getEarliestConnectTime());
+
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestCreateTime());
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ assertFalse(mGSMPhone.canConference());
+
+ // One rejected call
+ mGSMPhone.rejectCall();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.IDLE);
+
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+ assertFalse(mGSMPhone.getRingingCall().isRinging());
+
+ assertEquals(1, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getRingingCall().getEarliestCreateTime() > 0);
+ assertEquals(0, mGSMPhone.getRingingCall().getEarliestConnectTime());
+
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestCreateTime());
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ cn = mGSMPhone.getRingingCall().getEarliestConnection();
+ assertEquals(Call.State.DISCONNECTED, cn.getState());
+
+ assertEquals(Connection.DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
+
+ assertFalse(mGSMPhone.canConference());
+
+ // Back to idle state
+
+ mGSMPhone.clearDisconnected();
+
+ assertEquals(Connection.DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
+ assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestCreateTime());
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ assertFalse(mGSMPhone.canConference());
+ assertEquals(Call.State.DISCONNECTED, cn.getState());
+
+ // One ringing call
+
+ mRadioControl.triggerRing("18005551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getRingingCall().getConnections().isEmpty());
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+
+ cn = mGSMPhone.getRingingCall().getEarliestConnection();
+
+ // Ringing call disconnects
+
+ mRadioControl.triggerHangupForeground();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.IDLE);
+
+ assertEquals(Connection.DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
+
+ // One Ringing Call
+
+ mRadioControl.triggerRing("18005551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.RINGING);
+
+
+ cn = mGSMPhone.getRingingCall().getEarliestConnection();
+
+ // One answered call
+ mGSMPhone.acceptCall();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.OFFHOOK);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // one holding call
+ mGSMPhone.switchHoldingAndActive();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() == Call.State.IDLE);
+
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+
+ // one active call
+ mGSMPhone.switchHoldingAndActive();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ }
+ while (mGSMPhone.getBackgroundCall().getState() == Call.State.HOLDING);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // One disconnected call in the foreground slot
+
+ mRadioControl.triggerHangupAll();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.IDLE);
+
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Connection.DisconnectCause.NORMAL, cn.getDisconnectCause());
+
+ // Test missed calls
+
+ mRadioControl.triggerRing("18005551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.RINGING);
+
+ mGSMPhone.rejectCall();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (msg.what != EVENT_DISCONNECT);
+
+ ar = (AsyncResult) msg.obj;
+ cn = (Connection) ar.result;
+
+ assertEquals(Connection.DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getRingingCall().getState());
+
+ // Test incoming not missed calls
+
+ mRadioControl.triggerRing("18005551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.RINGING);
+
+ cn = mGSMPhone.getRingingCall().getEarliestConnection();
+
+ mGSMPhone.acceptCall();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.OFFHOOK);
+
+ assertEquals(Connection.DisconnectCause.NOT_DISCONNECTED, cn.getDisconnectCause());
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+
+ try {
+ mGSMPhone.getForegroundCall().hangup();
+ } catch (CallStateException ex) {
+ ex.printStackTrace();
+ fail("unexpected ex");
+ }
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState()
+ != Call.State.DISCONNECTED);
+
+ assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+
+ //
+ // Test held and hangup held calls
+ //
+
+ // One ALERTING call
+ mGSMPhone.dial("+13125551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.OFFHOOK);
+
+ assertTrue(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+
+ mRadioControl.progressConnectingCallState();
+ mRadioControl.progressConnectingCallState();
+
+ // One ACTIVE call
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+
+ // One ACTIVE call, one ringing call
+
+ mRadioControl.triggerRing("18005551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.RINGING);
+
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+
+ // One HOLDING call, one ACTIVE call
+ mGSMPhone.acceptCall();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.OFFHOOK);
+
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+ assertTrue(mGSMPhone.canConference());
+
+ // Conference the two
+ mGSMPhone.conference();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() != Call.State.IDLE);
+
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertTrue(mGSMPhone.getForegroundCall().isMultiparty());
+ assertFalse(mGSMPhone.canConference());
+
+ // Hold the multiparty call
+ mGSMPhone.switchHoldingAndActive();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ }
+ while (mGSMPhone.getBackgroundCall().getState() != Call.State.HOLDING);
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertTrue(mGSMPhone.getBackgroundCall().isMultiparty());
+ assertFalse(mGSMPhone.canConference());
+
+ // Multiparty call on hold, call waiting added
+
+ mRadioControl.triggerRing("18005558355");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.RINGING);
+
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+ assertTrue(mGSMPhone.getBackgroundCall().isMultiparty());
+ assertEquals(Call.State.WAITING, mGSMPhone.getRingingCall().getState());
+ assertFalse(mGSMPhone.canConference());
+
+ // Hangup conference call, ringing call still around
+ mGSMPhone.getBackgroundCall().hangup();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() != Call.State.DISCONNECTED);
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getBackgroundCall().getState());
+
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+
+ // Reject waiting call
+ mGSMPhone.rejectCall();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.IDLE);
+
+ assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
+ assertFalse(mGSMPhone.getRingingCall().isRinging());
+ }
+
+ public void testOutgoingCallFailImmediately() throws Exception {
+ Message msg;
+
+ // Test outgoing call fail-immediately edge case
+ // This happens when a call terminated before ever appearing in a
+ // call list
+ // This should land the immediately-failing call in the
+ // ForegroundCall list as an IDLE call
+ mRadioControl.setNextDialFailImmediately(true);
+
+ Connection cn = mGSMPhone.dial("+13125551212");
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_DISCONNECT);
+ assertNotNull("Message Time Out", msg);
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+
+ assertEquals(Connection.DisconnectCause.NORMAL, cn.getDisconnectCause());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+ }
+
+ public void testHangupOnOutgoing() throws Exception {
+ Connection cn;
+ Message msg;
+
+ mRadioControl.setAutoProgressConnectingCall(false);
+
+ // Test 1: local hangup in "DIALING" state
+ mGSMPhone.dial("+13125551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ }
+ while (mGSMPhone.getForegroundCall().getState() != Call.State.DIALING);
+
+ cn = mGSMPhone.getForegroundCall().getEarliestConnection();
+
+ mGSMPhone.getForegroundCall().hangup();
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_DISCONNECT);
+ assertNotNull("Message Time Out", msg);
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+
+ // Test 2: local hangup in "ALERTING" state
+ mGSMPhone.dial("+13125551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.OFFHOOK);
+
+ mRadioControl.progressConnectingCallState();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ }
+ while (mGSMPhone.getForegroundCall().getState() != Call.State.ALERTING);
+
+ cn = mGSMPhone.getForegroundCall().getEarliestConnection();
+
+ mGSMPhone.getForegroundCall().hangup();
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_DISCONNECT);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+
+ // Test 3: local immediate hangup before GSM index is
+ // assigned (CallTracker.hangupPendingMO case)
+
+ mRadioControl.pauseResponses();
+
+ cn = mGSMPhone.dial("+13125551212");
+
+ cn.hangup();
+
+ mRadioControl.resumeResponses();
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_DISCONNECT);
+ assertNotNull("Message Time Out", msg);
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+
+ assertEquals(Connection.DisconnectCause.LOCAL,
+ mGSMPhone.getForegroundCall().getEarliestConnection().getDisconnectCause());
+ }
+
+ public void testHangupOnChannelClose() throws Exception {
+ mGSMPhone.dial("+13125551212");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getConnections().isEmpty());
+
+ mRadioControl.shutdown();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ mGSMPhone.clearDisconnected();
+ } while (!mGSMPhone.getForegroundCall().getConnections().isEmpty());
+ }
+
+ public void testIncallMmiCallDeflection() throws Exception {
+ Message msg;
+
+ // establish an active call
+ mGSMPhone.dial("+13125551212");
+
+ do {
+ mRadioControl.progressConnectingCallState();
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // establish a ringing (WAITING) call
+
+ mRadioControl.triggerRing("18005551212");
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_RINGING);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(Call.State.WAITING, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // Simulate entering 0 followed by SEND: release all held calls
+ // or sets UDUB for a waiting call.
+ mGSMPhone.handleInCallMmiCommands("0");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getRingingCall().getState() == Call.State.WAITING);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // change the active call to holding call
+ mGSMPhone.switchHoldingAndActive();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() == Call.State.IDLE);
+
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+
+ // Simulate entering 0 followed by SEND: release all held calls
+ // or sets UDUB for a waiting call.
+ mGSMPhone.handleInCallMmiCommands("0");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() == Call.State.HOLDING);
+
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getBackgroundCall().getState());
+ }
+
+ public void testIncallMmiCallWaiting() throws Exception {
+ Message msg;
+
+ // establish an active call
+ mGSMPhone.dial("+13125551212");
+
+ do {
+ mRadioControl.progressConnectingCallState();
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // establish a ringing (WAITING) call
+
+ mRadioControl.triggerRing("18005551212");
+
+ do {
+ msg = mGSMTestHandler.waitForMessage(ANY_MESSAGE);
+ assertNotNull("Message Time Out", msg);
+ } while (msg.what != EVENT_RINGING);
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(Call.State.WAITING, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // Simulate entering 1 followed by SEND: release all active calls
+ // (if any exist) and accepts the other (held or waiting) call.
+
+ mGSMPhone.handleInCallMmiCommands("1");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getRingingCall().getState() == Call.State.WAITING);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals("18005551212",
+ mGSMPhone.getForegroundCall().getConnections().get(0).getAddress());
+
+ // change the active call to holding call
+ mGSMPhone.switchHoldingAndActive();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() == Call.State.IDLE);
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+
+ // Simulate entering 1 followed by SEND: release all active calls
+ // (if any exist) and accepts the other (held or waiting) call.
+ mGSMPhone.handleInCallMmiCommands("1");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() != Call.State.IDLE);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+ assertEquals("18005551212",
+ mGSMPhone.getForegroundCall().getConnections().get(0).getAddress());
+
+ // at this point, the active call with number==18005551212 should
+ // have the gsm index of 2
+
+ mRadioControl.triggerRing("16505550100");
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_RINGING);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(Call.State.WAITING, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // Simulate entering "12" followed by SEND: release the call with
+ // gsm index equals to 2.
+ mGSMPhone.handleInCallMmiCommands("12");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() == Call.State.ACTIVE);
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(Call.State.WAITING, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ mGSMPhone.acceptCall();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getState() != PhoneConstants.State.OFFHOOK);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertFalse(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // at this point, the call with number==16505550100 should
+ // have the gsm index of 1
+ mGSMPhone.dial("+13125551212");
+
+ do {
+ mRadioControl.progressConnectingCallState();
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE ||
+ mGSMPhone.getBackgroundCall().getState() != Call.State.HOLDING);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+
+ // at this point, the active call with number==13125551212 should
+ // have the gsm index of 2
+
+ // Simulate entering "11" followed by SEND: release the call with
+ // gsm index equals to 1. This should not be allowed, and a
+ // Supplementary Service notification must be received.
+ mGSMPhone.handleInCallMmiCommands("11");
+
+ msg = mGSMTestHandler.waitForMessage(SUPP_SERVICE_FAILED);
+ assertNotNull("Message Time Out", msg);
+ assertFalse("IncallMmiCallWaiting: command should not work on holding call", msg == null);
+
+ // Simulate entering "12" followed by SEND: release the call with
+ // gsm index equals to 2.
+ mGSMPhone.handleInCallMmiCommands("12");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() == Call.State.ACTIVE);
+
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+
+ // Simulate entering 1 followed by SEND: release all active calls
+ // (if any exist) and accepts the other (held or waiting) call.
+ mGSMPhone.handleInCallMmiCommands("1");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() != Call.State.IDLE);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+ assertEquals("16505550100",
+ mGSMPhone.getForegroundCall().getConnections().get(0).getAddress());
+
+ // Simulate entering "11" followed by SEND: release the call with
+ // gsm index equals to 1.
+ mGSMPhone.handleInCallMmiCommands("11");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() == Call.State.ACTIVE);
+
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+ }
+
+ public void testIncallMmiCallHold() throws Exception {
+ Message msg;
+
+ // establish an active call
+ mGSMPhone.dial("13125551212");
+
+ do {
+ mRadioControl.progressConnectingCallState();
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // establish a ringing (WAITING) call
+
+ mRadioControl.triggerRing("18005551212");
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_RINGING);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(Call.State.WAITING, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // simulate entering 2 followed by SEND: place all active calls
+ // (if any exist) on hold and accepts the other (held or waiting)
+ // call
+
+ mGSMPhone.handleInCallMmiCommands("2");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getRingingCall().getState() == Call.State.WAITING);
+
+
+ assertFalse(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE,
+ mGSMPhone.getForegroundCall().getState());
+ assertEquals("18005551212",
+ mGSMPhone.getForegroundCall().getConnections().get(0).getAddress());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+ assertEquals("13125551212",
+ mGSMPhone.getBackgroundCall().getConnections().get(0).getAddress());
+
+ // swap the active and holding calls
+ mGSMPhone.handleInCallMmiCommands("2");
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_PHONE_STATE_CHANGED);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals("13125551212",
+ mGSMPhone.getForegroundCall().getConnections().get(0).getAddress());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+ assertEquals("18005551212",
+ mGSMPhone.getBackgroundCall().getConnections().get(0).getAddress());
+
+ // merge the calls
+ mGSMPhone.conference();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() != Call.State.IDLE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+ assertEquals(2, mGSMPhone.getForegroundCall().getConnections().size());
+
+ // at this point, we have an active conference call, with
+ // call(1) = 13125551212 and call(2) = 18005551212
+
+ // Simulate entering "23" followed by SEND: places all active call
+ // on hold except call 3. This should fail and a supplementary service
+ // failed notification should be received.
+
+ mGSMPhone.handleInCallMmiCommands("23");
+
+ msg = mGSMTestHandler.waitForMessage(SUPP_SERVICE_FAILED);
+ assertNotNull("Message Time Out", msg);
+ assertFalse("IncallMmiCallHold: separate should have failed!", msg == null);
+
+ // Simulate entering "21" followed by SEND: places all active call
+ // on hold except call 1.
+ mGSMPhone.handleInCallMmiCommands("21");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() == Call.State.IDLE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals("13125551212",
+ mGSMPhone.getForegroundCall().getConnections().get(0).getAddress());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+ assertEquals("18005551212",
+ mGSMPhone.getBackgroundCall().getConnections().get(0).getAddress());
+ }
+
+ public void testIncallMmiMultipartyServices() throws Exception {
+ // establish an active call
+ mGSMPhone.dial("13125551212");
+
+ do {
+ mRadioControl.progressConnectingCallState();
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ // dial another call
+ mGSMPhone.dial("18005551212");
+
+ do {
+ mRadioControl.progressConnectingCallState();
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+
+ mGSMPhone.handleInCallMmiCommands("3");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() != Call.State.IDLE);
+
+ assertEquals(PhoneConstants.State.OFFHOOK, mGSMPhone.getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals("18005551212",
+ mGSMPhone.getForegroundCall().getConnections().get(0).getAddress());
+ assertEquals("13125551212",
+ mGSMPhone.getForegroundCall().getConnections().get(1).getAddress());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+ }
+
+ public void testCallIndex() throws Exception {
+ Message msg;
+
+ // establish the first call
+ mGSMPhone.dial("16505550100");
+
+ do {
+ mRadioControl.progressConnectingCallState();
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ String baseNumber = "1650555010";
+
+ for (int i = 1; i < 6; i++) {
+ String number = baseNumber + i;
+
+ mGSMPhone.dial(number);
+
+ do {
+ mRadioControl.progressConnectingCallState();
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+
+ if (mGSMPhone.getBackgroundCall().getConnections().size() >= 5) {
+ break;
+ }
+
+ mGSMPhone.conference();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getBackgroundCall().getState() != Call.State.IDLE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+ }
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals("16505550105",
+ mGSMPhone.getForegroundCall().getConnections().get(0).getAddress());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+
+ // create an incoming call, this call should have the call index
+ // of 7
+ mRadioControl.triggerRing("18005551212");
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_RINGING);
+ assertNotNull("Message Time Out", msg);
+
+ assertEquals(PhoneConstants.State.RINGING, mGSMPhone.getState());
+ assertTrue(mGSMPhone.getRingingCall().isRinging());
+ assertEquals(Call.State.WAITING, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+
+ // hangup the background call and accept the ringing call
+ mGSMPhone.getBackgroundCall().hangup();
+ mGSMPhone.acceptCall();
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getRingingCall().getState() != Call.State.IDLE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals("18005551212",
+ mGSMPhone.getForegroundCall().getConnections().get(0).getAddress());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+ assertEquals("16505550105",
+ mGSMPhone.getBackgroundCall().getConnections().get(0).getAddress());
+
+ mGSMPhone.handleInCallMmiCommands("17");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() == Call.State.ACTIVE);
+
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.HOLDING, mGSMPhone.getBackgroundCall().getState());
+ assertEquals("16505550105",
+ mGSMPhone.getBackgroundCall().getConnections().get(0).
+ getAddress());
+
+ mGSMPhone.handleInCallMmiCommands("1");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() != Call.State.ACTIVE);
+
+ assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ mGSMPhone.handleInCallMmiCommands("16");
+
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (mGSMPhone.getForegroundCall().getState() == Call.State.ACTIVE);
+
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+ }
+
+ public void testPostDialSequences() throws Exception {
+ Message msg;
+ AsyncResult ar;
+ Connection cn;
+
+ mGSMPhone.dial("+13125551212,1234;5N8xx");
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ ar = (AsyncResult) (msg.obj);
+ cn = (Connection) (ar.result);
+ assertEquals(',', msg.arg1);
+ assertEquals("1234;5N8", cn.getRemainingPostDialString());
+
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals('1', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ assertEquals(Connection.PostDialState.STARTED, ar.userObj);
+
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals('2', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ assertEquals(Connection.PostDialState.STARTED, ar.userObj);
+
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals('3', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ assertEquals(Connection.PostDialState.STARTED, ar.userObj);
+
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals('4', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ assertEquals(Connection.PostDialState.STARTED, ar.userObj);
+
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals(';', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ cn = (Connection) (ar.result);
+ assertEquals(Connection.PostDialState.WAIT, cn.getPostDialState());
+ assertEquals(Connection.PostDialState.WAIT, ar.userObj);
+ cn.proceedAfterWaitChar();
+
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals('5', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ assertEquals(Connection.PostDialState.STARTED, ar.userObj);
+
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertEquals('N', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ cn = (Connection) (ar.result);
+ assertEquals(Connection.PostDialState.WILD, cn.getPostDialState());
+ assertEquals(Connection.PostDialState.WILD, ar.userObj);
+ cn.proceedAfterWildChar(",6;7");
+
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ ar = (AsyncResult) (msg.obj);
+ cn = (Connection) (ar.result);
+ assertEquals(',', msg.arg1);
+ assertEquals("6;78", cn.getRemainingPostDialString());
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals('6', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ assertEquals(Connection.PostDialState.STARTED, ar.userObj);
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals(';', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ cn = (Connection) (ar.result);
+ assertEquals(Connection.PostDialState.WAIT, cn.getPostDialState());
+ assertEquals(Connection.PostDialState.WAIT, ar.userObj);
+ cn.proceedAfterWaitChar();
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals('7', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ assertEquals(Connection.PostDialState.STARTED, ar.userObj);
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals('8', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ assertEquals(Connection.PostDialState.STARTED, ar.userObj);
+
+ // Bogus chars at end should be ignored
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals(0, msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ cn = (Connection) (ar.result);
+ assertEquals(Connection.PostDialState.COMPLETE,
+ cn.getPostDialState());
+ assertEquals(Connection.PostDialState.COMPLETE, ar.userObj);
+ }
+
+ public void testPostDialCancel() throws Exception {
+ Message msg;
+ AsyncResult ar;
+ Connection cn;
+
+ mGSMPhone.dial("+13125551212,N");
+ mRadioControl.progressConnectingToActive();
+
+ mRadioControl.progressConnectingToActive();
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertNotNull("Message Time Out", msg);
+ assertEquals(',', msg.arg1);
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_POST_DIAL);
+ assertEquals('N', msg.arg1);
+ ar = (AsyncResult) (msg.obj);
+ cn = (Connection) (ar.result);
+ assertEquals(Connection.PostDialState.WILD, cn.getPostDialState());
+ cn.cancelPostDial();
+
+ assertEquals(Connection.PostDialState.CANCELLED, cn.getPostDialState());
+ }
+
+ public void testOutgoingCallFail() throws Exception {
+ Message msg;
+ /*
+ * normal clearing
+ */
+
+ mRadioControl.setNextCallFailCause(CallFailCause.NORMAL_CLEARING);
+ mRadioControl.setAutoProgressConnectingCall(false);
+
+ Connection cn = mGSMPhone.dial("+13125551212");
+
+ mRadioControl.progressConnectingCallState();
+
+ // I'm just progressing the call state to
+ // ensure getCurrentCalls() gets processed...
+ // Normally these failure conditions would happen in DIALING
+ // not ALERTING
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (cn.getState() == Call.State.DIALING);
+
+
+ mRadioControl.triggerHangupAll();
+ msg = mGSMTestHandler.waitForMessage(EVENT_DISCONNECT);
+ assertNotNull("Message Time Out", msg);
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+
+ assertEquals(Connection.DisconnectCause.NORMAL, cn.getDisconnectCause());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ /*
+ * busy
+ */
+
+ mRadioControl.setNextCallFailCause(CallFailCause.USER_BUSY);
+ mRadioControl.setAutoProgressConnectingCall(false);
+
+ cn = mGSMPhone.dial("+13125551212");
+
+ mRadioControl.progressConnectingCallState();
+
+ // I'm just progressing the call state to
+ // ensure getCurrentCalls() gets processed...
+ // Normally these failure conditions would happen in DIALING
+ // not ALERTING
+ do {
+ assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
+ } while (cn.getState() == Call.State.DIALING);
+
+
+ mRadioControl.triggerHangupAll();
+ msg = mGSMTestHandler.waitForMessage(EVENT_DISCONNECT);
+ assertNotNull("Message Time Out", msg);
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+
+ assertEquals(Connection.DisconnectCause.BUSY, cn.getDisconnectCause());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.DISCONNECTED,
+ mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+
+ /*
+ * congestion
+ */
+
+ mRadioControl.setNextCallFailCause(CallFailCause.NO_CIRCUIT_AVAIL);
+ mRadioControl.setAutoProgressConnectingCall(false);
+
+ cn = mGSMPhone.dial("+13125551212");
+
+ mRadioControl.progressConnectingCallState();
+
+ // I'm just progressing the call state to
+ // ensure getCurrentCalls() gets processed...
+ // Normally these failure conditions would happen in DIALING
+ // not ALERTING
+ do {
+ msg = mGSMTestHandler.waitForMessage(ANY_MESSAGE);
+ assertNotNull("Message Time Out", msg);
+ } while (cn.getState() == Call.State.DIALING);
+
+
+ mRadioControl.triggerHangupAll();
+
+ // Unlike the while loops above, this one waits
+ // for a "phone state changed" message back to "idle"
+ do {
+ msg = mGSMTestHandler.waitForMessage(ANY_MESSAGE);
+ assertNotNull("Message Time Out", msg);
+ } while (!(msg.what == EVENT_PHONE_STATE_CHANGED
+ && mGSMPhone.getState() == PhoneConstants.State.IDLE));
+
+ assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
+
+ assertEquals(Connection.DisconnectCause.CONGESTION, cn.getDisconnectCause());
+
+ assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
+ assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
+ assertEquals(0, mGSMPhone.getBackgroundCall().getConnections().size());
+
+ assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
+ assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
+ assertEquals(Call.State.IDLE, mGSMPhone.getBackgroundCall().getState());
+
+ assertTrue(mGSMPhone.getForegroundCall().getEarliestCreateTime() > 0);
+ assertEquals(0, mGSMPhone.getForegroundCall().getEarliestConnectTime());
+ }
+
+ public void testSSNotification() throws Exception {
+ // MO
+ runTest(0, SuppServiceNotification.MO_CODE_UNCONDITIONAL_CF_ACTIVE);
+ runTest(0, SuppServiceNotification.MO_CODE_CALL_IS_WAITING);
+ runTest(0, SuppServiceNotification.MO_CODE_CALL_DEFLECTED);
+
+ // MT
+ runTest(1, SuppServiceNotification.MT_CODE_FORWARDED_CALL);
+ runTest(1, SuppServiceNotification.MT_CODE_CALL_CONNECTED_ECT);
+ runTest(1, SuppServiceNotification.MT_CODE_ADDITIONAL_CALL_FORWARDED);
+ }
+
+ private void runTest(int type, int code) {
+ Message msg;
+
+ mRadioControl.triggerSsn(type, code);
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_SSN);
+ assertNotNull("Message Time Out", msg);
+ AsyncResult ar = (AsyncResult) msg.obj;
+
+ assertNull(ar.exception);
+
+ SuppServiceNotification notification =
+ (SuppServiceNotification) ar.result;
+
+ assertEquals(type, notification.notificationType);
+ assertEquals(code, notification.code);
+ }
+
+ public void testUssd() throws Exception {
+ // Quick hack to work around a race condition in this test:
+ // We may initiate a USSD MMI before GSMPhone receives its initial
+ // GSMTestHandler.EVENT_RADIO_OFF_OR_NOT_AVAILABLE event. When the phone sees this
+ // event, it will cancel the just issued USSD MMI, which we don't
+ // want. So sleep a little first.
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ // do nothing
+ }
+
+ verifyNormal();
+ verifyCancel();
+ varifyNetworkInitiated();
+ }
+
+ private void varifyNetworkInitiated() {
+ Message msg;
+ AsyncResult ar;
+ MmiCode mmi;
+
+ // Receive an incoming NOTIFY
+ mRadioControl.triggerIncomingUssd("0", "NOTIFY message");
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_COMPLETE);
+ assertNotNull("Message Time Out", msg);
+ ar = (AsyncResult) msg.obj;
+ mmi = (MmiCode) ar.result;
+
+ assertFalse(mmi.isUssdRequest());
+
+ // Receive a REQUEST and send response
+ mRadioControl.triggerIncomingUssd("1", "REQUEST Message");
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_COMPLETE);
+ assertNotNull("Message Time Out", msg);
+ ar = (AsyncResult) msg.obj;
+ mmi = (MmiCode) ar.result;
+
+ assertTrue(mmi.isUssdRequest());
+
+ mGSMPhone.sendUssdResponse("## TEST: TEST_GSMPhone responding...");
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_INITIATE);
+ assertNotNull("Message Time Out", msg);
+ ar = (AsyncResult) msg.obj;
+ mmi = (MmiCode) ar.result;
+
+ GsmMmiCode gsmMmi = (GsmMmiCode) mmi;
+ assertTrue(gsmMmi.isPendingUSSD());
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_COMPLETE);
+ assertNotNull("Message Time Out", msg);
+ ar = (AsyncResult) msg.obj;
+ mmi = (MmiCode) ar.result;
+
+ assertNull(ar.exception);
+ assertFalse(mmi.isUssdRequest());
+
+ // Receive a REQUEST and cancel
+ mRadioControl.triggerIncomingUssd("1", "REQUEST Message");
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_COMPLETE);
+ assertNotNull("Message Time Out", msg);
+ ar = (AsyncResult) msg.obj;
+ mmi = (MmiCode) ar.result;
+
+ assertTrue(mmi.isUssdRequest());
+
+ mmi.cancel();
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_COMPLETE);
+ assertNotNull("Message Time Out", msg);
+
+ ar = (AsyncResult) msg.obj;
+ mmi = (MmiCode) ar.result;
+
+ assertNull(ar.exception);
+ assertEquals(MmiCode.State.CANCELLED, mmi.getState());
+
+ List mmiList = mGSMPhone.getPendingMmiCodes();
+ assertEquals(0, mmiList.size());
+ }
+
+ private void verifyNormal() throws CallStateException {
+ Message msg;
+ AsyncResult ar;
+ MmiCode mmi;
+
+ mGSMPhone.dial("#646#");
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_INITIATE);
+ assertNotNull("Message Time Out", msg);
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_COMPLETE);
+ assertNotNull("Message Time Out", msg);
+
+ ar = (AsyncResult) msg.obj;
+ mmi = (MmiCode) ar.result;
+ assertEquals(MmiCode.State.COMPLETE, mmi.getState());
+ }
+
+
+ private void verifyCancel() throws CallStateException {
+ /**
+ * This case makes an assumption that dial() will add the USSD
+ * to the "pending MMI codes" list before it returns. This seems
+ * like reasonable semantics. It also assumes that the USSD
+ * request in question won't complete until we get back to the
+ * event loop, thus cancel() is safe.
+ */
+ Message msg;
+
+ mGSMPhone.dial("#646#");
+
+ List<? extends MmiCode> pendingMmis = mGSMPhone.getPendingMmiCodes();
+
+ assertEquals(1, pendingMmis.size());
+
+ MmiCode mmi = pendingMmis.get(0);
+ assertTrue(mmi.isCancelable());
+ mmi.cancel();
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_INITIATE);
+ assertNotNull("Message Time Out", msg);
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_COMPLETE);
+ assertNotNull("Message Time Out", msg);
+
+ AsyncResult ar = (AsyncResult) msg.obj;
+ mmi = (MmiCode) ar.result;
+
+ assertEquals(MmiCode.State.CANCELLED, mmi.getState());
+ }
+
+ public void testRilHooks() throws Exception {
+ //
+ // These test cases all assume the RIL OEM hooks
+ // just echo back their input
+ //
+
+ Message msg;
+ AsyncResult ar;
+
+ // null byte array
+
+ mGSMPhone.invokeOemRilRequestRaw(null, mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+ assertNotNull("Message Time Out", msg);
+
+ ar = ((AsyncResult) msg.obj);
+
+ assertNull(ar.result);
+ assertNull(ar.exception);
+
+ // empty byte array
+
+ mGSMPhone.invokeOemRilRequestRaw(new byte[0], mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+ assertNotNull("Message Time Out", msg);
+
+ ar = ((AsyncResult) msg.obj);
+
+ assertEquals(0, ((byte[]) (ar.result)).length);
+ assertNull(ar.exception);
+
+ // byte array with data
+
+ mGSMPhone.invokeOemRilRequestRaw("Hello".getBytes("utf-8"),
+ mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+ assertNotNull("Message Time Out", msg);
+
+ ar = ((AsyncResult) msg.obj);
+
+ assertEquals("Hello", new String(((byte[]) (ar.result)), "utf-8"));
+ assertNull(ar.exception);
+
+ // null strings
+
+ mGSMPhone.invokeOemRilRequestStrings(null, mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+ assertNotNull("Message Time Out", msg);
+
+ ar = ((AsyncResult) msg.obj);
+
+ assertNull(ar.result);
+ assertNull(ar.exception);
+
+ // empty byte array
+
+ mGSMPhone.invokeOemRilRequestStrings(new String[0],
+ mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+ assertNotNull("Message Time Out", msg);
+
+ ar = ((AsyncResult) msg.obj);
+
+ assertEquals(0, ((String[]) (ar.result)).length);
+ assertNull(ar.exception);
+
+ // Strings with data
+
+ String s[] = new String[1];
+
+ s[0] = "Hello";
+
+ mGSMPhone.invokeOemRilRequestStrings(s, mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+ assertNotNull("Message Time Out", msg);
+
+ ar = ((AsyncResult) msg.obj);
+
+ assertEquals("Hello", ((String[]) (ar.result))[0]);
+ assertEquals(1, ((String[]) (ar.result)).length);
+ assertNull(ar.exception);
+ }
+
+ public void testMmi() throws Exception {
+ mRadioControl.setAutoProgressConnectingCall(false);
+
+ // "valid" MMI sequences
+ runValidMmi("*#67#", false);
+ runValidMmi("##43*11#", false);
+ runValidMmi("#33*1234*11#", false);
+ runValidMmi("*21*6505551234**5#", false);
+ runValidMmi("**03**1234*4321*4321#", false);
+ // pound string
+ runValidMmi("5308234092307540923#", true);
+ // short code
+ runValidMmi("22", true);
+ // as part of call setup
+ runValidMmiWithConnect("*31#6505551234");
+
+ // invalid MMI sequences
+ runNotMmi("6505551234");
+ runNotMmi("1234#*12#34566654");
+ runNotMmi("*#*#12#*");
+ }
+
+ private void runValidMmi(String dialString, boolean cancelable) throws CallStateException {
+ Connection c = mGSMPhone.dial(dialString);
+ assertNull(c);
+ Message msg = mGSMTestHandler.waitForMessage(EVENT_MMI_INITIATE);
+ assertNotNull("Message Time Out", msg);
+ // Should not be cancelable.
+ AsyncResult ar = (AsyncResult) msg.obj;
+ MmiCode mmi = (MmiCode) ar.result;
+ assertEquals(cancelable, mmi.isCancelable());
+
+ msg = mGSMTestHandler.waitForMessage(EVENT_MMI_COMPLETE);
+ assertNotNull("Message Time Out", msg);
+ }
+
+ private void runValidMmiWithConnect(String dialString) throws CallStateException {
+ mRadioControl.pauseResponses();
+
+ Connection c = mGSMPhone.dial(dialString);
+ assertNotNull(c);
+
+ hangup(c);
+ }
+
+ private void hangup(Connection cn) throws CallStateException {
+ cn.hangup();
+
+ mRadioControl.resumeResponses();
+ assertNotNull(mGSMTestHandler.waitForMessage(EVENT_DISCONNECT));
+
+ }
+
+ private void runNotMmi(String dialString) throws CallStateException {
+ mRadioControl.pauseResponses();
+
+ Connection c = mGSMPhone.dial(dialString);
+ assertNotNull(c);
+
+ hangup(c);
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMTestHandler.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMTestHandler.java
new file mode 100644
index 0000000..fb8a5d9
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMTestHandler.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2009 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.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.gsm.GSMPhone;
+import com.android.internal.telephony.test.SimulatedCommands;
+import com.android.internal.telephony.TestPhoneNotifier;
+
+/**
+ * This class creates a HandlerThread which waits for the various messages.
+ */
+public class GSMTestHandler extends HandlerThread implements Handler.Callback {
+
+ private Handler mHandler;
+ private Message mCurrentMessage;
+
+ private Boolean mMsgConsumed;
+ private SimulatedCommands sc;
+ private GSMPhone mGSMPhone;
+ private Context mContext;
+
+ private static final int FAIL_TIMEOUT_MILLIS = 5 * 1000;
+
+ public GSMTestHandler(Context context) {
+ super("GSMPhoneTest");
+ mMsgConsumed = false;
+ mContext = context;
+ }
+
+ @Override
+ protected void onLooperPrepared() {
+ sc = new SimulatedCommands();
+ mGSMPhone = new GSMPhone(mContext, sc, new TestPhoneNotifier(), true);
+ mHandler = new Handler(getLooper(), this);
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ public boolean handleMessage(Message msg) {
+ synchronized (this) {
+ mCurrentMessage = msg;
+ this.notifyAll();
+ while(!mMsgConsumed) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {}
+ }
+ mMsgConsumed = false;
+ }
+ return true;
+ }
+
+
+ public void cleanup() {
+ Looper looper = getLooper();
+ if (looper != null) looper.quit();
+ mHandler = null;
+ }
+
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ public SimulatedCommands getSimulatedCommands() {
+ return sc;
+ }
+
+ public GSMPhone getGSMPhone() {
+ return mGSMPhone;
+ }
+
+ public Message waitForMessage(int code) {
+ Message msg;
+ while(true) {
+ msg = null;
+ synchronized (this) {
+ try {
+ this.wait(FAIL_TIMEOUT_MILLIS);
+ } catch (InterruptedException e) {
+ }
+
+ // Check if timeout has occurred.
+ if (mCurrentMessage != null) {
+ // Consume the message
+ msg = Message.obtain();
+ msg.copyFrom(mCurrentMessage);
+ mCurrentMessage = null;
+ mMsgConsumed = true;
+ this.notifyAll();
+ }
+ }
+ if (msg == null || code == GSMPhoneTest.ANY_MESSAGE || msg.what == code) return msg;
+ }
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java
new file mode 100644
index 0000000..82c6944
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java
@@ -0,0 +1,758 @@
+/*
+ * Copyright (C) 2010 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.SmsCbEtwsInfo;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.internal.telephony.IccUtils;
+
+import java.util.Random;
+
+/**
+ * Test cases for basic SmsCbMessage operations
+ */
+public class GsmSmsCbTest extends AndroidTestCase {
+
+ private static final String TAG = "GsmSmsCbTest";
+
+ private static final SmsCbLocation sTestLocation = new SmsCbLocation("94040", 1234, 5678);
+
+ private static SmsCbMessage createFromPdu(byte[] pdu) {
+ try {
+ SmsCbHeader header = new SmsCbHeader(pdu);
+ byte[][] pdus = new byte[1][];
+ pdus[0] = pdu;
+ return GsmSmsCbMessage.createSmsCbMessage(header, sTestLocation, pdus);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private static void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) {
+ pdu[0] = b;
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected geographical scope decoded", expectedGs, msg
+ .getGeographicalScope());
+ }
+
+ public void testCreateNullPdu() {
+ SmsCbMessage msg = createFromPdu(null);
+ assertNull("createFromPdu(byte[] with null pdu should return null", msg);
+ }
+
+ public void testCreateTooShortPdu() {
+ byte[] pdu = new byte[4];
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertNull("createFromPdu(byte[] with too short pdu should return null", msg);
+ }
+
+ public void testGetGeographicalScope() {
+ byte[] pdu = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
+ (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
+ (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
+ (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+
+ doTestGeographicalScopeValue(pdu, (byte)0x00,
+ SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE);
+ doTestGeographicalScopeValue(pdu, (byte)0x40, SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE);
+ doTestGeographicalScopeValue(pdu, (byte)0x80, SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE);
+ doTestGeographicalScopeValue(pdu, (byte)0xC0, SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE);
+ }
+
+ public void testGetGeographicalScopeUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x40,
+
+ (byte)0x01,
+
+ (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
+ (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
+ (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
+ (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
+ (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
+ (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
+ (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
+ (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x34
+ };
+
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected geographical scope decoded",
+ SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE, msg.getGeographicalScope());
+ }
+
+ public void testGetMessageBody7Bit() {
+ byte[] pdu = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
+ (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
+ (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
+ (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected 7-bit string decoded",
+ "A GSM default alphabet message with carriage return padding",
+ msg.getMessageBody());
+ }
+
+ public void testGetMessageBody7BitUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x40,
+
+ (byte)0x01,
+
+ (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
+ (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
+ (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
+ (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
+ (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
+ (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
+ (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
+ (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x34
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected 7-bit string decoded",
+ "A GSM default alphabet message with carriage return padding",
+ msg.getMessageBody());
+ }
+
+ public void testGetMessageBody7BitMultipageUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x00, (byte)0x01, (byte)0xC0, (byte)0x00, (byte)0x40,
+
+ (byte)0x02,
+
+ (byte)0xC6, (byte)0xB4, (byte)0x7C, (byte)0x4E, (byte)0x07, (byte)0xC1,
+ (byte)0xC3, (byte)0xE7, (byte)0xF2, (byte)0xAA, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A,
+ (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34,
+ (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x0A,
+
+ (byte)0xD3, (byte)0xF2, (byte)0xF8, (byte)0xED, (byte)0x26, (byte)0x83,
+ (byte)0xE0, (byte)0xE1, (byte)0x73, (byte)0xB9, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A,
+ (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34,
+ (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x0A
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected multipage 7-bit string decoded",
+ "First page+Second page",
+ msg.getMessageBody());
+ }
+
+ public void testGetMessageBody7BitFull() {
+ byte[] pdu = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xC4, (byte)0xE5,
+ (byte)0xB4, (byte)0xFB, (byte)0x0C, (byte)0x2A, (byte)0xE3, (byte)0xC3, (byte)0x63,
+ (byte)0x3A, (byte)0x3B, (byte)0x0F, (byte)0xCA, (byte)0xCD, (byte)0x40, (byte)0x63,
+ (byte)0x74, (byte)0x58, (byte)0x1E, (byte)0x1E, (byte)0xD3, (byte)0xCB, (byte)0xF2,
+ (byte)0x39, (byte)0x88, (byte)0xFD, (byte)0x76, (byte)0x9F, (byte)0x59, (byte)0xA0,
+ (byte)0x76, (byte)0x39, (byte)0xEC, (byte)0x4E, (byte)0xBB, (byte)0xCF, (byte)0x20,
+ (byte)0x3A, (byte)0xBA, (byte)0x2C, (byte)0x2F, (byte)0x83, (byte)0xD2, (byte)0x73,
+ (byte)0x90, (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4,
+ (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals(
+ "Unexpected 7-bit string decoded",
+ "A GSM default alphabet message being exactly 93 characters long, " +
+ "meaning there is no padding!",
+ msg.getMessageBody());
+ }
+
+ public void testGetMessageBody7BitFullUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x40,
+
+ (byte)0x01,
+
+ (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
+ (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
+ (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
+ (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
+ (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xC4, (byte)0xE5, (byte)0xB4,
+ (byte)0xFB, (byte)0x0C, (byte)0x2A, (byte)0xE3, (byte)0xC3, (byte)0x63,
+ (byte)0x3A, (byte)0x3B, (byte)0x0F, (byte)0xCA, (byte)0xCD, (byte)0x40,
+ (byte)0x63, (byte)0x74, (byte)0x58, (byte)0x1E, (byte)0x1E, (byte)0xD3,
+ (byte)0xCB, (byte)0xF2, (byte)0x39, (byte)0x88, (byte)0xFD, (byte)0x76,
+ (byte)0x9F, (byte)0x59, (byte)0xA0, (byte)0x76, (byte)0x39, (byte)0xEC,
+ (byte)0x4E, (byte)0xBB, (byte)0xCF, (byte)0x20, (byte)0x3A, (byte)0xBA,
+ (byte)0x2C, (byte)0x2F, (byte)0x83, (byte)0xD2, (byte)0x73, (byte)0x90,
+ (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4,
+ (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02,
+
+ (byte)0x52
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals(
+ "Unexpected 7-bit string decoded",
+ "A GSM default alphabet message being exactly 93 characters long, " +
+ "meaning there is no padding!",
+ msg.getMessageBody());
+ }
+
+ public void testGetMessageBody7BitWithLanguage() {
+ byte[] pdu = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x04, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
+ (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
+ (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
+ (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected 7-bit string decoded",
+ "A GSM default alphabet message with carriage return padding",
+ msg.getMessageBody());
+
+ assertEquals("Unexpected language indicator decoded", "es", msg.getLanguageCode());
+ }
+
+ public void testGetMessageBody7BitWithLanguageInBody() {
+ byte[] pdu = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x10, (byte)0x11, (byte)0x73,
+ (byte)0x7B, (byte)0x23, (byte)0x08, (byte)0x3A, (byte)0x4E, (byte)0x9B, (byte)0x20,
+ (byte)0x72, (byte)0xD9, (byte)0x1C, (byte)0xAE, (byte)0xB3, (byte)0xE9, (byte)0xA0,
+ (byte)0x30, (byte)0x1B, (byte)0x8E, (byte)0x0E, (byte)0x8B, (byte)0xCB, (byte)0x74,
+ (byte)0x50, (byte)0xBB, (byte)0x3C, (byte)0x9F, (byte)0x87, (byte)0xCF, (byte)0x65,
+ (byte)0xD0, (byte)0x3D, (byte)0x4D, (byte)0x47, (byte)0x83, (byte)0xC6, (byte)0x61,
+ (byte)0xB9, (byte)0x3C, (byte)0x1D, (byte)0x3E, (byte)0x97, (byte)0x41, (byte)0xF2,
+ (byte)0x32, (byte)0xBD, (byte)0x2E, (byte)0x77, (byte)0x83, (byte)0xE0, (byte)0x61,
+ (byte)0x32, (byte)0x39, (byte)0xED, (byte)0x3E, (byte)0x37, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected 7-bit string decoded",
+ "A GSM default alphabet message with carriage return padding",
+ msg.getMessageBody());
+
+ assertEquals("Unexpected language indicator decoded", "sv", msg.getLanguageCode());
+ }
+
+ public void testGetMessageBody7BitWithLanguageInBodyUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x10,
+
+ (byte)0x01,
+
+ (byte)0x73, (byte)0x7B, (byte)0x23, (byte)0x08, (byte)0x3A, (byte)0x4E,
+ (byte)0x9B, (byte)0x20, (byte)0x72, (byte)0xD9, (byte)0x1C, (byte)0xAE,
+ (byte)0xB3, (byte)0xE9, (byte)0xA0, (byte)0x30, (byte)0x1B, (byte)0x8E,
+ (byte)0x0E, (byte)0x8B, (byte)0xCB, (byte)0x74, (byte)0x50, (byte)0xBB,
+ (byte)0x3C, (byte)0x9F, (byte)0x87, (byte)0xCF, (byte)0x65, (byte)0xD0,
+ (byte)0x3D, (byte)0x4D, (byte)0x47, (byte)0x83, (byte)0xC6, (byte)0x61,
+ (byte)0xB9, (byte)0x3C, (byte)0x1D, (byte)0x3E, (byte)0x97, (byte)0x41,
+ (byte)0xF2, (byte)0x32, (byte)0xBD, (byte)0x2E, (byte)0x77, (byte)0x83,
+ (byte)0xE0, (byte)0x61, (byte)0x32, (byte)0x39, (byte)0xED, (byte)0x3E,
+ (byte)0x37, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x37
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected 7-bit string decoded",
+ "A GSM default alphabet message with carriage return padding",
+ msg.getMessageBody());
+
+ assertEquals("Unexpected language indicator decoded", "sv", msg.getLanguageCode());
+ }
+
+ public void testGetMessageBody8Bit() {
+ byte[] pdu = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x44, (byte)0x11, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
+ (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("8-bit message body should be empty", "", msg.getMessageBody());
+ }
+
+ public void testGetMessageBodyUcs2() {
+ byte[] pdu = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x48, (byte)0x11, (byte)0x00,
+ (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55, (byte)0x00, (byte)0x43,
+ (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00, (byte)0x20, (byte)0x00,
+ (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65, (byte)0x00,
+ (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, (byte)0x00, (byte)0x6E,
+ (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x69, (byte)0x00,
+ (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x67,
+ (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x20, (byte)0x04,
+ (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61, (byte)0x00,
+ (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72,
+ (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected 7-bit string decoded",
+ "A UCS2 message containing a \u0434 character", msg.getMessageBody());
+ }
+
+ public void testGetMessageBodyUcs2Umts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x48,
+
+ (byte)0x01,
+
+ (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55,
+ (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32,
+ (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x6D, (byte)0x00, (byte)0x65,
+ (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x20,
+ (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, (byte)0x00, (byte)0x6E,
+ (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x69,
+ (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E,
+ (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x20, (byte)0x04, (byte)0x34, (byte)0x00, (byte)0x20,
+ (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x63,
+ (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72,
+ (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D,
+
+ (byte)0x4E
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected 7-bit string decoded",
+ "A UCS2 message containing a \u0434 character", msg.getMessageBody());
+ }
+
+ public void testGetMessageBodyUcs2MultipageUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x48,
+
+ (byte)0x02,
+
+ (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x41,
+ (byte)0x00, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+
+ (byte)0x06,
+
+ (byte)0x00, (byte)0x42, (byte)0x00, (byte)0x42, (byte)0x00, (byte)0x42,
+ (byte)0x00, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+
+ (byte)0x06
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected multipage UCS2 string decoded",
+ "AAABBB", msg.getMessageBody());
+ }
+
+ public void testGetMessageBodyUcs2WithLanguageInBody() {
+ byte[] pdu = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x11, (byte)0x11, (byte)0x78,
+ (byte)0x3C, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55,
+ (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00,
+ (byte)0x20, (byte)0x00, (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73,
+ (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00,
+ (byte)0x65, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F,
+ (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00,
+ (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E,
+ (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00,
+ (byte)0x20, (byte)0x04, (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63,
+ (byte)0x00, (byte)0x68, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00,
+ (byte)0x61, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65,
+ (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected 7-bit string decoded",
+ "A UCS2 message containing a \u0434 character", msg.getMessageBody());
+
+ assertEquals("Unexpected language indicator decoded", "xx", msg.getLanguageCode());
+ }
+
+ public void testGetMessageBodyUcs2WithLanguageInBodyUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x11,
+
+ (byte)0x01,
+
+ (byte)0x78, (byte)0x3C, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20,
+ (byte)0x00, (byte)0x55, (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53,
+ (byte)0x00, (byte)0x32, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x6D,
+ (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65,
+ (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F,
+ (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69,
+ (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x20, (byte)0x04, (byte)0x34,
+ (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65,
+ (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D,
+
+ (byte)0x50
+ };
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected 7-bit string decoded",
+ "A UCS2 message containing a \u0434 character", msg.getMessageBody());
+
+ assertEquals("Unexpected language indicator decoded", "xx", msg.getLanguageCode());
+ }
+
+ public void testGetMessageIdentifier() {
+ byte[] pdu = {
+ (byte)0xC0, (byte)0x00, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
+ (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
+ (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
+ (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory());
+ }
+
+ public void testGetMessageIdentifierUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x30, (byte)0x39, (byte)0x2A, (byte)0xA5, (byte)0x40,
+
+ (byte)0x01,
+
+ (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
+ (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
+ (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
+ (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
+ (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
+ (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
+ (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
+ (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x34
+ };
+
+ SmsCbMessage msg = createFromPdu(pdu);
+
+ assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory());
+ }
+
+ public void testGetMessageCode() {
+ byte[] pdu = {
+ (byte)0x2A, (byte)0xA5, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
+ (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
+ (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
+ (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+
+ SmsCbMessage msg = createFromPdu(pdu);
+ int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4;
+
+ assertEquals("Unexpected message code decoded", 682, messageCode);
+ }
+
+ public void testGetMessageCodeUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x30, (byte)0x39, (byte)0x2A, (byte)0xA5, (byte)0x40,
+
+ (byte)0x01,
+
+ (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
+ (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
+ (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
+ (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
+ (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
+ (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
+ (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
+ (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x34
+ };
+
+ SmsCbMessage msg = createFromPdu(pdu);
+ int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4;
+
+ assertEquals("Unexpected message code decoded", 682, messageCode);
+ }
+
+ public void testGetUpdateNumber() {
+ byte[] pdu = {
+ (byte)0x2A, (byte)0xA5, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
+ (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
+ (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
+ (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+
+ SmsCbMessage msg = createFromPdu(pdu);
+ int updateNumber = msg.getSerialNumber() & 0x000f;
+
+ assertEquals("Unexpected update number decoded", 5, updateNumber);
+ }
+
+ public void testGetUpdateNumberUmts() {
+ byte[] pdu = {
+ (byte)0x01, (byte)0x30, (byte)0x39, (byte)0x2A, (byte)0xA5, (byte)0x40,
+
+ (byte)0x01,
+
+ (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
+ (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
+ (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
+ (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
+ (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
+ (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
+ (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
+ (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x34
+ };
+
+ SmsCbMessage msg = createFromPdu(pdu);
+ int updateNumber = msg.getSerialNumber() & 0x000f;
+
+ assertEquals("Unexpected update number decoded", 5, updateNumber);
+ }
+
+ /* ETWS Test message including header */
+ private static final byte[] etwsMessageNormal = IccUtils.hexStringToBytes("000011001101" +
+ "0D0A5BAE57CE770C531790E85C716CBF3044573065B930675730" +
+ "9707767A751F30025F37304463FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" +
+ "0000000000000000000000000000");
+
+ private static final byte[] etwsMessageCancel = IccUtils.hexStringToBytes("000011001101" +
+ "0D0A5148307B3069002800310030003A0035" +
+ "00320029306E7DCA602557309707901F5831309253D66D883057307E3059FF086C178C615E81FF09" +
+ "00000000000000000000000000000000000000000000");
+
+ private static final byte[] etwsMessageTest = IccUtils.hexStringToBytes("000011031101" +
+ "0D0A5BAE57CE770C531790E85C716CBF3044" +
+ "573065B9306757309707300263FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" +
+ "00000000000000000000000000000000000000000000");
+
+ // FIXME: add example of ETWS primary notification PDU
+
+ public void testEtwsMessageNormal() {
+ SmsCbMessage msg = createFromPdu(etwsMessageNormal);
+ Log.d(TAG, msg.toString());
+ assertEquals("GS mismatch", 0, msg.getGeographicalScope());
+ assertEquals("serial number mismatch", 0, msg.getSerialNumber());
+ assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory());
+ assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE,
+ msg.getEtwsWarningInfo().getWarningType());
+ }
+
+ public void testEtwsMessageCancel() {
+ SmsCbMessage msg = createFromPdu(etwsMessageCancel);
+ Log.d(TAG, msg.toString());
+ assertEquals("GS mismatch", 0, msg.getGeographicalScope());
+ assertEquals("serial number mismatch", 0, msg.getSerialNumber());
+ assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory());
+ assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE,
+ msg.getEtwsWarningInfo().getWarningType());
+ }
+
+ public void testEtwsMessageTest() {
+ SmsCbMessage msg = createFromPdu(etwsMessageTest);
+ Log.d(TAG, msg.toString());
+ assertEquals("GS mismatch", 0, msg.getGeographicalScope());
+ assertEquals("serial number mismatch", 0, msg.getSerialNumber());
+ assertEquals("message ID mismatch", 0x1103, msg.getServiceCategory());
+ assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE,
+ msg.getEtwsWarningInfo().getWarningType());
+ }
+
+ // Make sure we don't throw an exception if we feed random data to the PDU parser.
+ public void testRandomPdus() {
+ Random r = new Random(94040);
+ for (int run = 0; run < 10000; run++) {
+ int len = r.nextInt(140);
+ byte[] data = new byte[len];
+ for (int i = 0; i < len; i++) {
+ data[i] = (byte) r.nextInt(256);
+ }
+ try {
+ // this should return a SmsCbMessage object or null for invalid data
+ SmsCbMessage msg = createFromPdu(data);
+ } catch (Exception e) {
+ Log.d(TAG, "exception thrown", e);
+ fail("Exception in decoder at run " + run + " length " + len + ": " + e);
+ }
+ }
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java
new file mode 100644
index 0000000..ea6836d
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java
@@ -0,0 +1,624 @@
+/*
+ * Copyright (C) 2011 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.AsyncResult;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.telephony.BaseCommands;
+import com.android.internal.telephony.IccIoResult;
+import com.android.internal.telephony.UUSInfo;
+
+import junit.framework.Assert;
+
+/**
+ * Dummy BaseCommands for UsimDataDownloadTest. Only implements UICC envelope and
+ * SMS acknowledgement commands.
+ */
+class UsimDataDownloadCommands extends BaseCommands {
+ private static final String TAG = "UsimDataDownloadCommands";
+
+ private boolean mExpectingAcknowledgeGsmSms; // true if expecting ack GSM SMS
+ private boolean mExpectingAcknowledgeGsmSmsSuccess; // true if expecting ack SMS success
+ private int mExpectingAcknowledgeGsmSmsFailureCause; // expecting ack SMS failure cause
+ private String mExpectingAcknowledgeGsmSmsPdu; // expecting ack SMS PDU
+
+ private boolean mExpectingSendEnvelope; // true to expect a send envelope command
+ private String mExpectingSendEnvelopeContents; // expected string for send envelope
+ private int mExpectingSendEnvelopeResponseSw1; // SW1/SW2 response status
+ private int mExpectingSendEnvelopeResponseSw2; // SW1/SW2 response status
+ private String mExpectingSendEnvelopeResponse; // Response string for Send Envelope
+
+ UsimDataDownloadCommands(Context context) {
+ super(context);
+ }
+
+ /**
+ * Expect a call to acknowledgeLastIncomingGsmSms with success flag and failure cause.
+ * @param success true if expecting success; false if expecting failure
+ * @param cause the failure cause, if success is false
+ */
+ synchronized void expectAcknowledgeGsmSms(boolean success, int cause) {
+ Assert.assertFalse("expectAcknowledgeGsmSms called twice", mExpectingAcknowledgeGsmSms);
+ mExpectingAcknowledgeGsmSms = true;
+ mExpectingAcknowledgeGsmSmsSuccess = success;
+ mExpectingAcknowledgeGsmSmsFailureCause = cause;
+ }
+
+ /**
+ * Expect a call to acknowledgeLastIncomingGsmSmsWithPdu with success flag and PDU.
+ * @param success true if expecting success; false if expecting failure
+ * @param ackPdu the acknowledgement PDU to expect
+ */
+ synchronized void expectAcknowledgeGsmSmsWithPdu(boolean success, String ackPdu) {
+ Assert.assertFalse("expectAcknowledgeGsmSms called twice", mExpectingAcknowledgeGsmSms);
+ mExpectingAcknowledgeGsmSms = true;
+ mExpectingAcknowledgeGsmSmsSuccess = success;
+ mExpectingAcknowledgeGsmSmsPdu = ackPdu;
+ }
+
+ /**
+ * Expect a call to sendEnvelopeWithStatus().
+ * @param contents expected envelope contents to send
+ * @param sw1 simulated SW1 status to return
+ * @param sw2 simulated SW2 status to return
+ * @param response simulated envelope response to return
+ */
+ synchronized void expectSendEnvelope(String contents, int sw1, int sw2, String response) {
+ Assert.assertFalse("expectSendEnvelope called twice", mExpectingSendEnvelope);
+ mExpectingSendEnvelope = true;
+ mExpectingSendEnvelopeContents = contents;
+ mExpectingSendEnvelopeResponseSw1 = sw1;
+ mExpectingSendEnvelopeResponseSw2 = sw2;
+ mExpectingSendEnvelopeResponse = response;
+ }
+
+ synchronized void assertExpectedMethodsCalled() {
+ long stopTime = SystemClock.elapsedRealtime() + 5000;
+ while ((mExpectingAcknowledgeGsmSms || mExpectingSendEnvelope)
+ && SystemClock.elapsedRealtime() < stopTime) {
+ try {
+ wait();
+ } catch (InterruptedException ignored) {}
+ }
+ Assert.assertFalse("expecting SMS acknowledge call", mExpectingAcknowledgeGsmSms);
+ Assert.assertFalse("expecting send envelope call", mExpectingSendEnvelope);
+ }
+
+ @Override
+ public synchronized void acknowledgeLastIncomingGsmSms(boolean success, int cause,
+ Message response) {
+ Log.d(TAG, "acknowledgeLastIncomingGsmSms: success=" + success + ", cause=" + cause);
+ Assert.assertTrue("unexpected call to acknowledge SMS", mExpectingAcknowledgeGsmSms);
+ Assert.assertEquals(mExpectingAcknowledgeGsmSmsSuccess, success);
+ Assert.assertEquals(mExpectingAcknowledgeGsmSmsFailureCause, cause);
+ mExpectingAcknowledgeGsmSms = false;
+ if (response != null) {
+ AsyncResult.forMessage(response);
+ response.sendToTarget();
+ }
+ notifyAll(); // wake up assertExpectedMethodsCalled()
+ }
+
+ @Override
+ public synchronized void acknowledgeIncomingGsmSmsWithPdu(boolean success, String ackPdu,
+ Message response) {
+ Log.d(TAG, "acknowledgeLastIncomingGsmSmsWithPdu: success=" + success
+ + ", ackPDU= " + ackPdu);
+ Assert.assertTrue("unexpected call to acknowledge SMS", mExpectingAcknowledgeGsmSms);
+ Assert.assertEquals(mExpectingAcknowledgeGsmSmsSuccess, success);
+ Assert.assertEquals(mExpectingAcknowledgeGsmSmsPdu, ackPdu);
+ mExpectingAcknowledgeGsmSms = false;
+ if (response != null) {
+ AsyncResult.forMessage(response);
+ response.sendToTarget();
+ }
+ notifyAll(); // wake up assertExpectedMethodsCalled()
+ }
+
+ @Override
+ public synchronized void sendEnvelopeWithStatus(String contents, Message response) {
+ // Add spaces between hex bytes for readability
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < contents.length(); i += 2) {
+ builder.append(contents.charAt(i)).append(contents.charAt(i+1)).append(' ');
+ }
+ Log.d(TAG, "sendEnvelopeWithStatus: " + builder.toString());
+
+ Assert.assertTrue("unexpected call to send envelope", mExpectingSendEnvelope);
+ Assert.assertEquals(mExpectingSendEnvelopeContents, contents);
+ mExpectingSendEnvelope = false;
+
+ IccIoResult result = new IccIoResult(mExpectingSendEnvelopeResponseSw1,
+ mExpectingSendEnvelopeResponseSw2, mExpectingSendEnvelopeResponse);
+
+ if (response != null) {
+ AsyncResult.forMessage(response, result, null);
+ response.sendToTarget();
+ }
+ notifyAll(); // wake up assertExpectedMethodsCalled()
+ }
+
+ @Override
+ public void setSuppServiceNotifications(boolean enable, Message result) {
+ }
+
+ @Override
+ public void supplyIccPin(String pin, Message result) {
+ }
+
+ @Override
+ public void supplyIccPinForApp(String pin, String aid, Message result) {
+ }
+
+ @Override
+ public void supplyIccPuk(String puk, String newPin, Message result) {
+ }
+
+ @Override
+ public void supplyIccPukForApp(String puk, String newPin, String aid, Message result) {
+ }
+
+ @Override
+ public void supplyIccPin2(String pin2, Message result) {
+ }
+
+ @Override
+ public void supplyIccPin2ForApp(String pin2, String aid, Message result) {
+ }
+
+ @Override
+ public void supplyIccPuk2(String puk2, String newPin2, Message result) {
+ }
+
+ @Override
+ public void supplyIccPuk2ForApp(String puk2, String newPin2, String aid, Message result) {
+ }
+
+ @Override
+ public void changeIccPin(String oldPin, String newPin, Message result) {
+ }
+
+ @Override
+ public void changeIccPinForApp(String oldPin, String newPin, String aidPtr, Message result) {
+ }
+
+ @Override
+ public void changeIccPin2(String oldPin2, String newPin2, Message result) {
+ }
+
+ @Override
+ public void changeIccPin2ForApp(String oldPin2, String newPin2, String aidPtr, Message result) {
+ }
+
+ @Override
+ public void changeBarringPassword(String facility, String oldPwd, String newPwd,
+ Message result) {
+ }
+
+ @Override
+ public void supplyNetworkDepersonalization(String netpin, Message result) {
+ }
+
+ @Override
+ public void getCurrentCalls(Message result) {
+ }
+
+ @Override
+ public void getPDPContextList(Message result) {
+ }
+
+ @Override
+ public void getDataCallList(Message result) {
+ }
+
+ @Override
+ public void dial(String address, int clirMode, Message result) {
+ }
+
+ @Override
+ public void dial(String address, int clirMode, UUSInfo uusInfo, Message result) {
+ }
+
+ @Override
+ public void getIMSI(Message result) {
+ }
+
+ @Override
+ public void getIMEI(Message result) {
+ }
+
+ @Override
+ public void getIMEISV(Message result) {
+ }
+
+ @Override
+ public void hangupConnection(int gsmIndex, Message result) {
+ }
+
+ @Override
+ public void hangupWaitingOrBackground(Message result) {
+ }
+
+ @Override
+ public void hangupForegroundResumeBackground(Message result) {
+ }
+
+ @Override
+ public void switchWaitingOrHoldingAndActive(Message result) {
+ }
+
+ @Override
+ public void conference(Message result) {
+ }
+
+ @Override
+ public void setPreferredVoicePrivacy(boolean enable, Message result) {
+ }
+
+ @Override
+ public void getPreferredVoicePrivacy(Message result) {
+ }
+
+ @Override
+ public void separateConnection(int gsmIndex, Message result) {
+ }
+
+ @Override
+ public void acceptCall(Message result) {
+ }
+
+ @Override
+ public void rejectCall(Message result) {
+ }
+
+ @Override
+ public void explicitCallTransfer(Message result) {
+ }
+
+ @Override
+ public void getLastCallFailCause(Message result) {
+ }
+
+ @Override
+ public void getLastPdpFailCause(Message result) {
+ }
+
+ @Override
+ public void getLastDataCallFailCause(Message result) {
+ }
+
+ @Override
+ public void setMute(boolean enableMute, Message response) {
+ }
+
+ @Override
+ public void getMute(Message response) {
+ }
+
+ @Override
+ public void getSignalStrength(Message response) {
+ }
+
+ @Override
+ public void getVoiceRegistrationState(Message response) {
+ }
+
+ @Override
+ public void getDataRegistrationState(Message response) {
+ }
+
+ @Override
+ public void getOperator(Message response) {
+ }
+
+ @Override
+ public void sendDtmf(char c, Message result) {
+ }
+
+ @Override
+ public void startDtmf(char c, Message result) {
+ }
+
+ @Override
+ public void stopDtmf(Message result) {
+ }
+
+ @Override
+ public void sendBurstDtmf(String dtmfString, int on, int off, Message result) {
+ }
+
+ @Override
+ public void sendSMS(String smscPDU, String pdu, Message response) {
+ }
+
+ @Override
+ public void sendCdmaSms(byte[] pdu, Message response) {
+ }
+
+ @Override
+ public void deleteSmsOnSim(int index, Message response) {
+ }
+
+ @Override
+ public void deleteSmsOnRuim(int index, Message response) {
+ }
+
+ @Override
+ public void writeSmsToSim(int status, String smsc, String pdu, Message response) {
+ }
+
+ @Override
+ public void writeSmsToRuim(int status, String pdu, Message response) {
+ }
+
+ @Override
+ public void setRadioPower(boolean on, Message response) {
+ }
+
+ @Override
+ public void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message response) {
+ }
+
+ @Override
+ public void iccIO(int command, int fileid, String path, int p1, int p2, int p3, String data,
+ String pin2, Message response) {
+ }
+
+ @Override
+ public void queryCLIP(Message response) {
+ }
+
+ @Override
+ public void getCLIR(Message response) {
+ }
+
+ @Override
+ public void setCLIR(int clirMode, Message response) {
+ }
+
+ @Override
+ public void queryCallWaiting(int serviceClass, Message response) {
+ }
+
+ @Override
+ public void setCallWaiting(boolean enable, int serviceClass, Message response) {
+ }
+
+ @Override
+ public void setCallForward(int action, int cfReason, int serviceClass, String number,
+ int timeSeconds, Message response) {
+ }
+
+ @Override
+ public void queryCallForwardStatus(int cfReason, int serviceClass, String number,
+ Message response) {
+ }
+
+ @Override
+ public void setNetworkSelectionModeAutomatic(Message response) {
+ }
+
+ @Override
+ public void setNetworkSelectionModeManual(String operatorNumeric, Message response) {
+ }
+
+ @Override
+ public void getNetworkSelectionMode(Message response) {
+ }
+
+ @Override
+ public void getAvailableNetworks(Message response) {
+ }
+
+ @Override
+ public void getBasebandVersion(Message response) {
+ }
+
+ @Override
+ public void queryFacilityLock(String facility, String password, int serviceClass,
+ Message response) {
+ }
+
+ @Override
+ public void queryFacilityLockForApp(String facility, String password, int serviceClass,
+ String appId, Message response) {
+ }
+
+ @Override
+ public void setFacilityLock(String facility, boolean lockState, String password,
+ int serviceClass, Message response) {
+ }
+
+ @Override
+ public void setFacilityLockForApp(String facility, boolean lockState, String password,
+ int serviceClass, String appId, Message response) {
+ }
+
+ @Override
+ public void sendUSSD(String ussdString, Message response) {
+ }
+
+ @Override
+ public void cancelPendingUssd(Message response) {
+ }
+
+ @Override
+ public void resetRadio(Message result) {
+ }
+
+ @Override
+ public void setBandMode(int bandMode, Message response) {
+ }
+
+ @Override
+ public void queryAvailableBandMode(Message response) {
+ }
+
+ @Override
+ public void setPreferredNetworkType(int networkType, Message response) {
+ }
+
+ @Override
+ public void getPreferredNetworkType(Message response) {
+ }
+
+ @Override
+ public void getNeighboringCids(Message response) {
+ }
+
+ @Override
+ public void setLocationUpdates(boolean enable, Message response) {
+ }
+
+ @Override
+ public void getSmscAddress(Message result) {
+ }
+
+ @Override
+ public void setSmscAddress(String address, Message result) {
+ }
+
+ @Override
+ public void reportSmsMemoryStatus(boolean available, Message result) {
+ }
+
+ @Override
+ public void reportStkServiceIsRunning(Message result) {
+ }
+
+ @Override
+ public void invokeOemRilRequestRaw(byte[] data, Message response) {
+ }
+
+ @Override
+ public void invokeOemRilRequestStrings(String[] strings, Message response) {
+ }
+
+ @Override
+ public void sendTerminalResponse(String contents, Message response) {
+ }
+
+ @Override
+ public void sendEnvelope(String contents, Message response) {
+ }
+
+ @Override
+ public void handleCallSetupRequestFromSim(boolean accept, Message response) {
+ }
+
+ @Override
+ public void setGsmBroadcastActivation(boolean activate, Message result) {
+ }
+
+ @Override
+ public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) {
+ }
+
+ @Override
+ public void getGsmBroadcastConfig(Message response) {
+ }
+
+ @Override
+ public void getDeviceIdentity(Message response) {
+ }
+
+ @Override
+ public void getCDMASubscription(Message response) {
+ }
+
+ @Override
+ public void sendCDMAFeatureCode(String FeatureCode, Message response) {
+ }
+
+ @Override
+ public void setPhoneType(int phoneType) {
+ }
+
+ @Override
+ public void queryCdmaRoamingPreference(Message response) {
+ }
+
+ @Override
+ public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+ }
+
+ @Override
+ public void setCdmaSubscriptionSource(int cdmaSubscriptionType, Message response) {
+ }
+
+ @Override
+ public void getCdmaSubscriptionSource(Message response) {
+ }
+
+ @Override
+ public void setTTYMode(int ttyMode, Message response) {
+ }
+
+ @Override
+ public void queryTTYMode(Message response) {
+ }
+
+ @Override
+ public void setupDataCall(String radioTechnology, String profile, String apn, String user,
+ String password, String authType, String protocol, Message result) {
+ }
+
+ @Override
+ public void deactivateDataCall(int cid, int reason, Message result) {
+ }
+
+ @Override
+ public void setCdmaBroadcastActivation(boolean activate, Message result) {
+ }
+
+ @Override
+ public void setCdmaBroadcastConfig(int[] configValuesArray, Message result) {
+ }
+
+ @Override
+ public void getCdmaBroadcastConfig(Message result) {
+ }
+
+ @Override
+ public void exitEmergencyCallbackMode(Message response) {
+ }
+
+ @Override
+ public void getIccCardStatus(Message result) {
+ }
+
+ @Override
+ public void requestIsimAuthentication(String nonce, Message response) {
+ }
+
+ @Override
+ public void getVoiceRadioTechnology(Message response) {
+ }
+
+ @Override
+ public void getIMSIForApp(String aid, Message result) {
+ }
+
+ @Override
+ public void iccIOForApp(int command, int fileid, String path, int p1, int p2, int p3,
+ String data, String pin2, String aid, Message response) {
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java
new file mode 100644
index 0000000..6c8ba5e
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2011 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.HandlerThread;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.nio.charset.Charset;
+
+/**
+ * Test SMS-PP data download to UICC.
+ * Uses test messages from 3GPP TS 31.124 section 27.22.5.
+ */
+public class UsimDataDownloadTest extends AndroidTestCase {
+ private static final String TAG = "UsimDataDownloadTest";
+
+ class TestHandlerThread extends HandlerThread {
+ private UsimDataDownloadHandler mHandler;
+
+ TestHandlerThread() {
+ super("TestHandlerThread");
+ }
+
+ @Override
+ protected void onLooperPrepared() {
+ synchronized (this) {
+ mHandler = new UsimDataDownloadHandler(mCm);
+ notifyAll();
+ }
+ }
+
+ UsimDataDownloadHandler getHandler() {
+ synchronized (this) {
+ while (mHandler == null) {
+ try {
+ wait();
+ } catch (InterruptedException ignored) {}
+ }
+ return mHandler;
+ }
+ }
+ }
+
+ private UsimDataDownloadCommands mCm;
+ private TestHandlerThread mHandlerThread;
+ UsimDataDownloadHandler mHandler;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCm = new UsimDataDownloadCommands(mContext);
+ mHandlerThread = new TestHandlerThread();
+ mHandlerThread.start();
+ mHandler = mHandlerThread.getHandler();
+ Log.d(TAG, "mHandler is constructed");
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mHandlerThread.quit();
+ super.tearDown();
+ }
+
+ // SMS-PP Message 3.1.1
+ private static final byte[] SMS_PP_MESSAGE_3_1_1 = {
+ // Service center address
+ 0x09, (byte) 0x91, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0xf8,
+
+ 0x04, 0x04, (byte) 0x91, 0x21, 0x43, 0x7f, 0x16, (byte) 0x89, 0x10, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x0d, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61,
+ 0x67, 0x65, 0x20, 0x31
+ };
+
+ // SMS-PP Download Envelope 3.1.1
+ private static final String SMS_PP_ENVELOPE_3_1_1 = "d12d8202838106099111223344556677f88b1c04"
+ + "049121437f16891010000000000d546573744d6573736167652031";
+
+ // SMS-PP Message 3.1.5
+ private static final byte[] SMS_PP_MESSAGE_3_1_5 = {
+ // Service center address
+ 0x09, (byte) 0x91, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0xf8,
+
+ 0x44, 0x04, (byte) 0x91, 0x21, 0x43, 0x7f, (byte) 0xf6, (byte) 0x89, 0x10, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x1e, 0x02, 0x70, 0x00, 0x00, 0x19, 0x00, 0x0d, 0x00, 0x00,
+ 0x00, 0x00, (byte) 0xbf, (byte) 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ (byte) 0xdc, (byte) 0xdc, (byte) 0xdc, (byte) 0xdc, (byte) 0xdc, (byte) 0xdc,
+ (byte) 0xdc, (byte) 0xdc, (byte) 0xdc, (byte) 0xdc
+ };
+
+ // SMS-PP Download Envelope 3.1.5
+ private static final String SMS_PP_ENVELOPE_3_1_5 = "d13e8202838106099111223344556677f88b2d44"
+ + "049121437ff6891010000000001e0270000019000d00000000bfff00000000000100"
+ + "dcdcdcdcdcdcdcdcdcdc";
+
+ public void testDataDownloadMessage1() {
+ SmsMessage message = SmsMessage.createFromPdu(SMS_PP_MESSAGE_3_1_1);
+ assertTrue("message is SMS-PP data download", message.isUsimDataDownload());
+
+ mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_1, 0x90, 0x00, "");
+ mCm.expectAcknowledgeGsmSms(true, 0);
+ mHandler.startDataDownload(message);
+ mCm.assertExpectedMethodsCalled();
+
+ mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_1, 0x90, 0x00, "0123456789");
+ mCm.expectAcknowledgeGsmSmsWithPdu(true, "00077f16050123456789");
+ mHandler.startDataDownload(message);
+ mCm.assertExpectedMethodsCalled();
+
+ mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_1, 0x62, 0xff, "0123456789abcdef");
+ mCm.expectAcknowledgeGsmSmsWithPdu(false, "00d5077f16080123456789abcdef");
+ mHandler.startDataDownload(message);
+ mCm.assertExpectedMethodsCalled();
+ }
+
+ public void testDataDownloadMessage5() {
+ SmsMessage message = SmsMessage.createFromPdu(SMS_PP_MESSAGE_3_1_5);
+ assertTrue("message is SMS-PP data download", message.isUsimDataDownload());
+
+ mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_5, 0x90, 0x00, "9876543210");
+ mCm.expectAcknowledgeGsmSmsWithPdu(true, "00077ff6059876543210");
+ mHandler.startDataDownload(message);
+ mCm.assertExpectedMethodsCalled();
+
+ mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_5, 0x93, 0x00, "");
+ mCm.expectAcknowledgeGsmSms(false, 0xd4); // SIM toolkit busy
+ mHandler.startDataDownload(message);
+ mCm.assertExpectedMethodsCalled();
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimServiceTableTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimServiceTableTest.java
new file mode 100644
index 0000000..56854ed
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimServiceTableTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Test UsimServiceTable class.
+ */
+public class UsimServiceTableTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testUsimServiceTable() {
+ byte[] noServices = {0x00};
+ byte[] service1 = {0x01, 0x00};
+ byte[] service8 = {(byte) 0x80, 0x00, 0x00};
+ byte[] service8And9 = {(byte) 0x80, 0x01};
+ byte[] service28 = {0x00, 0x00, 0x00, 0x08};
+ byte[] service89To96 = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, (byte) 0xff};
+
+ UsimServiceTable testTable1 = new UsimServiceTable(noServices);
+ assertFalse(testTable1.isAvailable(UsimServiceTable.UsimService.PHONEBOOK));
+ assertFalse(testTable1.isAvailable(UsimServiceTable.UsimService.FDN));
+ assertFalse(testTable1.isAvailable(UsimServiceTable.UsimService.NAS_CONFIG_BY_USIM));
+
+ UsimServiceTable testTable2 = new UsimServiceTable(service1);
+ assertTrue(testTable2.isAvailable(UsimServiceTable.UsimService.PHONEBOOK));
+ assertFalse(testTable2.isAvailable(UsimServiceTable.UsimService.FDN));
+ assertFalse(testTable2.isAvailable(UsimServiceTable.UsimService.NAS_CONFIG_BY_USIM));
+
+ UsimServiceTable testTable3 = new UsimServiceTable(service8);
+ assertFalse(testTable3.isAvailable(UsimServiceTable.UsimService.PHONEBOOK));
+ assertFalse(testTable3.isAvailable(UsimServiceTable.UsimService.BDN_EXTENSION));
+ assertTrue(testTable3.isAvailable(UsimServiceTable.UsimService.OUTGOING_CALL_INFO));
+ assertFalse(testTable3.isAvailable(UsimServiceTable.UsimService.INCOMING_CALL_INFO));
+ assertFalse(testTable3.isAvailable(UsimServiceTable.UsimService.NAS_CONFIG_BY_USIM));
+
+ UsimServiceTable testTable4 = new UsimServiceTable(service8And9);
+ assertFalse(testTable4.isAvailable(UsimServiceTable.UsimService.PHONEBOOK));
+ assertFalse(testTable4.isAvailable(UsimServiceTable.UsimService.BDN_EXTENSION));
+ assertTrue(testTable4.isAvailable(UsimServiceTable.UsimService.OUTGOING_CALL_INFO));
+ assertTrue(testTable4.isAvailable(UsimServiceTable.UsimService.INCOMING_CALL_INFO));
+ assertFalse(testTable4.isAvailable(UsimServiceTable.UsimService.SM_STORAGE));
+ assertFalse(testTable4.isAvailable(UsimServiceTable.UsimService.NAS_CONFIG_BY_USIM));
+
+ UsimServiceTable testTable5 = new UsimServiceTable(service28);
+ assertFalse(testTable5.isAvailable(UsimServiceTable.UsimService.PHONEBOOK));
+ assertTrue(testTable5.isAvailable(UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP));
+ assertFalse(testTable5.isAvailable(UsimServiceTable.UsimService.NAS_CONFIG_BY_USIM));
+
+ UsimServiceTable testTable6 = new UsimServiceTable(service89To96);
+ assertFalse(testTable6.isAvailable(UsimServiceTable.UsimService.PHONEBOOK));
+ assertFalse(testTable6.isAvailable(UsimServiceTable.UsimService.HPLMN_DIRECT_ACCESS));
+ assertTrue(testTable6.isAvailable(UsimServiceTable.UsimService.ECALL_DATA));
+ assertTrue(testTable6.isAvailable(UsimServiceTable.UsimService.SM_OVER_IP));
+ assertTrue(testTable6.isAvailable(UsimServiceTable.UsimService.UICC_ACCESS_TO_IMS));
+ assertTrue(testTable6.isAvailable(UsimServiceTable.UsimService.NAS_CONFIG_BY_USIM));
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java b/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
new file mode 100644
index 0000000..3149ee1
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2010 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.mockril;
+
+import android.util.Log;
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import com.android.internal.communication.MsgHeader;
+import com.android.internal.communication.Msg;
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.ril_proto.RilCtrlCmds;
+import com.android.internal.telephony.ril_proto.RilCmds;
+
+import com.android.frameworks.telephonytests.TelephonyMockRilTestRunner;
+import com.google.protobuf.micro.InvalidProtocolBufferMicroException;
+
+// Test suite for test ril
+public class MockRilTest extends InstrumentationTestCase {
+ private static final String TAG = "MockRilTest";
+
+ RilChannel mMockRilChannel;
+ TelephonyMockRilTestRunner mRunner;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mRunner = (TelephonyMockRilTestRunner)getInstrumentation();
+ mMockRilChannel = mRunner.mMockRilChannel;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ static void log(String s) {
+ Log.v(TAG, s);
+ }
+
+ /**
+ * Test Case 1: Test protobuf serialization and deserialization
+ * @throws InvalidProtocolBufferMicroException
+ */
+ public void testProtobufSerDes() throws InvalidProtocolBufferMicroException {
+ log("testProtobufSerdes E");
+
+ RilCtrlCmds.CtrlRspRadioState rs = new RilCtrlCmds.CtrlRspRadioState();
+ assertTrue(String.format("expected rs.state == 0 was %d", rs.getState()),
+ rs.getState() == 0);
+ rs.setState(1);
+ assertTrue(String.format("expected rs.state == 1 was %d", rs.getState()),
+ rs.getState() == 1);
+
+ byte[] rs_ser = rs.toByteArray();
+ RilCtrlCmds.CtrlRspRadioState rsNew = RilCtrlCmds.CtrlRspRadioState.parseFrom(rs_ser);
+ assertTrue(String.format("expected rsNew.state == 1 was %d", rs.getState()),
+ rs.getState() == 1);
+
+ log("testProtobufSerdes X");
+ }
+
+ /**
+ * Test case 2: Test echo command works using writeMsg & readMsg
+ */
+ public void testEchoMsg() throws IOException {
+ log("testEchoMsg E");
+
+ MsgHeader mh = new MsgHeader();
+ mh.setCmd(0);
+ mh.setToken(1);
+ mh.setStatus(2);
+ ByteBuffer data = ByteBuffer.allocate(3);
+ data.put((byte)3);
+ data.put((byte)4);
+ data.put((byte)5);
+ Msg.send(mMockRilChannel, mh, data);
+
+ Msg respMsg = Msg.recv(mMockRilChannel);
+ assertTrue(String.format("expected mhd.header.cmd == 0 was %d",respMsg.getCmd()),
+ respMsg.getCmd() == 0);
+ assertTrue(String.format("expected mhd.header.token == 1 was %d",respMsg.getToken()),
+ respMsg.getToken() == 1);
+ assertTrue(String.format("expected mhd.header.status == 2 was %d", respMsg.getStatus()),
+ respMsg.getStatus() == 2);
+ assertTrue(String.format("expected mhd.data[0] == 3 was %d", respMsg.getData(0)),
+ respMsg.getData(0) == 3);
+ assertTrue(String.format("expected mhd.data[1] == 4 was %d", respMsg.getData(1)),
+ respMsg.getData(1) == 4);
+ assertTrue(String.format("expected mhd.data[2] == 5 was %d", respMsg.getData(2)),
+ respMsg.getData(2) == 5);
+
+ log("testEchoMsg X");
+ }
+
+ /**
+ * Test case 3: Test get as
+ */
+ public void testGetAs() {
+ log("testGetAs E");
+
+ // Use a message header as the protobuf data content
+ MsgHeader mh = new MsgHeader();
+ mh.setCmd(12345);
+ mh.setToken(9876);
+ mh.setStatus(7654);
+ mh.setLengthData(4321);
+ byte[] data = mh.toByteArray();
+ MsgHeader mhResult = Msg.getAs(MsgHeader.class, data);
+
+ assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()),
+ mhResult.getCmd() == 12345);
+ assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()),
+ mhResult.getToken() == 9876);
+ assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()),
+ mhResult.getStatus() == 7654);
+ assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()),
+ mhResult.getLengthData() == 4321);
+
+ Msg msg = Msg.obtain();
+ msg.setData(ByteBuffer.wrap(data));
+
+ mhResult = msg.getDataAs(MsgHeader.class);
+
+ assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()),
+ mhResult.getCmd() == 12345);
+ assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()),
+ mhResult.getToken() == 9876);
+ assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()),
+ mhResult.getStatus() == 7654);
+ assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()),
+ mhResult.getLengthData() == 4321);
+
+ log("testGetAs X");
+ }
+
+ /**
+ * Test case 3: test get radio state
+ */
+ public void testGetRadioState() throws IOException {
+ log("testGetRadioState E");
+
+ Msg.send(mMockRilChannel, 1, 9876, 0, null);
+
+ Msg resp = Msg.recv(mMockRilChannel);
+ //resp.printHeader("testGetRadioState");
+
+ assertTrue(String.format("expected cmd == 1 was %d", resp.getCmd()),
+ resp.getCmd() == 1);
+ assertTrue(String.format("expected token == 9876 was %d", resp.getToken()),
+ resp.getToken() == 9876);
+ assertTrue(String.format("expected status == 0 was %d", resp.getStatus()),
+ resp.getStatus() == 0);
+
+ RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+
+ int state = rsp.getState();
+ log("testGetRadioState state=" + state);
+ assertTrue(String.format("expected RadioState >= 0 && RadioState <= 9 was %d", state),
+ ((state >= 0) && (state <= 9)));
+
+ log("testGetRadioState X");
+ }
+
+ /**
+ * Test case 5: test set radio state
+ */
+ public void testSetRadioState() throws IOException {
+ log("testSetRadioState E");
+
+ RilCtrlCmds.CtrlReqRadioState cmdrs = new RilCtrlCmds.CtrlReqRadioState();
+ assertEquals(0, cmdrs.getState());
+
+ cmdrs.setState(RilCmds.RADIOSTATE_SIM_NOT_READY);
+ assertEquals(2, cmdrs.getState());
+
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, cmdrs);
+
+ Msg resp = Msg.recv(mMockRilChannel);
+ log("get response status :" + resp.getStatus());
+ log("get response for command: " + resp.getCmd());
+ log("get command token: " + resp.getToken());
+
+ RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+
+ int state = rsp.getState();
+ log("get response for testSetRadioState: " + state);
+ assertTrue(RilCmds.RADIOSTATE_SIM_NOT_READY == state);
+ }
+
+ /**
+ * Test case 6: test start incoming call and hangup it.
+ */
+ public void testStartIncomingCallAndHangup() throws IOException {
+ log("testStartIncomingCallAndHangup");
+ RilCtrlCmds.CtrlReqSetMTCall cmd = new RilCtrlCmds.CtrlReqSetMTCall();
+ String incomingCall = "6502889108";
+ // set the MT call
+ cmd.setPhoneNumber(incomingCall);
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_MT_CALL, 0, 0, cmd);
+ // get response
+ Msg resp = Msg.recv(mMockRilChannel);
+ log("Get response status: " + resp.getStatus());
+ assertTrue("The ril is not in a proper state to set MT calls.",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ // allow the incoming call alerting for some time
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {}
+
+ // we are playing a trick to assume the current is 1
+ RilCtrlCmds.CtrlHangupConnRemote hangupCmd = new RilCtrlCmds.CtrlHangupConnRemote();
+ hangupCmd.setConnectionId(1);
+ hangupCmd.setCallFailCause(16); // normal hangup
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_HANGUP_CONN_REMOTE, 0, 0, hangupCmd);
+
+ // get response
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response for hangup connection: " + resp.getStatus());
+ assertTrue("CTRL_CMD_HANGUP_CONN_REMOTE failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+ }
+
+ /**
+ * Test case 7: test set call transition flag
+ */
+ public void testSetCallTransitionFlag() throws IOException {
+ log("testSetCallTransitionFlag");
+ // Set flag to true:
+ RilCtrlCmds.CtrlSetCallTransitionFlag cmd = new RilCtrlCmds.CtrlSetCallTransitionFlag();
+ cmd.setFlag(true);
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_TRANSITION_FLAG, 0, 0, cmd);
+
+ Msg resp = Msg.recv(mMockRilChannel);
+ log("Get response status: " + resp.getStatus());
+ assertTrue("Set call transition flag failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ // add a dialing call
+ RilCtrlCmds.CtrlReqAddDialingCall cmdDialCall = new RilCtrlCmds.CtrlReqAddDialingCall();
+ String phoneNumber = "5102345678";
+ cmdDialCall.setPhoneNumber(phoneNumber);
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_ADD_DIALING_CALL, 0, 0, cmdDialCall);
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response status for adding a dialing call: " + resp.getStatus());
+ assertTrue("add dialing call failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {}
+
+ // send command to force call state change
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_ALERT, 0, 0, null);
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response status: " + resp.getStatus());
+ assertTrue("Set call alert failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {}
+
+ // send command to force call state change
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_ACTIVE, 0, 0, null);
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response status: " + resp.getStatus());
+ assertTrue("Set call active failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ // hangup the active all remotely
+ RilCtrlCmds.CtrlHangupConnRemote hangupCmd = new RilCtrlCmds.CtrlHangupConnRemote();
+ hangupCmd.setConnectionId(1);
+ hangupCmd.setCallFailCause(16); // normal hangup
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_HANGUP_CONN_REMOTE, 0, 0, hangupCmd);
+ resp = Msg.recv(mMockRilChannel);
+ log("Get response for hangup connection: " + resp.getStatus());
+ assertTrue("CTRL_CMD_HANGUP_CONN_REMOTE failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+
+ // set the flag to false
+ cmd.setFlag(false);
+ Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_TRANSITION_FLAG, 0, 0, cmd);
+ resp = Msg.recv(mMockRilChannel);
+ assertTrue("Set call transition flag failed",
+ resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK);
+ }
+}