Iterate through Phones when routing Emergency Call
am: 0159eeb32c
* commit '0159eeb32c48ecb17856d54f193deb2f04323285':
Iterate through Phones when routing Emergency Call
Change-Id: I8993a736e2371f45bbfb04f63a75b516271edfbf
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9655756..781bd40 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -655,8 +655,7 @@
<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" />
+ <action android:name="android.intent.action.VOICEMAIL_SMS_RECEIVED"/>
</intent-filter>
</receiver>
<receiver
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..57a1050
--- /dev/null
+++ b/res/xml/vvm_config.xml
@@ -0,0 +1,92 @@
+<?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>
+ <!-- Test -->
+ <string-array name="mccmnc">
+ <item value="TEST"/>
+ </string-array>
+
+ <int name="vvm_port_number_int" value="20481"/>
+ <string name="vvm_destination_number_string">887</string>
+ <string name="vvm_type_string">vvm_type_omtp</string>
+ <boolean name="vvm_cellular_data_required_bool" value="true"/>
+ </pbundle_as_map>
+
+ <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">21101</string>
+ <string-array name="carrier_vvm_package_name_string_array">
+ <item value="com.orange.vvm"/>
+ </string-array>
+ <string name="vvm_type_string">vvm_type_omtp</string>
+ <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">881</string>
+ <string name="vvm_type_string">vvm_type_omtp</string>
+ </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"/>
+ <int name="vvm_ssl_port_number_int" value="993"/>
+ <string name="vvm_destination_number_string">122</string>
+ <string-array name="carrier_vvm_package_name_string_array">
+ <item value="com.tmobile.vvm.application"/>
+ </string-array>
+ <string name="vvm_type_string">vvm_type_cvvm</string>>
+ <string-array name="vvm_disabled_capabilities_string_array">
+ <!-- b/28717550 -->
+ <item value="AUTH=DIGEST-MD5"/>
+ </string-array>
+ </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..acffb79 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -16,6 +16,8 @@
package com.android.phone;
+import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
+
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
@@ -44,13 +46,13 @@
import android.telephony.CarrierConfigManager;
import android.telephony.CellInfo;
import android.telephony.IccOpenLogicalChannelResponse;
+import android.telephony.ModemActivityInfo;
import android.telephony.NeighboringCellInfo;
import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.telephony.ModemActivityInfo;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -67,9 +69,9 @@
import com.android.internal.telephony.MccTable;
import com.android.internal.telephony.OperatorInfo;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.ProxyController;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.uicc.IccIoResult;
@@ -79,8 +81,6 @@
import com.android.internal.util.HexDump;
import com.android.phone.settings.VoicemailNotificationSettingsUtil;
-import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -1827,6 +1827,56 @@
return success;
}
+ @Override
+ public void setVisualVoicemailSmsFilterEnabled(int subId, boolean value) {
+ VisualVoicemailSmsFilterConfig
+ .setVisualVoicemailSmsFilterEnabled(mPhone.getContext(), subId, value);
+ }
+
+ @Override
+ public boolean isVisualVoicemailSmsFilterEnabled(String packageName, int subId) {
+ return VisualVoicemailSmsFilterConfig
+ .isVisualVoicemailSmsFilterEnabled(mPhone.getContext(), packageName, subId);
+ }
+
+ @Override
+ public void setVisualVoicemailSmsFilterClientPrefix(int subId, String prefix) {
+ VisualVoicemailSmsFilterConfig
+ .setVisualVoicemailSmsFilterClientPrefix(mPhone.getContext(), subId, prefix);
+ }
+
+ @Override
+ public String getVisualVoicemailSmsFilterClientPrefix(String packageName, int subId) {
+ return VisualVoicemailSmsFilterConfig
+ .getVisualVoicemailSmsFilterClientPrefix(mPhone.getContext(), packageName, subId);
+ }
+
+ @Override
+ public void setVisualVoicemailSmsFilterOriginatingNumbers(int subId, String[] numbers) {
+ VisualVoicemailSmsFilterConfig
+ .setVisualVoicemailSmsFilterOriginatingNumbers(mPhone.getContext(), subId, numbers);
+ }
+
+ @Override
+ public String[] getVisualVoicemailSmsFilterOriginatingNumbers(String packageName, int subId) {
+ return VisualVoicemailSmsFilterConfig
+ .getVisualVoicemailSmsFilterOriginatingNumbers(mPhone.getContext(), packageName,
+ subId);
+ }
+
+ @Override
+ public void setVisualVoicemailSmsFilterDestinationPort(int subId, int port) {
+ VisualVoicemailSmsFilterConfig
+ .setVisualVoicemailSmsFilterDestinationPort(mPhone.getContext(), subId, port);
+ }
+
+ @Override
+ public int getVisualVoicemailSmsFilterDestinationPort(String packageName, int subId) {
+ return VisualVoicemailSmsFilterConfig
+ .getVisualVoicemailSmsFilterDestinationPort(mPhone.getContext(), packageName,
+ subId);
+ }
+
/**
* Returns the unread count of voicemails
*/
@@ -3049,4 +3099,62 @@
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;
+ }
+
+
+ /**
+ * Return the Electronic Serial Number.
+ *
+ * @param subId the subscription ID that this request applies to.
+ * @return ESN or null if error.
+ */
+ @Override
+ public String getEsn(int subId) {
+ enforceReadPrivilegedPermission();
+ Phone phone = getPhone(subId);
+ if (phone == null) {
+ return null;
+ }
+ String esn = null;
+ try {
+ esn = phone.getEsn();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Not getting ESN. Exception ex=" + e);
+ }
+ return esn;
+ }
+
}
diff --git a/src/com/android/phone/VisualVoicemailSmsFilterConfig.java b/src/com/android/phone/VisualVoicemailSmsFilterConfig.java
new file mode 100644
index 0000000..e0139ee
--- /dev/null
+++ b/src/com/android/phone/VisualVoicemailSmsFilterConfig.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Stores the config values needed for visual voicemail sms filtering. The values from
+ * OmtpVvmCarrierConfigHelper are stored here during activation instead. These values are read and
+ * written through TelephonyManager.
+ */
+public class VisualVoicemailSmsFilterConfig {
+
+ private static final String VVM_SMS_FILTER_COFIG_SHARED_PREFS_KEY_PREFIX =
+ "vvm_sms_filter_config_";
+ private static final String ENABLED_KEY = "_enabled";
+ private static final String PREFIX_KEY = "_prefix";
+ private static final String ORIGINATING_NUMBERS_KEY = "_originating_numbers";
+ private static final String DESTINATION_PORT_KEY = "_destination_port";
+
+ public static void setVisualVoicemailSmsFilterEnabled(Context context, int subId,
+ boolean value) {
+ setBoolean(context, subId, ENABLED_KEY, value);
+ }
+
+ public static boolean isVisualVoicemailSmsFilterEnabled(Context context, String packageName,
+ int subId) {
+ return getBoolean(context, packageName, subId, ENABLED_KEY);
+ }
+
+ public static void setVisualVoicemailSmsFilterClientPrefix(Context context, int subId,
+ String prefix) {
+ setString(context, subId, PREFIX_KEY, prefix);
+ }
+
+ public static String getVisualVoicemailSmsFilterClientPrefix(Context context,
+ String packageName, int subId) {
+ return getString(context, packageName, subId, PREFIX_KEY);
+ }
+
+ public static void setVisualVoicemailSmsFilterOriginatingNumbers(Context context, int subId,
+ String[] numbers) {
+ ArraySet<String> set = new ArraySet<>();
+ set.addAll(Arrays.asList(numbers));
+ setStringSet(context, subId, ORIGINATING_NUMBERS_KEY, set);
+ }
+
+ public static String[] getVisualVoicemailSmsFilterOriginatingNumbers(Context context,
+ String packageName, int subId) {
+ Set<String> numbers = getStringSet(context, packageName, subId, ORIGINATING_NUMBERS_KEY);
+ return numbers.toArray(new String[numbers.size()]);
+ }
+
+ public static void setVisualVoicemailSmsFilterDestinationPort(Context context, int subId,
+ int port) {
+ setInt(context, subId, DESTINATION_PORT_KEY, port);
+ }
+
+ public static int getVisualVoicemailSmsFilterDestinationPort(Context context,
+ String packageName, int subId) {
+ return getInt(context, packageName, subId, DESTINATION_PORT_KEY,
+ TelephonyManager.VVM_SMS_FILTER_DESTINATION_PORT_ANY);
+ }
+
+ private static int getInt(Context context, String packageName, int subId, String key,
+ int defaultValue) {
+ SharedPreferences prefs = getSharedPreferences(context);
+ return prefs.getInt(makePerPhoneAccountKey(packageName, subId, key), defaultValue);
+ }
+
+ private static void setInt(Context context, int subId, String key, int value) {
+ SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+ editor.putInt(makePerPhoneAccountKey(context.getOpPackageName(), subId, key), value);
+ editor.apply();
+ }
+
+ private static boolean getBoolean(Context context, String packageName, int subId, String key) {
+ SharedPreferences prefs = getSharedPreferences(context);
+ return prefs.getBoolean(makePerPhoneAccountKey(packageName, subId, key), false);
+ }
+
+ private static void setBoolean(Context context, int subId, String key, boolean value) {
+ SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+ editor.putBoolean(makePerPhoneAccountKey(context.getOpPackageName(), subId, key), value);
+ editor.apply();
+ }
+
+ private static String getString(Context context, String packageName, int subId, String key) {
+ SharedPreferences prefs = getSharedPreferences(context);
+ return prefs.getString(makePerPhoneAccountKey(packageName, subId, key), null);
+ }
+
+ private static void setString(Context context, int subId, String key, String value) {
+ SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+ editor.putString(makePerPhoneAccountKey(context.getOpPackageName(), subId, key), value);
+ editor.apply();
+ }
+
+ private static Set<String> getStringSet(Context context, String packageName, int subId,
+ String key) {
+ return getSharedPreferences(context)
+ .getStringSet(makePerPhoneAccountKey(packageName, subId, key), null);
+ }
+
+ private static void setStringSet(Context context, int subId, String key, Set<String> value) {
+ SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+ editor.putStringSet(makePerPhoneAccountKey(context.getOpPackageName(), subId, key), value);
+ editor.apply();
+ }
+
+ private static SharedPreferences getSharedPreferences(Context context) {
+ return PreferenceManager
+ .getDefaultSharedPreferences(context.createDeviceProtectedStorageContext());
+ }
+
+ private static String makePerPhoneAccountKey(String packageName, int subId, String key) {
+ // TODO: make sure subId is persistent enough to serve as a key
+ return VVM_SMS_FILTER_COFIG_SHARED_PREFS_KEY_PREFIX + packageName + "_"
+ + subId + key;
+ }
+}
diff --git a/src/com/android/phone/common/mail/MailTransport.java b/src/com/android/phone/common/mail/MailTransport.java
index f452bab..cc09044 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.
*
@@ -272,6 +290,10 @@
mSocket = null;
}
+ public String getHost() {
+ return mHost;
+ }
+
public InputStream getInputStream() {
return mIn;
}
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
index 9207aa9..914ab10 100644
--- a/src/com/android/phone/common/mail/store/ImapConnection.java
+++ b/src/com/android/phone/common/mail/store/ImapConnection.java
@@ -17,21 +17,26 @@
import android.provider.VoicemailContract.Status;
import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Base64;
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.DigestMd5Utils;
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.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLException;
@@ -46,6 +51,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 +108,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,11 +155,30 @@
}
/**
+ * 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 {
try {
- executeSimpleCommand(getLoginPhrase(), true);
+ if (mCapabilities.contains(ImapConstants.CAPABILITY_AUTH_DIGEST_MD5)) {
+ doDigestMd5Auth();
+ } else {
+ executeSimpleCommand(getLoginPhrase(), true);
+ }
} catch (ImapException ie) {
LogUtils.d(TAG, "ImapException", ie);
final String status = ie.getStatus();
@@ -157,6 +198,80 @@
}
}
+ private void doDigestMd5Auth() throws IOException, MessagingException {
+
+ // Initiate the authentication.
+ // The server will issue us a challenge, asking to run MD5 on the nonce with our password
+ // and other data, including the cnonce we randomly generated.
+ //
+ // C: a AUTHENTICATE DIGEST-MD5
+ // S: (BASE64) realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
+ // algorithm=md5-sess,charset=utf-8
+ List<ImapResponse> responses = executeSimpleCommand(
+ ImapConstants.AUTHENTICATE + " " + ImapConstants.AUTH_DIGEST_MD5);
+ String decodedChallenge = decodeBase64(responses.get(0).getStringOrEmpty(0).getString());
+
+ Map<String, String> challenge = DigestMd5Utils.parseDigestMessage(decodedChallenge);
+ DigestMd5Utils.Data data = new DigestMd5Utils.Data(mImapStore, mTransport, challenge);
+
+ String response = data.createResponse();
+ // Respond to the challenge. If the server accepts it, it will reply a response-auth which
+ // is the MD5 of our password and the cnonce we've provided, to prove the server does know
+ // the password.
+ //
+ // C: (BASE64) charset=utf-8,username="chris",realm="elwood.innosoft.com",
+ // nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",
+ // digest-uri="imap/elwood.innosoft.com",
+ // response=d388dad90d4bbd760a152321f2143af7,qop=auth
+ // S: (BASE64) rspauth=ea40f60335c427b5527b84dbabcdfffd
+
+ responses = executeContinuationResponse(encodeBase64(response), true);
+
+ // Verify response-auth.
+ // If failed verifyResponseAuth() will throw a MessagingException, terminating the
+ // connection
+ String decodedResponseAuth = decodeBase64(responses.get(0).getStringOrEmpty(0).getString());
+ data.verifyResponseAuth(decodedResponseAuth);
+
+ // Send a empty response to indicate we've accepted the response-auth
+ //
+ // C: (empty)
+ // S: a OK User logged in
+ executeContinuationResponse("", false);
+
+ }
+
+ private static String decodeBase64(String string) {
+ return new String(Base64.decode(string, Base64.DEFAULT));
+ }
+
+ private static String encodeBase64(String string) {
+ return Base64.encodeToString(string.getBytes(), Base64.NO_WRAP);
+ }
+
+ private void queryCapability() throws IOException, MessagingException {
+ List<ImapResponse> responses = executeSimpleCommand(ImapConstants.CAPABILITY);
+ mCapabilities.clear();
+ Set<String> disabledCapabilities = mImapStore.getImapHelper().getConfig()
+ .getDisabledCapabilities();
+ for (ImapResponse response : responses) {
+ if (response.isTagged()) {
+ continue;
+ }
+ for (int i = 0; i < response.size(); i++) {
+ String capability = response.getStringOrEmpty(i).getString();
+ if (disabledCapabilities != null && !disabledCapabilities.contains(capability)) {
+ mCapabilities.add(capability);
+ }
+ }
+ }
+
+ 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}.
@@ -218,6 +333,12 @@
return tag;
}
+ List<ImapResponse> executeContinuationResponse(String response, boolean sensitive)
+ throws IOException, MessagingException {
+ mTransport.writeLine(response, (sensitive ? IMAP_REDACTED_LOG : response));
+ return getCommandResponses();
+ }
+
/**
* Read and return all of the responses from the most recent command sent to the server
*
@@ -231,9 +352,9 @@
do {
response = mParser.readResponse();
responses.add(response);
- } while (!response.isTagged());
+ } while (!(response.isTagged() || response.isContinuationRequest()));
- if (!response.isOk()) {
+ if (!(response.isOk() || response.isContinuationRequest())) {
final String toString = response.toString();
final String status = response.getStatusOrEmpty().getString();
final String alert = response.getAlertTextOrEmpty().getString();
diff --git a/src/com/android/phone/common/mail/store/imap/DigestMd5Utils.java b/src/com/android/phone/common/mail/store/imap/DigestMd5Utils.java
new file mode 100644
index 0000000..e6376a3
--- /dev/null
+++ b/src/com/android/phone/common/mail/store/imap/DigestMd5Utils.java
@@ -0,0 +1,333 @@
+/*
+ * 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.common.mail.store.imap;
+
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.common.mail.MailTransport;
+import com.android.phone.common.mail.MessagingException;
+import com.android.phone.common.mail.store.ImapStore;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Map;
+
+public class DigestMd5Utils {
+
+ private static final String TAG = "DigestMd5Utils";
+
+ private static final String DIGEST_CHARSET = "CHARSET";
+ private static final String DIGEST_USERNAME = "username";
+ private static final String DIGEST_REALM = "realm";
+ private static final String DIGEST_NONCE = "nonce";
+ private static final String DIGEST_NC = "nc";
+ private static final String DIGEST_CNONCE = "cnonce";
+ private static final String DIGEST_URI = "digest-uri";
+ private static final String DIGEST_RESPONSE = "response";
+ private static final String DIGEST_QOP = "qop";
+
+ private static final String RESPONSE_AUTH_HEADER = "rspauth=";
+ private static final String HEX_CHARS = "0123456789abcdef";
+
+ /**
+ * Represents the set of data we need to generate the DIGEST-MD5 response.
+ */
+ public static class Data {
+
+ private static final String CHARSET = "utf-8'";
+
+ public String username;
+ public String password;
+ public String realm;
+ public String nonce;
+ public String nc;
+ public String cnonce;
+ public String digestUri;
+ public String qop;
+
+ @VisibleForTesting
+ Data() {
+ // Do nothing
+ }
+
+ public Data(ImapStore imapStore, MailTransport transport, Map<String, String> challenge) {
+ username = imapStore.getUsername();
+ password = imapStore.getPassword();
+ realm = challenge.getOrDefault(DIGEST_REALM, "");
+ nonce = challenge.get(DIGEST_NONCE);
+ cnonce = createCnonce();
+ nc = "00000001"; // Subsequent Authentication not supported, nounce count always 1.
+ qop = "auth"; // Other config not supported
+ digestUri = "imap/" + transport.getHost();
+ }
+
+ private static String createCnonce() {
+ SecureRandom generator = new SecureRandom();
+
+ // At least 64 bits of entropy is required
+ byte[] rawBytes = new byte[8];
+ generator.nextBytes(rawBytes);
+
+ return Base64.encodeToString(rawBytes, Base64.NO_WRAP);
+ }
+
+ /**
+ * Verify the response-auth returned by the server is correct.
+ */
+ public void verifyResponseAuth(String response)
+ throws MessagingException {
+ if (!response.startsWith(RESPONSE_AUTH_HEADER)) {
+ throw new MessagingException("response-auth expected");
+ }
+ if (!response.substring(RESPONSE_AUTH_HEADER.length())
+ .equals(DigestMd5Utils.getResponse(this, true))) {
+ throw new MessagingException("invalid response-auth return from the server.");
+ }
+ }
+
+ public String createResponse() {
+ String response = getResponse(this, false);
+ ResponseBuilder builder = new ResponseBuilder();
+ builder
+ .append(DIGEST_CHARSET, CHARSET)
+ .appendQuoted(DIGEST_USERNAME, username)
+ .appendQuoted(DIGEST_REALM, realm)
+ .appendQuoted(DIGEST_NONCE, nonce)
+ .append(DIGEST_NC, nc)
+ .appendQuoted(DIGEST_CNONCE, cnonce)
+ .appendQuoted(DIGEST_URI, digestUri)
+ .append(DIGEST_RESPONSE, response)
+ .append(DIGEST_QOP, qop);
+ return builder.toString();
+ }
+
+ private static class ResponseBuilder {
+
+ private StringBuilder mBuilder = new StringBuilder();
+
+ public ResponseBuilder appendQuoted(String key, String value) {
+ if (mBuilder.length() != 0) {
+ mBuilder.append(",");
+ }
+ mBuilder.append(key).append("=\"").append(value).append("\"");
+ return this;
+ }
+
+ public ResponseBuilder append(String key, String value) {
+ if (mBuilder.length() != 0) {
+ mBuilder.append(",");
+ }
+ mBuilder.append(key).append("=").append(value);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return mBuilder.toString();
+ }
+ }
+ }
+
+ /*
+ response-value =
+ toHex( getKeyDigest ( toHex(getMd5(a1)),
+ { nonce-value, ":" nc-value, ":",
+ cnonce-value, ":", qop-value, ":", toHex(getMd5(a2)) }))
+ * @param isResponseAuth is the response the one the server is returning us. response-auth has
+ * different a2 format.
+ */
+ @VisibleForTesting
+ static String getResponse(Data data, boolean isResponseAuth) {
+ StringBuilder a1 = new StringBuilder();
+ a1.append(new String(
+ getMd5(data.username + ":" + data.realm + ":" + data.password),
+ StandardCharsets.ISO_8859_1));
+ a1.append(":").append(data.nonce).append(":").append(data.cnonce);
+
+ StringBuilder a2 = new StringBuilder();
+ if (!isResponseAuth) {
+ a2.append("AUTHENTICATE");
+ }
+ a2.append(":").append(data.digestUri);
+
+ return toHex(getKeyDigest(
+ toHex(getMd5(a1.toString())),
+ data.nonce + ":" + data.nc + ":" + data.cnonce + ":" + data.qop + ":" + toHex(
+ getMd5(a2.toString()))
+ ));
+ }
+
+ /**
+ * Let getMd5(s) be the 16 octet MD5 hash [RFC 1321] of the octet string s.
+ */
+ private static byte[] getMd5(String s) {
+ try {
+ MessageDigest digester = MessageDigest.getInstance("MD5");
+ digester.update(s.getBytes(StandardCharsets.ISO_8859_1));
+ return digester.digest();
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Let getKeyDigest(k, s) be getMd5({k, ":", s}), i.e., the 16 octet hash of the string k, a colon and the
+ * string s.
+ */
+ private static byte[] getKeyDigest(String k, String s) {
+ StringBuilder builder = new StringBuilder(k).append(":").append(s);
+ return getMd5(builder.toString());
+ }
+
+ /**
+ * Let toHex(n) be the representation of the 16 octet MD5 hash n as a string of 32 hex digits
+ * (with alphabetic characters always in lower case, since MD5 is case sensitive).
+ */
+ private static String toHex(byte[] n) {
+ StringBuilder result = new StringBuilder();
+ for (byte b : n) {
+ int unsignedByte = b & 0xFF;
+ result.append(HEX_CHARS.charAt(unsignedByte / 16))
+ .append(HEX_CHARS.charAt(unsignedByte % 16));
+ }
+ return result.toString();
+ }
+
+ public static Map<String, String> parseDigestMessage(String message) throws MessagingException {
+ Map<String, String> result = new DigestMessageParser(message).parse();
+ if (!result.containsKey(DIGEST_NONCE)) {
+ throw new MessagingException("nonce missing from server DIGEST-MD5 challenge");
+ }
+ return result;
+ }
+
+ /**
+ * Parse the key-value pair returned by the server.
+ */
+ private static class DigestMessageParser {
+
+ private final String mMessage;
+ private int mPosition = 0;
+ private Map<String, String> mResult = new ArrayMap<>();
+
+ public DigestMessageParser(String message) {
+ mMessage = message;
+ }
+
+ @Nullable
+ public Map<String, String> parse() {
+ try {
+ while (mPosition < mMessage.length()) {
+ parsePair();
+ if (mPosition != mMessage.length()) {
+ expect(',');
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(TAG, e.toString());
+ return null;
+ }
+ return mResult;
+ }
+
+ private void parsePair() {
+ String key = parseKey();
+ expect('=');
+ String value = parseValue();
+ mResult.put(key, value);
+ }
+
+ private void expect(char c) {
+ if (pop() != c) {
+ throw new IllegalStateException(
+ "unexpected character " + mMessage.charAt(mPosition));
+ }
+ }
+
+ private char pop() {
+ char result = peek();
+ mPosition++;
+ return result;
+ }
+
+ private char peek() {
+ return mMessage.charAt(mPosition);
+ }
+
+ private void goToNext(char c) {
+ while (peek() != c) {
+ mPosition++;
+ }
+ }
+
+ private String parseKey() {
+ int start = mPosition;
+ goToNext('=');
+ return mMessage.substring(start, mPosition);
+ }
+
+ private String parseValue() {
+ if (peek() == '"') {
+ return parseQuotedValue();
+ } else {
+ return parseUnquotedValue();
+ }
+ }
+
+ private String parseQuotedValue() {
+ expect('"');
+ StringBuilder result = new StringBuilder();
+ while (true) {
+ char c = pop();
+ if (c == '\\') {
+ result.append(pop());
+ } else if (c == '"') {
+ break;
+ } else {
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+
+ private String parseUnquotedValue() {
+ StringBuilder result = new StringBuilder();
+ while (true) {
+ char c = pop();
+ if (c == '\\') {
+ result.append(pop());
+ } else if (c == ',') {
+ mPosition--;
+ break;
+ } else {
+ result.append(c);
+ }
+
+ if (mPosition == mMessage.length()) {
+ break;
+ }
+ }
+ return result.toString();
+ }
+ }
+}
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..9e6e247 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,15 @@
public static final String EXPIRED = "EXPIRED";
public static final String AUTHENTICATIONFAILED = "AUTHENTICATIONFAILED";
public static final String UNAVAILABLE = "UNAVAILABLE";
+
+ /**
+ * capabilities
+ */
+ public static final String CAPABILITY_AUTH_DIGEST_MD5 = "AUTH=DIGEST-MD5";
+ public static final String CAPABILITY_STARTTLS = "STARTTLS";
+
+ /**
+ * authentication
+ */
+ public static final String AUTH_DIGEST_MD5 = "DIGEST-MD5";
}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/OmtpConstants.java b/src/com/android/phone/vvm/omtp/OmtpConstants.java
index fa3cb63..5fc0317 100644
--- a/src/com/android/phone/vvm/omtp/OmtpConstants.java
+++ b/src/com/android/phone/vvm/omtp/OmtpConstants.java
@@ -28,9 +28,8 @@
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:";
+ public static final String SYNC_SMS_PREFIX = "SYNC";
+ public static final String STATUS_SMS_PREFIX = "STATUS";
// This is the format designated by the OMTP spec.
public static final String DATE_TIME_FORMAT = "dd/MM/yyyy HH:mm Z";
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 9534a10..df706d5 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,125 @@
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;
+
+ /**
+ * @see #getSslPort()
+ */
+ static final String KEY_VVM_SSL_PORT_NUMBER_INT =
+ "vvm_ssl_port_number_int";
+
+ /**
+ * Ban a capability reported by the server from being used. The array of string should be a
+ * subset of the capabilities returned IMAP CAPABILITY command.
+ *
+ * @see #getDisabledCapabilities()
+ */
+ static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY =
+ "vvm_disabled_capabilities_string_array";
+ static final String KEY_VVM_CLIENT_PREFIX_STRING =
+ "vvm_client_prefix_string";
+
+ 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,36 +155,104 @@
* 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) {
+ Set<String> carrierPackages = getCarrierVvmPackageNames();
+ if (carrierPackages == null) {
return true;
}
- try {
- mContext.getPackageManager().getPackageInfo(packageName, 0);
- return false;
- } catch (NameNotFoundException e) {
- return true;
+ for (String packageName : carrierPackages) {
+ try {
+ mContext.getPackageManager().getPackageInfo(packageName, 0);
+ return false;
+ } catch (NameNotFoundException e) {
+ // Do nothing.
+ }
}
+ 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
+ */
+ public int getSslPort() {
+ Integer port = (Integer) getValue(KEY_VVM_SSL_PORT_NUMBER_INT);
+ if (port != null) {
+ return port;
+ }
+ return 0;
+ }
+
+ /**
+ * Hidden Config.
+ *
+ * <p>Sometimes the server states it supports a certain feature but we found they have bug on
+ * the server side. For example, in b/28717550 the server reported AUTH=DIGEST-MD5 capability
+ * but using it to login will cause subsequent response to be erroneous.
+ *
+ * @return A set of capabilities that is reported by the IMAP CAPABILITY command, but determined
+ * to have issues and should not be 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 String getClientPrefix() {
+ String prefix = (String) getValue(KEY_VVM_CLIENT_PREFIX_STRING);
+ if (prefix != null) {
+ return prefix;
+ }
+ return "//VVM";
}
public void startActivation() {
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ telephonyManager.setVisualVoicemailSmsFilterEnabled(mSubId, true);
+ telephonyManager.setVisualVoicemailSmsFilterClientPrefix(mSubId, getClientPrefix());
+
OmtpMessageSender messageSender = getMessageSender();
if (messageSender != null) {
Log.i(TAG, "Requesting VVM activation for subId: " + mSubId);
@@ -114,6 +261,8 @@
}
public void startDeactivation() {
+ mContext.getSystemService(TelephonyManager.class)
+ .setVisualVoicemailSmsFilterEnabled(mSubId, false);
OmtpMessageSender messageSender = getMessageSender();
if (messageSender != null) {
Log.i(TAG, "Requesting VVM deactivation for subId: " + mSubId);
@@ -121,6 +270,7 @@
}
}
+ @Nullable
private PersistableBundle getCarrierConfig() {
if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
Log.w(TAG, "Invalid subscriptionId or subscriptionId not provided in intent.");
@@ -134,19 +284,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 +323,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..e91481e
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java
@@ -0,0 +1,158 @@
+/*
+ * 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 boolean USE_DEBUG_CONFIG = false; //STOPSHIP if true
+
+ 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) {
+ if (USE_DEBUG_CONFIG) {
+ return mConfigs.get("TEST");
+ }
+ 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/imap/ImapHelper.java b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
index 2c10377..404c771 100644
--- a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
+++ b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
@@ -25,7 +25,6 @@
import android.provider.VoicemailContract.Status;
import android.telecom.PhoneAccountHandle;
import android.telecom.Voicemail;
-import android.telephony.TelephonyManager;
import android.util.Base64;
import android.util.Log;
@@ -80,10 +79,14 @@
private int mQuotaOccupied;
private int mQuotaTotal;
+ private final OmtpVvmCarrierConfigHelper mConfig;
+
public ImapHelper(Context context, PhoneAccountHandle phoneAccount, Network network) {
mContext = context;
mPhoneAccount = phoneAccount;
mNetwork = network;
+ mConfig = new OmtpVvmCarrierConfigHelper(context,
+ PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount));
try {
TempDirectory.setTempDirectory(context);
@@ -98,11 +101,9 @@
OmtpConstants.IMAP_PORT, phoneAccount));
int auth = ImapStore.FLAG_NONE;
- OmtpVvmCarrierConfigHelper carrierConfigHelper = new OmtpVvmCarrierConfigHelper(context,
- PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount));
- if (TelephonyManager.VVM_TYPE_CVVM.equals(carrierConfigHelper.getVvmType())) {
- // TODO: move these into the carrier config app
- port = 993;
+ int sslPort = mConfig.getSslPort();
+ if (sslPort != 0) {
+ port = sslPort;
auth = ImapStore.FLAG_SSL;
}
@@ -142,6 +143,10 @@
return info.isRoaming();
}
+ public OmtpVvmCarrierConfigHelper getConfig() {
+ return mConfig;
+ }
+
/** The caller thread will block until the method returns. */
public boolean markMessagesAsRead(List<Voicemail> voicemails) {
return setFlags(voicemails, Flag.SEEN);
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index 9ac37a4..67ffef7 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -20,15 +20,14 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.os.Bundle;
import android.os.UserManager;
-import android.provider.Telephony;
import android.provider.VoicemailContract;
import android.telecom.PhoneAccountHandle;
import android.telecom.Voicemail;
-import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
import android.util.Log;
-import com.android.internal.telephony.PhoneConstants;
import com.android.phone.PhoneGlobals;
import com.android.phone.PhoneUtils;
import com.android.phone.settings.VisualVoicemailSettingsUtil;
@@ -57,7 +56,8 @@
mContext = context;
mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
- intent.getExtras().getInt(PhoneConstants.PHONE_KEY));
+ SubscriptionManager.getPhoneId(
+ intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID)));
if (mPhoneAccount == null) {
Log.w(TAG, "Received message for null phone account");
@@ -69,41 +69,26 @@
return;
}
- SmsMessage[] messages = Telephony.Sms.Intents.getMessagesFromIntent(intent);
+ String eventType = intent.getExtras()
+ .getString(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX);
+ Bundle data = intent.getExtras().getBundle(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS);
- if (messages == null) {
- Log.w(TAG, "Message does not exist in the intent.");
- return;
+ if (eventType.equals(OmtpConstants.SYNC_SMS_PREFIX)) {
+ SyncMessage message = new SyncMessage(data);
+
+ Log.v(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
+ " with event " + message.getSyncTriggerEvent());
+ LocalLogHelper.log(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
+ " with event " + message.getSyncTriggerEvent());
+ processSync(message);
+ } else if (eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) {
+ Log.v(TAG, "Received STATUS sms for " + mPhoneAccount.getId());
+ LocalLogHelper.log(TAG, "Received Status sms for " + mPhoneAccount.getId());
+ StatusMessage message = new StatusMessage(data);
+ updateSource(message);
+ } else {
+ Log.e(TAG, "Unknown prefix: " + eventType);
}
-
- StringBuilder messageBody = new StringBuilder();
-
- for (int i = 0; i < messages.length; i++) {
- if (messages[i].mWrappedSmsMessage != null) {
- messageBody.append(messages[i].getMessageBody());
- }
- }
-
- WrappedMessageData messageData = OmtpSmsParser.parse(messageBody.toString());
- if (messageData != null) {
- if (messageData.getPrefix() == OmtpConstants.SYNC_SMS_PREFIX) {
- SyncMessage message = new SyncMessage(messageData);
-
- Log.v(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
- " with event " + message.getSyncTriggerEvent());
- LocalLogHelper.log(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
- " with event " + message.getSyncTriggerEvent());
- processSync(message);
- } else if (messageData.getPrefix() == OmtpConstants.STATUS_SMS_PREFIX) {
- Log.v(TAG, "Received STATUS sms for " + mPhoneAccount.getId());
- LocalLogHelper.log(TAG, "Received Status sms for " + mPhoneAccount.getId());
- StatusMessage message = new StatusMessage(messageData);
- updateSource(message);
- } else {
- Log.e(TAG, "This should never have happened");
- }
- }
- // Let this fall through: this is not a message we're interested in.
}
/**
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java b/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
deleted file mode 100644
index 54a2a02..0000000
--- a/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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
index 7e4faac..4d8c815 100644
--- a/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
+++ b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
@@ -15,6 +15,7 @@
*/
package com.android.phone.vvm.omtp.sms;
+import android.os.Bundle;
import android.telecom.Log;
import com.android.phone.vvm.omtp.OmtpConstants;
@@ -59,20 +60,20 @@
+ ", 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(
+ public StatusMessage(Bundle wrappedData) {
+ mProvisioningStatus = wrappedData.getString(OmtpConstants.PROVISIONING_STATUS);
+ mStatusReturnCode = wrappedData.getString(OmtpConstants.RETURN_CODE);
+ mSubscriptionUrl = wrappedData.getString(OmtpConstants.SUBSCRIPTION_URL);
+ mServerAddress = wrappedData.getString(OmtpConstants.SERVER_ADDRESS);
+ mTuiAccessNumber = wrappedData.getString(OmtpConstants.TUI_ACCESS_NUMBER);
+ mClientSmsDestinationNumber = wrappedData.getString(
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);
+ mImapPort = wrappedData.getString(OmtpConstants.IMAP_PORT);
+ mImapUserName = wrappedData.getString(OmtpConstants.IMAP_USER_NAME);
+ mImapPassword = wrappedData.getString(OmtpConstants.IMAP_PASSWORD);
+ mSmtpPort = wrappedData.getString(OmtpConstants.SMTP_PORT);
+ mSmtpUserName = wrappedData.getString(OmtpConstants.SMTP_USER_NAME);
+ mSmtpPassword = wrappedData.getString(OmtpConstants.SMTP_PASSWORD);
}
/**
diff --git a/src/com/android/phone/vvm/omtp/sms/SyncMessage.java b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
index 6829981..1e565da 100644
--- a/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
+++ b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
@@ -15,8 +15,14 @@
*/
package com.android.phone.vvm.omtp.sms;
+import android.os.Bundle;
+
import com.android.phone.vvm.omtp.OmtpConstants;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
/**
* Structured data representation of an OMTP SYNC message.
*
@@ -49,16 +55,25 @@
+ ", 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);
+ public SyncMessage(Bundle wrappedData) {
+ mSyncTriggerEvent = wrappedData.getString(OmtpConstants.SYNC_TRIGGER_EVENT);
+ mMessageId = wrappedData.getString(OmtpConstants.MESSAGE_UID);
+ mMessageLength = Integer.parseInt(wrappedData.getString(OmtpConstants.MESSAGE_LENGTH));
+ mContentType = wrappedData.getString(OmtpConstants.CONTENT_TYPE);
+ mSender = wrappedData.getString(OmtpConstants.SENDER);
+ mNewMessageCount = Integer.parseInt(wrappedData.getString(OmtpConstants.NUM_MESSAGE_COUNT));
+ mMsgTimeMillis = parseTime(wrappedData.getString(OmtpConstants.TIME));
}
+ static Long parseTime(String value) {
+ try {
+ return new SimpleDateFormat(
+ OmtpConstants.DATE_TIME_FORMAT, Locale.US)
+ .parse(value).getTime();
+ } catch (ParseException e) {
+ return 0L;
+ }
+ }
/**
* @return the event that triggered the sync message. This is a mandatory field and must always
* be set.
diff --git a/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java b/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
deleted file mode 100644
index b4c86d4..0000000
--- a/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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);
- if (value == null) {
- return null;
- }
-
- 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/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/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index bfb0d23..8020996 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -26,6 +26,7 @@
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.internal.telephony.Phone;
import com.android.phone.settings.SettingsConstants;
@@ -84,7 +85,9 @@
mAllowMute = allowMute;
mIsOutgoing = isOutgoing;
mIsCallWaiting = connection != null && connection.getState() == Call.State.WAITING;
- if (mIsCallWaiting) {
+ boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection;
+ // Start call waiting timer for CDMA waiting call.
+ if (mIsCallWaiting && !isImsCall) {
startCallWaitingTimer();
}
}
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 0af667a..a4434dd 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/common/mail/store/imap/DigestMd5UtilsTest.java b/tests/src/com/android/phone/common/mail/store/imap/DigestMd5UtilsTest.java
new file mode 100644
index 0000000..5534632
--- /dev/null
+++ b/tests/src/com/android/phone/common/mail/store/imap/DigestMd5UtilsTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.common.mail.store.imap;
+
+import junit.framework.TestCase;
+
+public class DigestMd5UtilsTest extends TestCase {
+
+ public void testGetResponse() {
+ // Example data from RFC 2831.4
+ DigestMd5Utils.Data data = new DigestMd5Utils.Data();
+ data.username = "chris";
+ data.password = "secret";
+ data.realm = "elwood.innosoft.com";
+ data.nonce = "OA6MG9tEQGm2hh";
+ data.cnonce = "OA6MHXh6VqTrRk";
+ data.nc = "00000001";
+ data.qop = "auth";
+ data.digestUri = "imap/elwood.innosoft.com";
+ String response = DigestMd5Utils.getResponse(data, false);
+ assertEquals("d388dad90d4bbd760a152321f2143af7", response);
+ }
+
+ public void testGetResponse_ResponseAuth() {
+ // Example data from RFC 2831.4
+ DigestMd5Utils.Data data = new DigestMd5Utils.Data();
+ data.username = "chris";
+ data.password = "secret";
+ data.realm = "elwood.innosoft.com";
+ data.nonce = "OA6MG9tEQGm2hh";
+ data.cnonce = "OA6MHXh6VqTrRk";
+ data.nc = "00000001";
+ data.qop = "auth";
+ data.digestUri = "imap/elwood.innosoft.com";
+ String response = DigestMd5Utils.getResponse(data, true);
+ assertEquals("ea40f60335c427b5527b84dbabcdfffd", response);
+ }
+
+}
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);
+ }
+ }
+
+}