Merge commit '9261e50' into m-wireless-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bc84bf5..97ed1ce 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -97,7 +97,12 @@
<uses-permission android:name="android.permission.DUMP" />
<uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
<uses-permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION" />
+ <uses-permission android:name="android.permission.BIND_CARRIER_CONFIG_SERVICE" />
<uses-permission android:name="android.permission.BIND_CARRIER_MESSAGING_SERVICE" />
+ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<!-- This tells the activity manager to not delay any of our activity
start requests, even if they happen immediately after the user
@@ -583,5 +588,31 @@
<action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
</intent-filter>
</provider>
+ <receiver android:name="com.android.phone.vvm.omtp.sms.OmtpMessageReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DATA_SMS_RECEIVED" />
+ <data android:scheme="sms" />
+ </intent-filter>
+ </receiver>
+ <service
+ android:name="com.android.phone.vvm.omtp.OmtpVvmSyncService"
+ android:exported="true"
+ android:process=":sync">
+ <intent-filter>
+ <action android:name="android.content.SyncAdapter"/>
+ </intent-filter>
+ <meta-data android:name="android.content.SyncAdapter"
+ android:resource="@xml/syncadapter" />
+ </service>
+ <service
+ android:name="android.telecom.AuthenticatorService">
+ <intent-filter>
+ <action android:name="android.accounts.AccountAuthenticator"/>
+ </intent-filter>
+ <meta-data
+ android:name="android.accounts.AccountAuthenticator"
+ android:resource="@xml/authenticator" />
+ </service>
</application>
</manifest>
diff --git a/res/values-mcc310-mnc410/config.xml b/res/values-mcc310-mnc410/config.xml
new file mode 100644
index 0000000..bf89c34
--- /dev/null
+++ b/res/values-mcc310-mnc410/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <bool name="support_pause_ims_video_calls" translatable="false">false</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values-mcc311-mnc480/config.xml b/res/values-mcc311-mnc480/config.xml
index df95aa4..2b032d5 100755
--- a/res/values-mcc311-mnc480/config.xml
+++ b/res/values-mcc311-mnc480/config.xml
@@ -20,4 +20,5 @@
<!-- Flag indicating if dtmf tone type is enabled -->
<bool name="dtmf_type_enabled">true</bool>
<bool name="support_swap_after_merge" translatable="false">false</bool>
+ <bool name="support_pause_ims_video_calls" translatable="false">true</bool>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 881762d..b207be7 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -152,4 +152,7 @@
<!-- After a CDMA conference call is merged, the swap button should be displayed. -->
<bool name="support_swap_after_merge" translatable="false">true</bool>
+
+ <!-- For IMS video over LTE calls, determines whether video pause signalling is supported. -->
+ <bool name="support_pause_ims_video_calls" translatable="false">true</bool>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index abeeee0..0b8bcef 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -753,7 +753,8 @@
<string name="tty_mode_option_summary">Set TTY mode</string>
<string name="auto_retry_mode_title">Auto-retry</string>
<string name="auto_retry_mode_summary">Enable Auto-retry mode</string>
-
+ <!-- TTY Mode change is NOT allowed during a video call -->
+ <string name="tty_mode_not_allowed_video_call">TTY Mode change is not allowed during a video call</string>
<!-- FDN list screen: menu item label -->
<string name="menu_add">Add contact</string>
<!-- FDN list screen: menu item label -->
@@ -1297,5 +1298,4 @@
<string name="tty_mode_key">button_tty_mode_key</string>
<!-- DO NOT TRANSLATE. Internal key for a voicemail notification preference. -->
<string name="wifi_calling_settings_key">button_wifi_calling_settings_key</string>
-
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 3d1b1ad..4493739 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -261,6 +261,7 @@
<item name="android:actionBarStyle">@style/TelephonyActionBarStyle</item>
<item name="android:colorPrimary">@color/dialer_theme_color</item>
<item name="android:colorPrimaryDark">@color/dialer_theme_color_dark</item>
+ <item name="android:homeAsUpIndicator">@drawable/ic_back_arrow</item>
</style>
<style name="SimImportTheme" parent="@android:style/Theme.Material.Light">
diff --git a/res/xml/authenticator.xml b/res/xml/authenticator.xml
new file mode 100644
index 0000000..f035d3a
--- /dev/null
+++ b/res/xml/authenticator.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<account-authenticator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accountType="com.android.phone.vvm.omtp" />
\ No newline at end of file
diff --git a/res/xml/call_feature_setting.xml b/res/xml/call_feature_setting.xml
index 543fae5..a6e78e4 100644
--- a/res/xml/call_feature_setting.xml
+++ b/res/xml/call_feature_setting.xml
@@ -148,5 +148,4 @@
android:targetClass="com.android.phone.CdmaCallOptions"/>
</PreferenceScreen>
-
</PreferenceScreen>
diff --git a/res/xml/syncadapter.xml b/res/xml/syncadapter.xml
new file mode 100644
index 0000000..b0484b1
--- /dev/null
+++ b/res/xml/syncadapter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<sync-adapter
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:contentAuthority="com.android.voicemail"
+ android:accountType="com.android.phone.vvm.omtp"
+ android:userVisible="false"
+ android:supportsUploading="true"
+ android:allowParallelSyncs="false"
+ android:isAlwaysSyncable="true"/>
\ No newline at end of file
diff --git a/sip/src/com/android/services/telephony/sip/SipUtil.java b/sip/src/com/android/services/telephony/sip/SipUtil.java
index ea5ea46..7e94804 100644
--- a/sip/src/com/android/services/telephony/sip/SipUtil.java
+++ b/sip/src/com/android/services/telephony/sip/SipUtil.java
@@ -105,7 +105,15 @@
* @return The PhoneAccount.
*/
static PhoneAccount createPhoneAccount(Context context, SipProfile profile) {
+ // Build a URI to represent the SIP account. Does not use SipProfile#getUriString() since
+ // that prototype can include transport information which we do not want to see in the
+ // phone account.
+ String sipAddress = profile.getUserName() + "@" + profile.getSipDomain();
+ Uri sipUri = Uri.parse(PhoneAccount.SCHEME_SIP + ":" + sipAddress);
+ // SipProfile#getUriString() is used here for legacy reasons. Changing to use the sipAddress
+ // defined above would cause SIP profiles with a TCP transport to be duplicated in the
+ // PhoneAccountRegistry since the ID would change on re-registration.
PhoneAccountHandle accountHandle =
SipUtil.createAccountHandle(context, profile.getUriString());
@@ -118,8 +126,8 @@
PhoneAccount.Builder builder = PhoneAccount.builder(accountHandle, profile.getDisplayName())
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
| PhoneAccount.CAPABILITY_MULTI_USER)
- .setAddress(Uri.parse(profile.getUriString()))
- .setShortDescription(profile.getDisplayName())
+ .setAddress(sipUri)
+ .setShortDescription(sipAddress)
.setIcon(context, R.drawable.ic_dialer_sip_black_24dp)
.setSupportedUriSchemes(supportedUriSchemes);
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index c211323..4fa28ab 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -336,6 +336,10 @@
} else if (preference == mButtonDTMF) {
return true;
} else if (preference == mButtonTTY) {
+ if(mPhone.isVideoCallPresent()) {
+ // TTY Mode change is not allowed during a VT call
+ showDialogIfForeground(VoicemailDialogUtil.TTY_SET_RESPONSE_ERROR);
+ }
return true;
} else if (preference == mButtonAutoRetry) {
android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
new file mode 100644
index 0000000..185aea0
--- /dev/null
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -0,0 +1,365 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.phone;
+
+import static android.Manifest.permission.READ_PHONE_STATE;
+import static com.android.internal.telephony.uicc.IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED;
+
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.service.carrier.CarrierConfigService;
+import android.service.carrier.CarrierIdentifier;
+import android.service.carrier.ICarrierConfigService;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.ICarrierConfigLoader;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyIntents;
+
+import java.util.List;
+
+/**
+ * CarrierConfigLoader binds to privileged carrier apps to fetch carrier config overlays.
+ * TODO: implement persist cache
+ * TODO: add gid2 to phone
+ * TODO: handle package install/uninstall events
+ */
+
+public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
+ private static final String TAG = "CarrierConfigLoader";
+ // Package name for default carrier config app, bundled with system image.
+ private static final String DEFAULT_CARRIER_CONFIG_PACKAGE = "com.android.carrierconfig";
+
+ /** The singleton instance. */
+ private static CarrierConfigLoader sInstance;
+ // The context for phone app, passed from PhoneGlobals.
+ private Context mContext;
+ // Carrier configs from default app, indexed by phoneID.
+ private Bundle[] mConfigFromDefaultApp;
+ // Carrier configs from privileged carrier config app, indexed by phoneID.
+ private Bundle[] mConfigFromCarrierApp;
+ // Service connection for binding to config app.
+ private ConfigServiceConnection[] mServiceConnection;
+
+ // Broadcast receiver for SIM and pkg intents, register intent filter in constructor.
+ private final BroadcastReceiver mReceiver = new ConfigLoaderBroadcastReceiver();
+
+ // Message codes; see mHandler below.
+ // Request from SubscriptionInfoUpdater when SIM becomes absent or error.
+ private static final int EVENT_CLEAR_CONFIG = 0;
+ // Request from SubscriptionInfoUpdater to update config.
+ private static final int EVENT_UPDATE_CONFIG = 1;
+ // Request from carrier app to reload config.
+ private static final int EVENT_RELOAD_CONFIG = 2;
+ // Has connected to default app.
+ private static final int EVENT_CONNECTED_TO_DEFAULT = 3;
+ // Has connected to carrier app.
+ private static final int EVENT_CONNECTED_TO_CARRIER = 4;
+ // Config has been loaded from default app.
+ private static final int EVENT_LOADED_FROM_DEFAULT = 5;
+ // Config has been loaded from carrier app.
+ private static final int EVENT_LOADED_FROM_CARRIER = 6;
+
+
+ // Handler to process various events.
+ // For each phoneId, state transition should be: default app bind->connected->loaded,
+ // carrier app (if exists) bind-> connected->loaded. At any time, at most one connection is active.
+ // If events are not in this order, previous connection will be unbind, so only latest event takes effect.
+ // We broadcast config change when:
+ // 1. loaded from carrier app
+ // 2. loaded from default app if no carrier app
+ // 3. config cleared, possibly due to sim removed
+ // 4. bind or IPC error
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ int phoneId = msg.arg1;
+ log("mHandler: " + msg.what + " phoneId: " + phoneId);
+ CarrierIdentifier carrierId;
+ ConfigServiceConnection conn;
+ Bundle config;
+ switch (msg.what) {
+ case EVENT_CLEAR_CONFIG:
+ mConfigFromDefaultApp[phoneId] = null;
+ mConfigFromCarrierApp[phoneId] = null;
+ mServiceConnection[phoneId] = null;
+ broadcastConfigChangedIntent(phoneId);
+ break;
+ case EVENT_UPDATE_CONFIG:
+ // Use persist cache to avoid loading from app.
+ // Fall through to next event if cache not hit.
+ case EVENT_RELOAD_CONFIG:
+ if (!bindToConfigPackage(DEFAULT_CARRIER_CONFIG_PACKAGE,
+ phoneId, EVENT_CONNECTED_TO_DEFAULT)) {
+ //Send bcast if bind fails
+ broadcastConfigChangedIntent(phoneId);
+ }
+ break;
+
+ case EVENT_CONNECTED_TO_DEFAULT:
+ carrierId = getCarrierIdForPhoneId(phoneId);
+ conn = (ConfigServiceConnection) msg.obj;
+ // If new service connection has been created, unbind.
+ if (mServiceConnection[phoneId] != conn || conn.service == null) {
+ mContext.unbindService(conn);
+ break;
+ }
+ try {
+ ICarrierConfigService configService = ICarrierConfigService.Stub.asInterface(conn.service);
+ config = configService.getCarrierConfig(carrierId);
+ mConfigFromDefaultApp[phoneId] = config;
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_LOADED_FROM_DEFAULT, phoneId));
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to get carrier config: " + ex.toString());
+ } finally {
+ mContext.unbindService(mServiceConnection[phoneId]);
+ }
+ break;
+
+ case EVENT_LOADED_FROM_DEFAULT:
+ if (mServiceConnection[phoneId] == null) {
+ break;
+ }
+ List<String> carrierPackageNames = TelephonyManager.from(mContext)
+ .getCarrierPackageNamesForIntent(new Intent(CarrierConfigService.SERVICE_INTERFACE));
+ log("Found carrier config app: " + carrierPackageNames);
+ if (carrierPackageNames != null && carrierPackageNames.size() > 0) {
+ if (!bindToConfigPackage(carrierPackageNames.get(0),
+ phoneId, EVENT_CONNECTED_TO_CARRIER)) {
+ broadcastConfigChangedIntent(phoneId);
+ }
+ } else {
+ broadcastConfigChangedIntent(phoneId);
+ }
+ break;
+
+ case EVENT_CONNECTED_TO_CARRIER:
+ carrierId = getCarrierIdForPhoneId(phoneId);
+ conn = (ConfigServiceConnection) msg.obj;
+ // If new service connection has been created, unbind.
+ if (mServiceConnection[phoneId] != conn ||
+ conn.service == null) {
+ mContext.unbindService(conn);
+ break;
+ }
+ try {
+ ICarrierConfigService configService = ICarrierConfigService.Stub.asInterface(conn.service);
+ config = configService.getCarrierConfig(carrierId);
+ mConfigFromCarrierApp[phoneId] = config;
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_LOADED_FROM_CARRIER, phoneId));
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to get carrier config: " + ex.toString());
+ } finally {
+ mContext.unbindService(mServiceConnection[phoneId]);
+ }
+ break;
+
+ case EVENT_LOADED_FROM_CARRIER:
+ if (mServiceConnection[phoneId] == null) {
+ break;
+ }
+ broadcastConfigChangedIntent(phoneId);
+ break;
+ }
+ }
+ };
+
+ /**
+ * Constructs a CarrierConfigLoader, registers it as a service, and registers a broadcast
+ * receiver for relevant events.
+ */
+ private CarrierConfigLoader(Context context) {
+ mContext = context;
+
+ // Register for package updates.
+ IntentFilter triggers = new IntentFilter();
+ triggers.addAction(Intent.ACTION_PACKAGE_ADDED);
+ triggers.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ triggers.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ mContext.registerReceiver(mReceiver, triggers);
+
+ int numPhones = TelephonyManager.from(context).getPhoneCount();
+ mConfigFromDefaultApp = new Bundle[numPhones];
+ mConfigFromCarrierApp = new Bundle[numPhones];
+ mServiceConnection = new ConfigServiceConnection[numPhones];
+ // Make this service available through ServiceManager.
+ ServiceManager.addService(Context.CARRIER_CONFIG_SERVICE, this);
+ log("CarrierConfigLoader has started");
+ }
+
+ /**
+ * Initialize the singleton CarrierConfigLoader instance.
+ *
+ * This is only done once, at startup, from {@link com.android.phone.PhoneApp#onCreate}.
+ */
+ /* package */
+ static CarrierConfigLoader init(Context context) {
+ synchronized (CarrierConfigLoader.class) {
+ if (sInstance == null) {
+ sInstance = new CarrierConfigLoader(context);
+ } else {
+ Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
+ }
+ return sInstance;
+ }
+ }
+
+ private void broadcastConfigChangedIntent(int phoneId) {
+ Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId);
+ ActivityManagerNative.broadcastStickyIntent(intent, READ_PHONE_STATE,
+ UserHandle.USER_ALL);
+ }
+
+ /** Binds to the default or carrier config app. */
+ private boolean bindToConfigPackage(String pkgName, int phoneId, int eventId) {
+ log("Binding to " + pkgName + " for phone " + phoneId);
+ Intent carrierConfigService = new Intent(CarrierConfigService.SERVICE_INTERFACE);
+ carrierConfigService.setPackage(pkgName);
+ mServiceConnection[phoneId] = new ConfigServiceConnection(phoneId, eventId);
+ try {
+ return mContext.bindService(carrierConfigService, mServiceConnection[phoneId],
+ Context.BIND_AUTO_CREATE);
+ } catch (SecurityException ex) {
+ return false;
+ }
+ }
+
+ private CarrierIdentifier getCarrierIdForPhoneId(int phoneId) {
+ String mcc = "";
+ String mnc = "";
+ String imsi = "";
+ String gid1 = "";
+ String gid2 = "";
+ String spn = TelephonyManager.from(mContext).getSimOperatorNameForPhone(phoneId);
+ String simOperator = TelephonyManager.from(mContext).getSimOperatorNumericForPhone(phoneId);
+ if (simOperator != null) {
+ mcc = simOperator.substring(0, 3);
+ mnc = simOperator.substring(3);
+ }
+ Phone phone = PhoneFactory.getPhone(phoneId);
+ if (phone != null) {
+ imsi = phone.getSubscriberId();
+ gid1 = phone.getGroupIdLevel1();
+ // add gid2 after phone supports it.
+ }
+
+ return new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2);
+ }
+
+ @Override
+ public Bundle getConfigForSubId(int subId) {
+ int phoneId = SubscriptionManager.getPhoneId(subId);
+ Bundle retConfig = CarrierConfigManager.getDefaultConfig();
+ if (SubscriptionManager.isValidPhoneId(phoneId)) {
+ Bundle config = mConfigFromDefaultApp[phoneId];
+ if (config != null) retConfig.putAll(config);
+ config = mConfigFromCarrierApp[phoneId];
+ if (config != null) retConfig.putAll(config);
+ }
+ return retConfig;
+ }
+
+ @Override
+ public void reloadCarrierConfigForSubId(int subId) {
+ int phoneId = SubscriptionManager.getPhoneId(subId);
+ if (SubscriptionManager.isValidPhoneId(phoneId)) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELOAD_CONFIG, phoneId));
+ } else {
+ log("Ignore invalid phoneId: " + phoneId + " for subId: " + subId);
+ }
+ }
+
+ @Override
+ public void updateConfigForPhoneId(int phoneId, String simState) {
+ log("update config for phoneId: " + phoneId + " simState: " + simState);
+ if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+ return;
+ }
+ // requires Java 7 for switch on string.
+ switch (simState) {
+ case IccCardConstants.INTENT_VALUE_ICC_ABSENT:
+ case IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR:
+ case IccCardConstants.INTENT_VALUE_ICC_UNKNOWN:
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_CLEAR_CONFIG, phoneId));
+ break;
+ case IccCardConstants.INTENT_VALUE_ICC_LOADED:
+ case IccCardConstants.INTENT_VALUE_ICC_LOCKED:
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UPDATE_CONFIG, phoneId));
+ break;
+ }
+ }
+
+ private class ConfigServiceConnection implements ServiceConnection {
+ int phoneId;
+ int eventId;
+ IBinder service;
+
+ public ConfigServiceConnection(int phoneId, int eventId) {
+ this.phoneId = phoneId;
+ this.eventId = eventId;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ log("Connected to config app: " + name.flattenToString());
+ this.service = service;
+ mHandler.sendMessage(mHandler.obtainMessage(eventId, phoneId, -1, this));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ this.service = null;
+ }
+ }
+
+ private class ConfigLoaderBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ log("Receive action: " + action);
+ }
+ }
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+}
+
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index d13ebfe..c3a8d0c 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -144,6 +144,7 @@
CallerInfoCache callerInfoCache;
NotificationMgr notificationMgr;
PhoneInterfaceManager phoneMgr;
+ CarrierConfigLoader configLoader;
private BluetoothManager bluetoothManager;
private CallGatewayManager callGatewayManager;
@@ -399,6 +400,8 @@
phoneMgr = PhoneInterfaceManager.init(this, PhoneFactory.getDefaultPhone());
+ configLoader = CarrierConfigLoader.init(this);
+
// Create the CallNotifer singleton, which handles
// asynchronous events from the telephony layer (like
// launching the incoming-call UI when an incoming call comes
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 4037f6d..9369b6a 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -35,6 +35,7 @@
import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.provider.Settings;
+import android.telecom.PhoneAccount;
import android.telephony.CellInfo;
import android.telephony.IccOpenLogicalChannelResponse;
import android.telephony.NeighboringCellInfo;
@@ -2256,10 +2257,16 @@
* {@hide}
* Returns the IMS Registration Status
*/
+ @Override
public boolean isImsRegistered() {
return mPhone.isImsRegistered();
}
+ @Override
+ public int getSubIdForPhoneAccount(PhoneAccount phoneAccount) {
+ return PhoneUtils.getSubIdForPhoneAccount(phoneAccount);
+ }
+
/*
* {@hide}
* Returns the IMS Registration Status
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 6b34208..f7c5939 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -36,6 +36,7 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextThemeWrapper;
@@ -60,6 +61,7 @@
import com.android.internal.telephony.TelephonyCapabilities;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.sip.SipPhone;
+import com.android.internal.telephony.uicc.UiccCard;
import com.android.phone.CallGatewayManager.RawGatewayInfo;
import com.android.services.telephony.TelephonyConnectionService;
@@ -2447,20 +2449,58 @@
== Configuration.ORIENTATION_LANDSCAPE;
}
+ public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) {
+ return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId));
+ }
+
public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
return makePstnPhoneAccountHandleWithPrefix(phone, "", false);
}
public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
Phone phone, String prefix, boolean isEmergency) {
- ComponentName pstnConnectionServiceName =
- new ComponentName(phone.getContext(), TelephonyConnectionService.class);
+ ComponentName pstnConnectionServiceName = getPstnConnectionServiceName();
// TODO: Should use some sort of special hidden flag to decorate this account as
// an emergency-only account
- String id = isEmergency ? "E" : prefix + String.valueOf(phone.getSubId());
+ String id = isEmergency ? "E" : prefix + String.valueOf(phone.getIccSerialNumber());
return new PhoneAccountHandle(pstnConnectionServiceName, id);
}
+ public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) {
+ if (phoneAccount != null
+ && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+ return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle());
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
+ public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) {
+ if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) {
+ Phone phone = getPhoneFromIccId(handle.getId());
+ if (phone != null) {
+ return phone.getSubId();
+ }
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
+ private static ComponentName getPstnConnectionServiceName() {
+ return new ComponentName(PhoneGlobals.getInstance(), TelephonyConnectionService.class);
+ }
+
+ private static Phone getPhoneFromIccId(String iccId) {
+ if (!TextUtils.isEmpty(iccId)) {
+ for (Phone phone : PhoneFactory.getPhones()) {
+ String phoneIccId = phone.getIccSerialNumber();
+ if (iccId.equals(phoneIccId)) {
+ return phone;
+ }
+ }
+ }
+
+ return null;
+ }
+
/**
* Register ICC status for all phones.
*/
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 6ca146c..7f04302 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -98,8 +98,8 @@
mDefaultOutgoingAccount = (AccountSelectionPreference)
getPreferenceScreen().findPreference(DEFAULT_OUTGOING_ACCOUNT_KEY);
- if (mTelecomManager.hasMultipleCallCapableAccounts()) {
mDefaultOutgoingAccount.setListener(this);
+ if (mTelecomManager.getCallCapablePhoneAccounts().size() > 1) {
updateDefaultOutgoingAccountsModel();
} else {
getPreferenceScreen().removePreference(mDefaultOutgoingAccount);
diff --git a/src/com/android/phone/settings/VoicemailDialogUtil.java b/src/com/android/phone/settings/VoicemailDialogUtil.java
index 0a9e4bc..f1e7fa7 100644
--- a/src/com/android/phone/settings/VoicemailDialogUtil.java
+++ b/src/com/android/phone/settings/VoicemailDialogUtil.java
@@ -35,11 +35,12 @@
public static final int VM_FWD_SAVING_DIALOG = 601;
public static final int VM_FWD_READING_DIALOG = 602;
public static final int VM_REVERTING_DIALOG = 603;
+ public static final int TTY_SET_RESPONSE_ERROR = 800;
public static Dialog getDialog(CallFeaturesSetting parent, int id) {
if ((id == VM_RESPONSE_ERROR_DIALOG) || (id == VM_NOCHANGE_ERROR_DIALOG) ||
(id == FWD_SET_RESPONSE_ERROR_DIALOG) || (id == FWD_GET_RESPONSE_ERROR_DIALOG) ||
- (id == VM_CONFIRM_DIALOG)) {
+ (id == VM_CONFIRM_DIALOG) || (id == TTY_SET_RESPONSE_ERROR)) {
AlertDialog.Builder b = new AlertDialog.Builder(parent);
@@ -75,6 +76,12 @@
b.setPositiveButton(R.string.alert_dialog_yes, parent);
b.setNegativeButton(R.string.alert_dialog_no, parent);
break;
+ case TTY_SET_RESPONSE_ERROR:
+ titleId = R.string.tty_mode_option_title;
+ msgId = R.string.tty_mode_not_allowed_video_call;
+ b.setIconAttribute(android.R.attr.alertDialogIcon);
+ b.setPositiveButton(R.string.ok, parent);
+ break;
default:
msgId = R.string.exception_error;
// Set Button 3, tells the activity that the error is
diff --git a/src/com/android/phone/vvm/omtp/OmtpConstants.java b/src/com/android/phone/vvm/omtp/OmtpConstants.java
new file mode 100644
index 0000000..971d2d6
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpConstants.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Wrapper class to hold relevant OMTP constants as defined in the OMTP spec.
+ * <p>
+ * In essence this is a programmatic representation of the relevant portions of OMTP spec.
+ */
+public class OmtpConstants {
+ public static final String SMS_FIELD_SEPARATOR = ";";
+ public static final String SMS_KEY_VALUE_SEPARATOR = "=";
+ public static final String SMS_PREFIX_SEPARATOR = ":";
+
+ public static final String CLIENT_PREFIX = "//VVM";
+ public static final String SYNC_SMS_PREFIX = CLIENT_PREFIX + ":SYNC:";
+ public static final String STATUS_SMS_PREFIX = CLIENT_PREFIX + ":STATUS:";
+
+ // This is the format designated by the OMTP spec.
+ public static final String DATE_TIME_FORMAT = "dd/MM/yyyy HH:mm Z";
+
+ /** OMTP protocol versions. */
+ public static final String PROTOCOL_VERSION1_1 = "11";
+ public static final String PROTOCOL_VERSION1_2 = "12";
+ public static final String PROTOCOL_VERSION1_3 = "13";
+
+ ///////////////////////// Client/Mobile originated SMS //////////////////////
+
+ /** Mobile Originated requests */
+ public static final String ACTIVATE_REQUEST = "Activate";
+ public static final String DEACTIVATE_REQUEST = "Deactivate";
+ public static final String STATUS_REQUEST = "Status";
+
+ /** fields that can be present in a Mobile Originated OMTP SMS */
+ public static final String CLIENT_TYPE = "ct";
+ public static final String APPLICATION_PORT = "pt";
+ public static final String PROTOCOL_VERSION = "pv";
+
+
+ //////////////////////////////// Sync SMS fields ////////////////////////////
+
+ /**
+ * Sync SMS fields.
+ * <p>
+ * Each string constant is the field's key in the SMS body which is used by the parser to
+ * identify the field's value, if present, in the SMS body.
+ */
+
+ /**
+ * The event that triggered this SYNC SMS.
+ * See {@link OmtpConstants#SYNC_TRIGGER_EVENT_VALUES}
+ */
+ public static final String SYNC_TRIGGER_EVENT = "ev";
+ public static final String MESSAGE_UID = "id";
+ public static final String MESSAGE_LENGTH = "l";
+ public static final String NUM_MESSAGE_COUNT = "c";
+ /** See {@link OmtpConstants#CONTENT_TYPE_VALUES} */
+ public static final String CONTENT_TYPE = "t";
+ public static final String SENDER = "s";
+ public static final String TIME = "dt";
+
+ /**
+ * SYNC message trigger events.
+ * <p>
+ * These are the possible values of {@link OmtpConstants#SYNC_TRIGGER_EVENT}.
+ */
+ public static final String NEW_MESSAGE = "NM";
+ public static final String MAILBOX_UPDATE = "MBU";
+ public static final String GREETINGS_UPDATE = "GU";
+
+ public static final String[] SYNC_TRIGGER_EVENT_VALUES = {
+ NEW_MESSAGE,
+ MAILBOX_UPDATE,
+ GREETINGS_UPDATE
+ };
+
+ /**
+ * Content types supported by OMTP VVM.
+ * <p>
+ * These are the possible values of {@link OmtpConstants#CONTENT_TYPE}.
+ */
+ public static final String VOICE = "v";
+ public static final String VIDEO = "o";
+ public static final String FAX = "f";
+ /** Voice message deposited by an external application */
+ public static final String INFOTAINMENT = "i";
+ /** Empty Call Capture - i.e. voicemail with no voice message. */
+ public static final String ECC = "e";
+
+ public static final String[] CONTENT_TYPE_VALUES = {VOICE, VIDEO, FAX, INFOTAINMENT, ECC};
+
+ ////////////////////////////// Status SMS fields ////////////////////////////
+
+ /**
+ * Status SMS fields.
+ * <p>
+ * Each string constant is the field's key in the SMS body which is used by the parser to
+ * identify the field's value, if present, in the SMS body.
+ */
+ /** See {@link OmtpConstants#PROVISIONING_STATUS_VALUES} */
+ public static final String PROVISIONING_STATUS = "st";
+ /** See {@link OmtpConstants#RETURN_CODE_VALUES} */
+ public static final String RETURN_CODE = "rc";
+ /** URL to send users to for activation VVM */
+ public static final String SUBSCRIPTION_URL = "rs";
+ /** IMAP4/SMTP server IP address or fully qualified domain name */
+ public static final String SERVER_ADDRESS = "srv";
+ /** Phone number to access voicemails through Telephony User Interface */
+ public static final String TUI_ACCESS_NUMBER = "tui";
+ /** Number to send client origination SMS */
+ public static final String CLIENT_SMS_DESTINATION_NUMBER = "dn";
+ public static final String IMAP_PORT = "ipt";
+ public static final String IMAP_USER_NAME = "u";
+ public static final String IMAP_PASSWORD = "pw";
+ public static final String SMTP_PORT = "spt";
+ public static final String SMTP_USER_NAME = "smtp_u";
+ public static final String SMTP_PASSWORD = "smtp_pw";
+
+ /**
+ * User provisioning status values.
+ * <p>
+ * Referred by {@link OmtpConstants#PROVISIONING_STATUS}.
+ */
+ // TODO: As per the spec the code could be either be with or w/o quotes = "N"/N). Currently
+ // this only handles the w/o quotes values.
+ public static final String SUBSCRIBER_NEW = "N";
+ public static final String SUBSCRIBER_READY = "R";
+ public static final String SUBSCRIBER_PROVISIONED = "P";
+ public static final String SUBSCRIBER_UNKNOWN = "U";
+ public static final String SUBSCRIBER_BLOCKED = "B";
+
+ public static final String[] PROVISIONING_STATUS_VALUES = {
+ SUBSCRIBER_NEW,
+ SUBSCRIBER_READY,
+ SUBSCRIBER_PROVISIONED,
+ SUBSCRIBER_UNKNOWN,
+ SUBSCRIBER_BLOCKED
+ };
+
+ /**
+ * The return code included in a status message.
+ * <p>
+ * These are the possible values of {@link OmtpConstants#RETURN_CODE}.
+ */
+ public static final String SUCCESS = "0";
+ public static final String SYSTEM_ERROR = "1";
+ public static final String SUBSCRIBER_ERROR = "2";
+ public static final String MAILBOX_UNKNOWN = "3";
+ public static final String VVM_NOT_ACTIVATED = "4";
+ public static final String VVM_NOT_PROVISIONED = "5";
+ public static final String VVM_CLIENT_UKNOWN = "6";
+ public static final String VVM_MAILBOX_NOT_INITIALIZED = "7";
+
+ public static final String[] RETURN_CODE_VALUES = {
+ SUCCESS,
+ SYSTEM_ERROR,
+ SUBSCRIBER_ERROR,
+ MAILBOX_UNKNOWN,
+ VVM_NOT_ACTIVATED,
+ VVM_NOT_PROVISIONED,
+ VVM_CLIENT_UKNOWN,
+ VVM_MAILBOX_NOT_INITIALIZED,
+ };
+
+ /**
+ * A map of all the field keys to the possible values they can have.
+ */
+ public static final Map<String, String[]> possibleValuesMap = new HashMap<String, String[]>() {{
+ put(SYNC_TRIGGER_EVENT, SYNC_TRIGGER_EVENT_VALUES);
+ put(CONTENT_TYPE, CONTENT_TYPE_VALUES);
+ put(PROVISIONING_STATUS, PROVISIONING_STATUS_VALUES);
+ put(RETURN_CODE, RETURN_CODE_VALUES);
+ }};
+
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java b/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java
new file mode 100644
index 0000000..de39745
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * A singleton class designed to assist in OMTP visual voicemail sync behavior.
+ */
+public class OmtpVvmSyncAccountManager {
+ public static final String TAG = "OmtpVvmSyncAccountManager";
+ // Constants
+ // The authority for the sync adapter's content provider
+ public static final String AUTHORITY = "com.android.voicemail";
+ // An account type, in the form of a domain name
+ public static final String ACCOUNT_TYPE = "com.android.phone.vvm.omtp";
+
+ private static OmtpVvmSyncAccountManager sInstance = new OmtpVvmSyncAccountManager();
+
+ private AccountManager mAccountManager;
+
+ /**
+ * Private constructor. Instance should only be acquired through getInstance().
+ */
+ private OmtpVvmSyncAccountManager() {}
+
+ public static OmtpVvmSyncAccountManager getInstance(Context context) {
+ sInstance.setAccountManager(context);
+ return sInstance;
+ }
+
+ /**
+ * Set the account manager so it does not need to be retrieved every time.
+ * @param context The context to get the account manager for.
+ */
+ private void setAccountManager(Context context) {
+ if (mAccountManager == null) {
+ mAccountManager = AccountManager.get(context);
+ }
+ }
+
+ /**
+ * Register a sync account. There should be a one to one mapping of sync account to voicemail
+ * source. These sync accounts primarily service the purpose of keeping track of how many OMTP
+ * voicemail sources are active and which phone accounts they correspond to.
+ *
+ * @param account The account to register
+ */
+ public void createSyncAccount(Account account) {
+ // Add the account and account type, no password or user data
+ if (mAccountManager.addAccountExplicitly(account, null, null)) {
+ ContentResolver.setIsSyncable(account, AUTHORITY, 1);
+ ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
+ } else {
+ Log.w(TAG, "Attempted to re-register existing account.");
+ }
+ }
+
+ /**
+ * Check if a certain account is registered.
+ *
+ * @param account The account to look for.
+ * @return {@code true} if the account is in the list of registered OMTP voicemail sync
+ * accounts. {@code false} otherwise.
+ */
+ public boolean isAccountRegistered(Account account) {
+ Account[] accounts = mAccountManager.getAccountsByType(ACCOUNT_TYPE);
+ for (int i = 0; i < accounts.length; i++) {
+ if (account.equals(accounts[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
new file mode 100644
index 0000000..811e7e6
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+/**
+ * A {@link Service} which runs the internal implementation of {@link AbstractThreadedSyncAdapter},
+ * syncing voicemails to and from a visual voicemail server.
+ */
+
+package com.android.phone.vvm.omtp;
+
+import android.accounts.Account;
+import android.app.Service;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncResult;
+import android.os.Bundle;
+import android.os.IBinder;
+
+/**
+ * A service to run the VvmSyncAdapter.
+ */
+public class OmtpVvmSyncService extends Service {
+ // Storage for an instance of the sync adapter
+ private static OmtpVvmSyncAdapter sSyncAdapter = null;
+ // Object to use as a thread-safe lock
+ private static final Object sSyncAdapterLock = new Object();
+
+ @Override
+ public void onCreate() {
+ synchronized (sSyncAdapterLock) {
+ if (sSyncAdapter == null) {
+ sSyncAdapter = new OmtpVvmSyncAdapter(getApplicationContext(), true);
+ }
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return sSyncAdapter.getSyncAdapterBinder();
+ }
+
+ public class OmtpVvmSyncAdapter extends AbstractThreadedSyncAdapter {
+ public OmtpVvmSyncAdapter(Context context, boolean autoInitialize) {
+ super(context, autoInitialize);
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority,
+ ContentProviderClient provider, SyncResult syncResult) {
+ // TODO: Write code necessary for syncing.
+ }
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
new file mode 100644
index 0000000..932fb1a
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.accounts.Account;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Telephony;
+import android.provider.VoicemailContract;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.SmsMessage;
+import android.util.Log;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.phone.PhoneUtils;
+import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.OmtpVvmSyncAccountManager;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Receive SMS messages and send for processing by the OMTP visual voicemail source.
+ */
+public class OmtpMessageReceiver extends BroadcastReceiver {
+ private static final String TAG = "OmtpMessageReceiver";
+
+ private Context mContext;
+ private PhoneAccountHandle mPhoneAccount;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mContext = context;
+ mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
+ intent.getExtras().getInt(PhoneConstants.PHONE_KEY));
+
+ SmsMessage[] messages = Telephony.Sms.Intents.getMessagesFromIntent(intent);
+ StringBuilder userData = new StringBuilder();
+ StringBuilder messageBody = new StringBuilder();
+
+ for (int i = 0; i < messages.length; i++) {
+ messageBody.append(messages[i].getMessageBody());
+ userData.append(extractUserData(messages[i]));
+ }
+
+ WrappedMessageData messageData = OmtpSmsParser.parse(messageBody.toString());
+ if (messageData != null) {
+ if (messageData.getPrefix() == OmtpConstants.SYNC_SMS_PREFIX) {
+ SyncMessage message = new SyncMessage(messageData);
+ //TODO: handle message
+ } else if (messageData.getPrefix() == OmtpConstants.STATUS_SMS_PREFIX) {
+ StatusMessage message = new StatusMessage(messageData);
+ handleStatusMessage(message);
+ } else {
+ Log.e(TAG, "This should never have happened");
+ }
+ }
+ // Let this fall through: this is not a message we're interested in.
+ }
+
+ private String extractUserData(SmsMessage sms) {
+ try {
+ // OMTP spec does not tell about the encoding. We assume ASCII.
+ // UTF-8 sounds safer as it can handle ascii as well as other charsets.
+ return new String(sms.getUserData(), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("This should have never happened", e);
+ }
+ }
+
+ private void handleStatusMessage(StatusMessage message) {
+ OmtpVvmSyncAccountManager vvmSyncManager = OmtpVvmSyncAccountManager.getInstance(mContext);
+ Account account = new Account(mPhoneAccount.getId(),
+ OmtpVvmSyncAccountManager.ACCOUNT_TYPE);
+
+ if (!vvmSyncManager.isAccountRegistered(account)) {
+ // If the account has not been previously registered, it means that this STATUS sms
+ // is a result of the ACTIVATE sms, so register the voicemail source.
+ vvmSyncManager.createSyncAccount(account);
+ VoicemailContract.Status.setStatus(mContext, mPhoneAccount,
+ VoicemailContract.Status.CONFIGURATION_STATE_OK,
+ VoicemailContract.Status.DATA_CHANNEL_STATE_OK,
+ VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK);
+ }
+
+ //TODO: figure out how to pass IMAP credentials to sync adapter
+
+ ContentResolver.requestSync(account, VoicemailContract.AUTHORITY, new Bundle());
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java
new file mode 100644
index 0000000..f7cfb86
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 Google Inc. 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.phone.vvm.omtp.sms;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.telephony.SmsManager;
+
+/**
+ * Interface to send client originated OMTP messages to the OMTP server.
+ * <p>
+ * The interface uses {@link PendingIntent} instead of a call back to notify when the message is
+ * sent. This is primarily to keep the implementation simple and reuse what the underlying
+ * {@link SmsManager} interface provides.
+ */
+public interface OmtpMessageSender {
+ /**
+ * Sends a request to the VVM server to activate VVM for the current subscriber.
+ *
+ * @param sentIntent If not NULL this PendingIntent is broadcast when the message is
+ * successfully sent, or failed.
+ */
+ public void requestVvmActivation(@Nullable PendingIntent sentIntent);
+
+ /**
+ * Sends a request to the VVM server to deactivate VVM for the current subscriber.
+ *
+ * @param sentIntent If not NULL this PendingIntent is broadcast when the message is
+ * successfully sent, or failed.
+ */
+ public void requestVvmDeactivation(@Nullable PendingIntent sentIntent);
+
+ /**
+ * Send a request to the VVM server to get account status of the current subscriber.
+ *
+ * @param sentIntent If not NULL this PendingIntent is broadcast when the message is
+ * successfully sent, or failed.
+ */
+ public void requestVvmStatus(@Nullable PendingIntent sentIntent);
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageSenderImpl.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSenderImpl.java
new file mode 100644
index 0000000..15e8e3a
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSenderImpl.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2015 Google Inc. 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.phone.vvm.omtp.sms;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.services.telephony.Log;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Implementation of {@link OmtpMessageSender} interface.
+ * <p>
+ * Provides simple APIs to send different types of mobile originated OMTP SMS to the VVM server.
+ */
+public class OmtpMessageSenderImpl implements OmtpMessageSender {
+ private static final String TAG = "OmtpMessageSender";
+ private final SmsManager mSmsManager;
+ private final short mApplicationPort;
+ private final String mDestinationNumber;
+ private final String mClientType;
+ private final String mProtocolVersion;
+ private final String mClientPrefix;
+
+ /**
+ * Creates a new instance of OmtpMessageSenderImpl.
+ *
+ * @param smsManager SMS sending library. There is a different SmsManager for each SIM.
+ * @param applicationPort If set to a value > 0 then a binary sms is sent to this port number.
+ * Otherwise, a standard text SMS is sent.
+ * @param defaultDestinationNumber Destination number to be used.
+ * @param clientType The "ct" field to be set in the MO message. This is the value used by the
+ * VVM server to identify the client. Certain VVM servers require a specific agreed
+ * value for this field.
+ * @param protocolVersion OMTP protocol version.
+ * @param clientPrefix The client prefix requested to be used by the server in its MT messages.
+ */
+ public OmtpMessageSenderImpl(SmsManager smsManager, short applicationPort,
+ String defaultDestinationNumber, String clientType, String protocolVersion,
+ String clientPrefix) {
+ mSmsManager = smsManager;
+ mApplicationPort = applicationPort;
+ mDestinationNumber = defaultDestinationNumber;
+ mClientType = clientType;
+ mProtocolVersion = protocolVersion;
+ mClientPrefix = clientPrefix;
+ }
+
+
+ // Activate message:
+ // V1.1: Activate:pv=<value>;ct=<value>
+ // V1.2: Activate:pv=<value>;ct=<value>;pt=<value>;<Clientprefix>
+ // V1.3: Activate:pv=<value>;ct=<value>;pt=<value>;<Clientprefix>
+ @Override
+ public void requestVvmActivation(@Nullable PendingIntent sentIntent) {
+ StringBuilder sb = new StringBuilder().append(OmtpConstants.ACTIVATE_REQUEST);
+
+ appendProtocolVersionAndClientType(sb);
+ if (TextUtils.equals(mProtocolVersion, OmtpConstants.PROTOCOL_VERSION1_2) ||
+ TextUtils.equals(mProtocolVersion, OmtpConstants.PROTOCOL_VERSION1_3)) {
+ appendApplicationPort(sb);
+ appendClientPrefix(sb);
+ }
+
+ sendSms(sb.toString(), sentIntent);
+ }
+
+ // Deactivate message:
+ // V1.1: Deactivate:pv=<value>;ct=<string>
+ // V1.2: Deactivate:pv=<value>;ct=<string>
+ // V1.3: Deactivate:pv=<value>;ct=<string>
+ @Override
+ public void requestVvmDeactivation(@Nullable PendingIntent sentIntent) {
+ StringBuilder sb = new StringBuilder().append(OmtpConstants.DEACTIVATE_REQUEST);
+ appendProtocolVersionAndClientType(sb);
+
+ sendSms(sb.toString(), sentIntent);
+ }
+
+ // Status message:
+ // V1.1: STATUS
+ // V1.2: STATUS
+ // V1.3: STATUS:pv=<value>;ct=<value>;pt=<value>;<Clientprefix>
+ @Override
+ public void requestVvmStatus(@Nullable PendingIntent sentIntent) {
+ StringBuilder sb = new StringBuilder().append(OmtpConstants.STATUS_REQUEST);
+
+ if (TextUtils.equals(mProtocolVersion, OmtpConstants.PROTOCOL_VERSION1_3)) {
+ appendProtocolVersionAndClientType(sb);
+ appendApplicationPort(sb);
+ appendClientPrefix(sb);
+ }
+
+ sendSms(sb.toString(), sentIntent);
+ }
+
+ private void sendSms(String text, PendingIntent sentIntent) {
+ // If application port is set to 0 then send simple text message, else send data message.
+ if (mApplicationPort == 0) {
+ Log.v(TAG, String.format("Sending TEXT sms '%s' to %s", text, mDestinationNumber));
+ mSmsManager.sendTextMessage(mDestinationNumber, null, text, sentIntent, null);
+ } else {
+ byte[] data;
+ try {
+ data = text.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Failed to encode: " + text);
+ }
+ Log.v(TAG, String.format("Sending BINARY sms '%s' to %s:%d", text, mDestinationNumber,
+ mApplicationPort));
+ mSmsManager.sendDataMessage(mDestinationNumber, null, mApplicationPort, data,
+ sentIntent, null);
+ }
+ }
+
+ private void appendProtocolVersionAndClientType(StringBuilder sb) {
+ sb.append(OmtpConstants.SMS_PREFIX_SEPARATOR);
+ appendField(sb, OmtpConstants.PROTOCOL_VERSION, mProtocolVersion);
+ sb.append(OmtpConstants.SMS_FIELD_SEPARATOR);
+ appendField(sb, OmtpConstants.CLIENT_TYPE, mClientType);
+ }
+
+ private void appendApplicationPort(StringBuilder sb) {
+ sb.append(OmtpConstants.SMS_FIELD_SEPARATOR);
+ appendField(sb, OmtpConstants.APPLICATION_PORT, mApplicationPort);
+ }
+
+ private void appendClientPrefix(StringBuilder sb) {
+ sb.append(OmtpConstants.SMS_FIELD_SEPARATOR);
+ sb.append(mClientPrefix);
+ }
+
+ private void appendField(StringBuilder sb, String field, Object value) {
+ sb.append(field).append(OmtpConstants.SMS_KEY_VALUE_SEPARATOR).append(value);
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java b/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
new file mode 100644
index 0000000..54a2a02
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+import java.util.Map;
+
+/**
+ * OMTP SMS parser interface, for parsing SYNC and STATUS SMS sent by OMTP visual voicemail server.
+ */
+public class OmtpSmsParser {
+ private static String TAG = "OmtpSmsParser";
+ /**
+ * Parses the supplied SMS body and returns back a structured OMTP message.
+ * Returns null if unable to parse the SMS body.
+ */
+ public static WrappedMessageData parse(String smsBody) {
+ if (smsBody == null) {
+ return null;
+ }
+
+ WrappedMessageData messageData = null;
+ if (smsBody.startsWith(OmtpConstants.SYNC_SMS_PREFIX)) {
+ messageData = new WrappedMessageData(OmtpConstants.SYNC_SMS_PREFIX,
+ parseSmsBody(smsBody.substring(OmtpConstants.SYNC_SMS_PREFIX.length())));
+ // Check for a mandatory field.
+ String triggerEvent = messageData.extractString(OmtpConstants.SYNC_TRIGGER_EVENT);
+ if (triggerEvent == null) {
+ Log.e(TAG, "Missing mandatory field: " + OmtpConstants.SYNC_TRIGGER_EVENT);
+ return null;
+ }
+ } else if (smsBody.startsWith(OmtpConstants.STATUS_SMS_PREFIX)) {
+ messageData = new WrappedMessageData(OmtpConstants.STATUS_SMS_PREFIX,
+ parseSmsBody(smsBody.substring(OmtpConstants.STATUS_SMS_PREFIX.length())));
+ }
+
+ return messageData;
+ }
+
+ /**
+ * Converts a String of key/value pairs into a Map object. The WrappedMessageData object
+ * contains helper functions to retrieve the values.
+ *
+ * e.g. "//VVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"
+ * => "WrappedMessageData [mFields={st=R, ipt=1, srv=1, dn=1, u=eg@example.com, pw=1, rc=0}]"
+ *
+ * @param message The sms string with the prefix removed.
+ * @return A WrappedMessageData object containing the map.
+ */
+ private static Map<String, String> parseSmsBody(String message) {
+ Map<String, String> keyValues = new ArrayMap<String, String>();
+ String[] entries = message.split(OmtpConstants.SMS_FIELD_SEPARATOR);
+ for (String entry : entries) {
+ String[] keyValue = entry.split(OmtpConstants.SMS_KEY_VALUE_SEPARATOR);
+ if (keyValue.length != 2) {
+ continue;
+ }
+ keyValues.put(keyValue[0].trim(), keyValue[1].trim());
+ }
+
+ return keyValues;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/StatusMessage.java b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
new file mode 100644
index 0000000..7e4faac
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.telecom.Log;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+/**
+ * Structured data representation of OMTP STATUS message.
+ *
+ * The getters will return null if the field was not set in the message body or it could not be
+ * parsed.
+ */
+public class StatusMessage {
+ // NOTE: Following Status SMS fields are not yet parsed, as they do not seem
+ // to be useful for initial omtp source implementation.
+ // lang, g_len, vs_len, pw_len, pm, gm, vtc, vt
+
+ private final String mProvisioningStatus;
+ private final String mStatusReturnCode;
+ private final String mSubscriptionUrl;
+ private final String mServerAddress;
+ private final String mTuiAccessNumber;
+ private final String mClientSmsDestinationNumber;
+ private final String mImapPort;
+ private final String mImapUserName;
+ private final String mImapPassword;
+ private final String mSmtpPort;
+ private final String mSmtpUserName;
+ private final String mSmtpPassword;
+
+ @Override
+ public String toString() {
+ return "StatusMessage [mProvisioningStatus=" + mProvisioningStatus
+ + ", mStatusReturnCode=" + mStatusReturnCode
+ + ", mSubscriptionUrl=" + mSubscriptionUrl
+ + ", mServerAddress=" + mServerAddress
+ + ", mTuiAccessNumber=" + mTuiAccessNumber
+ + ", mClientSmsDestinationNumber=" + mClientSmsDestinationNumber
+ + ", mImapPort=" + mImapPort
+ + ", mImapUserName=" + mImapUserName
+ + ", mImapPassword=" + Log.pii(mImapPassword)
+ + ", mSmtpPort=" + mSmtpPort
+ + ", mSmtpUserName=" + mSmtpUserName
+ + ", mSmtpPassword=" + Log.pii(mSmtpPassword) + "]";
+ }
+
+ public StatusMessage(WrappedMessageData wrappedData) {
+ mProvisioningStatus = wrappedData.extractString(OmtpConstants.PROVISIONING_STATUS);
+ mStatusReturnCode = wrappedData.extractString(OmtpConstants.RETURN_CODE);
+ mSubscriptionUrl = wrappedData.extractString(OmtpConstants.SUBSCRIPTION_URL);
+ mServerAddress = wrappedData.extractString(OmtpConstants.SERVER_ADDRESS);
+ mTuiAccessNumber = wrappedData.extractString(OmtpConstants.TUI_ACCESS_NUMBER);
+ mClientSmsDestinationNumber = wrappedData.extractString(
+ OmtpConstants.CLIENT_SMS_DESTINATION_NUMBER);
+ mImapPort = wrappedData.extractString(OmtpConstants.IMAP_PORT);
+ mImapUserName = wrappedData.extractString(OmtpConstants.IMAP_USER_NAME);
+ mImapPassword = wrappedData.extractString(OmtpConstants.IMAP_PASSWORD);
+ mSmtpPort = wrappedData.extractString(OmtpConstants.SMTP_PORT);
+ mSmtpUserName = wrappedData.extractString(OmtpConstants.SMTP_USER_NAME);
+ mSmtpPassword = wrappedData.extractString(OmtpConstants.SMTP_PASSWORD);
+ }
+
+ /**
+ * @return the subscriber's VVM provisioning status.
+ */
+ public String getProvisioningStatus() {
+ return mProvisioningStatus;
+ }
+
+ /**
+ * @return the return-code of the status SMS.
+ */
+ public String getReturnCode() {
+ return mStatusReturnCode;
+ }
+
+ /**
+ * @return the URL of the voicemail server. This is the URL to send the users to for subscribing
+ * to the visual voicemail service.
+ */
+ public String getSubscriptionUrl() {
+ return mSubscriptionUrl;
+ }
+
+ /**
+ * @return the voicemail server address. Either server IP address or fully qualified domain
+ * name.
+ */
+ public String getServerAddress() {
+ return mServerAddress;
+ }
+
+ /**
+ * @return the Telephony User Interface number to call to access voicemails directly from the
+ * IVR.
+ */
+ public String getTuiAccessNumber() {
+ return mTuiAccessNumber;
+ }
+
+ /**
+ * @return the number to which client originated SMSes should be sent to.
+ */
+ public String getClientSmsDestinationNumber() {
+ return mClientSmsDestinationNumber;
+ }
+
+ /**
+ * @return the IMAP server port to talk to.
+ */
+ public String getImapPort() {
+ return mImapPort;
+ }
+
+ /**
+ * @return the IMAP user name to be used for authentication.
+ */
+ public String getImapUserName() {
+ return mImapUserName;
+ }
+
+ /**
+ * @return the IMAP password to be used for authentication.
+ */
+ public String getImapPassword() {
+ return mImapPassword;
+ }
+
+ /**
+ * @return the SMTP server port to talk to.
+ */
+ public String getSmtpPort() {
+ return mSmtpPort;
+ }
+
+ /**
+ * @return the SMTP user name to be used for SMTP authentication.
+ */
+ public String getSmtpUserName() {
+ return mSmtpUserName;
+ }
+
+ /**
+ * @return the SMTP password to be used for SMTP authentication.
+ */
+ public String getSmtpPassword() {
+ return mSmtpPassword;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/SyncMessage.java b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
new file mode 100644
index 0000000..9a78a6d
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+/**
+ * Structured data representation of an OMTP SYNC message.
+ *
+ * Getters will return null if the field was not set in the message body or it could not be parsed.
+ */
+public class SyncMessage {
+ // Sync event that triggered this message.
+ private final String mSyncTriggerEvent;
+ // Total number of new messages on the server.
+ private final Integer mNewMessageCount;
+ // UID of the new message.
+ private final String mMessageId;
+ // Length of the message.
+ private final Integer mMessageLength;
+ // Content type (voice, video, fax...) of the new message.
+ private final String mContentType;
+ // Sender of the new message.
+ private final String mSender;
+ // Timestamp (in millis) of the new message.
+ private final Long mMsgTimeMillis;
+
+ @Override
+ public String toString() {
+ return "SyncMessage [mSyncTriggerEvent=" + mSyncTriggerEvent
+ + ", mNewMessageCount=" + mNewMessageCount
+ + ", mMessageId=" + mMessageId
+ + ", mMessageLength=" + mMessageLength
+ + ", mContentType=" + mContentType
+ + ", mSender=" + mSender
+ + ", mMsgTimeMillis=" + mMsgTimeMillis + "]";
+ }
+
+ public SyncMessage(WrappedMessageData wrappedData) {
+ mSyncTriggerEvent = wrappedData.extractString(OmtpConstants.SYNC_TRIGGER_EVENT);
+ mMessageId = wrappedData.extractString(OmtpConstants.MESSAGE_UID);
+ mMessageLength = wrappedData.extractInteger(OmtpConstants.MESSAGE_LENGTH);
+ mContentType = wrappedData.extractString(OmtpConstants.CONTENT_TYPE);
+ mSender = wrappedData.extractString(OmtpConstants.SENDER);
+ mNewMessageCount = wrappedData.extractInteger(OmtpConstants.NUM_MESSAGE_COUNT);
+ mMsgTimeMillis = wrappedData.extractTime(OmtpConstants.TIME);
+ }
+
+ /**
+ * @return the event that triggered the sync message. This is a mandatory field and must always
+ * be set.
+ */
+ public String getSyncTriggerEvent() {
+ return mSyncTriggerEvent;
+ }
+
+ /**
+ * @return the number of new messages stored on the voicemail server.
+ */
+ public int getNewMessageCount() {
+ return mNewMessageCount;
+ }
+
+ /**
+ * @return the message ID of the new message.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public String getId() {
+ return mMessageId;
+ }
+
+ /**
+ * @return the content type of the new message.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public String getContentType() {
+ return mContentType;
+ }
+
+ /**
+ * @return the message length of the new message.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public int getLength() {
+ return mMessageLength;
+ }
+
+ /**
+ * @return the sender's phone number of the new message specified as MSISDN.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public String getSender() {
+ return mSender;
+ }
+
+ /**
+ * @return the timestamp as milliseconds for the new message.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public long getTimestampMillis() {
+ return mMsgTimeMillis;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java b/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
new file mode 100644
index 0000000..109dfb2
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Class wrapping the raw OMTP message data, internally represented as as map of all key-value pairs
+ * found in the SMS body.
+ * <p>
+ * Provides convenience methods to extract parse fields of different types.
+ * <p>
+ * All the methods return null if either the field was not present or it could not be parsed.
+ */
+public class WrappedMessageData {
+ private final String TAG = "WrappedMessageData";
+ private final String mPrefix;
+ private final Map<String, String> mFields;
+
+ @Override
+ public String toString() {
+ return "WrappedMessageData [mFields=" + mFields + "]";
+ }
+
+ WrappedMessageData(String prefix, Map<String, String> keyValues) {
+ mPrefix = prefix;
+ mFields = new ArrayMap<String, String>();
+ mFields.putAll(keyValues);
+ }
+
+ /**
+ * @return The String prefix of the message, designating whether this is the message data of a
+ * STATUS or SYNC sms.
+ */
+ String getPrefix() {
+ return mPrefix;
+ }
+
+ /**
+ * Extracts the requested field from underlying data and returns the String value as is.
+ *
+ * @param field The requested field.
+ * @return the parsed string value, or null if the field was not present or not valid.
+ */
+ String extractString(final String field) {
+ String value = mFields.get(field);
+ String[] possibleValues = OmtpConstants.possibleValuesMap.get(field);
+ if (possibleValues == null) {
+ return value;
+ }
+ for (int i = 0; i < possibleValues.length; i++) {
+ if (TextUtils.equals(value, possibleValues[i])) {
+ return value;
+ }
+ }
+ Log.e(TAG, "extractString - value \"" + value +
+ "\" of field \"" + field + "\" is not allowed.");
+ return null;
+ }
+
+ /**
+ * Extracts the requested field from underlying data and parses it as an {@link Integer}.
+ *
+ * @param field The requested field.
+ * @return the parsed integer value, or null if the field was not present.
+ */
+ Integer extractInteger(final String field) {
+ String value = mFields.get(field);
+ if (value == null) {
+ return null;
+ }
+
+ try {
+ return Integer.decode(value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "extractInteger - could not parse integer: " + value);
+ return null;
+ }
+ }
+
+ /**
+ * Extracts the requested field from underlying data and parses it as a date/time represented in
+ * {@link OmtpConstants#DATE_TIME_FORMAT} format.
+ *
+ * @param field The requested field.
+ * @return the parsed string value, or null if the field was not present.
+ */
+ Long extractTime(final String field) {
+ String value = mFields.get(field);
+ if (value == null) {
+ return null;
+ }
+
+ try {
+ return new SimpleDateFormat(
+ OmtpConstants.DATE_TIME_FORMAT, Locale.US).parse(value).getTime();
+ } catch (ParseException e) {
+ Log.e(TAG, "extractTime - could not parse time: " + value);
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/services/telephony/CdmaConference.java b/src/com/android/services/telephony/CdmaConference.java
index fdd2110..6a55efe 100755
--- a/src/com/android/services/telephony/CdmaConference.java
+++ b/src/com/android/services/telephony/CdmaConference.java
@@ -42,7 +42,7 @@
public void updateCapabilities(int capabilities) {
capabilities |= Connection.CAPABILITY_MUTE | Connection.CAPABILITY_GENERIC_CONFERENCE;
- setCapabilities(capabilities);
+ setConnectionCapabilities(capabilities);
}
/**
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index 0baa3cc..3016039 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -155,13 +155,16 @@
mIsCallWaiting = originalConnection != null &&
originalConnection.getState() == Call.State.WAITING;
- if (state == android.telecom.Connection.STATE_DIALING) {
- if (isEmergency()) {
- mEmergencyTonePlayer.start();
+ if (mEmergencyTonePlayer != null) {
+ if (state == android.telecom.Connection.STATE_DIALING) {
+ if (isEmergency()) {
+ mEmergencyTonePlayer.start();
+ }
+ } else {
+ // No need to check if it is an emergency call, since it is a no-op if it
+ // isn't started.
+ mEmergencyTonePlayer.stop();
}
- } else {
- // No need to check if it is an emergency call, since it is a no-op if it isn't started.
- mEmergencyTonePlayer.stop();
}
super.onStateChanged(state);
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 19432a5..e7a02ec 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -19,12 +19,16 @@
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
+import android.telecom.Conference.Listener;
import android.telecom.Conference;
import android.telecom.ConferenceParticipant;
+import android.telecom.Connection.VideoProvider;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
+import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
import android.telecom.StatusHints;
+import android.telecom.VideoProfile;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
@@ -151,6 +155,28 @@
}
@Override
+ public void onVideoStateChanged(android.telecom.Connection c, int videoState) {
+ Log.d(this, "onVideoStateChanged video state %d", videoState);
+ setVideoState(c, videoState);
+ }
+
+ @Override
+ public void onVideoProviderChanged(android.telecom.Connection c,
+ Connection.VideoProvider videoProvider) {
+ Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
+ videoProvider);
+ setVideoProvider(c, videoProvider);
+ }
+
+ @Override
+ public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {
+ Log.d(this, "onCallCapabilitiesChanged: Connection: %s, callCapabilities: %s", c,
+ connectionCapabilities);
+ int capabilites = ImsConference.this.getConnectionCapabilities();
+ setConnectionCapabilities(applyVideoCapabilities(capabilites, connectionCapabilities));
+ }
+
+ @Override
public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
Log.v(this, "onStatusHintsChanged");
updateStatusHints();
@@ -186,7 +212,10 @@
public ImsConference(TelephonyConnectionService telephonyConnectionService,
TelephonyConnection conferenceHost) {
- super(null);
+ super((conferenceHost != null && conferenceHost.getCall() != null &&
+ conferenceHost.getCall().getPhone() != null) ?
+ PhoneUtils.makePstnPhoneAccountHandle(
+ conferenceHost.getCall().getPhone()) : null);
// Specify the connection time of the conference to be the connection time of the original
// connection.
@@ -194,18 +223,40 @@
mTelephonyConnectionService = telephonyConnectionService;
setConferenceHost(conferenceHost);
- if (conferenceHost != null && conferenceHost.getCall() != null
- && conferenceHost.getCall().getPhone() != null) {
- mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
- conferenceHost.getCall().getPhone());
- Log.v(this, "set phacc to " + mPhoneAccount);
+
+ int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
+ Connection.CAPABILITY_MUTE;
+
+ capabilities = applyVideoCapabilities(capabilities, mConferenceHost.getConnectionCapabilities());
+ setConnectionCapabilities(capabilities);
+
+ }
+
+ private int applyVideoCapabilities(int conferenceCapabilities, int capabilities) {
+ if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
+ conferenceCapabilities = applyCapability(conferenceCapabilities,
+ Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
+ } else {
+ conferenceCapabilities = removeCapability(conferenceCapabilities,
+ Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
}
- setConnectionCapabilities(
- Connection.CAPABILITY_SUPPORT_HOLD |
- Connection.CAPABILITY_HOLD |
- Connection.CAPABILITY_MUTE
- );
+ if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) {
+ conferenceCapabilities = applyCapability(conferenceCapabilities,
+ Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
+ } else {
+ conferenceCapabilities = removeCapability(conferenceCapabilities,
+ Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
+ }
+
+ if (can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
+ conferenceCapabilities = applyCapability(conferenceCapabilities,
+ Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
+ } else {
+ conferenceCapabilities = removeCapability(conferenceCapabilities,
+ Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
+ }
+ return conferenceCapabilities;
}
/**
@@ -219,6 +270,32 @@
}
/**
+ * Returns VideoProvider of the conference. This can be null.
+ *
+ * @hide
+ */
+ @Override
+ public VideoProvider getVideoProvider() {
+ if (mConferenceHost != null) {
+ return mConferenceHost.getVideoProvider();
+ }
+ return null;
+ }
+
+ /**
+ * Returns video state of conference
+ *
+ * @hide
+ */
+ @Override
+ public int getVideoState() {
+ if (mConferenceHost != null) {
+ return mConferenceHost.getVideoState();
+ }
+ return VideoProfile.VideoState.AUDIO_ONLY;
+ }
+
+ /**
* Invoked when the Conference and all its {@link Connection}s should be disconnected.
* <p>
* Hangs up the call via the conference host connection. When the host connection has been
@@ -331,6 +408,16 @@
// No-op
}
+ private int applyCapability(int capabilities, int capability) {
+ int newCapabilities = capabilities | capability;
+ return newCapabilities;
+ }
+
+ private int removeCapability(int capabilities, int capability) {
+ int newCapabilities = capabilities & ~capability;
+ return newCapabilities;
+ }
+
/**
* Determines if this conference is hosted on the current device or the peer device.
*
@@ -386,6 +473,7 @@
mConferenceHost = conferenceHost;
mConferenceHost.addConnectionListener(mConferenceHostListener);
mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
+ setState(mConferenceHost.getState());
updateStatusHints();
}
diff --git a/src/com/android/services/telephony/PstnPhoneCapabilitiesNotifier.java b/src/com/android/services/telephony/PstnPhoneCapabilitiesNotifier.java
new file mode 100644
index 0000000..7a2adf1
--- /dev/null
+++ b/src/com/android/services/telephony/PstnPhoneCapabilitiesNotifier.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.services.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.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.Preconditions;
+import com.android.phone.PhoneUtils;
+
+/**
+ * Listens to phone's capabilities changed event and notifies Telecomm. One instance of these exists
+ * for each of the telephony-based call services.
+ */
+final class PstnPhoneCapabilitiesNotifier {
+ private static final int EVENT_VIDEO_CAPABILITIES_CHANGED = 1;
+
+ /**
+ * Listener called when video capabilities have changed.
+ */
+ public interface Listener {
+ public void onVideoCapabilitiesChanged(boolean isVideoCapable);
+ }
+
+ private final PhoneProxy mPhoneProxy;
+ private final Listener mListener;
+ private Phone mPhoneBase;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_VIDEO_CAPABILITIES_CHANGED:
+ handleVideoCapabilitesChanged((AsyncResult) msg.obj);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private final BroadcastReceiver mRatReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED.equals(action)) {
+ String newPhone = intent.getStringExtra(PhoneConstants.PHONE_NAME_KEY);
+ Log.d(this, "Radio technology switched. Now %s is active.", newPhone);
+
+ registerForNotifications();
+ }
+ }
+ };
+
+ /*package*/
+ PstnPhoneCapabilitiesNotifier(PhoneProxy phoneProxy, Listener listener) {
+ Preconditions.checkNotNull(phoneProxy);
+
+ mPhoneProxy = phoneProxy;
+ mListener = listener;
+
+ registerForNotifications();
+
+ IntentFilter intentFilter =
+ new IntentFilter(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
+ mPhoneProxy.getContext().registerReceiver(mRatReceiver, intentFilter);
+ }
+
+ /*package*/
+ void teardown() {
+ unregisterForNotifications();
+ mPhoneProxy.getContext().unregisterReceiver(mRatReceiver);
+ }
+
+ private void registerForNotifications() {
+ Phone newPhone = mPhoneProxy.getActivePhone();
+ if (newPhone != mPhoneBase) {
+ unregisterForNotifications();
+
+ if (newPhone != null) {
+ Log.d(this, "Registering: " + newPhone);
+ mPhoneBase = newPhone;
+ mPhoneBase.registerForVideoCapabilityChanged(
+ mHandler, EVENT_VIDEO_CAPABILITIES_CHANGED, null);
+ }
+ }
+ }
+
+ private void unregisterForNotifications() {
+ if (mPhoneBase != null) {
+ Log.d(this, "Unregistering: " + mPhoneBase);
+ mPhoneBase.unregisterForVideoCapabilityChanged(mHandler);
+ }
+ }
+
+ private void handleVideoCapabilitesChanged(AsyncResult ar) {
+ try {
+ boolean isVideoCapable = (Boolean) ar.result;
+ Log.d(this, "handleVideoCapabilitesChanged. Video capability - " + isVideoCapable);
+ PhoneAccountHandle accountHandle =
+ PhoneUtils.makePstnPhoneAccountHandle(mPhoneProxy);
+
+ TelecomManager telecomMgr = TelecomManager.from(mPhoneProxy.getContext());
+ PhoneAccount oldPhoneAccount = telecomMgr.getPhoneAccount(accountHandle);
+ PhoneAccount.Builder builder = new PhoneAccount.Builder(oldPhoneAccount);
+
+ int capabilites = newCapabilities(oldPhoneAccount.getCapabilities(),
+ PhoneAccount.CAPABILITY_VIDEO_CALLING, isVideoCapable);
+
+ builder.setCapabilities(capabilites);
+ telecomMgr.registerPhoneAccount(builder.build());
+ mListener.onVideoCapabilitiesChanged(isVideoCapable);
+ } catch (Exception e) {
+ Log.d(this, "handleVideoCapabilitesChanged. Exception=" + e);
+ }
+ }
+
+ private int newCapabilities(int capabilities, int capability, boolean set) {
+ if (set) {
+ capabilities |= capability;
+ } else {
+ capabilities &= ~capability;
+ }
+ return capabilities;
+ }
+}
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 1cb6442..0a78180 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
@@ -53,10 +54,13 @@
// is not supported, i.e. SubscriptionManager.INVALID_SLOT_ID or the 5th SIM in a phone.
private final static int defaultPhoneAccountIcon = R.drawable.ic_multi_sim;
- private final class AccountEntry {
+ final class AccountEntry implements PstnPhoneCapabilitiesNotifier.Listener {
private final Phone mPhone;
private final PhoneAccount mAccount;
private final PstnIncomingCallNotifier mIncomingCallNotifier;
+ private final PstnPhoneCapabilitiesNotifier mPhoneCapabilitiesNotifier;
+ private boolean mIsVideoCapable;
+ private boolean mIsVideoPauseSupported;
AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
mPhone = phone;
@@ -64,10 +68,13 @@
Log.d(this, "Registered phoneAccount: %s with handle: %s",
mAccount, mAccount.getAccountHandle());
mIncomingCallNotifier = new PstnIncomingCallNotifier((PhoneProxy) mPhone);
+ mPhoneCapabilitiesNotifier = new PstnPhoneCapabilitiesNotifier((PhoneProxy) mPhone,
+ this);
}
void teardown() {
mIncomingCallNotifier.teardown();
+ mPhoneCapabilitiesNotifier.teardown();
}
/**
@@ -98,6 +105,11 @@
String description;
Bitmap iconBitmap = null;
+ // We can only get the real slotId from the SubInfoRecord, we can't calculate the
+ // slotId from the subId or the phoneId in all instances.
+ SubscriptionInfo record =
+ mSubscriptionManager.getActiveSubscriptionInfo(subId);
+
if (isEmergency) {
label = mContext.getResources().getString(R.string.sim_label_emergency_calls);
description =
@@ -108,10 +120,7 @@
description = label = mTelephonyManager.getNetworkOperatorName();
} else {
CharSequence subDisplayName = null;
- // We can only get the real slotId from the SubInfoRecord, we can't calculate the
- // slotId from the subId or the phoneId in all instances.
- SubscriptionInfo record =
- mSubscriptionManager.getActiveSubscriptionInfo(subId);
+
if (record != null) {
subDisplayName = record.getDisplayName();
slotId = record.getSimSlotIndex();
@@ -146,6 +155,14 @@
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS |
PhoneAccount.CAPABILITY_MULTI_USER;
+ mIsVideoCapable = mPhone.isVideoEnabled();
+ if (mIsVideoCapable) {
+ capabilities |= PhoneAccount.CAPABILITY_VIDEO_CALLING;
+ }
+ if (record != null) {
+ updateVideoPauseSupport(record);
+ }
+
if (iconBitmap == null) {
iconBitmap = BitmapFactory.decodeResource(
mContext.getResources(),
@@ -172,6 +189,56 @@
public PhoneAccountHandle getPhoneAccountHandle() {
return mAccount != null ? mAccount.getAccountHandle() : null;
}
+
+ /**
+ * Updates indicator for this {@link AccountEntry} to determine if the carrier supports
+ * pause/resume signalling for IMS video calls. The carrier setting is stored in MNC/MCC
+ * configuration files.
+ *
+ * @param subscriptionInfo The subscription info.
+ */
+ private void updateVideoPauseSupport(SubscriptionInfo subscriptionInfo) {
+ // Get the configuration for the MNC/MCC specified in the current subscription info.
+ Configuration configuration = new Configuration();
+ if (subscriptionInfo.getMcc() == 0 && subscriptionInfo.getMnc() == 0) {
+ Configuration config = mContext.getResources().getConfiguration();
+ configuration.mcc = config.mcc;
+ configuration.mnc = config.mnc;
+ Log.i(this, "updateVideoPauseSupport -- no mcc/mnc for sub: " + subscriptionInfo +
+ " using mcc/mnc from main context: " + configuration.mcc + "/" +
+ configuration.mnc);
+ } else {
+ Log.i(this, "updateVideoPauseSupport -- mcc/mnc for sub: " + subscriptionInfo);
+
+ configuration.mcc = subscriptionInfo.getMcc();
+ configuration.mnc = subscriptionInfo.getMnc();
+ }
+
+ // Load the MNC/MCC specific configuration.
+ Context subContext = mContext.createConfigurationContext(configuration);
+ mIsVideoPauseSupported = subContext.getResources().getBoolean(
+ R.bool.support_pause_ims_video_calls);
+ }
+
+ /**
+ * Receives callback from {@link PstnPhoneCapabilitiesNotifier} when the video capabilities
+ * have changed.
+ *
+ * @param isVideoCapable {@code true} if video is capable.
+ */
+ @Override
+ public void onVideoCapabilitiesChanged(boolean isVideoCapable) {
+ mIsVideoCapable = isVideoCapable;
+ }
+
+ /**
+ * Indicates whether this account supports pausing video calls.
+ * @return {@code true} if the account supports pausing video calls, {@code false}
+ * otherwise.
+ */
+ public boolean isVideoPauseSupported() {
+ return mIsVideoCapable && mIsVideoPauseSupported;
+ }
}
private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
@@ -231,6 +298,22 @@
}
/**
+ * Determines if the {@link AccountEntry} associated with a {@link PhoneAccountHandle} supports
+ * pausing video calls.
+ *
+ * @param handle The {@link PhoneAccountHandle}.
+ * @return {@code True} if video pausing is supported.
+ */
+ boolean isVideoPauseSupported(PhoneAccountHandle handle) {
+ for (AccountEntry entry : mAccounts) {
+ if (entry.getPhoneAccountHandle().equals(handle)) {
+ return entry.isVideoPauseSupported();
+ }
+ }
+ return false;
+ }
+
+ /**
* Sets up all the phone accounts for SIMs on first boot.
*/
void setupOnBoot() {
@@ -248,6 +331,7 @@
// because this could signal a removal or addition of a SIM in a single SIM phone.
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
}
+
/**
* Determines if the list of {@link AccountEntry}(s) contains an {@link AccountEntry} with a
* specified {@link PhoneAccountHandle}.
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index d6b2514..d72192d 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -204,6 +204,17 @@
}
/**
+ * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
+ * substate of the current call
+ *
+ * @param callSubstate The call substate.
+ */
+ @Override
+ public void onCallSubstateChanged(int callSubstate) {
+ setCallSubstate(callSubstate);
+ }
+
+ /*
* Handles a change to the multiparty state for this connection.
*
* @param isMultiParty {@code true} if the call became multiparty, {@code false}
@@ -259,6 +270,12 @@
private boolean mHasHighDefAudio;
/**
+ * For video calls, indicates whether the outgoing video for the call can be paused using
+ * the {@link android.telecom.VideoProfile.VideoState#PAUSED} VideoState.
+ */
+ private boolean mIsVideoPauseSupported;
+
+ /**
* Listeners to our TelephonyConnection specific callbacks
*/
private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
@@ -471,6 +488,13 @@
callCapabilities |= CAPABILITY_HOLD;
}
}
+
+ // If the phone is in ECM mode, mark the call to indicate that the callback number should be
+ // shown.
+ Phone phone = getPhone();
+ if (phone != null && phone.isInEcm()) {
+ callCapabilities |= CAPABILITY_SHOW_CALLBACK_NUMBER;
+ }
return callCapabilities;
}
@@ -478,12 +502,14 @@
int newCapabilities = buildConnectionCapabilities();
newCapabilities = changeCapability(newCapabilities,
- CAPABILITY_SUPPORTS_VT_REMOTE, mRemoteVideoCapable);
+ CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, mRemoteVideoCapable);
newCapabilities = changeCapability(newCapabilities,
- CAPABILITY_SUPPORTS_VT_LOCAL, mLocalVideoCapable);
+ CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, mLocalVideoCapable);
newCapabilities = changeCapability(newCapabilities,
CAPABILITY_HIGH_DEF_AUDIO, mHasHighDefAudio);
newCapabilities = changeCapability(newCapabilities, CAPABILITY_WIFI, mIsWifi);
+ newCapabilities = changeCapability(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO,
+ mIsVideoPauseSupported && mRemoteVideoCapable && mLocalVideoCapable);
newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
@@ -533,11 +559,13 @@
// Set video state and capabilities
setVideoState(mOriginalConnection.getVideoState());
+ updateState();
setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable());
setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable());
setWifi(mOriginalConnection.isWifi());
setVideoProvider(mOriginalConnection.getVideoProvider());
setAudioQuality(mOriginalConnection.getAudioQuality());
+ setCallSubstate(mOriginalConnection.getCallSubstate());
if (isImsConnection()) {
mWasImsConnection = true;
@@ -874,6 +902,16 @@
}
/**
+ * For video calls, sets whether this connection supports pausing the outgoing video for the
+ * call using the {@link android.telecom.VideoProfile.VideoState#PAUSED} VideoState.
+ *
+ * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise.
+ */
+ public void setVideoPauseSupported(boolean isVideoPauseSupported) {
+ mIsVideoPauseSupported = isVideoPauseSupported;
+ }
+
+ /**
* Whether the original connection is an IMS connection.
* @return {@code True} if the original connection is an IMS connection, {@code false}
* otherwise.
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 1fb70c0..eba3461 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -26,6 +26,7 @@
import android.telecom.PhoneAccountHandle;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -38,6 +39,7 @@
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.cdma.CDMAPhone;
import com.android.phone.MMIDialogActivity;
+import com.android.phone.PhoneUtils;
import java.util.ArrayList;
import java.util.List;
@@ -182,7 +184,7 @@
}
final TelephonyConnection connection =
- createConnectionFor(phone, null, true /* isOutgoing */);
+ createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle());
if (connection == null) {
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
@@ -255,9 +257,9 @@
}
Connection connection =
- createConnectionFor(phone, originalConnection, false /* isOutgoing */);
+ createConnectionFor(phone, originalConnection, false /* isOutgoing */,
+ request.getAccountHandle());
if (connection == null) {
- connection = Connection.createCanceledConnection();
return Connection.createCanceledConnection();
} else {
return connection;
@@ -305,7 +307,8 @@
TelephonyConnection connection =
createConnectionFor(phone, unknownConnection,
- !unknownConnection.isIncoming() /* isOutgoing */);
+ !unknownConnection.isIncoming() /* isOutgoing */,
+ request.getAccountHandle());
if (connection == null) {
return Connection.createCanceledConnection();
@@ -365,7 +368,8 @@
private TelephonyConnection createConnectionFor(
Phone phone,
com.android.internal.telephony.Connection originalConnection,
- boolean isOutgoing) {
+ boolean isOutgoing,
+ PhoneAccountHandle phoneAccountHandle) {
TelephonyConnection returnConnection = null;
int phoneType = phone.getPhoneType();
if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
@@ -378,6 +382,9 @@
if (returnConnection != null) {
// Listen to Telephony specific callbacks from the connection
returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener);
+ returnConnection.setVideoPauseSupported(
+ TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
+ phoneAccountHandle));
}
return returnConnection;
}
@@ -396,22 +403,14 @@
}
private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
- if (Objects.equals(mExpectedComponentName, accountHandle.getComponentName())) {
- if (accountHandle.getId() != null) {
- try {
- int phoneId = SubscriptionController.getInstance().getPhoneId(
- Integer.parseInt(accountHandle.getId()));
- return PhoneFactory.getPhone(phoneId);
- } catch (NumberFormatException e) {
- Log.w(this, "Could not get subId from account: " + accountHandle.getId());
- }
- }
+ if (isEmergency) {
+ return PhoneFactory.getDefaultPhone();
}
- if (isEmergency) {
- // If this is an emergency number and we've been asked to dial it using a PhoneAccount
- // which does not exist, then default to whatever subscription is available currently.
- return getFirstPhoneForEmergencyCall();
+ int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
+ if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+ return PhoneFactory.getPhone(phoneId);
}
return null;