Merge remote-tracking branch 'goog/mirror-m-wireless-internal-release'
Change-Id: If474effff986156771d4da080d373b9bcf352c3d
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index d6bb030..f2fcb49 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -47,6 +47,7 @@
import android.widget.ListAdapter;
import android.widget.Toast;
+import com.android.ims.ImsConfig;
import com.android.ims.ImsManager;
import com.android.internal.telephony.CallForwardInfo;
import com.android.internal.telephony.Phone;
@@ -1238,6 +1239,38 @@
} else {
prefSet.removePreference(mEnableVideoCalling);
}
+
+ if (ImsManager.isVolteEnabledByPlatform(this) &&
+ !mPhone.getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_carrier_volte_tty_supported)) {
+ TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ /* tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); */
+ }
+
+ Preference wifiCallingSettings = findPreference(
+ getResources().getString(R.string.wifi_calling_settings_key));
+ if (!ImsManager.isWfcEnabledByPlatform(mPhone.getContext())) {
+ prefSet.removePreference(wifiCallingSettings);
+ } else {
+ int resId = R.string.wifi_calling_off_summary;
+ if (ImsManager.isWfcEnabledByUser(mPhone.getContext())) {
+ int wfcMode = ImsManager.getWfcMode(mPhone.getContext());
+ switch (wfcMode) {
+ case ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY:
+ resId = R.string.wfc_mode_wifi_only_summary;
+ break;
+ case ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED:
+ resId = R.string.wfc_mode_cellular_preferred_summary;
+ break;
+ case ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED:
+ resId = R.string.wfc_mode_wifi_preferred_summary;
+ break;
+ default:
+ if (DBG) log("Unexpected WFC mode value: " + wfcMode);
+ }
+ }
+ wifiCallingSettings.setSummary(resId);
+ }
}
@Override
diff --git a/src/com/android/phone/ImsUtil.java b/src/com/android/phone/ImsUtil.java
index c3d780b..868a0f1 100644
--- a/src/com/android/phone/ImsUtil.java
+++ b/src/com/android/phone/ImsUtil.java
@@ -16,9 +16,16 @@
package com.android.phone;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.ims.ImsConfig;
+import com.android.ims.ImsManager;
import com.android.phone.PhoneGlobals;
public class ImsUtil {
+ private static final String LOG_TAG = ImsUtil.class.getSimpleName();
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private static boolean sImsPhoneSupported = false;
@@ -31,10 +38,32 @@
}
/**
- * @return true if this device supports voice calls using the built-in SIP stack.
+ * @return {@code true} if this device supports voice calls using the built-in SIP stack.
*/
static boolean isImsPhoneSupported() {
return sImsPhoneSupported;
}
+
+ /**
+ * @return {@code true} if WFC is supported by the platform and has been enabled by the user.
+ */
+ public static boolean isWfcEnabled(Context context) {
+ boolean isEnabledByPlatform = ImsManager.isWfcEnabledByPlatform(context);
+ boolean isEnabledByUser = ImsManager.isWfcEnabledByUser(context);
+ if (DBG) Log.d(LOG_TAG, "isWfcEnabled :: isEnabledByPlatform=" + isEnabledByPlatform);
+ if (DBG) Log.d(LOG_TAG, "isWfcEnabled :: isEnabledByUser=" + isEnabledByUser);
+ return isEnabledByPlatform && isEnabledByUser;
+ }
+
+ /**
+ * @return {@code true} if the device is configured to use "Wi-Fi only" mode. If WFC is not
+ * enabled, this will return {@code false}.
+ */
+ public static boolean isWfcModeWifiOnly(Context context) {
+ boolean isWifiOnlyMode =
+ ImsManager.getWfcMode(context) == ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY;
+ if (DBG) Log.d(LOG_TAG, "isWfcModeWifiOnly :: isWifiOnlyMode" + isWifiOnlyMode);
+ return isWfcEnabled(context) && isWifiOnlyMode;
+ }
}
diff --git a/src/com/android/phone/MobileNetworkSettings.java b/src/com/android/phone/MobileNetworkSettings.java
index b28a5f9..8749f08 100644
--- a/src/com/android/phone/MobileNetworkSettings.java
+++ b/src/com/android/phone/MobileNetworkSettings.java
@@ -17,7 +17,6 @@
package com.android.phone;
import com.android.ims.ImsManager;
-import com.android.ims.ImsException;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneFactory;
@@ -35,7 +34,6 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.AsyncResult;
@@ -48,7 +46,6 @@
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
-import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.telephony.PhoneStateListener;
@@ -59,12 +56,10 @@
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
-import android.widget.ListView;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabHost.TabContentFactory;
import android.widget.TabHost.TabSpec;
-import android.widget.TabWidget;
/**
* "Mobile network settings" screen. This preference screen lets you
@@ -147,11 +142,10 @@
@Override
public void onCallStateChanged(int state, String incomingNumber) {
if (DBG) log("PhoneStateListener.onCallStateChanged: state=" + state);
+ boolean enabled = (state == TelephonyManager.CALL_STATE_IDLE) &&
+ ImsManager.isNonTtyOrTtyOnVolteEnabled(getApplicationContext());
Preference pref = getPreferenceScreen().findPreference(BUTTON_4G_LTE_KEY);
- if (pref != null) {
- pref.setEnabled((state == TelephonyManager.CALL_STATE_IDLE) &&
- ImsManager.isNonTtyOrTtyOnVolteEnabled(getApplicationContext()));
- }
+ if (pref != null) pref.setEnabled(enabled && hasActiveSubscriptions());
}
};
@@ -241,7 +235,7 @@
return true;
} else if (preference == mButtonEnabledNetworks) {
int settingsNetworkMode = android.provider.Settings.Global.getInt(mPhone.getContext().
- getContentResolver(),
+ getContentResolver(),
android.provider.Settings.Global.PREFERRED_NETWORK_MODE + phoneSubId,
preferredNetworkMode);
mButtonEnabledNetworks.setValue(Integer.toString(settingsNetworkMode));
@@ -436,7 +430,6 @@
addPreferencesFromResource(R.xml.network_setting);
mButton4glte = (SwitchPreference)findPreference(BUTTON_4G_LTE_KEY);
-
mButton4glte.setOnPreferenceChangeListener(this);
try {
@@ -508,9 +501,10 @@
tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
- mButton4glte.setChecked(ImsManager.isEnhanced4gLteModeSettingEnabledByUser(this)
- && ImsManager.isNonTtyOrTtyOnVolteEnabled(this));
- // NOTE: The button will be enabled/disabled in mPhoneStateListener
+ // NOTE: Buttons will be enabled/disabled in mPhoneStateListener
+ boolean enh4glteMode = ImsManager.isEnhanced4gLteModeSettingEnabledByUser(this)
+ && ImsManager.isNonTtyOrTtyOnVolteEnabled(this);
+ mButton4glte.setChecked(enh4glteMode);
mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
@@ -518,6 +512,10 @@
}
+ private boolean hasActiveSubscriptions() {
+ return mActiveSubInfos.size() > 0;
+ }
+
private void updateBody() {
final Context context = getApplicationContext();
PreferenceScreen prefSet = getPreferenceScreen();
@@ -707,11 +705,15 @@
* but you do need to remember that this all needs to work when subscriptions
* change dynamically such as when hot swapping sims.
*/
- boolean hasActiveSubscriptions = mActiveSubInfos.size() > 0;
+ boolean hasActiveSubscriptions = hasActiveSubscriptions();
+ TelephonyManager tm = (TelephonyManager) getSystemService(
+ Context.TELEPHONY_SERVICE);
+ boolean canChange4glte = (tm.getCallState() == TelephonyManager.CALL_STATE_IDLE) &&
+ ImsManager.isNonTtyOrTtyOnVolteEnabled(getApplicationContext());
mButtonDataRoam.setEnabled(hasActiveSubscriptions);
mButtonPreferredNetworkMode.setEnabled(hasActiveSubscriptions);
mButtonEnabledNetworks.setEnabled(hasActiveSubscriptions);
- mButton4glte.setEnabled(hasActiveSubscriptions);
+ mButton4glte.setEnabled(hasActiveSubscriptions && canChange4glte);
mLteDataServicePref.setEnabled(hasActiveSubscriptions);
Preference ps;
PreferenceScreen root = getPreferenceScreen();
@@ -843,9 +845,10 @@
.obtainMessage(MyHandler.MESSAGE_SET_PREFERRED_NETWORK_TYPE));
}
} else if (preference == mButton4glte) {
- SwitchPreference ltePref = (SwitchPreference)preference;
- ltePref.setChecked(!ltePref.isChecked());
- ImsManager.setEnhanced4gLteModeSetting(this, ltePref.isChecked());
+ SwitchPreference enhanced4gModePref = (SwitchPreference) preference;
+ boolean enhanced4gMode = !enhanced4gModePref.isChecked();
+ enhanced4gModePref.setChecked(enhanced4gMode);
+ ImsManager.setEnhanced4gLteModeSetting(this, enhanced4gModePref.isChecked());
} else if (preference == mButtonDataRoam) {
if (DBG) log("onPreferenceTreeClick: preference == mButtonDataRoam.");
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 615f777..f7c5939 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -2449,6 +2449,10 @@
== Configuration.ORIENTATION_LANDSCAPE;
}
+ public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) {
+ return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId));
+ }
+
public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
return makePstnPhoneAccountHandleWithPrefix(phone, "", false);
}
diff --git a/src/com/android/phone/vvm/omtp/OmtpConstants.java b/src/com/android/phone/vvm/omtp/OmtpConstants.java
new file mode 100644
index 0000000..971d2d6
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpConstants.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Wrapper class to hold relevant OMTP constants as defined in the OMTP spec.
+ * <p>
+ * In essence this is a programmatic representation of the relevant portions of OMTP spec.
+ */
+public class OmtpConstants {
+ public static final String SMS_FIELD_SEPARATOR = ";";
+ public static final String SMS_KEY_VALUE_SEPARATOR = "=";
+ public static final String SMS_PREFIX_SEPARATOR = ":";
+
+ public static final String CLIENT_PREFIX = "//VVM";
+ public static final String SYNC_SMS_PREFIX = CLIENT_PREFIX + ":SYNC:";
+ public static final String STATUS_SMS_PREFIX = CLIENT_PREFIX + ":STATUS:";
+
+ // This is the format designated by the OMTP spec.
+ public static final String DATE_TIME_FORMAT = "dd/MM/yyyy HH:mm Z";
+
+ /** OMTP protocol versions. */
+ public static final String PROTOCOL_VERSION1_1 = "11";
+ public static final String PROTOCOL_VERSION1_2 = "12";
+ public static final String PROTOCOL_VERSION1_3 = "13";
+
+ ///////////////////////// Client/Mobile originated SMS //////////////////////
+
+ /** Mobile Originated requests */
+ public static final String ACTIVATE_REQUEST = "Activate";
+ public static final String DEACTIVATE_REQUEST = "Deactivate";
+ public static final String STATUS_REQUEST = "Status";
+
+ /** fields that can be present in a Mobile Originated OMTP SMS */
+ public static final String CLIENT_TYPE = "ct";
+ public static final String APPLICATION_PORT = "pt";
+ public static final String PROTOCOL_VERSION = "pv";
+
+
+ //////////////////////////////// Sync SMS fields ////////////////////////////
+
+ /**
+ * Sync SMS fields.
+ * <p>
+ * Each string constant is the field's key in the SMS body which is used by the parser to
+ * identify the field's value, if present, in the SMS body.
+ */
+
+ /**
+ * The event that triggered this SYNC SMS.
+ * See {@link OmtpConstants#SYNC_TRIGGER_EVENT_VALUES}
+ */
+ public static final String SYNC_TRIGGER_EVENT = "ev";
+ public static final String MESSAGE_UID = "id";
+ public static final String MESSAGE_LENGTH = "l";
+ public static final String NUM_MESSAGE_COUNT = "c";
+ /** See {@link OmtpConstants#CONTENT_TYPE_VALUES} */
+ public static final String CONTENT_TYPE = "t";
+ public static final String SENDER = "s";
+ public static final String TIME = "dt";
+
+ /**
+ * SYNC message trigger events.
+ * <p>
+ * These are the possible values of {@link OmtpConstants#SYNC_TRIGGER_EVENT}.
+ */
+ public static final String NEW_MESSAGE = "NM";
+ public static final String MAILBOX_UPDATE = "MBU";
+ public static final String GREETINGS_UPDATE = "GU";
+
+ public static final String[] SYNC_TRIGGER_EVENT_VALUES = {
+ NEW_MESSAGE,
+ MAILBOX_UPDATE,
+ GREETINGS_UPDATE
+ };
+
+ /**
+ * Content types supported by OMTP VVM.
+ * <p>
+ * These are the possible values of {@link OmtpConstants#CONTENT_TYPE}.
+ */
+ public static final String VOICE = "v";
+ public static final String VIDEO = "o";
+ public static final String FAX = "f";
+ /** Voice message deposited by an external application */
+ public static final String INFOTAINMENT = "i";
+ /** Empty Call Capture - i.e. voicemail with no voice message. */
+ public static final String ECC = "e";
+
+ public static final String[] CONTENT_TYPE_VALUES = {VOICE, VIDEO, FAX, INFOTAINMENT, ECC};
+
+ ////////////////////////////// Status SMS fields ////////////////////////////
+
+ /**
+ * Status SMS fields.
+ * <p>
+ * Each string constant is the field's key in the SMS body which is used by the parser to
+ * identify the field's value, if present, in the SMS body.
+ */
+ /** See {@link OmtpConstants#PROVISIONING_STATUS_VALUES} */
+ public static final String PROVISIONING_STATUS = "st";
+ /** See {@link OmtpConstants#RETURN_CODE_VALUES} */
+ public static final String RETURN_CODE = "rc";
+ /** URL to send users to for activation VVM */
+ public static final String SUBSCRIPTION_URL = "rs";
+ /** IMAP4/SMTP server IP address or fully qualified domain name */
+ public static final String SERVER_ADDRESS = "srv";
+ /** Phone number to access voicemails through Telephony User Interface */
+ public static final String TUI_ACCESS_NUMBER = "tui";
+ /** Number to send client origination SMS */
+ public static final String CLIENT_SMS_DESTINATION_NUMBER = "dn";
+ public static final String IMAP_PORT = "ipt";
+ public static final String IMAP_USER_NAME = "u";
+ public static final String IMAP_PASSWORD = "pw";
+ public static final String SMTP_PORT = "spt";
+ public static final String SMTP_USER_NAME = "smtp_u";
+ public static final String SMTP_PASSWORD = "smtp_pw";
+
+ /**
+ * User provisioning status values.
+ * <p>
+ * Referred by {@link OmtpConstants#PROVISIONING_STATUS}.
+ */
+ // TODO: As per the spec the code could be either be with or w/o quotes = "N"/N). Currently
+ // this only handles the w/o quotes values.
+ public static final String SUBSCRIBER_NEW = "N";
+ public static final String SUBSCRIBER_READY = "R";
+ public static final String SUBSCRIBER_PROVISIONED = "P";
+ public static final String SUBSCRIBER_UNKNOWN = "U";
+ public static final String SUBSCRIBER_BLOCKED = "B";
+
+ public static final String[] PROVISIONING_STATUS_VALUES = {
+ SUBSCRIBER_NEW,
+ SUBSCRIBER_READY,
+ SUBSCRIBER_PROVISIONED,
+ SUBSCRIBER_UNKNOWN,
+ SUBSCRIBER_BLOCKED
+ };
+
+ /**
+ * The return code included in a status message.
+ * <p>
+ * These are the possible values of {@link OmtpConstants#RETURN_CODE}.
+ */
+ public static final String SUCCESS = "0";
+ public static final String SYSTEM_ERROR = "1";
+ public static final String SUBSCRIBER_ERROR = "2";
+ public static final String MAILBOX_UNKNOWN = "3";
+ public static final String VVM_NOT_ACTIVATED = "4";
+ public static final String VVM_NOT_PROVISIONED = "5";
+ public static final String VVM_CLIENT_UKNOWN = "6";
+ public static final String VVM_MAILBOX_NOT_INITIALIZED = "7";
+
+ public static final String[] RETURN_CODE_VALUES = {
+ SUCCESS,
+ SYSTEM_ERROR,
+ SUBSCRIBER_ERROR,
+ MAILBOX_UNKNOWN,
+ VVM_NOT_ACTIVATED,
+ VVM_NOT_PROVISIONED,
+ VVM_CLIENT_UKNOWN,
+ VVM_MAILBOX_NOT_INITIALIZED,
+ };
+
+ /**
+ * A map of all the field keys to the possible values they can have.
+ */
+ public static final Map<String, String[]> possibleValuesMap = new HashMap<String, String[]>() {{
+ put(SYNC_TRIGGER_EVENT, SYNC_TRIGGER_EVENT_VALUES);
+ put(CONTENT_TYPE, CONTENT_TYPE_VALUES);
+ put(PROVISIONING_STATUS, PROVISIONING_STATUS_VALUES);
+ put(RETURN_CODE, RETURN_CODE_VALUES);
+ }};
+
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java b/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java
new file mode 100644
index 0000000..de39745
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * A singleton class designed to assist in OMTP visual voicemail sync behavior.
+ */
+public class OmtpVvmSyncAccountManager {
+ public static final String TAG = "OmtpVvmSyncAccountManager";
+ // Constants
+ // The authority for the sync adapter's content provider
+ public static final String AUTHORITY = "com.android.voicemail";
+ // An account type, in the form of a domain name
+ public static final String ACCOUNT_TYPE = "com.android.phone.vvm.omtp";
+
+ private static OmtpVvmSyncAccountManager sInstance = new OmtpVvmSyncAccountManager();
+
+ private AccountManager mAccountManager;
+
+ /**
+ * Private constructor. Instance should only be acquired through getInstance().
+ */
+ private OmtpVvmSyncAccountManager() {}
+
+ public static OmtpVvmSyncAccountManager getInstance(Context context) {
+ sInstance.setAccountManager(context);
+ return sInstance;
+ }
+
+ /**
+ * Set the account manager so it does not need to be retrieved every time.
+ * @param context The context to get the account manager for.
+ */
+ private void setAccountManager(Context context) {
+ if (mAccountManager == null) {
+ mAccountManager = AccountManager.get(context);
+ }
+ }
+
+ /**
+ * Register a sync account. There should be a one to one mapping of sync account to voicemail
+ * source. These sync accounts primarily service the purpose of keeping track of how many OMTP
+ * voicemail sources are active and which phone accounts they correspond to.
+ *
+ * @param account The account to register
+ */
+ public void createSyncAccount(Account account) {
+ // Add the account and account type, no password or user data
+ if (mAccountManager.addAccountExplicitly(account, null, null)) {
+ ContentResolver.setIsSyncable(account, AUTHORITY, 1);
+ ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
+ } else {
+ Log.w(TAG, "Attempted to re-register existing account.");
+ }
+ }
+
+ /**
+ * Check if a certain account is registered.
+ *
+ * @param account The account to look for.
+ * @return {@code true} if the account is in the list of registered OMTP voicemail sync
+ * accounts. {@code false} otherwise.
+ */
+ public boolean isAccountRegistered(Account account) {
+ Account[] accounts = mAccountManager.getAccountsByType(ACCOUNT_TYPE);
+ for (int i = 0; i < accounts.length; i++) {
+ if (account.equals(accounts[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
new file mode 100644
index 0000000..811e7e6
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+/**
+ * A {@link Service} which runs the internal implementation of {@link AbstractThreadedSyncAdapter},
+ * syncing voicemails to and from a visual voicemail server.
+ */
+
+package com.android.phone.vvm.omtp;
+
+import android.accounts.Account;
+import android.app.Service;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncResult;
+import android.os.Bundle;
+import android.os.IBinder;
+
+/**
+ * A service to run the VvmSyncAdapter.
+ */
+public class OmtpVvmSyncService extends Service {
+ // Storage for an instance of the sync adapter
+ private static OmtpVvmSyncAdapter sSyncAdapter = null;
+ // Object to use as a thread-safe lock
+ private static final Object sSyncAdapterLock = new Object();
+
+ @Override
+ public void onCreate() {
+ synchronized (sSyncAdapterLock) {
+ if (sSyncAdapter == null) {
+ sSyncAdapter = new OmtpVvmSyncAdapter(getApplicationContext(), true);
+ }
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return sSyncAdapter.getSyncAdapterBinder();
+ }
+
+ public class OmtpVvmSyncAdapter extends AbstractThreadedSyncAdapter {
+ public OmtpVvmSyncAdapter(Context context, boolean autoInitialize) {
+ super(context, autoInitialize);
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority,
+ ContentProviderClient provider, SyncResult syncResult) {
+ // TODO: Write code necessary for syncing.
+ }
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
new file mode 100644
index 0000000..932fb1a
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.accounts.Account;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Telephony;
+import android.provider.VoicemailContract;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.SmsMessage;
+import android.util.Log;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.phone.PhoneUtils;
+import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.OmtpVvmSyncAccountManager;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Receive SMS messages and send for processing by the OMTP visual voicemail source.
+ */
+public class OmtpMessageReceiver extends BroadcastReceiver {
+ private static final String TAG = "OmtpMessageReceiver";
+
+ private Context mContext;
+ private PhoneAccountHandle mPhoneAccount;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mContext = context;
+ mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
+ intent.getExtras().getInt(PhoneConstants.PHONE_KEY));
+
+ SmsMessage[] messages = Telephony.Sms.Intents.getMessagesFromIntent(intent);
+ StringBuilder userData = new StringBuilder();
+ StringBuilder messageBody = new StringBuilder();
+
+ for (int i = 0; i < messages.length; i++) {
+ messageBody.append(messages[i].getMessageBody());
+ userData.append(extractUserData(messages[i]));
+ }
+
+ WrappedMessageData messageData = OmtpSmsParser.parse(messageBody.toString());
+ if (messageData != null) {
+ if (messageData.getPrefix() == OmtpConstants.SYNC_SMS_PREFIX) {
+ SyncMessage message = new SyncMessage(messageData);
+ //TODO: handle message
+ } else if (messageData.getPrefix() == OmtpConstants.STATUS_SMS_PREFIX) {
+ StatusMessage message = new StatusMessage(messageData);
+ handleStatusMessage(message);
+ } else {
+ Log.e(TAG, "This should never have happened");
+ }
+ }
+ // Let this fall through: this is not a message we're interested in.
+ }
+
+ private String extractUserData(SmsMessage sms) {
+ try {
+ // OMTP spec does not tell about the encoding. We assume ASCII.
+ // UTF-8 sounds safer as it can handle ascii as well as other charsets.
+ return new String(sms.getUserData(), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("This should have never happened", e);
+ }
+ }
+
+ private void handleStatusMessage(StatusMessage message) {
+ OmtpVvmSyncAccountManager vvmSyncManager = OmtpVvmSyncAccountManager.getInstance(mContext);
+ Account account = new Account(mPhoneAccount.getId(),
+ OmtpVvmSyncAccountManager.ACCOUNT_TYPE);
+
+ if (!vvmSyncManager.isAccountRegistered(account)) {
+ // If the account has not been previously registered, it means that this STATUS sms
+ // is a result of the ACTIVATE sms, so register the voicemail source.
+ vvmSyncManager.createSyncAccount(account);
+ VoicemailContract.Status.setStatus(mContext, mPhoneAccount,
+ VoicemailContract.Status.CONFIGURATION_STATE_OK,
+ VoicemailContract.Status.DATA_CHANNEL_STATE_OK,
+ VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK);
+ }
+
+ //TODO: figure out how to pass IMAP credentials to sync adapter
+
+ ContentResolver.requestSync(account, VoicemailContract.AUTHORITY, new Bundle());
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java
new file mode 100644
index 0000000..f7cfb86
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.telephony.SmsManager;
+
+/**
+ * Interface to send client originated OMTP messages to the OMTP server.
+ * <p>
+ * The interface uses {@link PendingIntent} instead of a call back to notify when the message is
+ * sent. This is primarily to keep the implementation simple and reuse what the underlying
+ * {@link SmsManager} interface provides.
+ */
+public interface OmtpMessageSender {
+ /**
+ * Sends a request to the VVM server to activate VVM for the current subscriber.
+ *
+ * @param sentIntent If not NULL this PendingIntent is broadcast when the message is
+ * successfully sent, or failed.
+ */
+ public void requestVvmActivation(@Nullable PendingIntent sentIntent);
+
+ /**
+ * Sends a request to the VVM server to deactivate VVM for the current subscriber.
+ *
+ * @param sentIntent If not NULL this PendingIntent is broadcast when the message is
+ * successfully sent, or failed.
+ */
+ public void requestVvmDeactivation(@Nullable PendingIntent sentIntent);
+
+ /**
+ * Send a request to the VVM server to get account status of the current subscriber.
+ *
+ * @param sentIntent If not NULL this PendingIntent is broadcast when the message is
+ * successfully sent, or failed.
+ */
+ public void requestVvmStatus(@Nullable PendingIntent sentIntent);
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageSenderImpl.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSenderImpl.java
new file mode 100644
index 0000000..15e8e3a
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSenderImpl.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.services.telephony.Log;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Implementation of {@link OmtpMessageSender} interface.
+ * <p>
+ * Provides simple APIs to send different types of mobile originated OMTP SMS to the VVM server.
+ */
+public class OmtpMessageSenderImpl implements OmtpMessageSender {
+ private static final String TAG = "OmtpMessageSender";
+ private final SmsManager mSmsManager;
+ private final short mApplicationPort;
+ private final String mDestinationNumber;
+ private final String mClientType;
+ private final String mProtocolVersion;
+ private final String mClientPrefix;
+
+ /**
+ * Creates a new instance of OmtpMessageSenderImpl.
+ *
+ * @param smsManager SMS sending library. There is a different SmsManager for each SIM.
+ * @param applicationPort If set to a value > 0 then a binary sms is sent to this port number.
+ * Otherwise, a standard text SMS is sent.
+ * @param defaultDestinationNumber Destination number to be used.
+ * @param clientType The "ct" field to be set in the MO message. This is the value used by the
+ * VVM server to identify the client. Certain VVM servers require a specific agreed
+ * value for this field.
+ * @param protocolVersion OMTP protocol version.
+ * @param clientPrefix The client prefix requested to be used by the server in its MT messages.
+ */
+ public OmtpMessageSenderImpl(SmsManager smsManager, short applicationPort,
+ String defaultDestinationNumber, String clientType, String protocolVersion,
+ String clientPrefix) {
+ mSmsManager = smsManager;
+ mApplicationPort = applicationPort;
+ mDestinationNumber = defaultDestinationNumber;
+ mClientType = clientType;
+ mProtocolVersion = protocolVersion;
+ mClientPrefix = clientPrefix;
+ }
+
+
+ // Activate message:
+ // V1.1: Activate:pv=<value>;ct=<value>
+ // V1.2: Activate:pv=<value>;ct=<value>;pt=<value>;<Clientprefix>
+ // V1.3: Activate:pv=<value>;ct=<value>;pt=<value>;<Clientprefix>
+ @Override
+ public void requestVvmActivation(@Nullable PendingIntent sentIntent) {
+ StringBuilder sb = new StringBuilder().append(OmtpConstants.ACTIVATE_REQUEST);
+
+ appendProtocolVersionAndClientType(sb);
+ if (TextUtils.equals(mProtocolVersion, OmtpConstants.PROTOCOL_VERSION1_2) ||
+ TextUtils.equals(mProtocolVersion, OmtpConstants.PROTOCOL_VERSION1_3)) {
+ appendApplicationPort(sb);
+ appendClientPrefix(sb);
+ }
+
+ sendSms(sb.toString(), sentIntent);
+ }
+
+ // Deactivate message:
+ // V1.1: Deactivate:pv=<value>;ct=<string>
+ // V1.2: Deactivate:pv=<value>;ct=<string>
+ // V1.3: Deactivate:pv=<value>;ct=<string>
+ @Override
+ public void requestVvmDeactivation(@Nullable PendingIntent sentIntent) {
+ StringBuilder sb = new StringBuilder().append(OmtpConstants.DEACTIVATE_REQUEST);
+ appendProtocolVersionAndClientType(sb);
+
+ sendSms(sb.toString(), sentIntent);
+ }
+
+ // Status message:
+ // V1.1: STATUS
+ // V1.2: STATUS
+ // V1.3: STATUS:pv=<value>;ct=<value>;pt=<value>;<Clientprefix>
+ @Override
+ public void requestVvmStatus(@Nullable PendingIntent sentIntent) {
+ StringBuilder sb = new StringBuilder().append(OmtpConstants.STATUS_REQUEST);
+
+ if (TextUtils.equals(mProtocolVersion, OmtpConstants.PROTOCOL_VERSION1_3)) {
+ appendProtocolVersionAndClientType(sb);
+ appendApplicationPort(sb);
+ appendClientPrefix(sb);
+ }
+
+ sendSms(sb.toString(), sentIntent);
+ }
+
+ private void sendSms(String text, PendingIntent sentIntent) {
+ // If application port is set to 0 then send simple text message, else send data message.
+ if (mApplicationPort == 0) {
+ Log.v(TAG, String.format("Sending TEXT sms '%s' to %s", text, mDestinationNumber));
+ mSmsManager.sendTextMessage(mDestinationNumber, null, text, sentIntent, null);
+ } else {
+ byte[] data;
+ try {
+ data = text.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Failed to encode: " + text);
+ }
+ Log.v(TAG, String.format("Sending BINARY sms '%s' to %s:%d", text, mDestinationNumber,
+ mApplicationPort));
+ mSmsManager.sendDataMessage(mDestinationNumber, null, mApplicationPort, data,
+ sentIntent, null);
+ }
+ }
+
+ private void appendProtocolVersionAndClientType(StringBuilder sb) {
+ sb.append(OmtpConstants.SMS_PREFIX_SEPARATOR);
+ appendField(sb, OmtpConstants.PROTOCOL_VERSION, mProtocolVersion);
+ sb.append(OmtpConstants.SMS_FIELD_SEPARATOR);
+ appendField(sb, OmtpConstants.CLIENT_TYPE, mClientType);
+ }
+
+ private void appendApplicationPort(StringBuilder sb) {
+ sb.append(OmtpConstants.SMS_FIELD_SEPARATOR);
+ appendField(sb, OmtpConstants.APPLICATION_PORT, mApplicationPort);
+ }
+
+ private void appendClientPrefix(StringBuilder sb) {
+ sb.append(OmtpConstants.SMS_FIELD_SEPARATOR);
+ sb.append(mClientPrefix);
+ }
+
+ private void appendField(StringBuilder sb, String field, Object value) {
+ sb.append(field).append(OmtpConstants.SMS_KEY_VALUE_SEPARATOR).append(value);
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java b/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
new file mode 100644
index 0000000..54a2a02
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+import java.util.Map;
+
+/**
+ * OMTP SMS parser interface, for parsing SYNC and STATUS SMS sent by OMTP visual voicemail server.
+ */
+public class OmtpSmsParser {
+ private static String TAG = "OmtpSmsParser";
+ /**
+ * Parses the supplied SMS body and returns back a structured OMTP message.
+ * Returns null if unable to parse the SMS body.
+ */
+ public static WrappedMessageData parse(String smsBody) {
+ if (smsBody == null) {
+ return null;
+ }
+
+ WrappedMessageData messageData = null;
+ if (smsBody.startsWith(OmtpConstants.SYNC_SMS_PREFIX)) {
+ messageData = new WrappedMessageData(OmtpConstants.SYNC_SMS_PREFIX,
+ parseSmsBody(smsBody.substring(OmtpConstants.SYNC_SMS_PREFIX.length())));
+ // Check for a mandatory field.
+ String triggerEvent = messageData.extractString(OmtpConstants.SYNC_TRIGGER_EVENT);
+ if (triggerEvent == null) {
+ Log.e(TAG, "Missing mandatory field: " + OmtpConstants.SYNC_TRIGGER_EVENT);
+ return null;
+ }
+ } else if (smsBody.startsWith(OmtpConstants.STATUS_SMS_PREFIX)) {
+ messageData = new WrappedMessageData(OmtpConstants.STATUS_SMS_PREFIX,
+ parseSmsBody(smsBody.substring(OmtpConstants.STATUS_SMS_PREFIX.length())));
+ }
+
+ return messageData;
+ }
+
+ /**
+ * Converts a String of key/value pairs into a Map object. The WrappedMessageData object
+ * contains helper functions to retrieve the values.
+ *
+ * e.g. "//VVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"
+ * => "WrappedMessageData [mFields={st=R, ipt=1, srv=1, dn=1, u=eg@example.com, pw=1, rc=0}]"
+ *
+ * @param message The sms string with the prefix removed.
+ * @return A WrappedMessageData object containing the map.
+ */
+ private static Map<String, String> parseSmsBody(String message) {
+ Map<String, String> keyValues = new ArrayMap<String, String>();
+ String[] entries = message.split(OmtpConstants.SMS_FIELD_SEPARATOR);
+ for (String entry : entries) {
+ String[] keyValue = entry.split(OmtpConstants.SMS_KEY_VALUE_SEPARATOR);
+ if (keyValue.length != 2) {
+ continue;
+ }
+ keyValues.put(keyValue[0].trim(), keyValue[1].trim());
+ }
+
+ return keyValues;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/StatusMessage.java b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
new file mode 100644
index 0000000..7e4faac
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.telecom.Log;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+/**
+ * Structured data representation of OMTP STATUS message.
+ *
+ * The getters will return null if the field was not set in the message body or it could not be
+ * parsed.
+ */
+public class StatusMessage {
+ // NOTE: Following Status SMS fields are not yet parsed, as they do not seem
+ // to be useful for initial omtp source implementation.
+ // lang, g_len, vs_len, pw_len, pm, gm, vtc, vt
+
+ private final String mProvisioningStatus;
+ private final String mStatusReturnCode;
+ private final String mSubscriptionUrl;
+ private final String mServerAddress;
+ private final String mTuiAccessNumber;
+ private final String mClientSmsDestinationNumber;
+ private final String mImapPort;
+ private final String mImapUserName;
+ private final String mImapPassword;
+ private final String mSmtpPort;
+ private final String mSmtpUserName;
+ private final String mSmtpPassword;
+
+ @Override
+ public String toString() {
+ return "StatusMessage [mProvisioningStatus=" + mProvisioningStatus
+ + ", mStatusReturnCode=" + mStatusReturnCode
+ + ", mSubscriptionUrl=" + mSubscriptionUrl
+ + ", mServerAddress=" + mServerAddress
+ + ", mTuiAccessNumber=" + mTuiAccessNumber
+ + ", mClientSmsDestinationNumber=" + mClientSmsDestinationNumber
+ + ", mImapPort=" + mImapPort
+ + ", mImapUserName=" + mImapUserName
+ + ", mImapPassword=" + Log.pii(mImapPassword)
+ + ", mSmtpPort=" + mSmtpPort
+ + ", mSmtpUserName=" + mSmtpUserName
+ + ", mSmtpPassword=" + Log.pii(mSmtpPassword) + "]";
+ }
+
+ public StatusMessage(WrappedMessageData wrappedData) {
+ mProvisioningStatus = wrappedData.extractString(OmtpConstants.PROVISIONING_STATUS);
+ mStatusReturnCode = wrappedData.extractString(OmtpConstants.RETURN_CODE);
+ mSubscriptionUrl = wrappedData.extractString(OmtpConstants.SUBSCRIPTION_URL);
+ mServerAddress = wrappedData.extractString(OmtpConstants.SERVER_ADDRESS);
+ mTuiAccessNumber = wrappedData.extractString(OmtpConstants.TUI_ACCESS_NUMBER);
+ mClientSmsDestinationNumber = wrappedData.extractString(
+ OmtpConstants.CLIENT_SMS_DESTINATION_NUMBER);
+ mImapPort = wrappedData.extractString(OmtpConstants.IMAP_PORT);
+ mImapUserName = wrappedData.extractString(OmtpConstants.IMAP_USER_NAME);
+ mImapPassword = wrappedData.extractString(OmtpConstants.IMAP_PASSWORD);
+ mSmtpPort = wrappedData.extractString(OmtpConstants.SMTP_PORT);
+ mSmtpUserName = wrappedData.extractString(OmtpConstants.SMTP_USER_NAME);
+ mSmtpPassword = wrappedData.extractString(OmtpConstants.SMTP_PASSWORD);
+ }
+
+ /**
+ * @return the subscriber's VVM provisioning status.
+ */
+ public String getProvisioningStatus() {
+ return mProvisioningStatus;
+ }
+
+ /**
+ * @return the return-code of the status SMS.
+ */
+ public String getReturnCode() {
+ return mStatusReturnCode;
+ }
+
+ /**
+ * @return the URL of the voicemail server. This is the URL to send the users to for subscribing
+ * to the visual voicemail service.
+ */
+ public String getSubscriptionUrl() {
+ return mSubscriptionUrl;
+ }
+
+ /**
+ * @return the voicemail server address. Either server IP address or fully qualified domain
+ * name.
+ */
+ public String getServerAddress() {
+ return mServerAddress;
+ }
+
+ /**
+ * @return the Telephony User Interface number to call to access voicemails directly from the
+ * IVR.
+ */
+ public String getTuiAccessNumber() {
+ return mTuiAccessNumber;
+ }
+
+ /**
+ * @return the number to which client originated SMSes should be sent to.
+ */
+ public String getClientSmsDestinationNumber() {
+ return mClientSmsDestinationNumber;
+ }
+
+ /**
+ * @return the IMAP server port to talk to.
+ */
+ public String getImapPort() {
+ return mImapPort;
+ }
+
+ /**
+ * @return the IMAP user name to be used for authentication.
+ */
+ public String getImapUserName() {
+ return mImapUserName;
+ }
+
+ /**
+ * @return the IMAP password to be used for authentication.
+ */
+ public String getImapPassword() {
+ return mImapPassword;
+ }
+
+ /**
+ * @return the SMTP server port to talk to.
+ */
+ public String getSmtpPort() {
+ return mSmtpPort;
+ }
+
+ /**
+ * @return the SMTP user name to be used for SMTP authentication.
+ */
+ public String getSmtpUserName() {
+ return mSmtpUserName;
+ }
+
+ /**
+ * @return the SMTP password to be used for SMTP authentication.
+ */
+ public String getSmtpPassword() {
+ return mSmtpPassword;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/SyncMessage.java b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
new file mode 100644
index 0000000..9a78a6d
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+/**
+ * Structured data representation of an OMTP SYNC message.
+ *
+ * Getters will return null if the field was not set in the message body or it could not be parsed.
+ */
+public class SyncMessage {
+ // Sync event that triggered this message.
+ private final String mSyncTriggerEvent;
+ // Total number of new messages on the server.
+ private final Integer mNewMessageCount;
+ // UID of the new message.
+ private final String mMessageId;
+ // Length of the message.
+ private final Integer mMessageLength;
+ // Content type (voice, video, fax...) of the new message.
+ private final String mContentType;
+ // Sender of the new message.
+ private final String mSender;
+ // Timestamp (in millis) of the new message.
+ private final Long mMsgTimeMillis;
+
+ @Override
+ public String toString() {
+ return "SyncMessage [mSyncTriggerEvent=" + mSyncTriggerEvent
+ + ", mNewMessageCount=" + mNewMessageCount
+ + ", mMessageId=" + mMessageId
+ + ", mMessageLength=" + mMessageLength
+ + ", mContentType=" + mContentType
+ + ", mSender=" + mSender
+ + ", mMsgTimeMillis=" + mMsgTimeMillis + "]";
+ }
+
+ public SyncMessage(WrappedMessageData wrappedData) {
+ mSyncTriggerEvent = wrappedData.extractString(OmtpConstants.SYNC_TRIGGER_EVENT);
+ mMessageId = wrappedData.extractString(OmtpConstants.MESSAGE_UID);
+ mMessageLength = wrappedData.extractInteger(OmtpConstants.MESSAGE_LENGTH);
+ mContentType = wrappedData.extractString(OmtpConstants.CONTENT_TYPE);
+ mSender = wrappedData.extractString(OmtpConstants.SENDER);
+ mNewMessageCount = wrappedData.extractInteger(OmtpConstants.NUM_MESSAGE_COUNT);
+ mMsgTimeMillis = wrappedData.extractTime(OmtpConstants.TIME);
+ }
+
+ /**
+ * @return the event that triggered the sync message. This is a mandatory field and must always
+ * be set.
+ */
+ public String getSyncTriggerEvent() {
+ return mSyncTriggerEvent;
+ }
+
+ /**
+ * @return the number of new messages stored on the voicemail server.
+ */
+ public int getNewMessageCount() {
+ return mNewMessageCount;
+ }
+
+ /**
+ * @return the message ID of the new message.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public String getId() {
+ return mMessageId;
+ }
+
+ /**
+ * @return the content type of the new message.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public String getContentType() {
+ return mContentType;
+ }
+
+ /**
+ * @return the message length of the new message.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public int getLength() {
+ return mMessageLength;
+ }
+
+ /**
+ * @return the sender's phone number of the new message specified as MSISDN.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public String getSender() {
+ return mSender;
+ }
+
+ /**
+ * @return the timestamp as milliseconds for the new message.
+ * <p>
+ * Expected to be set only for
+ * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+ */
+ public long getTimestampMillis() {
+ return mMsgTimeMillis;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java b/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
new file mode 100644
index 0000000..109dfb2
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.phone.vvm.omtp.sms;
+
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Class wrapping the raw OMTP message data, internally represented as as map of all key-value pairs
+ * found in the SMS body.
+ * <p>
+ * Provides convenience methods to extract parse fields of different types.
+ * <p>
+ * All the methods return null if either the field was not present or it could not be parsed.
+ */
+public class WrappedMessageData {
+ private final String TAG = "WrappedMessageData";
+ private final String mPrefix;
+ private final Map<String, String> mFields;
+
+ @Override
+ public String toString() {
+ return "WrappedMessageData [mFields=" + mFields + "]";
+ }
+
+ WrappedMessageData(String prefix, Map<String, String> keyValues) {
+ mPrefix = prefix;
+ mFields = new ArrayMap<String, String>();
+ mFields.putAll(keyValues);
+ }
+
+ /**
+ * @return The String prefix of the message, designating whether this is the message data of a
+ * STATUS or SYNC sms.
+ */
+ String getPrefix() {
+ return mPrefix;
+ }
+
+ /**
+ * Extracts the requested field from underlying data and returns the String value as is.
+ *
+ * @param field The requested field.
+ * @return the parsed string value, or null if the field was not present or not valid.
+ */
+ String extractString(final String field) {
+ String value = mFields.get(field);
+ String[] possibleValues = OmtpConstants.possibleValuesMap.get(field);
+ if (possibleValues == null) {
+ return value;
+ }
+ for (int i = 0; i < possibleValues.length; i++) {
+ if (TextUtils.equals(value, possibleValues[i])) {
+ return value;
+ }
+ }
+ Log.e(TAG, "extractString - value \"" + value +
+ "\" of field \"" + field + "\" is not allowed.");
+ return null;
+ }
+
+ /**
+ * Extracts the requested field from underlying data and parses it as an {@link Integer}.
+ *
+ * @param field The requested field.
+ * @return the parsed integer value, or null if the field was not present.
+ */
+ Integer extractInteger(final String field) {
+ String value = mFields.get(field);
+ if (value == null) {
+ return null;
+ }
+
+ try {
+ return Integer.decode(value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "extractInteger - could not parse integer: " + value);
+ return null;
+ }
+ }
+
+ /**
+ * Extracts the requested field from underlying data and parses it as a date/time represented in
+ * {@link OmtpConstants#DATE_TIME_FORMAT} format.
+ *
+ * @param field The requested field.
+ * @return the parsed string value, or null if the field was not present.
+ */
+ Long extractTime(final String field) {
+ String value = mFields.get(field);
+ if (value == null) {
+ return null;
+ }
+
+ try {
+ return new SimpleDateFormat(
+ OmtpConstants.DATE_TIME_FORMAT, Locale.US).parse(value).getTime();
+ } catch (ParseException e) {
+ Log.e(TAG, "extractTime - could not parse time: " + value);
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index 0e9c0d0..aaaf7db 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -22,6 +22,7 @@
import com.android.phone.PhoneGlobals;
import com.android.phone.common.R;
+import com.android.phone.ImsUtil;
public class DisconnectCauseUtil {
@@ -254,15 +255,18 @@
break;
case android.telephony.DisconnectCause.POWER_OFF:
- // Radio is explictly powered off, presumably because the
- // device is in airplane mode.
- //
- // TODO: For now this UI is ultra-simple: we simply display
- // a message telling the user to turn off airplane mode.
- // But it might be nicer for the dialog to offer the option
- // to turn the radio on right there (and automatically retry
- // the call once network registration is complete.)
- resourceId = R.string.incall_error_power_off;
+ // Radio is explictly powered off because the device is in airplane mode.
+
+ // TODO: Offer the option to turn the radio on, and automatically retry the call
+ // once network registration is complete.
+
+ if (ImsUtil.isWfcModeWifiOnly(context)) {
+ resourceId = R.string.incall_error_wfc_only_no_wireless_network;
+ } else if (ImsUtil.isWfcEnabled(context)) {
+ resourceId = R.string.incall_error_power_off_wfc;
+ } else {
+ resourceId = R.string.incall_error_power_off;
+ }
break;
case android.telephony.DisconnectCause.EMERGENCY_ONLY:
@@ -273,7 +277,13 @@
case android.telephony.DisconnectCause.OUT_OF_SERVICE:
// No network connection.
- resourceId = R.string.incall_error_out_of_service;
+ if (ImsUtil.isWfcModeWifiOnly(context)) {
+ resourceId = R.string.incall_error_wfc_only_no_wireless_network;
+ } else if (ImsUtil.isWfcEnabled(context)) {
+ resourceId = R.string.incall_error_out_of_service_wfc;
+ } else {
+ resourceId = R.string.incall_error_out_of_service;
+ }
break;
case android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 1cb6442..799d844 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -146,6 +146,10 @@
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS |
PhoneAccount.CAPABILITY_MULTI_USER;
+ if (mPhone.isVideoEnabled()) {
+ capabilities |= PhoneAccount.CAPABILITY_VIDEO_CALLING;
+ }
+
if (iconBitmap == null) {
iconBitmap = BitmapFactory.decodeResource(
mContext.getResources(),
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index f434aea..1ce8f64 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -158,6 +158,17 @@
}
/**
+ * Used by {@link com.android.internal.telephony.Connection} to report a change in whether
+ * the call is being made over a wifi network.
+ *
+ * @param isWifi True if call is made over wifi.
+ */
+ @Override
+ public void onWifiChanged(boolean isWifi) {
+ setWifi(isWifi);
+ }
+
+ /**
* Used by the {@link com.android.internal.telephony.Connection} to report a change in the
* audio quality for the current call.
*
@@ -167,7 +178,6 @@
public void onAudioQualityChanged(int audioQuality) {
setAudioQuality(audioQuality);
}
-
/**
* Handles a change in the state of conference participant(s), as reported by the
* {@link com.android.internal.telephony.Connection}.
@@ -210,11 +220,18 @@
private boolean mRemoteVideoCapable;
/**
- * Determines the current audio quality for the {@link TelephonyConnection}.
+ * Determines if the {@link TelephonyConnection} is using wifi.
+ * This is used when {@link TelephonyConnection#updateConnectionCapabilities} is called to
+ * indicate wheter a call has the {@link Connection#CAPABILITY_WIFI} capability.
+ */
+ private boolean mIsWifi;
+
+ /**
+ * Determines the audio quality is high for the {@link TelephonyConnection}.
* This is used when {@link TelephonyConnection#updateConnectionCapabilities}} is called to
* indicate whether a call has the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
*/
- private int mAudioQuality;
+ private boolean mHasHighDefAudio;
/**
* Listeners to our TelephonyConnection specific callbacks
@@ -425,13 +442,27 @@
callCapabilities |= CAPABILITY_HOLD;
}
}
+
+ // If the phone is in ECM mode, mark the call to indicate that the callback number should be
+ // shown.
+ Phone phone = getPhone();
+ if (phone != null && phone.isInEcm()) {
+ callCapabilities |= CAPABILITY_SHOW_CALLBACK_NUMBER;
+ }
return callCapabilities;
}
protected final void updateConnectionCapabilities() {
int newCapabilities = buildConnectionCapabilities();
- newCapabilities = applyVideoCapabilities(newCapabilities);
- newCapabilities = applyAudioQualityCapabilities(newCapabilities);
+
+ newCapabilities = changeCapability(newCapabilities,
+ CAPABILITY_SUPPORTS_VT_REMOTE, mRemoteVideoCapable);
+ newCapabilities = changeCapability(newCapabilities,
+ CAPABILITY_SUPPORTS_VT_LOCAL, mLocalVideoCapable);
+ newCapabilities = changeCapability(newCapabilities,
+ CAPABILITY_HIGH_DEF_AUDIO, mHasHighDefAudio);
+ newCapabilities = changeCapability(newCapabilities, CAPABILITY_WIFI, mIsWifi);
+
newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
if (getConnectionCapabilities() != newCapabilities) {
@@ -482,6 +513,7 @@
setVideoState(mOriginalConnection.getVideoState());
setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable());
setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable());
+ setWifi(mOriginalConnection.isWifi());
setVideoProvider(mOriginalConnection.getVideoProvider());
setAudioQuality(mOriginalConnection.getAudioQuality());
@@ -696,52 +728,6 @@
}
/**
- * Applies the video capability states to the CallCapabilities bit-mask.
- *
- * @param capabilities The CallCapabilities bit-mask.
- * @return The capabilities with video capabilities applied.
- */
- private int applyVideoCapabilities(int capabilities) {
- int currentCapabilities = capabilities;
- if (mRemoteVideoCapable) {
- currentCapabilities = applyCapability(currentCapabilities,
- CAPABILITY_SUPPORTS_VT_REMOTE);
- } else {
- currentCapabilities = removeCapability(currentCapabilities,
- CAPABILITY_SUPPORTS_VT_REMOTE);
- }
-
- if (mLocalVideoCapable) {
- currentCapabilities = applyCapability(currentCapabilities,
- CAPABILITY_SUPPORTS_VT_LOCAL);
- } else {
- currentCapabilities = removeCapability(currentCapabilities,
- CAPABILITY_SUPPORTS_VT_LOCAL);
- }
- return currentCapabilities;
- }
-
- /**
- * Applies the audio capabilities to the {@code CallCapabilities} bit-mask. A call with high
- * definition audio is considered to have the {@code HIGH_DEF_AUDIO} call capability.
- *
- * @param capabilities The {@code CallCapabilities} bit-mask.
- * @return The capabilities with the audio capabilities applied.
- */
- private int applyAudioQualityCapabilities(int capabilities) {
- int currentCapabilities = capabilities;
-
- if (mAudioQuality ==
- com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) {
- currentCapabilities = applyCapability(currentCapabilities, CAPABILITY_HIGH_DEF_AUDIO);
- } else {
- currentCapabilities = removeCapability(currentCapabilities, CAPABILITY_HIGH_DEF_AUDIO);
- }
-
- return currentCapabilities;
- }
-
- /**
* Applies capabilities specific to conferences termination to the
* {@code CallCapabilities} bit-mask.
*
@@ -802,23 +788,26 @@
}
/**
- * Sets the current call audio quality. Used during rebuild of the capabilities
+ * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset
+ * the {@link Connection#CAPABILITY_WIFI} capability.
+ */
+ public void setWifi(boolean isWifi) {
+ mIsWifi = isWifi;
+ updateConnectionCapabilities();
+ }
+
+ /**
+ * Sets the current call audio quality. Used during rebuild of the capabilities
* to set or unset the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
*
* @param audioQuality The audio quality.
*/
public void setAudioQuality(int audioQuality) {
- mAudioQuality = audioQuality;
+ mHasHighDefAudio = audioQuality ==
+ com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION;
updateConnectionCapabilities();
}
- /**
- * Obtains the current call audio quality.
- */
- public int getAudioQuality() {
- return mAudioQuality;
- }
-
void resetStateForConference() {
if (getState() == Connection.STATE_HOLDING) {
if (mOriginalConnection.getState() == Call.State.ACTIVE) {
@@ -862,27 +851,19 @@
}
/**
- * Applies a capability to a capabilities bit-mask.
+ * Changes a capabilities bit-mask to add or remove a capability.
*
* @param capabilities The capabilities bit-mask.
- * @param capability The capability to apply.
- * @return The capabilities bit-mask with the capability applied.
+ * @param capability The capability to change.
+ * @param enabled Whether the capability should be set or removed.
+ * @return The capabilities bit-mask with the capability changed.
*/
- private int applyCapability(int capabilities, int capability) {
- int newCapabilities = capabilities | capability;
- return newCapabilities;
- }
-
- /**
- * Removes a capability from a capabilities bit-mask.
- *
- * @param capabilities The capabilities bit-mask.
- * @param capability The capability to remove.
- * @return The capabilities bit-mask with the capability removed.
- */
- private int removeCapability(int capabilities, int capability) {
- int newCapabilities = capabilities & ~capability;
- return newCapabilities;
+ private int changeCapability(int capabilities, int capability, boolean enabled) {
+ if (enabled) {
+ return capabilities | capability;
+ } else {
+ return capabilities & ~capability;
+ }
}
/**
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 782e92c..c4eb2e9 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -156,7 +156,7 @@
boolean useEmergencyCallHelper = false;
if (isEmergencyNumber) {
- if (state == ServiceState.STATE_POWER_OFF) {
+ if (!phone.isRadioOn()) {
useEmergencyCallHelper = true;
}
} else {
@@ -395,18 +395,16 @@
}
private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
+ if (isEmergency) {
+ return PhoneFactory.getDefaultPhone();
+ }
+
int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
return PhoneFactory.getPhone(phoneId);
}
- if (isEmergency) {
- // If this is an emergency number and we've been asked to dial it using a PhoneAccount
- // which does not exist, then default to whatever subscription is available currently.
- return getFirstPhoneForEmergencyCall();
- }
-
return null;
}