Merge "IMS Bug Fixes" into nyc-mr1-dev
diff --git a/proguard.flags b/proguard.flags
index c4af490..41e26a1 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1 +1,6 @@
+# Keep classes and methods that have the guava @VisibleForTesting annotation
+-keep @**.VisibleForTesting class *
+-keepclassmembers class * {
+@**.VisibleForTesting *;
+}
-verbose
diff --git a/res/xml/vvm_config.xml b/res/xml/vvm_config.xml
new file mode 100644
index 0000000..5d120b8
--- /dev/null
+++ b/res/xml/vvm_config.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<list name="carrier_config_list">
+ <pbundle_as_map>
+ <!-- Orange France -->
+ <string-array name="mccmnc">
+ <item value="20801"/>
+ <item value="20802"/>
+ </string-array>
+
+ <int name="vvm_port_number_int" value="20481"/>
+ <string name="vvm_destination_number_string" value="21101"/>
+ <string-array name="carrier_vvm_package_name_string_array">
+ <item value="com.orange.vvm"/>
+ </string-array>
+ <string name="vvm_type_string" value="vvm_type_omtp"/>
+ <boolean name="vvm_cellular_data_required_bool" value="true"/>
+ </pbundle_as_map>
+
+ <pbundle_as_map>
+ <!-- Orange UK -->
+ <string-array name="mccmnc">
+ <item value="23433"/>
+ <item value="23434"/>
+ </string-array>
+
+ <int name="vvm_port_number_int" value="20481"/>
+ <string name="vvm_destination_number_string" value="881"/>
+ <string name="vvm_type_string" value="vvm_type_omtp"/>
+ </pbundle_as_map>
+
+ <pbundle_as_map>
+ <!-- T-Mobile USA-->
+ <string-array name="mccmnc">
+ <item value="310160"/>
+ <item value="310200"/>
+ <item value="310210"/>
+ <item value="310220"/>
+ <item value="310230"/>
+ <item value="310240"/>
+ <item value="310250"/>
+ <item value="310260"/>
+ <item value="310270"/>
+ <item value="310300"/>
+ <item value="310310"/>
+ <item value="310490"/>
+ <item value="310530"/>
+ <item value="310590"/>
+ <item value="310640"/>
+ <item value="310660"/>
+ <item value="310800"/>
+ </string-array>
+
+ <int name="vvm_port_number_int" value="1808"/>
+ <string name="vvm_destination_number_string" value="122"/>
+ <string-array name="carrier_vvm_package_name_string_array">
+ <item value="com.tmobile.vvm.application"/>
+ </string-array>
+ <string name="vvm_type_string" value="vvm_type_cvvm"/>
+ </pbundle_as_map>
+</list>
\ No newline at end of file
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 7738b77..9a9b6a8 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -3049,4 +3049,39 @@
return VoicemailNotificationSettingsUtil.isVibrationEnabled(phone);
}
+
+ /**
+ * Make sure either called from same process as self (phone) or IPC caller has read privilege.
+ *
+ * @throws SecurityException if the caller does not have the required permission
+ */
+ private void enforceReadPrivilegedPermission() {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ null);
+ }
+
+ /**
+ * Return the application ID for the app type.
+ *
+ * @param subId the subscription ID that this request applies to.
+ * @param appType the uicc app type.
+ * @return Application ID for specificied app type, or null if no uicc.
+ */
+ @Override
+ public String getAidForAppType(int subId, int appType) {
+ enforceReadPrivilegedPermission();
+ Phone phone = getPhone(subId);
+ if (phone == null) {
+ return null;
+ }
+ String aid = null;
+ try {
+ aid = UiccController.getInstance().getUiccCard(phone.getPhoneId())
+ .getApplicationByType(appType).getAid();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Not getting aid. Exception ex=" + e);
+ }
+ return aid;
+ }
+
}
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index aa62575..4dd7d0b 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -1853,10 +1853,14 @@
}
static boolean isInEmergencyCall(CallManager cm) {
- for (Connection cn : cm.getActiveFgCall().getConnections()) {
- if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(),
- cn.getAddress())) {
- return true;
+ Call fgCall = cm.getActiveFgCall();
+ // isIdle includes checks for the DISCONNECTING/DISCONNECTED state.
+ if(!fgCall.isIdle()) {
+ for (Connection cn : fgCall.getConnections()) {
+ if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(),
+ cn.getAddress())) {
+ return true;
+ }
}
}
return false;
@@ -2412,7 +2416,7 @@
// TODO: Should use some sort of special hidden flag to decorate this account as
// an emergency-only account
String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix +
- String.valueOf(phone.getIccSerialNumber());
+ String.valueOf(phone.getFullIccSerialNumber());
return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency);
}
@@ -2466,7 +2470,7 @@
private static Phone getPhoneFromIccId(String iccId) {
if (!TextUtils.isEmpty(iccId)) {
for (Phone phone : PhoneFactory.getPhones()) {
- String phoneIccId = phone.getIccSerialNumber();
+ String phoneIccId = phone.getFullIccSerialNumber();
if (iccId.equals(phoneIccId)) {
return phone;
}
diff --git a/src/com/android/phone/common/mail/MailTransport.java b/src/com/android/phone/common/mail/MailTransport.java
index f452bab..7d5cc20 100644
--- a/src/com/android/phone/common/mail/MailTransport.java
+++ b/src/com/android/phone/common/mail/MailTransport.java
@@ -32,7 +32,6 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
-import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
@@ -66,6 +65,7 @@
private BufferedOutputStream mOut;
private final int mFlags;
private SocketCreator mSocketCreator;
+ private InetSocketAddress mAddress;
public MailTransport(Context context, ImapHelper imapHelper, Network network, String address,
int port, int flags) {
@@ -126,29 +126,21 @@
while (socketAddresses.size() > 0) {
mSocket = createSocket();
try {
- InetSocketAddress address = socketAddresses.remove(0);
- mSocket.connect(address, SOCKET_CONNECT_TIMEOUT);
+ mAddress = socketAddresses.remove(0);
+ mSocket.connect(mAddress, SOCKET_CONNECT_TIMEOUT);
if (canTrySslSecurity()) {
- /**
- * {@link SSLSocket} must connect in its constructor, or create through a
- * already connected socket. Since we need to use
- * {@link Socket#connect(SocketAddress, int) } to set timeout, we can only
- * create it here.
+ /*
+ SSLSocket cannot be created with a connection timeout, so instead of doing a
+ direct SSL connection, we connect with a normal connection and upgrade it into
+ SSL
*/
- LogUtils.d(TAG, "open: converting to SSL socket");
- mSocket = HttpsURLConnection.getDefaultSSLSocketFactory()
- .createSocket(mSocket, address.getHostName(), address.getPort(), true);
- // After the socket connects to an SSL server, confirm that the hostname is as
- // expected
- if (!canTrustAllCertificates()) {
- verifyHostname(mSocket, mHost);
- }
+ reopenTls();
+ } else {
+ mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
+ mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
+ mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
}
-
- mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
- mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
- mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
success = true;
return;
} catch (IOException ioe) {
@@ -205,6 +197,32 @@
}
/**
+ * Attempts to reopen a normal connection into a TLS connection.
+ */
+ public void reopenTls() throws MessagingException {
+ try {
+ LogUtils.d(TAG, "open: converting to TLS socket");
+ mSocket = HttpsURLConnection.getDefaultSSLSocketFactory()
+ .createSocket(mSocket, mAddress.getHostName(), mAddress.getPort(), true);
+ // After the socket connects to an SSL server, confirm that the hostname is as
+ // expected
+ if (!canTrustAllCertificates()) {
+ verifyHostname(mSocket, mHost);
+ }
+ mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
+ mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
+ mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
+
+ } catch (SSLException e) {
+ LogUtils.d(TAG, e.toString());
+ throw new CertificateValidationException(e.getMessage(), e);
+ } catch (IOException ioe) {
+ LogUtils.d(TAG, ioe.toString());
+ throw new MessagingException(MessagingException.IOERROR, ioe.toString());
+ }
+ }
+
+ /**
* Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
* service but is not in the public API.
*
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
index 9207aa9..58f0f76 100644
--- a/src/com/android/phone/common/mail/store/ImapConnection.java
+++ b/src/com/android/phone/common/mail/store/ImapConnection.java
@@ -17,21 +17,23 @@
import android.provider.VoicemailContract.Status;
import android.text.TextUtils;
+import android.util.ArraySet;
import com.android.phone.common.mail.AuthenticationFailedException;
import com.android.phone.common.mail.CertificateValidationException;
import com.android.phone.common.mail.MailTransport;
import com.android.phone.common.mail.MessagingException;
+import com.android.phone.common.mail.store.ImapStore.ImapException;
import com.android.phone.common.mail.store.imap.ImapConstants;
import com.android.phone.common.mail.store.imap.ImapResponse;
import com.android.phone.common.mail.store.imap.ImapResponseParser;
import com.android.phone.common.mail.store.imap.ImapUtility;
import com.android.phone.common.mail.utils.LogUtils;
-import com.android.phone.common.mail.store.ImapStore.ImapException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLException;
@@ -46,6 +48,7 @@
private ImapStore mImapStore;
private MailTransport mTransport;
private ImapResponseParser mParser;
+ private Set<String> mCapabilities = new ArraySet<>();
static final String IMAP_REDACTED_LOG = "[IMAP command redacted]";
@@ -102,6 +105,22 @@
createParser();
+ // The server should greet us with something like
+ // * OK IMAP4rev1 Server
+ // consume the response before doing anything else.
+ ImapResponse response = mParser.readResponse();
+ if (!response.isOk()) {
+ mImapStore.getImapHelper()
+ .setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
+ throw new MessagingException(
+ MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR,
+ "Invalid server initial response");
+ }
+
+ queryCapability();
+
+ maybeDoStartTls();
+
// LOGIN
doLogin();
} catch (SSLException e) {
@@ -133,6 +152,21 @@
}
/**
+ * Attempts to convert the connection into secure connection.
+ */
+ private void maybeDoStartTls() throws IOException, MessagingException {
+ // STARTTLS is required in the OMTP standard but not every implementation support it.
+ // Make sure the server does have this capability
+ if (hasCapability(ImapConstants.CAPABILITY_STARTTLS)) {
+ executeSimpleCommand(ImapConstants.STARTTLS);
+ mTransport.reopenTls();
+ createParser();
+ // The cached capabilities should be refreshed after TLS is established.
+ queryCapability();
+ }
+ }
+
+ /**
* Logs into the IMAP server
*/
private void doLogin() throws IOException, MessagingException, AuthenticationFailedException {
@@ -157,6 +191,24 @@
}
}
+ private void queryCapability() throws IOException, MessagingException {
+ List<ImapResponse> responses = executeSimpleCommand(ImapConstants.CAPABILITY);
+ mCapabilities.clear();
+ for (ImapResponse response : responses) {
+ if (response.isTagged()) {
+ continue;
+ }
+ for (int i = 0; i < response.size(); i++) {
+ mCapabilities.add(response.getStringOrEmpty(i).getString());
+ }
+ }
+
+ LogUtils.d(TAG, "Capabilities: " + mCapabilities.toString());
+ }
+
+ private boolean hasCapability(String capability) {
+ return mCapabilities.contains(capability);
+ }
/**
* Create an {@link ImapResponseParser} from {@code mTransport.getInputStream()} and
* set it to {@link #mParser}.
diff --git a/src/com/android/phone/common/mail/store/imap/ImapConstants.java b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
index 63dda8c..a04d584 100644
--- a/src/com/android/phone/common/mail/store/imap/ImapConstants.java
+++ b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
@@ -109,4 +109,9 @@
public static final String EXPIRED = "EXPIRED";
public static final String AUTHENTICATIONFAILED = "AUTHENTICATIONFAILED";
public static final String UNAVAILABLE = "UNAVAILABLE";
+
+ /**
+ * capabilities
+ */
+ public static final String CAPABILITY_STARTTLS = "STARTTLS";
}
\ No newline at end of file
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 25c0f95..77de3f5 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -306,8 +306,8 @@
retval = isSim1 ? -1 : 1;
}
- int subId1 = PhoneUtils.getSubIdForPhoneAccount(account1);
- int subId2 = PhoneUtils.getSubIdForPhoneAccount(account2);
+ int subId1 = mTelephonyManager.getSubIdForPhoneAccount(account1);
+ int subId2 = mTelephonyManager.getSubIdForPhoneAccount(account2);
if (subId1 != SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
subId2 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
retval = (mSubscriptionManager.getSlotId(subId1) <
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 9534a10..81db684 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.phone.vvm.omtp;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.PersistableBundle;
@@ -23,47 +24,112 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.phone.vvm.omtp.sms.OmtpCvvmMessageSender;
import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
import com.android.phone.vvm.omtp.sms.OmtpStandardMessageSender;
+import java.util.Arrays;
+import java.util.Set;
+
/**
- * Handle activation and deactivation of a visual voicemail source. This class is necessary to
- * retrieve carrier vvm configuration details before sending the appropriate texts.
+ * Manages carrier dependent visual voicemail configuration values.
+ * The primary source is the value retrieved from CarrierConfigManager. If CarrierConfigManager does
+ * not provide the config (KEY_VVM_TYPE_STRING is empty, or "hidden" configs), then the value
+ * hardcoded in telephony will be used (in res/xml/vvm_config.xml)
+ *
+ * Hidden configs are new configs that are planned for future APIs, or miscellaneous settings that
+ * may clutter CarrierConfigManager too much.
+ *
+ * The current hidden configs are:
+ * {@link #getSslPort()}
+ * {@link #getDisabledCapabilities()}
*/
public class OmtpVvmCarrierConfigHelper {
private static final String TAG = "OmtpVvmCarrierCfgHlpr";
- private Context mContext;
- private int mSubId;
- private PersistableBundle mCarrierConfig;
- private String mVvmType;
+
+ static final String KEY_VVM_TYPE_STRING = CarrierConfigManager.KEY_VVM_TYPE_STRING;
+ static final String KEY_VVM_DESTINATION_NUMBER_STRING =
+ CarrierConfigManager.KEY_VVM_DESTINATION_NUMBER_STRING;
+ static final String KEY_VVM_PORT_NUMBER_INT =
+ CarrierConfigManager.KEY_VVM_PORT_NUMBER_INT;
+ static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING =
+ CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING;
+ static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY =
+ "carrier_vvm_package_name_string_array";
+ static final String KEY_VVM_PREFETCH_BOOL =
+ CarrierConfigManager.KEY_VVM_PREFETCH_BOOL;
+ static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL =
+ CarrierConfigManager.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL;
+ static final String KEY_VVM_SSL_PORT_NUMBER_INT =
+ "vvm_ssl_port_number_int";
+ static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY =
+ "vvm_disabled_capabilities_string_array";
+
+ private final Context mContext;
+ private final int mSubId;
+ private final PersistableBundle mCarrierConfig;
+ private final String mVvmType;
+
+ private final PersistableBundle mTelephonyConfig;
public OmtpVvmCarrierConfigHelper(Context context, int subId) {
mContext = context;
mSubId = subId;
mCarrierConfig = getCarrierConfig();
+
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mTelephonyConfig = new TelephonyVvmConfigManager(context.getResources())
+ .getConfig(telephonyManager.getNetworkOperator(subId));
+
mVvmType = getVvmType();
}
- public String getVvmType() {
- if (mCarrierConfig == null) {
- return null;
- }
-
- return mCarrierConfig.getString(
- CarrierConfigManager.KEY_VVM_TYPE_STRING, null);
+ @VisibleForTesting
+ OmtpVvmCarrierConfigHelper(PersistableBundle carrierConfig,
+ PersistableBundle telephonyConfig) {
+ mContext = null;
+ mSubId = 0;
+ mCarrierConfig = carrierConfig;
+ mTelephonyConfig = telephonyConfig;
+ mVvmType = getVvmType();
}
- public String getCarrierVvmPackageName() {
- if (mCarrierConfig == null) {
+ @Nullable
+ public String getVvmType() {
+ return (String) getValue(KEY_VVM_TYPE_STRING);
+ }
+
+ @Nullable
+ public Set<String> getCarrierVvmPackageNames() {
+ Set<String> names = getCarrierVvmPackageNames(mCarrierConfig);
+ if (names != null) {
+ return names;
+ }
+ return getCarrierVvmPackageNames(mTelephonyConfig);
+ }
+
+ private static Set<String> getCarrierVvmPackageNames(@Nullable PersistableBundle bundle) {
+ if (bundle == null) {
return null;
}
-
- return mCarrierConfig.getString(
- CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING, null);
+ Set<String> names = new ArraySet<>();
+ if (bundle.containsKey(KEY_CARRIER_VVM_PACKAGE_NAME_STRING)) {
+ names.add(bundle.getString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING));
+ }
+ if (bundle.containsKey(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY)) {
+ names.addAll(Arrays.asList(
+ bundle.getStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY)));
+ }
+ if (names.isEmpty()) {
+ return null;
+ }
+ return names;
}
public boolean isOmtpVvmType() {
@@ -76,33 +142,83 @@
* so by checking if the carrier's voicemail app is installed.
*/
public boolean isEnabledByDefault() {
- String packageName = mCarrierConfig.getString(
- CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING);
- if (packageName == null) {
- return true;
+ for (String packageName : getCarrierVvmPackageNames()) {
+ try {
+ mContext.getPackageManager().getPackageInfo(packageName, 0);
+ return false;
+ } catch (NameNotFoundException e) {
+ // Do nothing.
+ }
}
- try {
- mContext.getPackageManager().getPackageInfo(packageName, 0);
- return false;
- } catch (NameNotFoundException e) {
- return true;
- }
+ return true;
}
public boolean isCellularDataRequired() {
- if (mCarrierConfig == null) {
- return false;
- }
- return mCarrierConfig
- .getBoolean(CarrierConfigManager.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL);
+ return (boolean) getValue(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL);
}
public boolean isPrefetchEnabled() {
- if (mCarrierConfig == null) {
- return false;
+ return (boolean) getValue(KEY_VVM_PREFETCH_BOOL);
+ }
+
+
+ public int getApplicationPort() {
+ Integer port = (Integer) getValue(KEY_VVM_PORT_NUMBER_INT);
+ if (port != null) {
+ return port;
}
- return mCarrierConfig
- .getBoolean(CarrierConfigManager.KEY_VVM_PREFETCH_BOOL);
+ return 0;
+ }
+
+ @Nullable
+ public String getDestinationNumber() {
+ return (String) getValue(KEY_VVM_DESTINATION_NUMBER_STRING);
+ }
+
+ /**
+ * Hidden config.
+ *
+ * @return Port to start a SSL IMAP connection directly.
+ *
+ * TODO: make config public and add to CarrierConfigManager
+ */
+ @VisibleForTesting // TODO: remove after method used.
+ public int getSslPort() {
+ Integer port = (Integer) getValue(KEY_VVM_SSL_PORT_NUMBER_INT);
+ if (port != null) {
+ return port;
+ }
+ return 0;
+ }
+
+ /**
+ * Hidden Config.
+ *
+ * @return A set of capabilities that is reported by the IMAP CAPABILITY command, but determined
+ * to have issues and should not be used.
+ */
+ @VisibleForTesting // TODO: remove after method used.
+ @Nullable
+ public Set<String> getDisabledCapabilities() {
+ Set<String> disabledCapabilities = getDisabledCapabilities(mCarrierConfig);
+ if (disabledCapabilities != null) {
+ return disabledCapabilities;
+ }
+ return getDisabledCapabilities(mTelephonyConfig);
+ }
+
+ @Nullable
+ private static Set<String> getDisabledCapabilities(@Nullable PersistableBundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ if (!bundle.containsKey(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY)) {
+ return null;
+ }
+ ArraySet<String> result = new ArraySet<String>();
+ result.addAll(
+ Arrays.asList(bundle.getStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY)));
+ return result;
}
public void startActivation() {
@@ -121,6 +237,7 @@
}
}
+ @Nullable
private PersistableBundle getCarrierConfig() {
if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
Log.w(TAG, "Invalid subscriptionId or subscriptionId not provided in intent.");
@@ -134,19 +251,23 @@
return null;
}
- return carrierConfigManager.getConfigForSubId(mSubId);
+ PersistableBundle config = carrierConfigManager.getConfigForSubId(mSubId);
+
+ if (TextUtils.isEmpty(config.getString(CarrierConfigManager.KEY_VVM_TYPE_STRING))) {
+ Log.w(TAG, "Carrier config missing VVM type, ignoring.");
+ return null;
+ }
+ return config;
}
private OmtpMessageSender getMessageSender() {
- if (mCarrierConfig == null) {
+ if (mCarrierConfig == null && mTelephonyConfig == null) {
Log.w(TAG, "Empty carrier config.");
return null;
}
- int applicationPort = mCarrierConfig.getInt(
- CarrierConfigManager.KEY_VVM_PORT_NUMBER_INT, 0);
- String destinationNumber = mCarrierConfig.getString(
- CarrierConfigManager.KEY_VVM_DESTINATION_NUMBER_STRING);
+ int applicationPort = getApplicationPort();
+ String destinationNumber = getDestinationNumber();
if (TextUtils.isEmpty(destinationNumber)) {
Log.w(TAG, "No destination number for this carrier.");
return null;
@@ -169,4 +290,22 @@
return messageSender;
}
+
+ @Nullable
+ private Object getValue(String key) {
+ Object result;
+ if (mCarrierConfig != null) {
+ result = mCarrierConfig.get(key);
+ if (result != null) {
+ return result;
+ }
+ }
+ if (mTelephonyConfig != null) {
+ result = mTelephonyConfig.get(key);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java b/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java
new file mode 100644
index 0000000..3a1967f
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.annotation.Nullable;
+import android.content.res.Resources;
+import android.os.PersistableBundle;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.R;
+import com.android.phone.vvm.omtp.utils.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Load and caches telephony vvm config from res/xml/vvm_config.xml
+ */
+public class TelephonyVvmConfigManager {
+
+ private static final String TAG = "TelephonyVvmCfgMgr";
+
+ private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
+
+ static final String KEY_MCCMNC = "mccmnc";
+
+ private static Map<String, PersistableBundle> sCachedConfigs;
+
+ private final Map<String, PersistableBundle> mConfigs;
+
+ public TelephonyVvmConfigManager(Resources resources) {
+ if (sCachedConfigs == null) {
+ sCachedConfigs = loadConfigs(resources.getXml(R.xml.vvm_config));
+ }
+ mConfigs = sCachedConfigs;
+ }
+
+ @VisibleForTesting
+ TelephonyVvmConfigManager(XmlPullParser parser) {
+ mConfigs = loadConfigs(parser);
+ }
+
+ @Nullable
+ public PersistableBundle getConfig(String mccMnc) {
+ return mConfigs.get(mccMnc);
+ }
+
+ private static Map<String, PersistableBundle> loadConfigs(XmlPullParser parser) {
+ Map<String, PersistableBundle> configs = new ArrayMap<>();
+ try {
+ ArrayList list = readBundleList(parser);
+ for (Object object : list) {
+ if (!(object instanceof PersistableBundle)) {
+ throw new IllegalArgumentException("PersistableBundle expected, got " + object);
+ }
+ PersistableBundle bundle = (PersistableBundle) object;
+ String[] mccMncs = bundle.getStringArray(KEY_MCCMNC);
+ if (mccMncs == null) {
+ throw new IllegalArgumentException("MCCMNC is null");
+ }
+ for (String mccMnc : mccMncs) {
+ configs.put(mccMnc, bundle);
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ throw new RuntimeException(e);
+ }
+ return configs;
+ }
+
+ @Nullable
+ public static ArrayList readBundleList(XmlPullParser in) throws IOException,
+ XmlPullParserException {
+ final int outerDepth = in.getDepth();
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+ if (event == XmlPullParser.START_TAG) {
+ final String startTag = in.getName();
+ final String[] tagName = new String[1];
+ in.next();
+ return XmlUtils.readThisListXml(in, startTag, tagName,
+ new MyReadMapCallback(), false);
+ }
+ }
+ return null;
+ }
+
+ public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
+ XmlPullParserException {
+ final int outerDepth = in.getDepth();
+ final String startTag = in.getName();
+ final String[] tagName = new String[1];
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+ if (event == XmlPullParser.START_TAG) {
+ ArrayMap<String, ?> map =
+ XmlUtils.readThisArrayMapXml(in, startTag, tagName,
+ new MyReadMapCallback());
+ PersistableBundle result = new PersistableBundle();
+ for (Entry<String, ?> entry : map.entrySet()) {
+ Object value = entry.getValue();
+ if (value instanceof Integer) {
+ result.putInt(entry.getKey(), (int) value);
+ } else if (value instanceof Boolean) {
+ result.putBoolean(entry.getKey(), (boolean) value);
+ } else if (value instanceof String) {
+ result.putString(entry.getKey(), (String) value);
+ } else if (value instanceof String[]) {
+ result.putStringArray(entry.getKey(), (String[]) value);
+ } else if (value instanceof PersistableBundle) {
+ result.putPersistableBundle(entry.getKey(), (PersistableBundle) value);
+ }
+ }
+ return result;
+ }
+ }
+ return PersistableBundle.EMPTY;
+ }
+
+ static class MyReadMapCallback implements XmlUtils.ReadMapCallback {
+
+ @Override
+ public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
+ throws XmlPullParserException, IOException {
+ if (TAG_PERSISTABLEMAP.equals(tag)) {
+ return restoreFromXml(in);
+ }
+ throw new XmlPullParserException("Unknown tag=" + tag);
+ }
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java b/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java
index 0c4eb62..1767c6b 100644
--- a/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java
+++ b/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java
@@ -52,7 +52,10 @@
OmtpVvmCarrierConfigHelper carrierConfigHelper = new OmtpVvmCarrierConfigHelper(
context, PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount));
- if (packageName.equals(carrierConfigHelper.getCarrierVvmPackageName())) {
+ if (carrierConfigHelper.getCarrierVvmPackageNames() == null) {
+ continue;
+ }
+ if (carrierConfigHelper.getCarrierVvmPackageNames().contains(packageName)) {
VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(
context, phoneAccount, false, false);
OmtpVvmSourceManager.getInstance(context).removeSource(phoneAccount);
diff --git a/src/com/android/phone/vvm/omtp/utils/XmlUtils.java b/src/com/android/phone/vvm/omtp/utils/XmlUtils.java
new file mode 100644
index 0000000..4eeb5ce
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/utils/XmlUtils.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.utils;
+
+import android.util.ArrayMap;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class XmlUtils {
+
+ public static final ArrayMap<String, ?> readThisArrayMapXml(XmlPullParser parser, String endTag,
+ String[] name, ReadMapCallback callback)
+ throws XmlPullParserException, java.io.IOException {
+ ArrayMap<String, Object> map = new ArrayMap<>();
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ Object val = readThisValueXml(parser, name, callback, true);
+ map.put(name[0], val);
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return map;
+ }
+ throw new XmlPullParserException(
+ "Expected " + endTag + " end tag at: " + parser.getName());
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ throw new XmlPullParserException(
+ "Document ended before " + endTag + " end tag");
+ }
+
+ /**
+ * Read an ArrayList object from an XmlPullParser. The XML data could previously have been
+ * generated by writeListXml(). The XmlPullParser must be positioned <em>after</em> the tag
+ * that begins the list.
+ *
+ * @param parser The XmlPullParser from which to read the list data.
+ * @param endTag Name of the tag that will end the list, usually "list".
+ * @param name An array of one string, used to return the name attribute of the list's tag.
+ * @return HashMap The newly generated list.
+ */
+ public static final ArrayList readThisListXml(XmlPullParser parser, String endTag,
+ String[] name, ReadMapCallback callback, boolean arrayMap)
+ throws XmlPullParserException, java.io.IOException {
+ ArrayList list = new ArrayList();
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ Object val = readThisValueXml(parser, name, callback, arrayMap);
+ list.add(val);
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return list;
+ }
+ throw new XmlPullParserException(
+ "Expected " + endTag + " end tag at: " + parser.getName());
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ throw new XmlPullParserException(
+ "Document ended before " + endTag + " end tag");
+ }
+
+ /**
+ * Read a String[] object from an XmlPullParser. The XML data could previously have been
+ * generated by writeStringArrayXml(). The XmlPullParser must be positioned <em>after</em> the
+ * tag that begins the list.
+ *
+ * @param parser The XmlPullParser from which to read the list data.
+ * @param endTag Name of the tag that will end the list, usually "string-array".
+ * @param name An array of one string, used to return the name attribute of the list's tag.
+ * @return Returns a newly generated String[].
+ */
+ public static String[] readThisStringArrayXml(XmlPullParser parser, String endTag,
+ String[] name) throws XmlPullParserException, java.io.IOException {
+
+ parser.next();
+
+ List<String> array = new ArrayList<>();
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("item")) {
+ try {
+ array.add(parser.getAttributeValue(null, "value"));
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException("Need value attribute in item");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException("Not a number in value attribute in item");
+ }
+ } else {
+ throw new XmlPullParserException("Expected item tag at: " + parser.getName());
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return array.toArray(new String[0]);
+ } else if (parser.getName().equals("item")) {
+
+ } else {
+ throw new XmlPullParserException("Expected " + endTag + " end tag at: " +
+ parser.getName());
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ throw new XmlPullParserException("Document ended before " + endTag + " end tag");
+ }
+
+ private static Object readThisValueXml(XmlPullParser parser, String[] name,
+ ReadMapCallback callback, boolean arrayMap)
+ throws XmlPullParserException, java.io.IOException {
+ final String valueName = parser.getAttributeValue(null, "name");
+ final String tagName = parser.getName();
+
+ Object res;
+
+ if (tagName.equals("null")) {
+ res = null;
+ } else if (tagName.equals("string")) {
+ String value = "";
+ int eventType;
+ while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("string")) {
+ name[0] = valueName;
+ return value;
+ }
+ throw new XmlPullParserException(
+ "Unexpected end tag in <string>: " + parser.getName());
+ } else if (eventType == XmlPullParser.TEXT) {
+ value += parser.getText();
+ } else if (eventType == XmlPullParser.START_TAG) {
+ throw new XmlPullParserException(
+ "Unexpected start tag in <string>: " + parser.getName());
+ }
+ }
+ throw new XmlPullParserException(
+ "Unexpected end of document in <string>");
+ } else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) {
+ // all work already done by readThisPrimitiveValueXml
+ } else if (tagName.equals("string-array")) {
+ res = readThisStringArrayXml(parser, "string-array", name);
+ name[0] = valueName;
+ return res;
+ } else if (tagName.equals("list")) {
+ parser.next();
+ res = readThisListXml(parser, "list", name, callback, arrayMap);
+ name[0] = valueName;
+ return res;
+ } else if (callback != null) {
+ res = callback.readThisUnknownObjectXml(parser, tagName);
+ name[0] = valueName;
+ return res;
+ } else {
+ throw new XmlPullParserException("Unknown tag: " + tagName);
+ }
+
+ // Skip through to end tag.
+ int eventType;
+ while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(tagName)) {
+ name[0] = valueName;
+ return res;
+ }
+ throw new XmlPullParserException(
+ "Unexpected end tag in <" + tagName + ">: " + parser.getName());
+ } else if (eventType == XmlPullParser.TEXT) {
+ throw new XmlPullParserException(
+ "Unexpected text in <" + tagName + ">: " + parser.getName());
+ } else if (eventType == XmlPullParser.START_TAG) {
+ throw new XmlPullParserException(
+ "Unexpected start tag in <" + tagName + ">: " + parser.getName());
+ }
+ }
+ throw new XmlPullParserException(
+ "Unexpected end of document in <" + tagName + ">");
+ }
+
+ private static final Object readThisPrimitiveValueXml(XmlPullParser parser, String tagName)
+ throws XmlPullParserException, java.io.IOException {
+ try {
+ if (tagName.equals("int")) {
+ return Integer.parseInt(parser.getAttributeValue(null, "value"));
+ } else if (tagName.equals("long")) {
+ return Long.valueOf(parser.getAttributeValue(null, "value"));
+ } else if (tagName.equals("float")) {
+ return Float.valueOf(parser.getAttributeValue(null, "value"));
+ } else if (tagName.equals("double")) {
+ return Double.valueOf(parser.getAttributeValue(null, "value"));
+ } else if (tagName.equals("boolean")) {
+ return Boolean.valueOf(parser.getAttributeValue(null, "value"));
+ } else {
+ return null;
+ }
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException("Need value attribute in <" + tagName + ">");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException(
+ "Not a number in value attribute in <" + tagName + ">");
+ }
+ }
+
+ public interface ReadMapCallback {
+
+ /**
+ * Called from readThisMapXml when a START_TAG is not recognized. The input stream is
+ * positioned within the start tag so that attributes can be read using in.getAttribute.
+ *
+ * @param in the XML input stream
+ * @param tag the START_TAG that was not recognized.
+ * @return the Object parsed from the stream which will be put into the map.
+ * @throws XmlPullParserException if the START_TAG is not recognized.
+ * @throws IOException on XmlPullParser serialization errors.
+ */
+ Object readThisUnknownObjectXml(XmlPullParser in, String tag)
+ throws XmlPullParserException, IOException;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 3ed8356..19b1d8a 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -71,6 +71,7 @@
private boolean mIsVideoPresenceSupported;
private boolean mIsVideoPauseSupported;
private boolean mIsMergeCallSupported;
+ private boolean mIsVideoConferencingSupported;
AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
mPhone = phone;
@@ -189,6 +190,7 @@
instantLetteringExtras = getPhoneAccountExtras();
}
mIsMergeCallSupported = isCarrierMergeCallSupported();
+ mIsVideoConferencingSupported = isCarrierVideoConferencingSupported();
if (isEmergency && mContext.getResources().getBoolean(
R.bool.config_emergency_account_emergency_calls_only)) {
@@ -244,7 +246,8 @@
// Check if IMS video pause is supported.
PersistableBundle b =
PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
- return b.getBoolean(CarrierConfigManager.KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL);
+ return b != null &&
+ b.getBoolean(CarrierConfigManager.KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL);
}
/**
@@ -256,7 +259,8 @@
private boolean isCarrierVideoPresenceSupported() {
PersistableBundle b =
PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
- return b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
+ return b != null &&
+ b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
}
/**
@@ -267,7 +271,8 @@
private boolean isCarrierInstantLetteringSupported() {
PersistableBundle b =
PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
- return b.getBoolean(CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL);
+ return b != null &&
+ b.getBoolean(CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL);
}
/**
@@ -278,7 +283,8 @@
private boolean isCarrierMergeCallSupported() {
PersistableBundle b =
PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
- return b.getBoolean(CarrierConfigManager.KEY_SUPPORT_CONFERENCE_CALL_BOOL);
+ return b != null &&
+ b.getBoolean(CarrierConfigManager.KEY_SUPPORT_CONFERENCE_CALL_BOOL);
}
/**
@@ -289,7 +295,20 @@
private boolean isCarrierEmergencyVideoCallsAllowed() {
PersistableBundle b =
PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
- return b.getBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL);
+ return b != null &&
+ b.getBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL);
+ }
+
+ /**
+ * Determines from carrier config whether video conferencing is supported.
+ *
+ * @return {@code true} if video conferencing is supported, {@code false} otherwise.
+ */
+ private boolean isCarrierVideoConferencingSupported() {
+ PersistableBundle b =
+ PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+ return b != null &&
+ b.getBoolean(CarrierConfigManager.KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL);
}
/**
@@ -339,6 +358,14 @@
public boolean isMergeCallSupported() {
return mIsMergeCallSupported;
}
+
+ /**
+ * Indicates whether this account supports video conferencing.
+ * @return {@code true} if the account supports video conferencing, {@code false} otherwise.
+ */
+ public boolean isVideoConferencingSupported() {
+ return mIsVideoConferencingSupported;
+ }
}
private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
@@ -430,6 +457,22 @@
}
/**
+ * Determines if the {@link AccountEntry} associated with a {@link PhoneAccountHandle} supports
+ * video conferencing.
+ *
+ * @param handle The {@link PhoneAccountHandle}.
+ * @return {@code True} if video conferencing is supported.
+ */
+ boolean isVideoConferencingSupported(PhoneAccountHandle handle) {
+ for (AccountEntry entry : mAccounts) {
+ if (entry.getPhoneAccountHandle().equals(handle)) {
+ return entry.isVideoConferencingSupported();
+ }
+ }
+ return false;
+ }
+
+ /**
* @return Reference to the {@code TelecomAccountRegistry}'s subscription manager.
*/
SubscriptionManager getSubscriptionManager() {
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 40e4ef9..c1d54d8 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -296,7 +296,7 @@
final TelephonyConnection connection =
createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(),
- request.getTelecomCallId(), request.getAddress());
+ request.getTelecomCallId(), request.getAddress(), request.getVideoState());
if (connection == null) {
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
@@ -378,10 +378,15 @@
return Connection.createCanceledConnection();
}
+ // We should rely on the originalConnection to get the video state. The request coming
+ // from Telecom does not know the video state of the incoming call.
+ int videoState = originalConnection != null ? originalConnection.getVideoState() :
+ VideoProfile.STATE_AUDIO_ONLY;
+
Connection connection =
createConnectionFor(phone, originalConnection, false /* isOutgoing */,
request.getAccountHandle(), request.getTelecomCallId(),
- request.getAddress());
+ request.getAddress(), videoState);
if (connection == null) {
return Connection.createCanceledConnection();
} else {
@@ -478,11 +483,16 @@
return Connection.createCanceledConnection();
}
+ // We should rely on the originalConnection to get the video state. The request coming
+ // from Telecom does not know the video state of the unknown call.
+ int videoState = unknownConnection != null ? unknownConnection.getVideoState() :
+ VideoProfile.STATE_AUDIO_ONLY;
+
TelephonyConnection connection =
createConnectionFor(phone, unknownConnection,
!unknownConnection.isIncoming() /* isOutgoing */,
request.getAccountHandle(), request.getTelecomCallId(),
- request.getAddress());
+ request.getAddress(), videoState);
if (connection == null) {
return Connection.createCanceledConnection();
@@ -546,7 +556,8 @@
boolean isOutgoing,
PhoneAccountHandle phoneAccountHandle,
String telecomCallId,
- Uri address) {
+ Uri address,
+ int videoState) {
TelephonyConnection returnConnection = null;
int phoneType = phone.getPhoneType();
if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
@@ -564,9 +575,13 @@
phoneAccountHandle));
boolean isEmergencyCall = (address != null && PhoneNumberUtils.isEmergencyNumber(
address.getSchemeSpecificPart()));
- returnConnection.setConferenceSupported(!isEmergencyCall
- && TelecomAccountRegistry.getInstance(this).isMergeCallSupported(
- phoneAccountHandle));
+ boolean isVideoCall = VideoProfile.isVideo(videoState);
+ boolean isConferencingSupported = TelecomAccountRegistry.getInstance(this)
+ .isMergeCallSupported(phoneAccountHandle);
+ boolean isVideoConferencingSupported = TelecomAccountRegistry.getInstance(this)
+ .isVideoConferencingSupported(phoneAccountHandle);
+ returnConnection.setConferenceSupported(!isEmergencyCall && isConferencingSupported
+ && (!isVideoCall || (isVideoCall && isVideoConferencingSupported)));
}
return returnConnection;
}
diff --git a/tests/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelperTest.java b/tests/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelperTest.java
new file mode 100644
index 0000000..63c7f60
--- /dev/null
+++ b/tests/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelperTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_CARRIER_VVM_PACKAGE_NAME_STRING;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_DESTINATION_NUMBER_STRING;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_PORT_NUMBER_INT;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_PREFETCH_BOOL;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_SSL_PORT_NUMBER_INT;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_TYPE_STRING;
+
+import android.os.PersistableBundle;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class OmtpVvmCarrierConfigHelperTest extends TestCase {
+
+ private static final String CARRIER_TYPE = "omtp.carrier";
+ private static final String CARRIER_PACKAGE_NAME = "omtp.carrier.package";
+ private static final boolean CARRIER_CELLULAR_REQUIRED = false;
+ private static final boolean CARRIER_PREFETCH = true;
+ private static final String CARRIER_DESTINATION_NUMBER = "123";
+ private static final int CARRIER_APPLICATION_PORT = 456;
+ private static final int DEFAULT_SSL_PORT = 0;
+ private static final Set<String> DEFAULT_DISABLED_CAPABILITIES = null;
+
+ private static final String TELEPHONY_TYPE = "omtp.telephony";
+ private static final String[] TELEPHONY_PACKAGE_NAMES = {"omtp.telephony.package"};
+ private static final boolean TELEPHONY_CELLULAR_REQUIRED = true;
+ private static final boolean TELEPHONY_PREFETCH = false;
+ private static final String TELEPHONY_DESTINATION_NUMBER = "321";
+ private static final int TELEPHONY_APPLICATION_PORT = 654;
+ private static final int TELEPHONY_SSL_PORT = 997;
+ private static final String[] TELEPHONY_DISABLED_CAPABILITIES = {"foo"};
+
+ private OmtpVvmCarrierConfigHelper mHelper;
+
+ public void testCarrierConfig() {
+ mHelper = new OmtpVvmCarrierConfigHelper(createCarrierConfig(), null);
+ verifyCarrierConfig();
+ verifyDefaultExtraConfig();
+ }
+
+ public void testTelephonyConfig() {
+ mHelper = new OmtpVvmCarrierConfigHelper(null, createTelephonyConfig());
+ verifyTelephonyConfig();
+ verifyTelephonyExtraConfig();
+ }
+
+ public void testMixedConfig() {
+ mHelper = new OmtpVvmCarrierConfigHelper(createCarrierConfig(), createTelephonyConfig());
+ verifyCarrierConfig();
+ verifyTelephonyExtraConfig();
+ }
+
+ private PersistableBundle createCarrierConfig() {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putString(KEY_VVM_TYPE_STRING, CARRIER_TYPE);
+ bundle.putString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING,
+ CARRIER_PACKAGE_NAME);
+ bundle.putBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL,
+ CARRIER_CELLULAR_REQUIRED);
+ bundle.putBoolean(KEY_VVM_PREFETCH_BOOL,
+ CARRIER_PREFETCH);
+ bundle.putString(KEY_VVM_DESTINATION_NUMBER_STRING,
+ CARRIER_DESTINATION_NUMBER);
+ bundle.putInt(KEY_VVM_PORT_NUMBER_INT, CARRIER_APPLICATION_PORT);
+ return bundle;
+ }
+
+ private void verifyCarrierConfig() {
+ assertEquals(CARRIER_TYPE, mHelper.getVvmType());
+ assertEquals(new HashSet<>(Arrays.asList(CARRIER_PACKAGE_NAME)),
+ mHelper.getCarrierVvmPackageNames());
+ assertEquals(CARRIER_CELLULAR_REQUIRED, mHelper.isCellularDataRequired());
+ assertEquals(CARRIER_PREFETCH, mHelper.isPrefetchEnabled());
+ assertEquals(CARRIER_APPLICATION_PORT, mHelper.getApplicationPort());
+ assertEquals(CARRIER_DESTINATION_NUMBER, mHelper.getDestinationNumber());
+ }
+
+
+ private void verifyDefaultExtraConfig() {
+ assertEquals(DEFAULT_SSL_PORT, mHelper.getSslPort());
+ assertEquals(DEFAULT_DISABLED_CAPABILITIES, mHelper.getDisabledCapabilities());
+ }
+
+
+ private PersistableBundle createTelephonyConfig() {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putString(KEY_VVM_TYPE_STRING, TELEPHONY_TYPE);
+ bundle.putStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY,
+ TELEPHONY_PACKAGE_NAMES);
+ bundle.putBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL,
+ TELEPHONY_CELLULAR_REQUIRED);
+ bundle.putBoolean(KEY_VVM_PREFETCH_BOOL,
+ TELEPHONY_PREFETCH);
+ bundle.putString(KEY_VVM_DESTINATION_NUMBER_STRING,
+ TELEPHONY_DESTINATION_NUMBER);
+ bundle.putInt(KEY_VVM_PORT_NUMBER_INT, TELEPHONY_APPLICATION_PORT);
+ bundle.putInt(KEY_VVM_SSL_PORT_NUMBER_INT, TELEPHONY_SSL_PORT);
+ bundle.putStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY,
+ TELEPHONY_DISABLED_CAPABILITIES);
+ return bundle;
+ }
+
+ private void verifyTelephonyConfig() {
+ assertEquals(TELEPHONY_TYPE, mHelper.getVvmType());
+ assertEquals(new HashSet<>(Arrays.asList(TELEPHONY_PACKAGE_NAMES)),
+ mHelper.getCarrierVvmPackageNames());
+ assertEquals(TELEPHONY_CELLULAR_REQUIRED, mHelper.isCellularDataRequired());
+ assertEquals(TELEPHONY_PREFETCH, mHelper.isPrefetchEnabled());
+ assertEquals(TELEPHONY_APPLICATION_PORT, mHelper.getApplicationPort());
+ assertEquals(TELEPHONY_DESTINATION_NUMBER, mHelper.getDestinationNumber());
+ }
+
+ private void verifyTelephonyExtraConfig() {
+ assertEquals(TELEPHONY_SSL_PORT, mHelper.getSslPort());
+ assertEquals(new HashSet<>(Arrays.asList(TELEPHONY_DISABLED_CAPABILITIES)),
+ mHelper.getDisabledCapabilities());
+ }
+}
diff --git a/tests/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManagerTest.java b/tests/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManagerTest.java
new file mode 100644
index 0000000..8e7a0da
--- /dev/null
+++ b/tests/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManagerTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_DESTINATION_NUMBER_STRING;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_PORT_NUMBER_INT;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_PREFETCH_BOOL;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_SSL_PORT_NUMBER_INT;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_TYPE_STRING;
+
+import android.os.PersistableBundle;
+
+import junit.framework.TestCase;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.StringReader;
+import java.util.Arrays;
+
+public class TelephonyVvmConfigManagerTest extends TestCase {
+
+ private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<list name=\"carrier_config_list\">\n";
+ private static final String XML_FOOTER = "</list>";
+
+ private static final String CARRIER = " <pbundle_as_map>\n"
+ + " <string-array name=\"mccmnc\">\n"
+ + " <item value=\"12345\"/>\n"
+ + " <item value=\"67890\"/>\n"
+ + " </string-array>\n"
+ + " <int name=\"vvm_port_number_int\" value=\"54321\"/>\n"
+ + " <string name=\"vvm_destination_number_string\">11111</string>\n"
+ + " <string-array name=\"carrier_vvm_package_name_string_array\">\n"
+ + " <item value=\"com.android.phone\"/>\n"
+ + " </string-array>\n"
+ + " <string name=\"vvm_type_string\">vvm_type_omtp</string>\n"
+ + " <boolean name=\"vvm_cellular_data_required\" value=\"true\"/>\n"
+ + " <boolean name=\"vvm_prefetch\" value=\"true\"/>\n"
+ + " <int name=\"vvm_ssl_port_number_int\" value=\"997\"/>\n"
+ + " <string-array name=\"vvm_disabled_capabilities_string_array\">\n"
+ + " <item value =\"foo\"/>\n"
+ + " <item value =\"bar\"/>\n"
+ + " </string-array>\n"
+ + " </pbundle_as_map>\n";
+
+ private static final String CARRIER_EMPTY = "<pbundle_as_map></pbundle_as_map>\n";
+
+
+ public void testLoadConfigFromXml() {
+ TelephonyVvmConfigManager manager = createManager(XML_HEADER + CARRIER + XML_FOOTER);
+ verifyCarrier(manager.getConfig("12345"));
+ verifyCarrier(manager.getConfig("67890"));
+ }
+
+ public void testLoadConfigFromXml_Multiple() {
+ TelephonyVvmConfigManager manager =
+ createManager(XML_HEADER + CARRIER + CARRIER + XML_FOOTER);
+ verifyCarrier(manager.getConfig("12345"));
+ verifyCarrier(manager.getConfig("67890"));
+ }
+
+ public void testLoadConfigFromXml_Empty() {
+ createManager(XML_HEADER + CARRIER_EMPTY + XML_FOOTER);
+ }
+
+
+ private void verifyCarrier(PersistableBundle config) {
+ assertTrue(Arrays.equals(new String[]{"12345", "67890"},
+ config.getStringArray(TelephonyVvmConfigManager.KEY_MCCMNC)));
+ assertEquals(54321, config.getInt(KEY_VVM_PORT_NUMBER_INT));
+ assertEquals("11111", config.getString(KEY_VVM_DESTINATION_NUMBER_STRING));
+ assertTrue(Arrays.equals(new String[]{"com.android.phone"},
+ config.getStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY)));
+ assertEquals("vvm_type_omtp", config.getString(KEY_VVM_TYPE_STRING));
+ assertEquals(true, config.getBoolean(KEY_VVM_PREFETCH_BOOL));
+ assertEquals(true, config.getBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL));
+ assertEquals(997, config.getInt(KEY_VVM_SSL_PORT_NUMBER_INT));
+ assertTrue(Arrays.equals(new String[]{"foo", "bar"},
+ config.getStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY)));
+ }
+
+ private TelephonyVvmConfigManager createManager(String xml) {
+ try {
+ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+ parser.setInput(new StringReader(xml));
+ return new TelephonyVvmConfigManager(parser);
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}