am e3c72476: (-s ours) am 42f5cb37: Only mark as read voicemails with the "is_read" flag set.
* commit 'e3c72476ff59854db145062c8ec57b0345f7b74b':
diff --git a/res/values/config.xml b/res/values/config.xml
index c0e808d..a4f13f2 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -148,6 +148,11 @@
<!-- Class name for the default main Dialer activity [DO NOT TRANSLATE] -->
<string name="dialer_default_class" translatable="false">com.android.dialer.DialtactsActivity</string>
+ <!-- Package name for the network operator settings [DO NOT TRANSLATE] -->
+ <string name="network_operator_settings_package" translatable="false">com.android.phone</string>
+ <!-- Class name for the network operator settings activity [DO NOT TRANSLATE] -->
+ <string name="network_operator_settings_class" translatable="false">com.android.phone.NetworkSetting</string>
+
<!-- CDMA activation goes through HFA -->
<!-- DEPRECATED: Use CarrierConfigManager#KEY_USE_HFA_FOR_PROVISIONING_BOOL -->
<bool name="config_use_hfa_for_provisioning">false</bool>
@@ -186,4 +191,17 @@
<!-- Disables dialing "*228" (OTASP provisioning) on CDMA carriers where it is not supported or
is potentially harmful by locking the SIM to 3G. -->
<string name="config_disable_cdma_activation_code" translatable="false">false</string>
+
+ <!-- Flag indicating if SIM state should be checked before making an outgoing call. -->
+ <bool name="config_checkSimStateBeforeOutgoingCall">false</bool>
+ <!-- Package name for the SIM unlock dialog.[DO NOT TRANSLATE] -->
+ <string name="config_simUnlockUiPackage" translatable="false">@null</string>
+ <!-- Class name for the SIM unlock dialog.[DO NOT TRANSLATE] -->
+ <string name="config_simUnlockUiClass" translatable="false">@null</string>
+
+ <!-- Flag indicating whether to allow visual voicemail if available on the device.[DO NOT TRANSLATE] -->
+ <bool name="allow_visual_voicemail">true</bool>
+
+ <!-- Component for custom voicemail notification handling. [DO NOT TRANSLATE] -->
+ <string name="config_customVoicemailComponent">@null</string>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 52e4961..360e9ef 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -273,6 +273,12 @@
<string name="response_error">Unexpected response from network.</string>
<!-- Status message displayed in the "Call settings error" dialog -->
<string name="exception_error">Network or SIM card error.</string>
+ <!-- Status message displayed in the "Call settings error" dialog when
+ current SS request is modified to a different request by STK CC -->
+ <string name="stk_cc_ss_to_dial_error">SS request modified to DIAL request.</string>
+ <string name="stk_cc_ss_to_ussd_error">SS request modified to USSD request.</string>
+ <string name="stk_cc_ss_to_ss_error">SS request modified to new SS request.</string>
+
<!-- Status message displayed in the "Call settings error" dialog when operation fails due to FDN
[CHAR LIMIT=NONE] -->
<string name="fdn_check_failure">Your Phone app\'s Fixed Dialing Numbers setting is turned on. As a result, some call-related features aren\'t working.</string>
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index bc0e584..2b7d2ff 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -206,7 +206,9 @@
case CallStateMonitor.PHONE_DISCONNECT:
if (DBG) log("DISCONNECT");
- onDisconnect((AsyncResult) msg.obj);
+ // Stop any signalInfo tone being played when a call gets ended, the rest of the
+ // disconnect functionality in onDisconnect() is handled in ConnectionService.
+ stopSignalInfoTone();
break;
case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED:
diff --git a/src/com/android/phone/CallStateMonitor.java b/src/com/android/phone/CallStateMonitor.java
index 16a6f1f..512c30b 100644
--- a/src/com/android/phone/CallStateMonitor.java
+++ b/src/com/android/phone/CallStateMonitor.java
@@ -88,7 +88,7 @@
//
//callManager.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);
//callManager.registerForPreciseCallStateChanged(this, PHONE_STATE_CHANGED, null);
- //callManager.registerForDisconnect(this, PHONE_DISCONNECT, null);
+ callManager.registerForDisconnect(this, PHONE_DISCONNECT, null);
//callManager.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
callManager.registerForCdmaOtaStatusChange(this, EVENT_OTA_PROVISION_CHANGE, null);
//callManager.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
@@ -134,7 +134,7 @@
// Unregister all events from the old obsolete phone
//callManager.unregisterForNewRingingConnection(this);
//callManager.unregisterForPreciseCallStateChanged(this);
- //callManager.unregisterForDisconnect(this);
+ callManager.unregisterForDisconnect(this);
//callManager.unregisterForUnknownConnection(this);
//callManager.unregisterForCallWaiting(this);
callManager.unregisterForDisplayInfo(this);
diff --git a/src/com/android/phone/IccNetworkDepersonalizationPanel.java b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
index 4831423..9dff461 100644
--- a/src/com/android/phone/IccNetworkDepersonalizationPanel.java
+++ b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
@@ -48,6 +48,12 @@
*/
public class IccNetworkDepersonalizationPanel extends IccPanel {
+ /**
+ * Tracks whether there is an instance of the network depersonalization dialog showing or not.
+ * Ensures only a single instance of the dialog is visible.
+ */
+ private static boolean sShowingDialog = false;
+
//debug constants
private static final boolean DBG = false;
@@ -65,6 +71,21 @@
private Button mUnlockButton;
private Button mDismissButton;
+ /**
+ * Shows the network depersonalization dialog, but only if it is not already visible.
+ */
+ public static void showDialog() {
+ if (sShowingDialog) {
+ Log.i(TAG, "[IccNetworkDepersonalizationPanel] - showDialog; skipped already shown.");
+ return;
+ }
+ Log.i(TAG, "[IccNetworkDepersonalizationPanel] - showDialog; showing dialog.");
+ sShowingDialog = true;
+ IccNetworkDepersonalizationPanel ndpPanel =
+ new IccNetworkDepersonalizationPanel(PhoneGlobals.getInstance());
+ ndpPanel.show();
+ }
+
//private textwatcher to control text entry.
private TextWatcher mPinEntryWatcher = new TextWatcher() {
public void beforeTextChanged(CharSequence buffer, int start, int olen, int nlen) {
@@ -160,6 +181,13 @@
super.onStart();
}
+ @Override
+ public void onStop() {
+ super.onStop();
+ Log.i(TAG, "[IccNetworkDepersonalizationPanel] - showDialog; hiding dialog.");
+ sShowingDialog = false;
+ }
+
//Mirrors IccPinUnlockPanel.onKeyDown().
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
diff --git a/src/com/android/phone/NetworkQueryService.java b/src/com/android/phone/NetworkQueryService.java
index 1a497b4..84fde87 100644
--- a/src/com/android/phone/NetworkQueryService.java
+++ b/src/com/android/phone/NetworkQueryService.java
@@ -53,6 +53,8 @@
// error statuses that will be retured in the callback.
public static final int QUERY_OK = 0;
public static final int QUERY_EXCEPTION = 1;
+
+ static final String ACTION_LOCAL_BINDER = "com.android.phone.intent.action.LOCAL_BINDER";
/** state of the query service */
private int mState;
@@ -184,11 +186,12 @@
*/
@Override
public IBinder onBind(Intent intent) {
- // TODO: Currently, return only the LocalBinder instance. If we
- // end up requiring support for a remote binder, we will need to
- // return mBinder as well, depending upon the intent.
if (DBG) log("binding service implementation");
- return mLocalBinder;
+ if (ACTION_LOCAL_BINDER.equals(intent.getAction())) {
+ return mLocalBinder;
+ }
+
+ return mBinder;
}
/**
diff --git a/src/com/android/phone/NetworkSetting.java b/src/com/android/phone/NetworkSetting.java
index ad2fea3..3d32817 100644
--- a/src/com/android/phone/NetworkSetting.java
+++ b/src/com/android/phone/NetworkSetting.java
@@ -270,8 +270,9 @@
// we want this service to just stay in the background until it is killed, we
// don't bother stopping it from our end.
startService (new Intent(this, NetworkQueryService.class));
- bindService (new Intent(this, NetworkQueryService.class), mNetworkQueryServiceConnection,
- Context.BIND_AUTO_CREATE);
+ bindService (new Intent(this, NetworkQueryService.class).setAction(
+ NetworkQueryService.ACTION_LOCAL_BINDER),
+ mNetworkQueryServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index c579fe0..100a38c 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -93,6 +93,7 @@
private Context mContext;
private NotificationManager mNotificationManager;
+ private final ComponentName mNotificationComponent;
private StatusBarManager mStatusBarManager;
private UserManager mUserManager;
private Toast mToast;
@@ -125,6 +126,12 @@
mSubscriptionManager = SubscriptionManager.from(mContext);
mTelecomManager = TelecomManager.from(mContext);
mTelephonyManager = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE);
+
+ final String notificationComponent = mContext.getString(
+ R.string.config_customVoicemailComponent);
+
+ mNotificationComponent = notificationComponent != null
+ ? ComponentName.unflattenFromString(notificationComponent) : null;
}
/**
@@ -352,8 +359,10 @@
return;
}
+ Integer vmCount = null;
+
if (TelephonyCapabilities.supportsVoiceMessageCount(phone)) {
- int vmCount = phone.getVoiceMessageCount();
+ vmCount = phone.getVoiceMessageCount();
String titleFormat = mContext.getString(R.string.notification_voicemail_title_count);
notificationTitle = String.format(titleFormat, vmCount);
}
@@ -363,7 +372,9 @@
Intent intent;
String notificationText;
- if (TextUtils.isEmpty(vmNumber)) {
+ boolean isSettingsIntent = TextUtils.isEmpty(vmNumber);
+
+ if (isSettingsIntent) {
notificationText = mContext.getString(
R.string.notification_voicemail_no_vm_number);
@@ -423,22 +434,75 @@
if (!mUserManager.hasUserRestriction(
UserManager.DISALLOW_OUTGOING_CALLS, userHandle)
&& !user.isManagedProfile()) {
- mNotificationManager.notifyAsUser(
- Integer.toString(subId) /* tag */,
- VOICEMAIL_NOTIFICATION,
- notification,
- userHandle);
+ if (!sendNotificationCustomComponent(vmCount, vmNumber, pendingIntent,
+ isSettingsIntent)) {
+ mNotificationManager.notifyAsUser(
+ Integer.toString(subId) /* tag */,
+ VOICEMAIL_NOTIFICATION,
+ notification,
+ userHandle);
+ }
}
}
} else {
- mNotificationManager.cancelAsUser(
- Integer.toString(subId) /* tag */,
- VOICEMAIL_NOTIFICATION,
- UserHandle.ALL);
+ if (!sendNotificationCustomComponent(0, null, null, false)) {
+ mNotificationManager.cancelAsUser(
+ Integer.toString(subId) /* tag */,
+ VOICEMAIL_NOTIFICATION,
+ UserHandle.ALL);
+ }
}
}
/**
+ * Sends a broadcast with the voicemail notification information to a custom component to
+ * handle. This method is also used to indicate to the custom component when to clear the
+ * notification. A pending intent can be passed to the custom component to indicate an action to
+ * be taken as it would by a notification produced in this class.
+ * @param count The number of pending voicemail messages to indicate on the notification. A
+ * Value of 0 is passed here to indicate that the notification should be cleared.
+ * @param number The voicemail phone number if specified.
+ * @param pendingIntent The intent that should be passed as the action to be taken.
+ * @param isSettingsIntent {@code true} to indicate the pending intent is to launch settings.
+ * otherwise, {@code false} to indicate the intent launches voicemail.
+ * @return {@code true} if a custom component was notified of the notification.
+ */
+ private boolean sendNotificationCustomComponent(Integer count, String number,
+ PendingIntent pendingIntent, boolean isSettingsIntent) {
+ if (mNotificationComponent != null) {
+ Intent intent = new Intent();
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.setComponent(mNotificationComponent);
+ intent.setAction(TelephonyManager.ACTION_SHOW_VOICEMAIL_NOTIFICATION);
+
+ if (count != null) {
+ intent.putExtra(TelephonyManager.EXTRA_NOTIFICATION_COUNT, count);
+ }
+
+ // Additional information about the voicemail notification beyond the count is only
+ // present when the count not specified or greater than 0. The value of 0 represents
+ // clearing the notification, which does not require additional information.
+ if (count == null || count > 0) {
+ if (!TextUtils.isEmpty(number)) {
+ intent.putExtra(TelephonyManager.EXTRA_VOICEMAIL_NUMBER, number);
+ }
+
+ if (pendingIntent != null) {
+ intent.putExtra(isSettingsIntent
+ ? TelephonyManager.EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT
+ : TelephonyManager.EXTRA_CALL_VOICEMAIL_INTENT,
+ pendingIntent);
+ }
+ }
+
+ mContext.sendBroadcast(intent);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
* Updates the message call forwarding indicator notification.
*
* @param visible true if there are messages waiting
@@ -571,8 +635,9 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
// Use NetworkSetting to handle the selection intent
- intent.setComponent(new ComponentName("com.android.phone",
- "com.android.phone.NetworkSetting"));
+ intent.setComponent(new ComponentName(
+ mContext.getString(R.string.network_operator_settings_package),
+ mContext.getString(R.string.network_operator_settings_class)));
intent.putExtra(GsmUmtsOptions.EXTRA_SUB_ID, mPhone.getSubId());
PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 315929b..fc08c5f 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -250,9 +250,7 @@
// The user won't be able to do anything else until
// they enter a valid SIM network PIN.
Log.i(LOG_TAG, "show sim depersonal panel");
- IccNetworkDepersonalizationPanel ndpPanel =
- new IccNetworkDepersonalizationPanel(PhoneGlobals.getInstance());
- ndpPanel.show();
+ IccNetworkDepersonalizationPanel.showDialog();
}
break;
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index ebfab96..7e06fdf 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -2690,7 +2690,7 @@
* {@hide}
* Returns the IMS Registration Status
*/
- public boolean isWifiCallingEnabled() {
+ public boolean isWifiCallingAvailable() {
return mPhone.isWifiCallingEnabled();
}
@@ -2698,10 +2698,17 @@
* {@hide}
* Returns the IMS Registration Status
*/
- public boolean isVolteEnabled() {
+ public boolean isVolteAvailable() {
return mPhone.isVolteEnabled();
}
+ /*
+ * {@hide} Returns the IMS Registration Status
+ */
+ public boolean isVideoTelephonyAvailable() {
+ return mPhone.isVideoEnabled();
+ }
+
private boolean canReadPhoneState(String callingPackage, String message) {
try {
mApp.enforceCallingOrSelfPermission(
diff --git a/src/com/android/phone/TimeConsumingPreferenceActivity.java b/src/com/android/phone/TimeConsumingPreferenceActivity.java
index 08a5a95..05b86a5 100644
--- a/src/com/android/phone/TimeConsumingPreferenceActivity.java
+++ b/src/com/android/phone/TimeConsumingPreferenceActivity.java
@@ -50,6 +50,9 @@
static final int RESPONSE_ERROR = 400;
static final int RADIO_OFF_ERROR = 500;
static final int FDN_CHECK_FAILURE = 600;
+ static final int STK_CC_SS_TO_DIAL_ERROR = 700;
+ static final int STK_CC_SS_TO_USSD_ERROR = 800;
+ static final int STK_CC_SS_TO_SS_ERROR = 900;
private final ArrayList<String> mBusyList = new ArrayList<String>();
@@ -77,7 +80,8 @@
}
if (id == RESPONSE_ERROR || id == RADIO_OFF_ERROR || id == EXCEPTION_ERROR
- || id == FDN_CHECK_FAILURE) {
+ || id == FDN_CHECK_FAILURE || id == STK_CC_SS_TO_DIAL_ERROR
+ || id == STK_CC_SS_TO_USSD_ERROR || id == STK_CC_SS_TO_SS_ERROR) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
int msgId;
@@ -97,6 +101,18 @@
msgId = R.string.fdn_check_failure;
builder.setPositiveButton(R.string.close_dialog, mDismiss);
break;
+ case STK_CC_SS_TO_DIAL_ERROR:
+ msgId = R.string.stk_cc_ss_to_dial_error;
+ builder.setPositiveButton(R.string.close_dialog, mDismiss);
+ break;
+ case STK_CC_SS_TO_USSD_ERROR:
+ msgId = R.string.stk_cc_ss_to_ussd_error;
+ builder.setPositiveButton(R.string.close_dialog, mDismiss);
+ break;
+ case STK_CC_SS_TO_SS_ERROR:
+ msgId = R.string.stk_cc_ss_to_ss_error;
+ builder.setPositiveButton(R.string.close_dialog, mDismiss);
+ break;
case EXCEPTION_ERROR:
default:
msgId = R.string.exception_error;
diff --git a/src/com/android/phone/settings/fdn/FdnSetting.java b/src/com/android/phone/settings/fdn/FdnSetting.java
index 134d8e6..ac43cce 100644
--- a/src/com/android/phone/settings/fdn/FdnSetting.java
+++ b/src/com/android/phone/settings/fdn/FdnSetting.java
@@ -297,6 +297,15 @@
.setMessage(R.string.puk2_requested)
.setCancelable(true)
.setOnCancelListener(FdnSetting.this)
+ .setNeutralButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ resetPinChangeStateForPUK2();
+ displayPinChangeDialog(0,true);
+ }
+ })
.create();
a.getWindow().addFlags(
WindowManager.LayoutParams.FLAG_DIM_BEHIND);
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index 9aff2d7..e467bb3 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -28,6 +28,7 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.phone.PhoneUtils;
+import com.android.phone.R;
import com.android.phone.settings.VisualVoicemailSettingsUtil;
import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
@@ -78,8 +79,10 @@
boolean isEnabledInSettings =
VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(context,
phoneAccount);
- boolean isEnabled = isUserSet ? isEnabledInSettings :
- carrierConfigHelper.isEnabledByDefault();
+ boolean isSupported =
+ context.getResources().getBoolean(R.bool.allow_visual_voicemail);
+ boolean isEnabled = isSupported && (isUserSet ? isEnabledInSettings :
+ carrierConfigHelper.isEnabledByDefault());
if (!isUserSet) {
// Preserve the previous setting for "isVisualVoicemailEnabled" if it is
diff --git a/src/com/android/services/telephony/ConferenceParticipantConnection.java b/src/com/android/services/telephony/ConferenceParticipantConnection.java
index c142cc3..5406ab5 100644
--- a/src/com/android/services/telephony/ConferenceParticipantConnection.java
+++ b/src/com/android/services/telephony/ConferenceParticipantConnection.java
@@ -22,6 +22,8 @@
import android.telecom.Connection;
import android.telecom.ConferenceParticipant;
import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.text.TextUtils;
/**
* Represents a participant in a conference call.
@@ -53,7 +55,7 @@
ConferenceParticipant participant) {
mParentConnection = parentConnection;
- setAddress(participant.getHandle(), PhoneConstants.PRESENTATION_ALLOWED);
+ setAddress(getParticipantAddress(participant), PhoneConstants.PRESENTATION_ALLOWED);
setCallerDisplayName(participant.getDisplayName(), PhoneConstants.PRESENTATION_ALLOWED);
mUserEntity = participant.getHandle();
@@ -140,6 +142,51 @@
}
/**
+ * Attempts to build a tel: style URI from a conference participant.
+ * Conference event package data contains SIP URIs, so we try to extract the phone number and
+ * format into a typical tel: style URI.
+ *
+ * @param participant The conference participant.
+ * @return The participant's address URI.
+ */
+ private Uri getParticipantAddress(ConferenceParticipant participant) {
+ Uri address = participant.getHandle();
+ if (address == null) {
+ return address;
+ }
+
+ // If the participant's address is already a TEL scheme, just return it as is.
+ if (PhoneAccount.SCHEME_TEL.equals(address.getScheme())) {
+ return address;
+ }
+
+ // Conference event package participants are identified using SIP URIs (see RFC3261).
+ // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
+ // Per RFC3261, the "user" can be a telephone number.
+ // For example: sip:1650555121;phone-context=blah.com@host.com
+ // In this case, the phone number is in the user field of the URI, and the parameters can be
+ // ignored.
+ //
+ // A SIP URI can also specify a phone number in a format similar to:
+ // sip:+1-212-555-1212@something.com;user=phone
+ // In this case, the phone number is again in user field and the parameters can be ignored.
+ // We can get the user field in these instances by splitting the string on the @, ;, or :
+ // and looking at the first found item.
+ String number = address.getSchemeSpecificPart();
+ if (TextUtils.isEmpty(number)) {
+ return address;
+ }
+
+ String numberParts[] = number.split("[@;:]");
+ if (numberParts.length == 0) {
+ return address;
+ }
+ number = numberParts[0];
+
+ return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
+ }
+
+ /**
* Builds a string representation of this conference participant connection.
*
* @return String representation of connection.
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index 4a92847..81fc7aa 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -120,6 +120,9 @@
case android.telephony.DisconnectCause.TIMED_OUT:
case android.telephony.DisconnectCause.UNOBTAINABLE_NUMBER:
case android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING:
+ case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_USSD:
+ case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_SS:
+ case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_DIAL:
case android.telephony.DisconnectCause.ERROR_UNSPECIFIED:
return DisconnectCause.ERROR;
@@ -251,6 +254,18 @@
resourceId = R.string.callFailed_dsac_restricted_normal;
break;
+ case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_USSD:
+ resourceId = R.string.callFailed_dialToUssd;
+ break;
+
+ case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_SS:
+ resourceId = R.string.callFailed_dialToSs;
+ break;
+
+ case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_DIAL:
+ resourceId = R.string.callFailed_dialToDial;
+ break;
+
case android.telephony.DisconnectCause.OUTGOING_FAILURE:
// We couldn't successfully place the call; there was some
// failure in the telephony layer.
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 3dbf459..4f26a68 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -32,16 +32,19 @@
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Objects;
/**
* Represents an IMS conference call.
@@ -172,7 +175,7 @@
Log.d(this, "onCallCapabilitiesChanged: Connection: %s, callCapabilities: %s", c,
connectionCapabilities);
int capabilites = ImsConference.this.getConnectionCapabilities();
- setConnectionCapabilities(applyVideoCapabilities(capabilites, connectionCapabilities));
+ setConnectionCapabilities(applyHostCapabilities(capabilites, connectionCapabilities));
}
@Override
@@ -193,13 +196,30 @@
private TelephonyConnection mConferenceHost;
/**
- * The known conference participant connections. The HashMap is keyed by endpoint Uri.
- * A {@link ConcurrentHashMap} is used as there is a possibility for radio events impacting the
- * available participants to occur at the same time as an access via the connection service.
+ * The PhoneAccountHandle of the conference host.
*/
- private final ConcurrentHashMap<Uri, ConferenceParticipantConnection>
- mConferenceParticipantConnections =
- new ConcurrentHashMap<Uri, ConferenceParticipantConnection>(8, 0.9f, 1);
+ private PhoneAccountHandle mConferenceHostPhoneAccountHandle;
+
+ /**
+ * The address of the conference host.
+ */
+ private Uri mConferenceHostAddress;
+
+ /**
+ * The known conference participant connections. The HashMap is keyed by endpoint Uri.
+ * Access to the hashmap is protected by the {@link #mUpdateSyncRoot}.
+ */
+ private final HashMap<Uri, ConferenceParticipantConnection>
+ mConferenceParticipantConnections = new HashMap<Uri, ConferenceParticipantConnection>();
+
+ /**
+ * Sychronization root used to ensure that updates to the
+ * {@link #mConferenceParticipantConnections} happen atomically are are not interleaved across
+ * threads. There are some instances where the network will send conference event package
+ * data closely spaced. If that happens, it is possible that the interleaving of the update
+ * will cause duplicate participant info to be added.
+ */
+ private final Object mUpdateSyncRoot = new Object();
public void updateConferenceParticipantsAfterCreation() {
if (mConferenceHost != null) {
@@ -238,13 +258,20 @@
int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
Connection.CAPABILITY_MUTE | Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
-
- capabilities = applyVideoCapabilities(capabilities, mConferenceHost.getConnectionCapabilities());
+ capabilities = applyHostCapabilities(capabilities,
+ mConferenceHost.getConnectionCapabilities());
setConnectionCapabilities(capabilities);
}
- private int applyVideoCapabilities(int conferenceCapabilities, int capabilities) {
+ /**
+ * Transfers capabilities from the conference host to the conference itself.
+ *
+ * @param conferenceCapabilities The current conference capabilities.
+ * @param capabilities The new conference host capabilities.
+ * @return The merged capabilities to be applied to the conference.
+ */
+ private int applyHostCapabilities(int conferenceCapabilities, int capabilities) {
if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
conferenceCapabilities = applyCapability(conferenceCapabilities,
Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
@@ -268,6 +295,14 @@
conferenceCapabilities = removeCapability(conferenceCapabilities,
Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
}
+
+ if (can(capabilities, Connection.CAPABILITY_HIGH_DEF_AUDIO)) {
+ conferenceCapabilities = applyCapability(conferenceCapabilities,
+ Connection.CAPABILITY_HIGH_DEF_AUDIO);
+ } else {
+ conferenceCapabilities = removeCapability(conferenceCapabilities,
+ Connection.CAPABILITY_HIGH_DEF_AUDIO);
+ }
return conferenceCapabilities;
}
@@ -490,9 +525,24 @@
}
mConferenceHost = conferenceHost;
+
+ // Attempt to get the conference host's address (e.g. the host's own phone number).
+ // We need to look at the default phone for the ImsPhone when creating the phone account
+ // for the
+ if (mConferenceHost.getPhone() != null && mConferenceHost.getPhone() instanceof ImsPhone) {
+ // Look up the conference host's address; we need this later for filtering out the
+ // conference host in conference event package data.
+ ImsPhone imsPhone = (ImsPhone) mConferenceHost.getPhone();
+ mConferenceHostPhoneAccountHandle =
+ PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
+ mConferenceHostAddress = TelecomAccountRegistry.getInstance(mTelephonyConnectionService)
+ .getAddress(mConferenceHostPhoneAccountHandle);
+ }
+
mConferenceHost.addConnectionListener(mConferenceHostListener);
mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
setState(mConferenceHost.getState());
+
updateStatusHints();
}
@@ -508,59 +558,70 @@
if (participants == null) {
return;
}
- boolean newParticipantsAdded = false;
- boolean oldParticipantsRemoved = false;
- ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
- HashSet<Uri> participantUserEntities = new HashSet<>(participants.size());
- // Add any new participants and update existing.
- for (ConferenceParticipant participant : participants) {
- Uri userEntity = participant.getHandle();
+ // Perform the update in a synchronized manner. It is possible for the IMS framework to
+ // trigger two onConferenceParticipantsChanged callbacks in quick succession. If the first
+ // update adds new participants, and the second does something like update the status of one
+ // of the participants, we can get into a situation where the participant is added twice.
+ synchronized (mUpdateSyncRoot) {
+ boolean newParticipantsAdded = false;
+ boolean oldParticipantsRemoved = false;
+ ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
+ HashSet<Uri> participantUserEntities = new HashSet<>(participants.size());
- participantUserEntities.add(userEntity);
- if (!mConferenceParticipantConnections.containsKey(userEntity)) {
- createConferenceParticipantConnection(parent, participant);
- newParticipants.add(participant);
- newParticipantsAdded = true;
- } else {
- ConferenceParticipantConnection connection =
- mConferenceParticipantConnections.get(userEntity);
- connection.updateState(participant.getState());
+ // Add any new participants and update existing.
+ for (ConferenceParticipant participant : participants) {
+ Uri userEntity = participant.getHandle();
+
+ participantUserEntities.add(userEntity);
+ if (!mConferenceParticipantConnections.containsKey(userEntity)) {
+ // Some carriers will also include the conference host in the CEP. We will
+ // filter that out here.
+ if (!isParticipantHost(mConferenceHostAddress, userEntity)) {
+ createConferenceParticipantConnection(parent, participant);
+ newParticipants.add(participant);
+ newParticipantsAdded = true;
+ }
+ } else {
+ ConferenceParticipantConnection connection =
+ mConferenceParticipantConnections.get(userEntity);
+ connection.updateState(participant.getState());
+ }
}
- }
- // Set state of new participants.
- if (newParticipantsAdded) {
- // Set the state of the new participants at once and add to the conference
- for (ConferenceParticipant newParticipant : newParticipants) {
- ConferenceParticipantConnection connection =
- mConferenceParticipantConnections.get(newParticipant.getHandle());
- connection.updateState(newParticipant.getState());
+ // Set state of new participants.
+ if (newParticipantsAdded) {
+ // Set the state of the new participants at once and add to the conference
+ for (ConferenceParticipant newParticipant : newParticipants) {
+ ConferenceParticipantConnection connection =
+ mConferenceParticipantConnections.get(newParticipant.getHandle());
+ connection.updateState(newParticipant.getState());
+ }
}
- }
- // Finally, remove any participants from the conference that no longer exist in the
- // conference event package data.
- Iterator<Map.Entry<Uri, ConferenceParticipantConnection>> entryIterator =
- mConferenceParticipantConnections.entrySet().iterator();
- while (entryIterator.hasNext()) {
- Map.Entry<Uri, ConferenceParticipantConnection> entry = entryIterator.next();
+ // Finally, remove any participants from the conference that no longer exist in the
+ // conference event package data.
+ Iterator<Map.Entry<Uri, ConferenceParticipantConnection>> entryIterator =
+ mConferenceParticipantConnections.entrySet().iterator();
+ while (entryIterator.hasNext()) {
+ Map.Entry<Uri, ConferenceParticipantConnection> entry = entryIterator.next();
- if (!participantUserEntities.contains(entry.getKey())) {
- ConferenceParticipantConnection participant = entry.getValue();
- participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
- participant.removeConnectionListener(mParticipantListener);
- mTelephonyConnectionService.removeConnection(participant);
- removeConnection(participant);
- entryIterator.remove();
- oldParticipantsRemoved = true;
+ if (!participantUserEntities.contains(entry.getKey())) {
+ ConferenceParticipantConnection participant = entry.getValue();
+ participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+ participant.removeConnectionListener(mParticipantListener);
+ mTelephonyConnectionService.removeConnection(participant);
+ removeConnection(participant);
+ entryIterator.remove();
+ oldParticipantsRemoved = true;
+ }
}
- }
- // If new participants were added or old ones were removed, we need to ensure the state of
- // the manage conference capability is updated.
- if (newParticipantsAdded || oldParticipantsRemoved) {
- updateManageConference();
+ // If new participants were added or old ones were removed, we need to ensure the state
+ // of the manage conference capability is updated.
+ if (newParticipantsAdded || oldParticipantsRemoved) {
+ updateManageConference();
+ }
}
}
@@ -582,15 +643,17 @@
ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
parent.getOriginalConnection(), participant);
connection.addConnectionListener(mParticipantListener);
+ connection.setConnectTimeMillis(parent.getConnectTimeMillis());
if (Log.VERBOSE) {
Log.v(this, "createConferenceParticipantConnection: %s", connection);
}
- mConferenceParticipantConnections.put(participant.getHandle(), connection);
- PhoneAccountHandle phoneAccountHandle =
- PhoneUtils.makePstnPhoneAccountHandle(parent.getPhone());
- mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, connection);
+ synchronized(mUpdateSyncRoot) {
+ mConferenceParticipantConnections.put(participant.getHandle(), connection);
+ }
+ mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle,
+ connection);
addConnection(connection);
}
@@ -603,7 +666,10 @@
Log.d(this, "removeConferenceParticipant: %s", participant);
participant.removeConnectionListener(mParticipantListener);
- mConferenceParticipantConnections.remove(participant.getUserEntity());
+ synchronized(mUpdateSyncRoot) {
+ mConferenceParticipantConnections.remove(participant.getUserEntity());
+ }
+ mTelephonyConnectionService.removeConnection(participant);
}
/**
@@ -612,17 +678,63 @@
private void disconnectConferenceParticipants() {
Log.v(this, "disconnectConferenceParticipants");
- for (ConferenceParticipantConnection connection :
- mConferenceParticipantConnections.values()) {
+ synchronized(mUpdateSyncRoot) {
+ for (ConferenceParticipantConnection connection :
+ mConferenceParticipantConnections.values()) {
- connection.removeConnectionListener(mParticipantListener);
- // Mark disconnect cause as cancelled to ensure that the call is not logged in the
- // call log.
- connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
- mTelephonyConnectionService.removeConnection(connection);
- connection.destroy();
+ connection.removeConnectionListener(mParticipantListener);
+ // Mark disconnect cause as cancelled to ensure that the call is not logged in the
+ // call log.
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+ mTelephonyConnectionService.removeConnection(connection);
+ connection.destroy();
+ }
+ mConferenceParticipantConnections.clear();
}
- mConferenceParticipantConnections.clear();
+ }
+
+ /**
+ * Determines if the passed in participant handle is the same as the conference host's handle.
+ * Starts with a simple equality check. However, the handles from a conference event package
+ * will be a SIP uri, so we need to pull that apart to look for the participant's phone number.
+ *
+ * @param hostHandle The handle of the connection hosting the conference.
+ * @param handle The handle of the conference participant.
+ * @return {@code true} if the host's handle matches the participant's handle, {@code false}
+ * otherwise.
+ */
+ private boolean isParticipantHost(Uri hostHandle, Uri handle) {
+ // If host and participant handles are the same, bail early.
+ if (Objects.equals(hostHandle, handle)) {
+ return true;
+ }
+
+ // Conference event package participants are identified using SIP URIs (see RFC3261).
+ // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
+ // Per RFC3261, the "user" can be a telephone number.
+ // For example: sip:1650555121;phone-context=blah.com@host.com
+ // In this case, the phone number is in the user field of the URI, and the parameters can be
+ // ignored.
+ //
+ // A SIP URI can also specify a phone number in a format similar to:
+ // sip:+1-212-555-1212@something.com;user=phone
+ // In this case, the phone number is again in user field and the parameters can be ignored.
+ // We can get the user field in these instances by splitting the string on the @, ;, or :
+ // and looking at the first found item.
+
+ String number = handle.getSchemeSpecificPart();
+ String numberParts[] = number.split("[@;:]");
+
+ if (numberParts.length == 0) {
+ return false;
+ }
+ number = numberParts[0];
+
+ // The host number will be a tel: uri. Per RFC3966, the part after tel: is the phone
+ // number.
+ String hostNumber = hostHandle.getSchemeSpecificPart();
+
+ return Objects.equals(hostNumber, number);
}
/**
@@ -651,11 +763,19 @@
PhoneAccountHandle phoneAccountHandle =
PhoneUtils.makePstnPhoneAccountHandle(mConferenceHost.getPhone());
- mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, mConferenceHost);
+ if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
+ GsmConnection c = new GsmConnection(originalConnection);
+ c.updateState();
+ // Copy the connect time from the conferenceHost
+ c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis());
+ mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c);
+ mTelephonyConnectionService.addConnectionToConferenceController(c);
+ } // CDMA case not applicable for SRVCC
mConferenceHost.removeConnectionListener(mConferenceHostListener);
mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener);
mConferenceHost = null;
setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
+ disconnectConferenceParticipants();
destroy();
}
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index 7dcb97e..7c45657 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -18,7 +18,6 @@
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
-import android.net.Uri;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.ConnectionService;
@@ -152,9 +151,10 @@
List<Conferenceable> backgroundConnections = new ArrayList<>(mTelephonyConnections.size());
// Loop through and collect all calls which are active or holding
- for (Connection connection : mTelephonyConnections) {
+ for (TelephonyConnection connection : mTelephonyConnections) {
if (Log.DEBUG) {
- Log.d(this, "recalc - %s %s", connection.getState(), connection);
+ Log.d(this, "recalc - %s %s supportsConf? %s", connection.getState(), connection,
+ connection.isConferenceSupported());
}
// If this connection is a member of a conference hosted on another device, it is not
@@ -166,6 +166,12 @@
continue;
}
+ // If this connection does not support being in a conference call, then it is not
+ // conferenceable with any other connection.
+ if (!connection.isConferenceSupported()) {
+ continue;
+ }
+
switch (connection.getState()) {
case Connection.STATE_ACTIVE:
activeConnections.add(connection);
@@ -239,8 +245,8 @@
List<Connection> nonConferencedConnections =
new ArrayList<>(mTelephonyConnections.size());
- for (Connection c : mTelephonyConnections) {
- if (c.getConference() == null) {
+ for (TelephonyConnection c : mTelephonyConnections) {
+ if (c.getConference() == null && c.isConferenceSupported()) {
nonConferencedConnections.add(c);
}
}
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index df458fd..154fc33 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -18,10 +18,8 @@
import android.content.ComponentName;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
@@ -69,6 +67,7 @@
private final PstnPhoneCapabilitiesNotifier mPhoneCapabilitiesNotifier;
private boolean mIsVideoCapable;
private boolean mIsVideoPauseSupported;
+ private boolean mIsMergeCallSupported;
AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
mPhone = phone;
@@ -168,9 +167,11 @@
if (mIsVideoCapable) {
capabilities |= PhoneAccount.CAPABILITY_VIDEO_CALLING;
}
- if (record != null) {
- updateVideoPauseSupport(record);
+ mIsVideoPauseSupported = isCarrierVideoPauseSupported();
+ if (isCarrierInstantLetteringSupported()) {
+ capabilities |= PhoneAccount.CAPABILITY_CALL_SUBJECT;
}
+ mIsMergeCallSupported = isCarrierMergeCallSupported();
if (icon == null) {
// TODO: Switch to using Icon.createWithResource() once that supports tinting.
@@ -211,34 +212,37 @@
}
/**
- * Updates indicator for this {@link AccountEntry} to determine if the carrier supports
- * pause/resume signalling for IMS video calls. The carrier setting is stored in MNC/MCC
- * configuration files.
+ * Determines from carrier configuration whether pausing of IMS video calls is supported.
*
- * @param subscriptionInfo The subscription info.
+ * @return {@code true} if pausing IMS video calls is supported.
*/
- private void updateVideoPauseSupport(SubscriptionInfo subscriptionInfo) {
- // Get the configuration for the MNC/MCC specified in the current subscription info.
- Configuration configuration = new Configuration();
- if (subscriptionInfo.getMcc() == 0 && subscriptionInfo.getMnc() == 0) {
- Configuration config = mContext.getResources().getConfiguration();
- configuration.mcc = config.mcc;
- configuration.mnc = config.mnc;
- Log.i(this, "updateVideoPauseSupport -- no mcc/mnc for sub: " + subscriptionInfo +
- " using mcc/mnc from main context: " + configuration.mcc + "/" +
- configuration.mnc);
- } else {
- Log.i(this, "updateVideoPauseSupport -- mcc/mnc for sub: " + subscriptionInfo);
-
- configuration.mcc = subscriptionInfo.getMcc();
- configuration.mnc = subscriptionInfo.getMnc();
- }
-
+ private boolean isCarrierVideoPauseSupported() {
// Check if IMS video pause is supported.
PersistableBundle b =
PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
- mIsVideoPauseSupported
- = b.getBoolean(CarrierConfigManager.KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL);
+ return b.getBoolean(CarrierConfigManager.KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL);
+ }
+
+ /**
+ * Determines from carrier config whether instant lettering is supported.
+ *
+ * @return {@code true} if instant lettering is supported, {@code false} otherwise.
+ */
+ private boolean isCarrierInstantLetteringSupported() {
+ PersistableBundle b =
+ PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+ return b.getBoolean(CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL);
+ }
+
+ /**
+ * Determines from carrier config whether merging calls is supported.
+ *
+ * @return {@code true} if merging calls is supported, {@code false} otherwise.
+ */
+ private boolean isCarrierMergeCallSupported() {
+ PersistableBundle b =
+ PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+ return b.getBoolean(CarrierConfigManager.KEY_SUPPORT_CONFERENCE_CALL_BOOL);
}
/**
@@ -260,6 +264,14 @@
public boolean isVideoPauseSupported() {
return mIsVideoCapable && mIsVideoPauseSupported;
}
+
+ /**
+ * Indicates whether this account supports merging calls (i.e. conferencing).
+ * @return {@code true} if the account supports merging calls, {@code false} otherwise.
+ */
+ public boolean isMergeCallSupported() {
+ return mIsMergeCallSupported;
+ }
}
private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
@@ -335,6 +347,37 @@
}
/**
+ * Determines if the {@link AccountEntry} associated with a {@link PhoneAccountHandle} supports
+ * merging calls.
+ *
+ * @param handle The {@link PhoneAccountHandle}.
+ * @return {@code True} if merging calls is supported.
+ */
+ boolean isMergeCallSupported(PhoneAccountHandle handle) {
+ for (AccountEntry entry : mAccounts) {
+ if (entry.getPhoneAccountHandle().equals(handle)) {
+ return entry.isMergeCallSupported();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the address (e.g. the phone number) associated with a subscription.
+ *
+ * @param handle The phone account handle to find the subscription address for.
+ * @return The address.
+ */
+ Uri getAddress(PhoneAccountHandle handle) {
+ for (AccountEntry entry : mAccounts) {
+ if (entry.getPhoneAccountHandle().equals(handle)) {
+ return entry.mAccount.getAddress();
+ }
+ }
+ return null;
+ }
+
+ /**
* Sets up all the phone accounts for SIMs on first boot.
*/
void setupOnBoot() {
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 2af10a6..9724a32 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -17,6 +17,7 @@
package com.android.services.telephony;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -26,9 +27,11 @@
import android.net.Uri;
import android.telecom.Conference;
import android.telecom.ConferenceParticipant;
+import android.telecom.Conferenceable;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
+import com.android.phone.PhoneUtils;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.gsm.GsmConnection;
@@ -46,6 +49,8 @@
private final Connection.Listener mConnectionListener = new Connection.Listener() {
@Override
public void onStateChanged(Connection c, int state) {
+ Log.v(this, "onStateChange triggered in Conf Controller : connection = "+ c
+ + " state = " + state);
recalculate();
}
@@ -65,6 +70,7 @@
private final List<TelephonyConnection> mTelephonyConnections = new ArrayList<>();
private final TelephonyConnectionService mConnectionService;
+ private boolean mTriggerRecalculate = false;
public TelephonyConferenceController(TelephonyConnectionService connectionService) {
mConnectionService = connectionService;
@@ -73,6 +79,11 @@
/** The TelephonyConference connection object. */
private TelephonyConference mTelephonyConference;
+ boolean shouldRecalculate() {
+ Log.d(this, "shouldRecalculate is " + mTriggerRecalculate);
+ return mTriggerRecalculate;
+ }
+
void add(TelephonyConnection connection) {
mTelephonyConnections.add(connection);
connection.addConnectionListener(mConnectionListener);
@@ -82,11 +93,10 @@
void remove(Connection connection) {
connection.removeConnectionListener(mConnectionListener);
mTelephonyConnections.remove(connection);
-
recalculate();
}
- private void recalculate() {
+ void recalculate() {
recalculateConference();
recalculateConferenceable();
}
@@ -107,13 +117,15 @@
Log.v(this, "recalculateConferenceable : %d", mTelephonyConnections.size());
List<Connection> activeConnections = new ArrayList<>(mTelephonyConnections.size());
- List<Connection> backgroundConnections = new ArrayList<>(mTelephonyConnections.size());
+ List<Connection> backgroundConnections = new ArrayList<>(
+ mTelephonyConnections.size());
// Loop through and collect all calls which are active or holding
- for (Connection connection : mTelephonyConnections) {
- Log.d(this, "recalc - %s %s", connection.getState(), connection);
+ for (TelephonyConnection connection : mTelephonyConnections) {
+ Log.d(this, "recalc - %s %s supportsConf? %s", connection.getState(), connection,
+ connection.isConferenceSupported());
- if (!participatesInFullConference(connection)) {
+ if (connection.isConferenceSupported() && !participatesInFullConference(connection)) {
switch (connection.getState()) {
case Connection.STATE_ACTIVE:
activeConnections.add(connection);
@@ -149,7 +161,7 @@
List<Connection> nonConferencedConnections =
new ArrayList<>(mTelephonyConnections.size());
for (TelephonyConnection c : mTelephonyConnections) {
- if (c.getConference() == null) {
+ if (c.isConferenceSupported() && c.getConference() == null) {
nonConferencedConnections.add(c);
}
}
@@ -183,6 +195,21 @@
Log.d(this, "Recalculate conference calls %s %s.",
mTelephonyConference, conferencedConnections);
+ // Check if all conferenced connections are in Connection Service
+ boolean allConnInService = true;
+ Collection<Connection> allConnections = mConnectionService.getAllConnections();
+ for (Connection connection : conferencedConnections) {
+ Log.v (this, "Finding connection in Connection Service for " + connection);
+ if (!allConnections.contains(connection)) {
+ allConnInService = false;
+ Log.v(this, "Finding connection in Connection Service Failed");
+ break;
+ }
+ }
+
+ Log.d(this, "Is there a match for all connections in connection service " +
+ allConnInService);
+
// If this is a GSM conference and the number of connections drops below 2, we will
// terminate the conference.
if (numGsmConnections < 2) {
@@ -204,35 +231,47 @@
mTelephonyConference.removeConnection(connection);
}
}
-
- // Add any new ones
- for (Connection connection : conferencedConnections) {
- if (!existingConnections.contains(connection)) {
- mTelephonyConference.addConnection(connection);
+ if (allConnInService) {
+ mTriggerRecalculate = false;
+ // Add any new ones
+ for (Connection connection : conferencedConnections) {
+ if (!existingConnections.contains(connection)) {
+ mTelephonyConference.addConnection(connection);
+ }
}
+ } else {
+ Log.d(this, "Trigger recalculate later");
+ mTriggerRecalculate = true;
}
} else {
- mTelephonyConference = new TelephonyConference(null);
-
- for (Connection connection : conferencedConnections) {
- Log.d(this, "Adding a connection to a conference call: %s %s",
- mTelephonyConference, connection);
- mTelephonyConference.addConnection(connection);
+ if (allConnInService) {
+ mTriggerRecalculate = false;
+ mTelephonyConference = new TelephonyConference(null);
+ for (Connection connection : conferencedConnections) {
+ Log.d(this, "Adding a connection to a conference call: %s %s",
+ mTelephonyConference, connection);
+ mTelephonyConference.addConnection(connection);
+ }
+ mConnectionService.addConference(mTelephonyConference);
+ } else {
+ Log.d(this, "Trigger recalculate later");
+ mTriggerRecalculate = true;
}
-
- mConnectionService.addConference(mTelephonyConference);
}
-
- // Set the conference state to the same state as its child connections.
- Connection conferencedConnection = mTelephonyConference.getPrimaryConnection();
- if (conferencedConnection != null) {
- switch (conferencedConnection.getState()) {
- case Connection.STATE_ACTIVE:
- mTelephonyConference.setActive();
- break;
- case Connection.STATE_HOLDING:
- mTelephonyConference.setOnHold();
- break;
+ if (mTelephonyConference != null) {
+ Connection conferencedConnection = mTelephonyConference.getPrimaryConnection();
+ Log.v(this, "Primary Conferenced connection is " + conferencedConnection);
+ if (conferencedConnection != null) {
+ switch (conferencedConnection.getState()) {
+ case Connection.STATE_ACTIVE:
+ Log.v(this, "Setting conference to active");
+ mTelephonyConference.setActive();
+ break;
+ case Connection.STATE_HOLDING:
+ Log.v(this, "Setting conference to hold");
+ mTelephonyConference.setOnHold();
+ break;
+ }
}
}
}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index cbe7c0a..50f3c38 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -16,11 +16,11 @@
package com.android.services.telephony;
-import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncResult;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.telecom.CallAudioState;
@@ -29,16 +29,23 @@
import android.telecom.PhoneAccount;
import android.telecom.StatusHints;
+import com.android.ims.ImsCallProfile;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection.PostDialListener;
+import com.android.internal.telephony.gsm.SuppServiceNotification;
+
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.phone.R;
import java.lang.Override;
+import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -53,6 +60,14 @@
private static final int MSG_DISCONNECT = 4;
private static final int MSG_MULTIPARTY_STATE_CHANGED = 5;
private static final int MSG_CONFERENCE_MERGE_FAILED = 6;
+ private static final int MSG_SUPP_SERVICE_NOTIFY = 7;
+ /**
+ * Mappings from {@link com.android.internal.telephony.Connection} extras keys to their
+ * equivalents defined in {@link android.telecom.Connection}.
+ */
+ private static final Map<String, String> sExtrasMap = createExtrasMap();
+
+ private SuppServiceNotification mSsNotification = null;
private final Handler mHandler = new Handler() {
@Override
@@ -72,7 +87,7 @@
((connection.getAddress() != null &&
mOriginalConnection.getAddress() != null &&
mOriginalConnection.getAddress().contains(connection.getAddress())) ||
- connection.getStateBeforeHandover() == mOriginalConnection.getState())) {
+ connection.getState() == mOriginalConnection.getStateBeforeHandover())) {
Log.d(TelephonyConnection.this,
"SettingOriginalConnection " + mOriginalConnection.toString()
+ " with " + connection.toString());
@@ -108,6 +123,24 @@
case MSG_CONFERENCE_MERGE_FAILED:
notifyConferenceMergeFailed();
break;
+ case MSG_SUPP_SERVICE_NOTIFY:
+ Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : "
+ +getPhone().getPhoneId());
+ if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
+ mSsNotification =
+ (SuppServiceNotification)((AsyncResult) msg.obj).result;
+ if (mOriginalConnection != null && mSsNotification.history != null) {
+ Bundle extras = getExtras();
+ if (extras != null) {
+ Log.v(TelephonyConnection.this,
+ "Updating call history info in extras.");
+ extras.putStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER,
+ new ArrayList(Arrays.asList(mSsNotification.history)));
+ setExtras(extras);
+ }
+ }
+ }
+ break;
}
}
};
@@ -236,6 +269,7 @@
private com.android.internal.telephony.Connection mOriginalConnection;
private Call.State mOriginalConnectionState = Call.State.IDLE;
+ private Bundle mOriginalConnectionExtras = new Bundle();
private boolean mWasImsConnection;
@@ -284,6 +318,11 @@
private boolean mIsVideoPauseSupported;
/**
+ * Indicates whether this connection supports being a part of a conference..
+ */
+ private boolean mIsConferenceSupported;
+
+ /**
* Listeners to our TelephonyConnection specific callbacks
*/
private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
@@ -554,7 +593,7 @@
void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
clearOriginalConnection();
-
+ mOriginalConnectionExtras.clear();
mOriginalConnection = originalConnection;
getPhone().registerForPreciseCallStateChanged(
mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
@@ -562,6 +601,7 @@
mHandler, MSG_HANDOVER_STATE_CHANGED, null);
getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
+ getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
mOriginalConnection.addPostDialListener(mPostDialListener);
mOriginalConnection.addListener(mOriginalConnectionListener);
@@ -593,6 +633,7 @@
getPhone().unregisterForRingbackTone(mHandler);
getPhone().unregisterForHandoverStateChanged(mHandler);
getPhone().unregisterForDisconnect(mHandler);
+ getPhone().unregisterForSuppServiceNotification(mHandler);
}
mOriginalConnection.removePostDialListener(mPostDialListener);
mOriginalConnection.removeListener(mOriginalConnectionListener);
@@ -707,6 +748,73 @@
return true;
}
+ protected void updateExtras() {
+ Bundle extras = null;
+ if (mOriginalConnection != null) {
+ extras = mOriginalConnection.getExtras();
+ if (extras != null) {
+ // Check if extras have changed and need updating.
+ if (!areBundlesEqual(mOriginalConnectionExtras, extras)) {
+ if (Log.DEBUG) {
+ Log.d(TelephonyConnection.this, "Updating extras:");
+ for (String key : extras.keySet()) {
+ Object value = extras.get(key);
+ if (value instanceof String) {
+ Log.d(this, "updateExtras Key=" + Log.pii(key) +
+ " value=" + Log.pii((String)value));
+ }
+ }
+ }
+ mOriginalConnectionExtras.clear();
+
+ mOriginalConnectionExtras.putAll(extras);
+
+ // Remap any string extras that have a remapping defined.
+ for (String key : mOriginalConnectionExtras.keySet()) {
+ if (sExtrasMap.containsKey(key)) {
+ String newKey = sExtrasMap.get(key);
+ mOriginalConnectionExtras.putString(newKey, extras.getString(key));
+ mOriginalConnectionExtras.remove(key);
+ }
+ }
+
+ // Ensure extras are propagated to Telecom.
+ Bundle connectionExtras = getExtras();
+ if (connectionExtras == null) {
+ connectionExtras = new Bundle();
+ }
+ connectionExtras.putAll(mOriginalConnectionExtras);
+ setExtras(connectionExtras);
+ } else {
+ Log.d(this, "Extras update not required");
+ }
+ } else {
+ Log.d(this, "updateExtras extras: " + Log.pii(extras));
+ }
+ }
+ }
+
+ private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
+ if (extras == null || newExtras == null) {
+ return extras == newExtras;
+ }
+
+ if (extras.size() != newExtras.size()) {
+ return false;
+ }
+
+ for(String key : extras.keySet()) {
+ if (key != null) {
+ final Object value = extras.get(key);
+ final Object newValue = newExtras.get(key);
+ if (!Objects.equals(value, newValue)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
void updateState() {
if (mOriginalConnection == null) {
return;
@@ -747,6 +855,7 @@
updateConnectionCapabilities();
updateAddress();
updateMultiparty();
+ updateExtras();
}
/**
@@ -940,6 +1049,22 @@
}
/**
+ * Sets whether this connection supports conference calling.
+ * @param isConferenceSupported {@code true} if conference calling is supported by this
+ * connection, {@code false} otherwise.
+ */
+ public void setConferenceSupported(boolean isConferenceSupported) {
+ mIsConferenceSupported = isConferenceSupported;
+ }
+
+ /**
+ * @return {@code true} if this connection supports merging calls into a conference.
+ */
+ public boolean isConferenceSupported() {
+ return mIsConferenceSupported;
+ }
+
+ /**
* Whether the original connection is an IMS connection.
* @return {@code True} if the original connection is an IMS connection, {@code false}
* otherwise.
@@ -1038,6 +1163,23 @@
}
}
+
+ /**
+ * Provides a mapping from extras keys which may be found in the
+ * {@link com.android.internal.telephony.Connection} to their equivalents defined in
+ * {@link android.telecom.Connection}.
+ *
+ * @return Map containing key mappings.
+ */
+ private static Map<String, String> createExtrasMap() {
+ Map<String, String> result = new HashMap<String, String>();
+ result.put(ImsCallProfile.EXTRA_CHILD_NUMBER,
+ android.telecom.Connection.EXTRA_CHILD_ADDRESS);
+ result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT,
+ android.telecom.Connection.EXTRA_CALL_SUBJECT);
+ return Collections.unmodifiableMap(result);
+ }
+
/**
* Creates a string representation of this {@link TelephonyConnection}. Primarily intended for
* use in log statements.
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 9c53898..e3807b8 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -16,11 +16,10 @@
package com.android.services.telephony;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.net.Uri;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@@ -30,13 +29,14 @@
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneFactory;
@@ -49,7 +49,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -176,6 +175,38 @@
// Get the right phone object from the account data passed in.
final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
if (phone == null) {
+ final Context context = getApplicationContext();
+ if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) {
+ // Check SIM card state before the outgoing call.
+ // Start the SIM unlock activity if PIN_REQUIRED.
+ final Phone defaultPhone = PhoneFactory.getDefaultPhone();
+ final IccCard icc = defaultPhone.getIccCard();
+ IccCardConstants.State simState = IccCardConstants.State.UNKNOWN;
+ if (icc != null) {
+ simState = icc.getState();
+ }
+ if (simState == IccCardConstants.State.PIN_REQUIRED) {
+ final String simUnlockUiPackage = context.getResources().getString(
+ R.string.config_simUnlockUiPackage);
+ final String simUnlockUiClass = context.getResources().getString(
+ R.string.config_simUnlockUiClass);
+ if (simUnlockUiPackage != null && simUnlockUiClass != null) {
+ Intent simUnlockIntent = new Intent().setComponent(new ComponentName(
+ simUnlockUiPackage, simUnlockUiClass));
+ simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ context.startActivity(simUnlockIntent);
+ } catch (ActivityNotFoundException exception) {
+ Log.e(this, exception, "Unable to find SIM unlock UI activity.");
+ }
+ }
+ return Connection.createFailedConnection(
+ DisconnectCauseUtil.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.OUT_OF_SERVICE,
+ "SIM_STATE_PIN_REQUIRED"));
+ }
+ }
+
Log.d(this, "onCreateOutgoingConnection, phone is null");
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
@@ -202,10 +233,15 @@
case ServiceState.STATE_EMERGENCY_ONLY:
break;
case ServiceState.STATE_OUT_OF_SERVICE:
- return Connection.createFailedConnection(
- DisconnectCauseUtil.toTelecomDisconnectCause(
- android.telephony.DisconnectCause.OUT_OF_SERVICE,
- "ServiceState.STATE_OUT_OF_SERVICE"));
+ if (phone.isUtEnabled() && number.endsWith("#")) {
+ Log.d(this, "onCreateOutgoingConnection dial for UT");
+ break;
+ } else {
+ return Connection.createFailedConnection(
+ DisconnectCauseUtil.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.OUT_OF_SERVICE,
+ "ServiceState.STATE_OUT_OF_SERVICE"));
+ }
case ServiceState.STATE_POWER_OFF:
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
@@ -305,6 +341,13 @@
}
@Override
+ public void triggerConferenceRecalculate() {
+ if (mTelephonyConferenceController.shouldRecalculate()) {
+ mTelephonyConferenceController.recalculate();
+ }
+ }
+
+ @Override
public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
ConnectionRequest request) {
Log.i(this, "onCreateUnknownConnection, request: " + request);
@@ -323,9 +366,16 @@
allConnections.addAll(ringingCall.getConnections());
}
final Call foregroundCall = phone.getForegroundCall();
- if (foregroundCall.hasConnections()) {
+ if ((foregroundCall.getState() != Call.State.DISCONNECTED)
+ && (foregroundCall.hasConnections())) {
allConnections.addAll(foregroundCall.getConnections());
}
+ if (phone.getImsPhone() != null) {
+ final Call imsFgCall = phone.getImsPhone().getForegroundCall();
+ if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall.hasConnections()) {
+ allConnections.addAll(imsFgCall.getConnections());
+ }
+ }
final Call backgroundCall = phone.getBackgroundCall();
if (backgroundCall.hasConnections()) {
allConnections.addAll(phone.getBackgroundCall().getConnections());
@@ -335,6 +385,7 @@
for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
if (!isOriginalConnectionKnown(telephonyConnection)) {
unknownConnection = telephonyConnection;
+ Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection);
break;
}
}
@@ -425,6 +476,9 @@
returnConnection.setVideoPauseSupported(
TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
phoneAccountHandle));
+ returnConnection.setConferenceSupported(
+ TelecomAccountRegistry.getInstance(this).isMergeCallSupported(
+ phoneAccountHandle));
}
return returnConnection;
}