Disable on-screen keyboard for emergency dialer am: bdc9c884bb
am: 4035812eb4
* commit '4035812eb421f5786148932d9d1228d3893a9754':
Disable on-screen keyboard for emergency dialer
Change-Id: I8d38b57ffd2f760df1180d0d714b0c19a0074707
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7ca673c..7570d26 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..79edaa6
--- /dev/null
+++ b/res/xml/vvm_config.xml
@@ -0,0 +1,138 @@
+<?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>
+ </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>
+
+ <pbundle_as_map>
+ <!-- Verizon USA -->
+ <string-array name="mccmnc">
+ <item value="310004"/>
+ <item value="310010"/>
+ <item value="310012"/>
+ <item value="310013"/>
+ <item value="310590"/>
+ <item value="310890"/>
+ <item value="310910"/>
+ <item value="311110"/>
+ <item value="311270"/>
+ <item value="311271"/>
+ <item value="311272"/>
+ <item value="311273"/>
+ <item value="311274"/>
+ <item value="311275"/>
+ <item value="311276"/>
+ <item value="311277"/>
+ <item value="311278"/>
+ <item value="311279"/>
+ <item value="311280"/>
+ <item value="311281"/>
+ <item value="311282"/>
+ <item value="311283"/>
+ <item value="311284"/>
+ <item value="311285"/>
+ <item value="311286"/>
+ <item value="311287"/>
+ <item value="311288"/>
+ <item value="311289"/>
+ <item value="311390"/>
+ <item value="311480"/>
+ <item value="311481"/>
+ <item value="311482"/>
+ <item value="311483"/>
+ <item value="311484"/>
+ <item value="311485"/>
+ <item value="311486"/>
+ <item value="311487"/>
+ <item value="311488"/>
+ <item value="311489"/>
+ </string-array>
+
+ <int name="vvm_port_number_int" value="0"/>
+ <string name="vvm_destination_number_string">900080006200</string>
+ <string name="vvm_type_string">vvm_type_vvm3</string>
+ <string name="vvm_client_prefix_string">//VZWVVM</string>
+ <boolean name="vvm_cellular_data_required_bool" value="true"/>
+ </pbundle_as_map>
+</list>
\ No newline at end of file
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index df1a127..3204a9f 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -689,6 +689,7 @@
switch (simState) {
case IccCardConstants.INTENT_VALUE_ICC_ABSENT:
case IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR:
+ case IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED:
case IccCardConstants.INTENT_VALUE_ICC_UNKNOWN:
mHandler.sendMessage(mHandler.obtainMessage(EVENT_CLEAR_CONFIG, phoneId, -1));
break;
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 7738b77..bfc52b7 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;
@@ -38,19 +40,22 @@
import android.os.UserManager;
import android.preference.PreferenceManager;
import android.provider.Settings;
+import android.service.carrier.CarrierIdentifier;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
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.TelephonyHistogram;
import android.telephony.TelephonyManager;
-import android.telephony.ModemActivityInfo;
+import android.telephony.VisualVoicemailSmsFilterSettings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -67,9 +72,10 @@
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.RIL;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.uicc.IccIoResult;
@@ -79,8 +85,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;
@@ -138,6 +142,10 @@
private static final int EVENT_PERFORM_NETWORK_SCAN_DONE = 40;
private static final int CMD_SET_NETWORK_SELECTION_MODE_MANUAL = 41;
private static final int EVENT_SET_NETWORK_SELECTION_MODE_MANUAL_DONE = 42;
+ private static final int CMD_SET_ALLOWED_CARRIERS = 43;
+ private static final int EVENT_SET_ALLOWED_CARRIERS_DONE = 44;
+ private static final int CMD_GET_ALLOWED_CARRIERS = 45;
+ private static final int EVENT_GET_ALLOWED_CARRIERS_DONE = 46;
/** The singleton instance. */
private static PhoneInterfaceManager sInstance;
@@ -760,6 +768,68 @@
}
break;
+ case CMD_SET_ALLOWED_CARRIERS:
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_SET_ALLOWED_CARRIERS_DONE, request);
+ mPhone.setAllowedCarriers(
+ (List<CarrierIdentifier>) request.argument,
+ onCompleted);
+ break;
+
+ case EVENT_SET_ALLOWED_CARRIERS_DONE:
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ if (ar.exception == null && ar.result != null) {
+ request.result = ar.result;
+ } else {
+ if (ar.result == null) {
+ loge("setAllowedCarriers: Empty response");
+ } else if (ar.exception instanceof CommandException) {
+ loge("setAllowedCarriers: CommandException: " +
+ ar.exception);
+ } else {
+ loge("setAllowedCarriers: Unknown exception");
+ }
+ }
+ // Result cannot be null. Return -1 on error.
+ if (request.result == null) {
+ request.result = new int[]{-1};
+ }
+ synchronized (request) {
+ request.notifyAll();
+ }
+ break;
+
+ case CMD_GET_ALLOWED_CARRIERS:
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_GET_ALLOWED_CARRIERS_DONE, request);
+ mPhone.getAllowedCarriers(onCompleted);
+ break;
+
+ case EVENT_GET_ALLOWED_CARRIERS_DONE:
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ if (ar.exception == null && ar.result != null) {
+ request.result = ar.result;
+ } else {
+ if (ar.result == null) {
+ loge("getAllowedCarriers: Empty response");
+ } else if (ar.exception instanceof CommandException) {
+ loge("getAllowedCarriers: CommandException: " +
+ ar.exception);
+ } else {
+ loge("getAllowedCarriers: Unknown exception");
+ }
+ }
+ // Result cannot be null. Return empty list of CarrierIdentifier.
+ if (request.result == null) {
+ request.result = new ArrayList<CarrierIdentifier>(0);
+ }
+ synchronized (request) {
+ request.notifyAll();
+ }
+ break;
+
default:
Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
break;
@@ -1827,6 +1897,37 @@
return success;
}
+ @Override
+ public void enableVisualVoicemailSmsFilter(String callingPackage, int subId,
+ VisualVoicemailSmsFilterSettings settings) {
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ VisualVoicemailSmsFilterConfig
+ .enableVisualVoicemailSmsFilter(mPhone.getContext(), callingPackage, subId,
+ settings);
+ }
+
+ @Override
+ public void disableVisualVoicemailSmsFilter(String callingPackage, int subId) {
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ VisualVoicemailSmsFilterConfig
+ .disableVisualVoicemailSmsFilter(mPhone.getContext(), callingPackage, subId);
+ }
+
+ @Override
+ public VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings(
+ String callingPackage, int subId) {
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ return VisualVoicemailSmsFilterConfig
+ .getVisualVoicemailSmsFilterSettings(mPhone.getContext(), callingPackage, subId);
+ }
+
+ @Override
+ public VisualVoicemailSmsFilterSettings getSystemVisualVoicemailSmsFilterSettings(
+ String packageName, int subId) {
+ enforceReadPrivilegedPermission();
+ return VisualVoicemailSmsFilterConfig
+ .getVisualVoicemailSmsFilterSettings(mPhone.getContext(), packageName, subId);
+ }
/**
* Returns the unread count of voicemails
*/
@@ -3049,4 +3150,101 @@
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;
+ }
+
+ /**
+ * Get snapshot of Telephony histograms
+ * @return List of Telephony histograms
+ * @hide
+ */
+ @Override
+ public List<TelephonyHistogram> getTelephonyHistograms() {
+ enforceModifyPermissionOrCarrierPrivilege(getDefaultSubscription());
+ return RIL.getTelephonyRILTimingHistograms();
+ }
+
+ /**
+ * {@hide}
+ * Set the allowed carrier list for slotId
+ * Require system privileges. In the future we may add this to carrier APIs.
+ *
+ * @return The number of carriers set successfully, should match length of carriers
+ */
+ @Override
+ public int setAllowedCarriers(int slotId, List<CarrierIdentifier> carriers) {
+ enforceModifyPermission();
+ int subId = SubscriptionManager.getSubId(slotId)[0];
+ int[] retVal = (int[]) sendRequest(CMD_SET_ALLOWED_CARRIERS, carriers, subId);
+ return retVal[0];
+ }
+
+ /**
+ * {@hide}
+ * Get the allowed carrier list for slotId.
+ * Require system privileges. In the future we may add this to carrier APIs.
+ *
+ * @return List of {@link android.service.telephony.CarrierIdentifier}; empty list
+ * means all carriers are allowed.
+ */
+ @Override
+ public List<CarrierIdentifier> getAllowedCarriers(int slotId) {
+ enforceReadPrivilegedPermission();
+ int subId = SubscriptionManager.getSubId(slotId)[0];
+ return (List<CarrierIdentifier>) sendRequest(CMD_GET_ALLOWED_CARRIERS, null, subId);
+ }
+
}
diff --git a/src/com/android/phone/VisualVoicemailSmsFilterConfig.java b/src/com/android/phone/VisualVoicemailSmsFilterConfig.java
new file mode 100644
index 0000000..2b2e2f5
--- /dev/null
+++ b/src/com/android/phone/VisualVoicemailSmsFilterConfig.java
@@ -0,0 +1,165 @@
+/*
+ * 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.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.telephony.VisualVoicemailSmsFilterSettings;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.List;
+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 enableVisualVoicemailSmsFilter(Context context, String callingPackage,
+ int subId,
+ VisualVoicemailSmsFilterSettings settings) {
+ new Editor(context, callingPackage, subId)
+ .setBoolean(ENABLED_KEY, true)
+ .setString(PREFIX_KEY, settings.clientPrefix)
+ .setStringList(ORIGINATING_NUMBERS_KEY, settings.originatingNumbers)
+ .setInt(DESTINATION_PORT_KEY, settings.destinationPort)
+ .apply();
+ }
+
+ public static void disableVisualVoicemailSmsFilter(Context context, String callingPackage,
+ int subId) {
+ new Editor(context, callingPackage, subId)
+ .setBoolean(ENABLED_KEY, false)
+ .apply();
+ }
+
+ @Nullable
+ public static VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings(
+ Context context,
+ String packageName, int subId) {
+ Reader reader = new Reader(context, packageName, subId);
+ if (!reader.getBoolean(ENABLED_KEY, false)) {
+ return null;
+ }
+ return new VisualVoicemailSmsFilterSettings.Builder()
+ .setClientPrefix(reader.getString(PREFIX_KEY,
+ VisualVoicemailSmsFilterSettings.DEFAULT_CLIENT_PREFIX))
+ .setOriginatingNumbers(reader.getStringSet(ORIGINATING_NUMBERS_KEY,
+ VisualVoicemailSmsFilterSettings.DEFAULT_ORIGINATING_NUMBERS))
+ .setDestinationPort(reader.getInt(DESTINATION_PORT_KEY,
+ VisualVoicemailSmsFilterSettings.DEFAULT_DESTINATION_PORT))
+ .build();
+ }
+ private static SharedPreferences getSharedPreferences(Context context) {
+ return PreferenceManager
+ .getDefaultSharedPreferences(context.createDeviceProtectedStorageContext());
+ }
+
+ private static String makePerPhoneAccountKeyPrefix(String packageName, int subId) {
+ // subId is persistent across reboot and upgrade, but not across devices.
+ // ICC id is better as a key but it involves more trouble to get one as subId is more
+ // commonly passed around.
+ return VVM_SMS_FILTER_COFIG_SHARED_PREFS_KEY_PREFIX + packageName + "_"
+ + subId;
+ }
+
+ private static class Editor {
+
+ private final SharedPreferences.Editor mPrefsEditor;
+ private final String mKeyPrefix;
+
+ public Editor(Context context, String packageName, int subId) {
+ mPrefsEditor = getSharedPreferences(context).edit();
+ mKeyPrefix = makePerPhoneAccountKeyPrefix(packageName, subId);
+ }
+
+ private Editor setInt(String key, int value) {
+ mPrefsEditor.putInt(makeKey(key), value);
+ return this;
+ }
+
+ private Editor setString(String key, String value) {
+ mPrefsEditor.putString(makeKey(key), value);
+ return this;
+ }
+
+ private Editor setBoolean(String key, boolean value) {
+ mPrefsEditor.putBoolean(makeKey(key), value);
+ return this;
+ }
+
+ private Editor setStringList(String key, List<String> value) {
+ mPrefsEditor.putStringSet(makeKey(key), new ArraySet(value));
+ return this;
+ }
+
+ public void apply() {
+ mPrefsEditor.apply();
+ }
+
+ private String makeKey(String key) {
+ return mKeyPrefix + key;
+ }
+ }
+
+
+ private static class Reader {
+
+ private final SharedPreferences mPrefs;
+ private final String mKeyPrefix;
+
+ public Reader(Context context, String packageName, int subId) {
+ mPrefs = getSharedPreferences(context);
+ mKeyPrefix = makePerPhoneAccountKeyPrefix(packageName, subId);
+ }
+
+ private int getInt(String key, int defaultValue) {
+ return mPrefs.getInt(makeKey(key), defaultValue);
+ }
+
+ private String getString(String key, String defaultValue) {
+ return mPrefs.getString(makeKey(key), defaultValue);
+ }
+
+ private boolean getBoolean(String key, boolean defaultValue) {
+ return mPrefs.getBoolean(makeKey(key), defaultValue);
+ }
+
+ private List<String> getStringSet(String key, List<String> defaultValue) {
+ Set<String> result = mPrefs.getStringSet(makeKey(key), null);
+ if (result == null) {
+ return defaultValue;
+ }
+ return new ArrayList<>(result);
+ }
+
+ private String makeKey(String key) {
+ return mKeyPrefix + 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..ad47acc 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,84 @@
}
}
+ 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) {
+ if (!disabledCapabilities.contains(capability)) {
+ mCapabilities.add(capability);
+ }
+ } else {
+ 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 +337,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 +356,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/settings/VoicemailSettingsActivity.java b/src/com/android/phone/settings/VoicemailSettingsActivity.java
index a08fe30..d403161 100644
--- a/src/com/android/phone/settings/VoicemailSettingsActivity.java
+++ b/src/com/android/phone/settings/VoicemailSettingsActivity.java
@@ -30,7 +30,6 @@
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.provider.ContactsContract.CommonDataKinds;
-import android.telephony.TelephonyManager;
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
@@ -41,10 +40,9 @@
import com.android.internal.telephony.CallForwardInfo;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
-import com.android.phone.R;
import com.android.phone.EditPhoneNumberPreference;
import com.android.phone.PhoneGlobals;
-import com.android.phone.PhoneUtils;
+import com.android.phone.R;
import com.android.phone.SubscriptionInfoHelper;
import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
@@ -261,8 +259,7 @@
mVoicemailVisualVoicemail = (SwitchPreference) findPreference(
getResources().getString(R.string.voicemail_visual_voicemail_key));
- if (TelephonyManager.VVM_TYPE_OMTP.equals(mOmtpVvmCarrierConfigHelper.getVvmType()) ||
- TelephonyManager.VVM_TYPE_CVVM.equals(mOmtpVvmCarrierConfigHelper.getVvmType())) {
+ if (mOmtpVvmCarrierConfigHelper.isValid()) {
mVoicemailVisualVoicemail.setOnPreferenceChangeListener(this);
mVoicemailVisualVoicemail.setChecked(
VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(mPhone));
diff --git a/src/com/android/phone/vvm/omtp/OmtpConstants.java b/src/com/android/phone/vvm/omtp/OmtpConstants.java
index fa3cb63..896ffe0 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";
@@ -137,8 +136,6 @@
* <p>
* Referred by {@link OmtpConstants#PROVISIONING_STATUS}.
*/
- // TODO: As per the spec the code could be either be with or w/o quotes = "N"/N). Currently
- // this only handles the w/o quotes values.
public static final String SUBSCRIBER_NEW = "N";
public static final String SUBSCRIBER_READY = "R";
public static final String SUBSCRIBER_PROVISIONED = "P";
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 9534a10..64e7e31 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -15,60 +15,152 @@
*/
package com.android.phone.vvm.omtp;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
-import android.telephony.SmsManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.VisualVoicemailSmsFilterSettings;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
-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 com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol;
+import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocolFactory;
+
+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 VisualVoicemailProtocol mProtocol;
+ 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();
+ mProtocol = VisualVoicemailProtocolFactory.create(mVvmType);
}
+ @VisibleForTesting
+ OmtpVvmCarrierConfigHelper(PersistableBundle carrierConfig,
+ PersistableBundle telephonyConfig) {
+ mContext = null;
+ mSubId = 0;
+ mCarrierConfig = carrierConfig;
+ mTelephonyConfig = telephonyConfig;
+ mVvmType = getVvmType();
+ mProtocol = VisualVoicemailProtocolFactory.create(mVvmType);
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public int getSubId() {
+ return mSubId;
+ }
+
+ /**
+ * return whether the carrier's visual voicemail is supported, with KEY_VVM_TYPE_STRING set as a
+ * known protocol.
+ */
+ public boolean isValid() {
+ return mProtocol != null;
+ }
+
+ @Nullable
public String getVvmType() {
- if (mCarrierConfig == null) {
- return null;
- }
-
- return mCarrierConfig.getString(
- CarrierConfigManager.KEY_VVM_TYPE_STRING, null);
+ return (String) getValue(KEY_VVM_TYPE_STRING);
}
- public String getCarrierVvmPackageName() {
- if (mCarrierConfig == null) {
- return null;
+ @Nullable
+ public Set<String> getCarrierVvmPackageNames() {
+ Set<String> names = getCarrierVvmPackageNames(mCarrierConfig);
+ if (names != null) {
+ return names;
}
-
- return mCarrierConfig.getString(
- CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING, null);
+ return getCarrierVvmPackageNames(mTelephonyConfig);
}
- public boolean isOmtpVvmType() {
- return (TelephonyManager.VVM_TYPE_OMTP.equals(mVvmType) ||
- TelephonyManager.VVM_TYPE_CVVM.equals(mVvmType));
+ private static Set<String> getCarrierVvmPackageNames(@Nullable PersistableBundle bundle) {
+ if (bundle == null) {
+ return 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;
}
/**
@@ -76,51 +168,123 @@
* 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, false);
}
public boolean isPrefetchEnabled() {
- if (mCarrierConfig == null) {
- return false;
+ return (boolean) getValue(KEY_VVM_PREFETCH_BOOL, true);
+ }
+
+
+ public int getApplicationPort() {
+ return (int) getValue(KEY_VVM_PORT_NUMBER_INT, 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() {
+ return (int) getValue(KEY_VVM_SSL_PORT_NUMBER_INT, 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 mCarrierConfig
- .getBoolean(CarrierConfigManager.KEY_VVM_PREFETCH_BOOL);
+ 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() {
- OmtpMessageSender messageSender = getMessageSender();
- if (messageSender != null) {
- Log.i(TAG, "Requesting VVM activation for subId: " + mSubId);
- messageSender.requestVvmActivation(null);
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ telephonyManager.enableVisualVoicemailSmsFilter(mSubId,
+ new VisualVoicemailSmsFilterSettings.Builder().setClientPrefix(getClientPrefix())
+ .build());
+
+ if (mProtocol != null) {
+ mProtocol.startActivation(this);
}
}
public void startDeactivation() {
- OmtpMessageSender messageSender = getMessageSender();
- if (messageSender != null) {
- Log.i(TAG, "Requesting VVM deactivation for subId: " + mSubId);
- messageSender.requestVvmDeactivation(null);
+ mContext.getSystemService(TelephonyManager.class)
+ .disableVisualVoicemailSmsFilter(mSubId);
+ if (mProtocol != null) {
+ mProtocol.startDeactivation(this);
}
}
+ public void startProvisioning(Bundle data) {
+ if (mProtocol != null) {
+ mProtocol.startProvisioning(this, data);
+ }
+ }
+
+ public void requestStatus() {
+ if (mProtocol != null) {
+ mProtocol.requestStatus(this);
+ }
+ }
+
+ @Nullable
private PersistableBundle getCarrierConfig() {
if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
Log.w(TAG, "Invalid subscriptionId or subscriptionId not provided in intent.");
@@ -134,39 +298,35 @@
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) {
- Log.w(TAG, "Empty carrier config.");
- return null;
- }
+ @Nullable
+ private Object getValue(String key) {
+ return getValue(key, null);
+ }
- int applicationPort = mCarrierConfig.getInt(
- CarrierConfigManager.KEY_VVM_PORT_NUMBER_INT, 0);
- String destinationNumber = mCarrierConfig.getString(
- CarrierConfigManager.KEY_VVM_DESTINATION_NUMBER_STRING);
- if (TextUtils.isEmpty(destinationNumber)) {
- Log.w(TAG, "No destination number for this carrier.");
- return null;
+ @Nullable
+ private Object getValue(String key, Object defaultValue) {
+ Object result;
+ if (mCarrierConfig != null) {
+ result = mCarrierConfig.get(key);
+ if (result != null) {
+ return result;
+ }
}
-
- OmtpMessageSender messageSender = null;
- SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(mSubId);
- switch (mVvmType) {
- case TelephonyManager.VVM_TYPE_OMTP:
- messageSender = new OmtpStandardMessageSender(smsManager, (short) applicationPort,
- destinationNumber, null, OmtpConstants.PROTOCOL_VERSION1_1, null);
- break;
- case TelephonyManager.VVM_TYPE_CVVM:
- messageSender = new OmtpCvvmMessageSender(smsManager, (short) applicationPort,
- destinationNumber);
- break;
- default:
- Log.w(TAG, "Unexpected visual voicemail type: " + mVvmType);
+ if (mTelephonyConfig != null) {
+ result = mTelephonyConfig.get(key);
+ if (result != null) {
+ return result;
+ }
}
-
- return messageSender;
+ return defaultValue;
}
}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index 0a37493..830243a 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -87,7 +87,7 @@
public static void processSubId(Context context, int subId) {
OmtpVvmCarrierConfigHelper carrierConfigHelper =
new OmtpVvmCarrierConfigHelper(context, subId);
- if (carrierConfigHelper.isOmtpVvmType()) {
+ if (carrierConfigHelper.isValid()) {
PhoneAccountHandle phoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
SubscriptionManager.getPhoneId(subId));
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 4ec740e..8d438ef 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..4c2192e 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;
}
@@ -119,8 +120,6 @@
VoicemailContract.Status.QUOTA_UNAVAILABLE);
mQuotaTotal = mPrefs.getInt(getSharedPrefsKey(PREF_KEY_QUOTA_TOTAL),
VoicemailContract.Status.QUOTA_UNAVAILABLE);
-
- Log.v(TAG, "Quota:" + mQuotaOccupied + "/" + mQuotaTotal);
}
/**
@@ -142,6 +141,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/protocol/CvvmProtocol.java b/src/com/android/phone/vvm/omtp/protocol/CvvmProtocol.java
new file mode 100644
index 0000000..9c4d531
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/protocol/CvvmProtocol.java
@@ -0,0 +1,34 @@
+/*
+ * 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.protocol;
+
+import android.telephony.SmsManager;
+
+import com.android.phone.vvm.omtp.sms.OmtpCvvmMessageSender;
+import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
+
+/**
+ * A flavor of OMTP protocol with a different mobile originated (MO) format
+ */
+public class CvvmProtocol extends VisualVoicemailProtocol {
+
+ @Override
+ public OmtpMessageSender createMessageSender(SmsManager smsManager, short applicationPort,
+ String destinationNumber) {
+ return new OmtpCvvmMessageSender(smsManager, applicationPort, destinationNumber);
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/protocol/OmtpProtocol.java b/src/com/android/phone/vvm/omtp/protocol/OmtpProtocol.java
new file mode 100644
index 0000000..d002652
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/protocol/OmtpProtocol.java
@@ -0,0 +1,33 @@
+/*
+ * 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.protocol;
+
+import android.telephony.SmsManager;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
+import com.android.phone.vvm.omtp.sms.OmtpStandardMessageSender;
+
+public class OmtpProtocol extends VisualVoicemailProtocol {
+
+ @Override
+ public OmtpMessageSender createMessageSender(SmsManager smsManager, short applicationPort,
+ String destinationNumber) {
+ return new OmtpStandardMessageSender(smsManager, applicationPort, destinationNumber,
+ null, OmtpConstants.PROTOCOL_VERSION1_1, null);
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/protocol/ProtocolHelper.java b/src/com/android/phone/vvm/omtp/protocol/ProtocolHelper.java
new file mode 100644
index 0000000..d265bd0
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/protocol/ProtocolHelper.java
@@ -0,0 +1,43 @@
+/*
+ * 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.protocol;
+
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
+
+public class ProtocolHelper {
+
+ private static final String TAG = "ProtocolHelper";
+
+ public static OmtpMessageSender getMessageSender(VisualVoicemailProtocol protocol,
+ OmtpVvmCarrierConfigHelper config) {
+
+ int applicationPort = config.getApplicationPort();
+ String destinationNumber = config.getDestinationNumber();
+ if (TextUtils.isEmpty(destinationNumber)) {
+ Log.w(TAG, "No destination number for this carrier.");
+ return null;
+ }
+
+ SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(config.getSubId());
+ return protocol.createMessageSender(smsManager, (short) applicationPort, destinationNumber);
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
new file mode 100644
index 0000000..bc7dc82
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
@@ -0,0 +1,54 @@
+/*
+ * 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.protocol;
+
+import android.os.Bundle;
+import android.telephony.SmsManager;
+
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
+
+public abstract class VisualVoicemailProtocol {
+
+ public void startActivation(OmtpVvmCarrierConfigHelper config) {
+ OmtpMessageSender messageSender = ProtocolHelper.getMessageSender(this, config);
+ if (messageSender != null) {
+ messageSender.requestVvmActivation(null);
+ }
+ }
+
+ public void startDeactivation(OmtpVvmCarrierConfigHelper config) {
+ OmtpMessageSender messageSender = ProtocolHelper.getMessageSender(this, config);
+ if (messageSender != null) {
+ messageSender.requestVvmDeactivation(null);
+ }
+ }
+
+ public void startProvisioning(OmtpVvmCarrierConfigHelper config, Bundle data) {
+ // Do nothing
+ }
+
+ public void requestStatus(OmtpVvmCarrierConfigHelper config) {
+ OmtpMessageSender messageSender = ProtocolHelper.getMessageSender(this, config);
+ if (messageSender != null) {
+ messageSender.requestVvmStatus(null);
+ }
+ }
+
+ public abstract OmtpMessageSender createMessageSender(SmsManager smsManager,
+ short applicationPort, String destinationNumber);
+}
diff --git a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocolFactory.java b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocolFactory.java
new file mode 100644
index 0000000..dbf38c2
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocolFactory.java
@@ -0,0 +1,46 @@
+/*
+ * 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.protocol;
+
+import android.annotation.Nullable;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+public class VisualVoicemailProtocolFactory {
+
+ private static final String TAG = "VvmProtocolFactory";
+
+ private static final String VVM_TYPE_VVM3 = "vvm_type_vvm3";
+
+ @Nullable
+ public static VisualVoicemailProtocol create(String type) {
+ if (type == null) {
+ return null;
+ }
+ switch (type) {
+ case TelephonyManager.VVM_TYPE_OMTP:
+ return new OmtpProtocol();
+ case TelephonyManager.VVM_TYPE_CVVM:
+ return new CvvmProtocol();
+ case VVM_TYPE_VVM3:
+ return new Vvm3Protocol();
+ default:
+ Log.e(TAG, "Unexpected visual voicemail type: " + type);
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
new file mode 100644
index 0000000..f53d270
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
@@ -0,0 +1,63 @@
+/*
+ * 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.protocol;
+
+import android.os.Bundle;
+import android.telephony.SmsManager;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
+import com.android.phone.vvm.omtp.sms.Vvm3MessageSender;
+
+/**
+ * A flavor of OMTP protocol with a different provisioning process
+ */
+public class Vvm3Protocol extends VisualVoicemailProtocol {
+
+ private static String TAG = "Vvm3Protocol";
+
+ public Vvm3Protocol() {
+ Log.d(TAG, "Vvm3Protocol created");
+ }
+
+ @Override
+ public void startActivation(OmtpVvmCarrierConfigHelper config) {
+ // VVM3 does not support activation SMS.
+ // Send a status request which will start the provisioning process if the user is not
+ // provisioned.
+ config.requestStatus();
+ }
+
+ @Override
+ public void startDeactivation(OmtpVvmCarrierConfigHelper config) {
+ // VVM3 does not support deactivation.
+ // do nothing.
+ }
+
+ @Override
+ public void startProvisioning(OmtpVvmCarrierConfigHelper config, Bundle data) {
+ Log.d(TAG, "start vvm3 provisioning");
+ // TODO: implement (b/28697797).
+ }
+
+ @Override
+ public OmtpMessageSender createMessageSender(SmsManager smsManager, short applicationPort,
+ String destinationNumber) {
+ return new Vvm3MessageSender(smsManager, applicationPort, destinationNumber);
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index 9ac37a4..f2beabe 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -20,20 +20,20 @@
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;
import com.android.phone.vvm.omtp.LocalLogHelper;
import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
import com.android.phone.vvm.omtp.sync.VoicemailsQueryHelper;
@@ -45,7 +45,6 @@
private static final String TAG = "OmtpMessageReceiver";
private Context mContext;
- private PhoneAccountHandle mPhoneAccount;
@Override
public void onReceive(Context context, Intent intent) {
@@ -56,54 +55,51 @@
}
mContext = context;
- mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
- intent.getExtras().getInt(PhoneConstants.PHONE_KEY));
+ PhoneAccountHandle phone = PhoneUtils.makePstnPhoneAccountHandle(
+ SubscriptionManager.getPhoneId(
+ intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID)));
- if (mPhoneAccount == null) {
- Log.w(TAG, "Received message for null phone account");
+ if (phone == null) {
+ Log.i(TAG, "Received message for null phone account");
return;
}
- if (!VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(mContext, mPhoneAccount)) {
- Log.v(TAG, "Received vvm message for disabled vvm source.");
+ if (!VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(mContext, phone)) {
+ Log.i(TAG, "Received vvm message for disabled vvm source.");
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);
- 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);
+ Log.v(TAG, "Received SYNC sms for " + phone.getId() +
+ " with event " + message.getSyncTriggerEvent());
+ LocalLogHelper.log(TAG, "Received SYNC sms for " + phone.getId() +
+ " with event " + message.getSyncTriggerEvent());
+ processSync(phone, message);
+ } else if (eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) {
+ Log.v(TAG, "Received STATUS sms for " + phone.getId());
+ LocalLogHelper.log(TAG, "Received Status sms for " + phone.getId());
+ StatusMessage message = new StatusMessage(data);
+ if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) {
+ updateSource(phone, message);
} else {
- Log.e(TAG, "This should never have happened");
+ Log.v(TAG, "Subscriber not ready, start provisioning");
+ startProvisioning(phone, data);
}
+ } else {
+ Log.e(TAG, "Unknown prefix: " + eventType);
}
- // Let this fall through: this is not a message we're interested in.
+ }
+
+ private void startProvisioning(PhoneAccountHandle phone, Bundle data) {
+ OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(mContext,
+ PhoneUtils.getSubIdForPhoneAccountHandle(phone));
+ helper.startProvisioning(data);
}
/**
@@ -114,13 +110,13 @@
*
* @param message The sync message to extract data from.
*/
- private void processSync(SyncMessage message) {
+ private void processSync(PhoneAccountHandle phone, SyncMessage message) {
Intent serviceIntent = null;
switch (message.getSyncTriggerEvent()) {
case OmtpConstants.NEW_MESSAGE:
Voicemail.Builder builder = Voicemail.createForInsertion(
message.getTimestampMillis(), message.getSender())
- .setPhoneAccount(mPhoneAccount)
+ .setPhoneAccount(phone)
.setSourceData(message.getId())
.setDuration(message.getLength())
.setSourcePackage(mContext.getPackageName());
@@ -131,13 +127,13 @@
Uri uri = VoicemailContract.Voicemails.insert(mContext, voicemail);
voicemail = builder.setId(ContentUris.parseId(uri)).setUri(uri).build();
serviceIntent = OmtpVvmSyncService.getSyncIntent(mContext,
- OmtpVvmSyncService.SYNC_DOWNLOAD_ONE_TRANSCRIPTION, mPhoneAccount,
+ OmtpVvmSyncService.SYNC_DOWNLOAD_ONE_TRANSCRIPTION, phone,
voicemail, true /* firstAttempt */);
}
break;
case OmtpConstants.MAILBOX_UPDATE:
serviceIntent = OmtpVvmSyncService.getSyncIntent(
- mContext, OmtpVvmSyncService.SYNC_DOWNLOAD_ONLY, mPhoneAccount,
+ mContext, OmtpVvmSyncService.SYNC_DOWNLOAD_ONLY, phone,
true /* firstAttempt */);
break;
case OmtpConstants.GREETINGS_UPDATE:
@@ -153,12 +149,12 @@
}
}
- private void updateSource(StatusMessage message) {
+ private void updateSource(PhoneAccountHandle phone, StatusMessage message) {
OmtpVvmSourceManager vvmSourceManager =
OmtpVvmSourceManager.getInstance(mContext);
if (OmtpConstants.SUCCESS.equals(message.getReturnCode())) {
- VoicemailContract.Status.setStatus(mContext, mPhoneAccount,
+ VoicemailContract.Status.setStatus(mContext, phone,
VoicemailContract.Status.CONFIGURATION_STATE_OK,
VoicemailContract.Status.DATA_CHANNEL_STATE_OK,
VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK);
@@ -166,24 +162,24 @@
// Save the IMAP credentials in preferences so they are persistent and can be retrieved.
VisualVoicemailSettingsUtil.setVisualVoicemailCredentialsFromStatusMessage(
mContext,
- mPhoneAccount,
+ phone,
message);
// Add the source to indicate that it is active.
- vvmSourceManager.addSource(mPhoneAccount);
+ vvmSourceManager.addSource(phone);
Intent serviceIntent = OmtpVvmSyncService.getSyncIntent(
- mContext, OmtpVvmSyncService.SYNC_FULL_SYNC, mPhoneAccount,
+ mContext, OmtpVvmSyncService.SYNC_FULL_SYNC, phone,
true /* firstAttempt */);
mContext.startService(serviceIntent);
PhoneGlobals.getInstance().clearMwiIndicator(
- PhoneUtils.getSubIdForPhoneAccountHandle(mPhoneAccount));
+ PhoneUtils.getSubIdForPhoneAccountHandle(phone));
} else {
Log.w(TAG, "Visual voicemail not available for subscriber.");
// Override default isEnabled setting to false since visual voicemail is unable to
// be accessed for some reason.
- VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(mContext, mPhoneAccount,
+ VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(mContext, phone,
/* isEnabled */ false, /* isUserSet */ true);
}
}
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..ee1f07d 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,30 @@
+ ", 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 = unquote(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);
+ }
+
+ private static String unquote(String string) {
+ if (string.length() < 2) {
+ return string;
+ }
+ if (string.startsWith("\"") && string.endsWith("\"")) {
+ return string.substring(1, string.length() - 1);
+ }
+ return string;
}
/**
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/Vvm3MessageSender.java b/src/com/android/phone/vvm/omtp/sms/Vvm3MessageSender.java
new file mode 100644
index 0000000..24c2d03
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/Vvm3MessageSender.java
@@ -0,0 +1,56 @@
+/*
+ * 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.sms;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.telephony.SmsManager;
+
+public class Vvm3MessageSender extends OmtpMessageSender {
+
+ /**
+ * Creates a new instance of Vvm3MessageSender.
+ *
+ * @param smsManager SMS sending library. There is a different SmsManager for each SIM.
+ * @param applicationPort If set to a value > 0 then a binary sms is sent to this port number.
+ * Otherwise, a standard text SMS is sent.
+ */
+ public Vvm3MessageSender(SmsManager smsManager, short applicationPort,
+ String destinationNumber) {
+ super(smsManager, applicationPort, destinationNumber);
+ }
+
+ @Override
+ public void requestVvmActivation(@Nullable PendingIntent sentIntent) {
+ // Activation not supported for VVM3, send a status request instead.
+ requestVvmStatus(sentIntent);
+ }
+
+ @Override
+ public void requestVvmDeactivation(@Nullable PendingIntent sentIntent) {
+ // Deactivation not supported for VVM3, do nothing
+ }
+
+
+ @Override
+ public void requestVvmStatus(@Nullable PendingIntent sentIntent) {
+ // Status message:
+ // STATUS
+ StringBuilder sb = new StringBuilder().append("STATUS");
+ sendSms(sb.toString(), sentIntent);
+ }
+
+}
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/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index d7db345..f8fd918 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -137,6 +137,9 @@
case android.telephony.DisconnectCause.NOT_DISCONNECTED:
return DisconnectCause.UNKNOWN;
+ case android.telephony.DisconnectCause.CALL_PULLED:
+ return DisconnectCause.CALL_PULLED;
+
default:
Log.w("DisconnectCauseUtil.toTelecomDisconnectCauseCode",
"Unrecognized Telephony DisconnectCause "
@@ -214,6 +217,9 @@
resourceId = R.string.callFailed_unobtainable_number;
break;
+ case android.telephony.DisconnectCause.CALL_PULLED:
+ resourceId = R.string.callEnded_pulled;
+
default:
break;
}
@@ -329,6 +335,10 @@
resourceId = R.string.callFailed_video_call_tty_enabled;
break;
+ case android.telephony.DisconnectCause.CALL_PULLED:
+ resourceId = R.string.callEnded_pulled;
+ break;
+
case android.telephony.DisconnectCause.OUTGOING_CANCELED:
// We don't want to show any dialog for the canceled case since the call was
// either canceled by the user explicitly (end-call button pushed immediately)
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index b4733dd..47fac6a 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -312,6 +312,17 @@
Connection original = telephonyConnection.getOriginalConnection();
if (original != null && !original.isIncoming()
&& Objects.equals(original.getAddress(), unknown.getAddress())) {
+ // If the new unknown connection is an external connection, don't swap one with an
+ // actual connection. This means a call got pulled away. We want the actual connection
+ // to disconnect.
+ if (unknown instanceof ImsExternalConnection &&
+ !(telephonyConnection
+ .getOriginalConnection() instanceof ImsExternalConnection)) {
+ Log.v(this, "maybeSwapWithUnknownConnection - not swapping regular connection " +
+ "with external connection.");
+ return false;
+ }
+
telephonyConnection.setOriginalConnection(unknown);
return true;
}
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);
+ }
+ }
+
+}