Merge "Moving carrier checking privilege logic into UiccCarrierPrivilegeRules." into lmp-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f54b63a..ee47f18 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -567,7 +567,7 @@
</service>
<service
android:singleUser="true"
- android:name="com.android.services.telephony.PstnConnectionService"
+ android:name="com.android.services.telephony.TelephonyConnectionService"
android:label="@string/pstn_connection_service_label">
<intent-filter>
<action android:name="android.telecomm.ConnectionService" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 5dea4dd..59d9728 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -142,7 +142,7 @@
<!-- Default connection service setting.
TODO: This is GSM specific. Need to define a generic "use the builtin SIMs" Connection
Service and use it as the default. -->
- <string name="connection_service_default" translatable="false">com.android.phone/com.android.services.telephony.PstnConnectionService</string>
+ <string name="connection_service_default" translatable="false">com.android.phone/com.android.services.telephony.TelephonyConnectionService</string>
<!-- Does not display additional call seting for IMS phone based on GSM Phone -->
<bool name="config_additional_call_setting">true</bool>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4caeef2..2825a7e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -115,7 +115,7 @@
<!-- settings strings -->
<!-- Error message for users that aren't allowed to modify Mobile Network settings [CHAR LIMIT=none] -->
- <string name="mobile_network_settings_not_available">Mobile network settings are not available for this user</string>
+ <string name="mobile_network_settings_not_available">Cellular network settings are not available for this user</string>
<!-- GSM Call settings screen, setting option name -->
<string name="labelGSMMore">GSM call settings</string>
<!-- CDM Call settings screen, setting option name -->
@@ -278,7 +278,7 @@
<!-- networks setting strings --><skip/>
<!-- Mobile network settings screen title -->
- <string name="mobile_networks">Mobile network settings</string>
+ <string name="mobile_networks">Cellular network settings</string>
<!-- Available networks screen title/heading -->
<string name="label_available">Available networks</string>
<!-- Mobile network settings screen, toast when searching for available networks -->
@@ -371,8 +371,8 @@
</string-array>
<!-- Mobile network settings screen, data enabling checkbox name -->
<string name="data_enabled">Data enabled</string>
- <!-- Mobile network settings screen, setting summary text when check box is not selected (explains what selecting it would do) -->
- <string name="data_enable_summary">Enable data access over Mobile network</string>
+ <!-- Mobile network settings screen, setting summary text when check box is not selected (explains what selecting it would do) [CHAR LIMITS=40] -->
+ <string name="data_enable_summary">Allow data usage</string>
<!-- Mobile network settings screen, setting check box name -->
<string name="roaming">Data roaming</string>
<!-- Mobile network settings screen, setting summary text when check box is selected -->
@@ -413,7 +413,7 @@
<string name="throttle_rate_subtext">Data rate reduced to <xliff:g id="used">%1$d</xliff:g> Kb/s if data use limit is exceeded</string>
- <string name="throttle_help_subtext">More information about your carrier\'s mobile network data use policy</string>
+ <string name="throttle_help_subtext">More information about your carrier\'s cellular network data use policy</string>
<string name="cell_broadcast_sms">Cell Broadcast SMS</string>
@@ -892,7 +892,7 @@
This string is currently unused (see comments in InCallScreen.java.) -->
<string name="incall_error_emergency_only">Not registered on network.</string>
<!-- In-call screen: call failure message displayed in an error dialog -->
- <string name="incall_error_out_of_service">Mobile network not available.</string>
+ <string name="incall_error_out_of_service">Cellular network not available.</string>
<!-- In-call screen: call failure message displayed in an error dialog -->
<string name="incall_error_no_phone_number_supplied">Call not sent, no valid number entered.</string>
<!-- In-call screen: call failure message displayed in an error dialog -->
@@ -1196,15 +1196,6 @@
Do not translate. -->
<string name="call_settings_title_font_family">sans-serif-light</string>
- <!-- Title of the dialog to choose WiFi calling. -->
- <string name="choose_wifi_for_call_msg">Use Wi-Fi to place call?</string>
-
- <!-- Title for button to use WiFi calling. -->
- <string name="choose_wifi_for_call_yes">Yes</string>
-
- <!-- Title for button to not use WiFi calling. -->
- <string name="choose_wifi_for_call_no">No</string>
-
<!-- Label for PSTN connection service. -->
<string name="pstn_connection_service_label">Built-in SIM cards</string>
diff --git a/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java b/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
index 62b8266..5ca050e 100644
--- a/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
+++ b/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
@@ -62,8 +62,8 @@
Intent telecommIntent = new Intent(TelecommConstants.ACTION_INCOMING_CALL);
telecommIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- telecommIntent.putExtra(TelecommConstants.EXTRA_CALL_SERVICE_DESCRIPTOR,
- SipCallServiceProvider.getDescriptor(context));
+ telecommIntent.putExtra(TelecommConstants.EXTRA_PHONE_ACCOUNT,
+ SipConnectionService.getPhoneAccount(context));
telecommIntent.putExtra(TelecommConstants.EXTRA_INCOMING_CALL_EXTRAS, extras);
context.startActivityAsUser(telecommIntent, UserHandle.CURRENT);
diff --git a/sip/src/com/android/services/telephony/sip/SipCallServiceProvider.java b/sip/src/com/android/services/telephony/sip/SipCallServiceProvider.java
deleted file mode 100644
index cc467e9..0000000
--- a/sip/src/com/android/services/telephony/sip/SipCallServiceProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2014 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.services.telephony.sip;
-
-import android.content.Context;
-import android.telecomm.CallServiceDescriptor;
-import android.telecomm.CallServiceLookupResponse;
-import android.telecomm.CallServiceProvider;
-
-import java.util.ArrayList;
-
-public class SipCallServiceProvider extends CallServiceProvider {
- @Override
- public void lookupCallServices(CallServiceLookupResponse response) {
- ArrayList<CallServiceDescriptor> descriptors = new ArrayList<CallServiceDescriptor>();
- descriptors.add(getDescriptor(this));
- response.setCallServiceDescriptors(descriptors);
- }
-
- static CallServiceDescriptor getDescriptor(Context context) {
- return CallServiceDescriptor.newBuilder(context)
- .setConnectionService(SipConnectionService.class)
- .setNetworkType(CallServiceDescriptor.FLAG_WIFI | CallServiceDescriptor.FLAG_MOBILE)
- .build();
- }
-}
diff --git a/sip/src/com/android/services/telephony/sip/SipConnection.java b/sip/src/com/android/services/telephony/sip/SipConnection.java
index 5b065fd..5bca97b 100644
--- a/sip/src/com/android/services/telephony/sip/SipConnection.java
+++ b/sip/src/com/android/services/telephony/sip/SipConnection.java
@@ -40,7 +40,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_PRECISE_CALL_STATE_CHANGED:
- updateState();
+ updateState(false);
break;
}
}
@@ -210,14 +210,14 @@
call.getEarliestConnection() == mOriginalConnection;
}
- private void updateState() {
+ private void updateState(boolean force) {
if (mOriginalConnection == null) {
return;
}
Call.State newState = mOriginalConnection.getState();
if (VERBOSE) log("updateState, " + mOriginalConnectionState + " -> " + newState);
- if (mOriginalConnectionState != newState) {
+ if (force || mOriginalConnectionState != newState) {
mOriginalConnectionState = newState;
switch (newState) {
case IDLE:
@@ -243,7 +243,7 @@
case DISCONNECTING:
break;
}
- updateCallCapabilities();
+ updateCallCapabilities(force);
}
}
@@ -255,16 +255,17 @@
return capabilities;
}
- void updateCallCapabilities() {
+ void updateCallCapabilities(boolean force) {
int newCallCapabilities = buildCallCapabilities();
- if (getCallCapabilities() != newCallCapabilities) {
+ if (force || getCallCapabilities() != newCallCapabilities) {
setCallCapabilities(newCallCapabilities);
}
}
void onAddedToCallService() {
if (VERBOSE) log("onAddedToCallService");
- updateCallCapabilities();
+ updateState(true);
+ updateCallCapabilities(true);
setAudioModeIsVoip(true);
if (mOriginalConnection != null) {
setCallerDisplayName(mOriginalConnection.getCnapName(),
diff --git a/sip/src/com/android/services/telephony/sip/SipConnectionService.java b/sip/src/com/android/services/telephony/sip/SipConnectionService.java
index 513fbfd..a46180d 100644
--- a/sip/src/com/android/services/telephony/sip/SipConnectionService.java
+++ b/sip/src/com/android/services/telephony/sip/SipConnectionService.java
@@ -16,6 +16,7 @@
package com.android.services.telephony.sip;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.sip.SipAudioCall;
@@ -28,6 +29,7 @@
import android.telecomm.Connection;
import android.telecomm.ConnectionRequest;
import android.telecomm.ConnectionService;
+import android.telecomm.PhoneAccount;
import android.telecomm.Response;
import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
@@ -43,16 +45,24 @@
private static final String PREFIX = "[SipConnectionService] ";
private static final boolean VERBOSE = true; /* STOP SHIP if true */
+ static PhoneAccount getPhoneAccount(Context context) {
+ return new PhoneAccount(
+ new ComponentName(context, SipConnectionService.class),
+ null /* id */,
+ null /* handle */,
+ PhoneAccount.CAPABILITY_CALL_PROVIDER);
+ }
+
@Override
- protected void onCreateConnections(
+ protected void onCreateOutgoingConnection(
final ConnectionRequest request,
- final OutgoingCallResponse<Connection> response) {
- if (VERBOSE) log("onCreateConnections, request: " + request);
+ final CreateConnectionResponse<Connection> response) {
+ if (VERBOSE) log("onCreateOutgoingConnection, request: " + request);
SipProfileChooser.Callback callback = new SipProfileChooser.Callback() {
@Override
public void onSipChosen(SipProfile profile) {
- if (VERBOSE) log("onCreateConnections, onSipChosen: " + profile);
+ if (VERBOSE) log("onCreateOutgoingConnection, onSipChosen: " + profile);
SipConnection connection = createConnectionForProfile(profile, request);
if (connection == null) {
response.onCancel(request);
@@ -63,13 +73,13 @@
@Override
public void onSipNotChosen() {
- if (VERBOSE) log("onCreateConnections, onSipNotChosen");
+ if (VERBOSE) log("onCreateOutgoingConnection, onSipNotChosen");
response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
}
@Override
public void onCancelCall() {
- if (VERBOSE) log("onCreateConnections, onCancelCall");
+ if (VERBOSE) log("onCreateOutgoingConnection, onCancelCall");
response.onCancel(request);
}
};
@@ -89,14 +99,20 @@
@Override
protected void onCreateIncomingConnection(
ConnectionRequest request,
- Response<ConnectionRequest, Connection> response) {
+ CreateConnectionResponse<Connection> response) {
if (VERBOSE) log("onCreateIncomingConnection, request: " + request);
+ if (request.getExtras() == null) {
+ if (VERBOSE) log("onCreateIncomingConnection, no extras");
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+ return;
+ }
+
Intent sipIntent = (Intent) request.getExtras().getParcelable(
SipUtil.EXTRA_INCOMING_CALL_INTENT);
if (sipIntent == null) {
if (VERBOSE) log("onCreateIncomingConnection, no SIP intent");
- response.onError(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
return;
}
@@ -104,8 +120,8 @@
try {
sipAudioCall = SipManager.newInstance(this).takeAudioCall(sipIntent, null);
} catch (SipException e) {
- log("onCreateConferenceConnection, takeAudioCall exception: " + e);
- response.onError(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+ log("onCreateIncomingConnection, takeAudioCall exception: " + e);
+ response.onCancel(request);
return;
}
@@ -119,11 +135,11 @@
if (VERBOSE) log("onCreateIncomingConnection, new connection: " + originalConnection);
if (originalConnection != null) {
SipConnection connection = new SipConnection(originalConnection);
- response.onResult(getConnectionRequestForIncomingCall(request, originalConnection),
+ response.onSuccess(getConnectionRequestForIncomingCall(request, originalConnection),
connection);
} else {
if (VERBOSE) log("onCreateIncomingConnection, takingIncomingCall failed");
- response.onError(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+ response.onCancel(request);
}
}
}
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 95d648c..c3d0191 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -2251,7 +2251,7 @@
CharSequence label = mConnectionServiceLabelByComponentName.get(
value == null ? mConnectionService.getValue() : value);
if (label == null) {
- Log.wtf(LOG_TAG, "Unknown default connection service entry " +
+ Log.w(LOG_TAG, "Unknown default connection service entry " +
mConnectionService.getValue());
mConnectionService.setSummary("");
}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 721f7e2..5e8c9f5 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1693,7 +1693,7 @@
@Override
public void enableSimplifiedNetworkSettings(long subId, boolean enable) {
- enforceModifyPermission();
+ enforceModifyPermissionOrCarrierPrivilege();
if (enable) {
mSimplifiedNetworkSettings.add(subId);
} else {
@@ -1709,7 +1709,7 @@
@Override
public void setLine1NumberForDisplay(long subId, String alphaTag, String number) {
- enforceModifyPermission();
+ enforceModifyPermissionOrCarrierPrivilege();
mAdnRecordsForDisplay.put(subId, new AdnRecord(alphaTag, number));
}
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index abd0a08..52717f9 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -19,34 +19,32 @@
import android.telecomm.CallCapabilities;
import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
/**
* Manages a single phone call handled by CDMA.
*/
-public class CdmaConnection extends PstnConnection {
-
- public CdmaConnection(Phone phone, Connection connection) {
- super(phone, connection);
+final class CdmaConnection extends TelephonyConnection {
+ CdmaConnection(Connection connection) {
+ super(connection);
}
/** {@inheritDoc} */
@Override
- public void onPlayDtmfTone(char digit) {
+ protected void onPlayDtmfTone(char digit) {
// TODO(santoscordon): There are conditions where we should play dtmf tones with different
// timeouts.
// TODO(santoscordon): We get explicit response from the phone via a Message when the burst
// tone has completed. During this time we can get subsequent requests. We need to stop
// passing in null as the message and start handling it to implement a queue.
- getPhone().sendBurstDtmf(Character.toString(digit), 0, 0, null);
- super.onPlayDtmfTone(digit);
+ if (getPhone() != null) {
+ getPhone().sendBurstDtmf(Character.toString(digit), 0, 0, null);
+ }
}
/** {@inheritDoc} */
@Override
- public void onStopDtmfTone() {
+ protected void onStopDtmfTone() {
// no-op, we only play timed dtmf tones for cdma.
- super.onStopDtmfTone();
}
@Override
diff --git a/src/com/android/services/telephony/ConferenceConnection.java b/src/com/android/services/telephony/ConferenceConnection.java
index 3d62f6a..dbcdbfe 100644
--- a/src/com/android/services/telephony/ConferenceConnection.java
+++ b/src/com/android/services/telephony/ConferenceConnection.java
@@ -26,7 +26,7 @@
/**
* Manages state for a conference call.
*/
-class ConferenceConnection extends Connection {
+final class ConferenceConnection extends Connection {
@Override
protected void onChildrenChanged(List<Connection> children) {
if (children.isEmpty()) {
diff --git a/src/com/android/services/telephony/GsmConferenceController.java b/src/com/android/services/telephony/GsmConferenceController.java
index 593c2dc..375f34d 100644
--- a/src/com/android/services/telephony/GsmConferenceController.java
+++ b/src/com/android/services/telephony/GsmConferenceController.java
@@ -28,7 +28,8 @@
* Maintains a list of all the known GSM connections and implements GSM-specific conference
* call functionality.
*/
-public class GsmConferenceController {
+final class GsmConferenceController {
+ private static GsmConferenceController sInstance;
private final Connection.Listener mConnectionListener = new Connection.Listener() {
@Override
@@ -54,27 +55,41 @@
/** The GSM conference connection object. */
private ConferenceConnection mGsmConferenceConnection;
- public void add(GsmConnection connection) {
- connection.addConnectionListener(mConnectionListener);
- mGsmConnections.add(connection);
- recalculate();
+ static void add(GsmConnection connection) {
+ if (sInstance == null) {
+ sInstance = new GsmConferenceController();
+ }
+ connection.addConnectionListener(sInstance.mConnectionListener);
+ sInstance.mGsmConnections.add(connection);
+ sInstance.recalculate();
}
- public void remove(GsmConnection connection) {
- connection.removeConnectionListener(mConnectionListener);
- mGsmConnections.remove(connection);
- recalculate();
+ static void remove(GsmConnection connection) {
+ if (sInstance != null) {
+ connection.removeConnectionListener(sInstance.mConnectionListener);
+ sInstance.mGsmConnections.remove(connection);
+ sInstance.recalculate();
+
+ if (sInstance.mGsmConnections.size() == 0 &&
+ sInstance.mGsmConferenceConnection == null) {
+ sInstance = null;
+ }
+ }
}
- public ConferenceConnection createConferenceConnection(Connection rootConnection) {
- if (mGsmConferenceConnection == null) {
- mGsmConferenceConnection = new ConferenceConnection();
- Log.d(this, "creating the conference connection: %s", mGsmConferenceConnection);
+ static ConferenceConnection createConferenceConnection(Connection rootConnection) {
+ if (sInstance != null) {
+ if (sInstance.mGsmConferenceConnection == null) {
+ sInstance.mGsmConferenceConnection = new ConferenceConnection();
+ Log.d(sInstance, "creating the conference connection: %s",
+ sInstance.mGsmConferenceConnection);
+ }
+ if (rootConnection instanceof GsmConnection) {
+ ((GsmConnection) rootConnection).performConference();
+ }
+ return sInstance.mGsmConferenceConnection;
}
- if (rootConnection instanceof GsmConnection) {
- ((GsmConnection) rootConnection).performConference();
- }
- return mGsmConferenceConnection;
+ return null;
}
/**
diff --git a/src/com/android/services/telephony/GsmConnection.java b/src/com/android/services/telephony/GsmConnection.java
index f7ab344..8b02b52 100644
--- a/src/com/android/services/telephony/GsmConnection.java
+++ b/src/com/android/services/telephony/GsmConnection.java
@@ -20,45 +20,49 @@
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
/**
* Manages a single phone call handled by GSM.
*/
-public class GsmConnection extends PstnConnection {
+final class GsmConnection extends TelephonyConnection {
private boolean mIsConferenceCapable;
- public GsmConnection(Phone phone, Connection connection) {
- super(phone, connection);
+ GsmConnection(Connection connection) {
+ super(connection);
+ GsmConferenceController.add(this);
}
/** {@inheritDoc} */
@Override
- public void onPlayDtmfTone(char digit) {
- getPhone().startDtmf(digit);
- super.onPlayDtmfTone(digit);
+ protected void onPlayDtmfTone(char digit) {
+ if (getPhone() != null) {
+ getPhone().startDtmf(digit);
+ }
}
/** {@inheritDoc} */
@Override
- public void onStopDtmfTone() {
- getPhone().stopDtmf();
- super.onStopDtmfTone();
+ protected void onStopDtmfTone() {
+ if (getPhone() != null) {
+ getPhone().stopDtmf();
+ }
}
void setIsConferenceCapable(boolean isConferenceCapable) {
if (mIsConferenceCapable != isConferenceCapable) {
mIsConferenceCapable = isConferenceCapable;
- updateCallCapabilities();
+ updateCallCapabilities(false);
}
}
- public void performConference() {
- try {
- Log.d(this, "conference - %s", this);
- getPhone().conference();
- } catch (CallStateException e) {
- Log.e(this, e, "Failed to conference call.");
+ void performConference() {
+ Log.d(this, "performConference - %s", this);
+ if (getPhone() != null) {
+ try {
+ getPhone().conference();
+ } catch (CallStateException e) {
+ Log.e(this, e, "Failed to conference call.");
+ }
}
}
@@ -73,4 +77,10 @@
}
return capabilities;
}
+
+ @Override
+ void onRemovedFromCallService() {
+ super.onRemovedFromCallService();
+ GsmConferenceController.remove(this);
+ }
}
diff --git a/src/com/android/services/telephony/PstnConnection.java b/src/com/android/services/telephony/PstnConnection.java
deleted file mode 100644
index b11c9d0..0000000
--- a/src/com/android/services/telephony/PstnConnection.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2014 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.services.telephony;
-
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Message;
-
-import android.telephony.DisconnectCause;
-
-import com.android.internal.telephony.Call;
-import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
-
-/**
- * Manages a single phone call handled by the PSTN infrastructure.
- */
-public abstract class PstnConnection extends TelephonyConnection {
-
- private static final int EVENT_RINGBACK_TONE = 1;
-
- private final Handler mHandler = new Handler() {
- private Connection getForegroundConnection() {
- return mPhone.getForegroundCall().getEarliestConnection();
- }
-
- public void handleMessage(Message msg) {
- // TODO: This code assumes that there is only one connection in the foreground call,
- // in other words, it punts on network-mediated conference calling.
- if (getOriginalConnection() != getForegroundConnection()) {
- return;
- }
- switch (msg.what) {
- case EVENT_RINGBACK_TONE:
- setRequestingRingback((Boolean) ((AsyncResult) msg.obj).result);
- break;
- }
- }
- };
-
- private final Phone mPhone;
-
- public PstnConnection(Phone phone, Connection connection) {
- super(connection);
- mPhone = phone;
- phone.registerForRingbackTone(mHandler, EVENT_RINGBACK_TONE, null);
- }
-
- /** {@inheritDoc} */
- @Override
- protected void onAnswer() {
- // TODO(santoscordon): Tons of hairy logic is missing here around multiple active calls on
- // CDMA devices. See {@link CallManager.acceptCall}.
-
- Log.i(this, "Answer call.");
- if (isValidRingingCall(getOriginalConnection())) {
- try {
- mPhone.acceptCall();
- } catch (CallStateException e) {
- Log.e(this, e, "Failed to accept call.");
- }
- }
- super.onAnswer();
- }
-
- /** {@inheritDoc} */
- @Override
- protected void onReject() {
- Log.i(this, "Reject call.");
- if (isValidRingingCall(getOriginalConnection())) {
- hangup(DisconnectCause.INCOMING_REJECTED);
- }
- super.onReject();
- }
-
- /** {@inheritDoc} */
- @Override
- protected void onDisconnect() {
- mPhone.unregisterForRingbackTone(mHandler);
- super.onDisconnect();
- }
-
- @Override
- public void onPostDialContinue(boolean proceed) {
- if (proceed) {
- getOriginalConnection().proceedAfterWaitChar();
- } else {
- getOriginalConnection().cancelPostDial();
- }
- }
-
- protected Phone getPhone() {
- return mPhone;
- }
-
- /**
- * Checks to see if the specified low-level Telephony {@link Connection} corresponds to an
- * active incoming call. Returns false if there is no such actual call, or if the
- * associated call is not incoming (See {@link Call.State#isRinging}).
- *
- * @param connection The connection to ask about.
- */
- private boolean isValidRingingCall(Connection connection) {
- Call ringingCall = mPhone.getRingingCall();
-
- if (ringingCall.getState().isRinging()) {
- // The ringingCall object is always not-null so we have to check its current state.
- if (ringingCall.getEarliestConnection() == connection) {
- // The ringing connection is the same one for this call. We have a match!
- return true;
- } else {
- Log.w(this, "A ringing connection exists, but it is not the same connection.");
- }
- } else {
- Log.i(this, "There is no longer a ringing call.");
- }
-
- return false;
- }
-}
diff --git a/src/com/android/services/telephony/PstnConnectionService.java b/src/com/android/services/telephony/PstnConnectionService.java
deleted file mode 100644
index a38fc8d..0000000
--- a/src/com/android/services/telephony/PstnConnectionService.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2014 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.services.telephony;
-
-import android.net.Uri;
-
-import android.telephony.DisconnectCause;
-import android.telecomm.CallCapabilities;
-import android.telecomm.Connection;
-import android.telecomm.ConnectionRequest;
-import android.telecomm.Response;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-
-import com.android.internal.telephony.Call;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.phone.Constants;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * The connection service for making PSTN connections using built-in SIM cards.
- */
-public final class PstnConnectionService extends TelephonyConnectionService {
-
- private EmergencyCallHelper mEmergencyCallHelper;
-
- private final Set<ConnectionRequest> mPendingOutgoingEmergencyCalls = new HashSet<>();
-
- private final GsmConferenceController mGsmConferenceController = new GsmConferenceController();
-
- @Override
- public void onCreate() {
- super.onCreate();
- mEmergencyCallHelper = new EmergencyCallHelper(this);
- }
-
- /** {@inheritDoc} */
- @Override
- public void onCreateConnections(
- final ConnectionRequest request,
- final OutgoingCallResponse<Connection> response) {
-
- if (!canCall(request.getHandle())) {
- Log.d(this, "Cannot place the call with %s", this.getClass().getSimpleName());
- respondWithError(
- request,
- response,
- DisconnectCause.ERROR_UNSPECIFIED, // TODO: Code for "ConnSvc cannot call"
- "Cannot place call.");
- return;
- }
-
- // TODO: Consider passing call emergency information as part of ConnectionRequest so
- // that we do not have to make the check here once again.
- String handle = request.getHandle().getSchemeSpecificPart();
- final Phone phone = PhoneFactory.getDefaultPhone();
- if (PhoneNumberUtils.isPotentialEmergencyNumber(handle)) {
- EmergencyCallHelper.Callback callback = new EmergencyCallHelper.Callback() {
- @Override
- public void onComplete(boolean isRadioReady) {
- if (mPendingOutgoingEmergencyCalls.remove(request)) {
- // The emergency call was still pending (not aborted) so continue with the
- // rest of the logic.
-
- if (isRadioReady) {
- startCallWithPhone(phone, request, response);
- } else {
- respondWithError(
- request,
- response,
- DisconnectCause.POWER_OFF,
- "Failed to turn on radio.");
- }
- }
- }
- };
-
- mPendingOutgoingEmergencyCalls.add(request);
-
- // If the radio is already on, this will call us back fairly quickly.
- mEmergencyCallHelper.startTurnOnRadioSequence(phone, callback);
- } else {
- startCallWithPhone(phone, request, response);
- }
- super.onCreateConnections(request, response);
- }
-
- /** {@inheritDoc} */
- @Override
- public void onCreateIncomingConnection(
- ConnectionRequest request,
- Response<ConnectionRequest, Connection> response) {
- Log.d(this, "onCreateIncomingConnection");
- Call call = PhoneFactory.getDefaultPhone().getRingingCall();
-
- // The ringing call is always not-null, check if it is truly ringing by checking its state.
- if (call.getState().isRinging()) {
- com.android.internal.telephony.Connection connection = call.getEarliestConnection();
-
- if (isConnectionKnown(connection)) {
- respondWithError(
- request,
- response,
- DisconnectCause.ERROR_UNSPECIFIED, // Internal error
- "Cannot set incoming call ID, ringing connection already registered.");
- } else {
- // Address can be null for blocked calls.
- String address = connection.getAddress();
- if (address == null) {
- address = "";
- }
-
- Uri handle = Uri.fromParts(Constants.SCHEME_TEL, address, null);
-
- TelephonyConnection telephonyConnection;
- try {
- telephonyConnection = createTelephonyConnection(
- request,
- PhoneFactory.getDefaultPhone(),
- connection);
- } catch (Exception e) {
- respondWithError(
- request,
- response,
- DisconnectCause.ERROR_UNSPECIFIED, // Internal error
- e.getMessage());
- return;
- }
-
- respondWithResult(
- new ConnectionRequest(
- request.getAccount(),
- request.getCallId(),
- handle,
- connection.getNumberPresentation(),
- request.getExtras(),
- request.getVideoState()),
- response,
- telephonyConnection);
- }
- } else {
- respondWithError(
- request,
- response,
- DisconnectCause.INCOMING_MISSED, // Most likely cause
- String.format("Found no ringing call, call state: %s", call.getState()));
- }
- super.onCreateIncomingConnection(request, response);
- }
-
- @Override
- protected void onConnectionAdded(Connection connection) {
- if (connection instanceof TelephonyConnection) {
- ((TelephonyConnection) connection).onAddedToCallService();
- }
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean canCall(Uri handle) {
- return Constants.SCHEME_TEL.equals(handle.getScheme());
- }
-
- /** {@inheritDoc} */
- @Override
- protected TelephonyConnection onCreateTelephonyConnection(
- ConnectionRequest request,
- Phone phone,
- com.android.internal.telephony.Connection connection) {
- switch (phone.getPhoneType()) {
- case TelephonyManager.PHONE_TYPE_GSM: {
- final GsmConnection gsmConn = new GsmConnection(phone, connection);
- mGsmConferenceController.add(gsmConn);
- gsmConn.addConnectionListener(new Connection.Listener() {
- @Override
- public void onDestroyed(Connection c) {
- mGsmConferenceController.remove(gsmConn);
- }
- });
- return gsmConn;
- }
- case TelephonyManager.PHONE_TYPE_CDMA:
- return new CdmaConnection(phone, connection);
- default:
- Log.d(this, "Inappropriate phone type for PSTN: %d", phone.getPhoneType());
- return null;
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public void onCreateConferenceConnection(
- String token,
- Connection connection,
- Response<String, Connection> callback) {
- // TODO: Create a more general framework for conferencing. At the moment, our goal is
- // simply not to break the previous GSM-specific conferencing functionality.
- if (connection instanceof GsmConnection || connection instanceof ConferenceConnection) {
- if ((connection.getCallCapabilities() & CallCapabilities.MERGE_CALLS) != 0) {
- callback.onResult(token,
- mGsmConferenceController.createConferenceConnection(connection));
- }
- }
- }
-}
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index 0d29ed9..e19991a 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -20,11 +20,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.Uri;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
-import android.telecomm.CallServiceDescriptor;
import android.telecomm.TelecommConstants;
import com.android.internal.telephony.Call;
@@ -143,7 +143,7 @@
// Final verification of the ringing state before sending the intent to Telecomm.
if (call != null && call.getState().isRinging()) {
- sendIncomingCallIntent();
+ sendIncomingCallIntent(connection);
}
}
}
@@ -151,16 +151,13 @@
/**
* Sends the incoming call intent to telecomm.
*/
- private void sendIncomingCallIntent() {
+ private void sendIncomingCallIntent(Connection connection) {
Context context = mPhoneProxy.getContext();
- CallServiceDescriptor.Builder builder = CallServiceDescriptor.newBuilder(context);
- builder.setConnectionService(PstnConnectionService.class);
- builder.setNetworkType(CallServiceDescriptor.FLAG_PSTN);
-
Intent intent = new Intent(TelecommConstants.ACTION_INCOMING_CALL);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(TelecommConstants.EXTRA_CALL_SERVICE_DESCRIPTOR, builder.build());
+ intent.putExtra(TelecommConstants.EXTRA_PHONE_ACCOUNT,
+ TelephonyConnectionService.getPhoneAccount(context));
Log.d(this, "Sending incoming call intent: %s", intent);
context.startActivityAsUser(intent, UserHandle.CURRENT);
diff --git a/src/com/android/services/telephony/TelephonyCallServiceProvider.java b/src/com/android/services/telephony/TelephonyCallServiceProvider.java
deleted file mode 100644
index 5225759..0000000
--- a/src/com/android/services/telephony/TelephonyCallServiceProvider.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2014 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.services.telephony;
-
-import android.telecomm.CallServiceDescriptor;
-import android.telecomm.CallServiceLookupResponse;
-import android.telecomm.CallServiceProvider;
-
-import java.util.ArrayList;
-
-/**
- * This class is used to get a list of all CallServices.
- */
-public class TelephonyCallServiceProvider extends CallServiceProvider {
- /** {@inheritDoc} */
- @Override
- public void lookupCallServices(CallServiceLookupResponse response) {
- ArrayList<CallServiceDescriptor> descriptors = new ArrayList<CallServiceDescriptor>();
- descriptors.add(CallServiceDescriptor.newBuilder(this)
- .setConnectionService(PstnConnectionService.class)
- .setNetworkType(CallServiceDescriptor.FLAG_PSTN)
- .build());
- response.setCallServiceDescriptors(descriptors);
- }
-}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 551e018..5886ef2 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -16,6 +16,8 @@
package com.android.services.telephony;
+import android.net.Uri;
+import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.telecomm.CallAudioState;
@@ -23,45 +25,87 @@
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection.PostDialListener;
import com.android.internal.telephony.Phone;
import android.telecomm.Connection;
+import java.util.List;
+import java.util.Objects;
+
/**
- * Manages a single phone call in Telephony.
+ * Base class for CDMA and GSM connections.
*/
abstract class TelephonyConnection extends Connection {
- private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 1;
+ private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
+ private static final int MSG_RINGBACK_TONE = 2;
- private final StateHandler mHandler = new StateHandler();
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PRECISE_CALL_STATE_CHANGED:
+ Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
+ updateState(false);
+ break;
+ case MSG_RINGBACK_TONE:
+ Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
+ // TODO: This code assumes that there is only one connection in the foreground
+ // call, in other words, it punts on network-mediated conference calling.
+ if (getOriginalConnection() != getForegroundConnection()) {
+ Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
+ "not foreground connection, skipping");
+ return;
+ }
+ setRequestingRingback((Boolean) ((AsyncResult) msg.obj).result);
+ break;
+ }
+ }
+ };
+
+ private final PostDialListener mPostDialListener = new PostDialListener() {
+ @Override
+ public void onPostDialWait() {
+ Log.v(TelephonyConnection.this, "onPostDialWait");
+ if (mOriginalConnection != null) {
+ setPostDialWait(mOriginalConnection.getRemainingPostDialString());
+ }
+ }
+ };
private com.android.internal.telephony.Connection mOriginalConnection;
- private Call.State mState = Call.State.IDLE;
+ private Call.State mOriginalConnectionState = Call.State.IDLE;
protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) {
+ Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
mOriginalConnection = originalConnection;
- mOriginalConnection.getCall().getPhone().registerForPreciseCallStateChanged(mHandler,
- EVENT_PRECISE_CALL_STATE_CHANGED, null);
- updateState();
- }
-
- com.android.internal.telephony.Connection getOriginalConnection() {
- return mOriginalConnection;
+ getPhone().registerForPreciseCallStateChanged(
+ mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
+ getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
+ mOriginalConnection.addPostDialListener(mPostDialListener);
}
@Override
- protected void onAbort() {
- hangup(DisconnectCause.LOCAL);
- super.onAbort();
+ protected void onSetAudioState(CallAudioState audioState) {
+ // TODO: update TTY mode.
+ if (getPhone() != null) {
+ getPhone().setEchoSuppressionEnabled();
+ }
+ }
+
+ @Override
+ protected void onSetState(int state) {
+ Log.v(this, "onSetState, state: " + Connection.stateToString(state));
}
@Override
protected void onDisconnect() {
+ Log.v(this, "onDisconnect");
hangup(DisconnectCause.LOCAL);
- super.onDisconnect();
}
@Override
protected void onSeparate() {
+ Log.v(this, "onSeparate");
if (mOriginalConnection != null) {
try {
mOriginalConnection.separate();
@@ -69,15 +113,20 @@
Log.e(this, e, "Call to Connection.separate failed with exception");
}
}
- super.onSeparate();
}
@Override
- public void onHold() {
- Log.d(this, "Attempting to put call on hold");
+ protected void onAbort() {
+ Log.v(this, "onAbort");
+ hangup(DisconnectCause.LOCAL);
+ }
+
+ @Override
+ protected void onHold() {
+ Log.v(this, "onHold");
// TODO(santoscordon): Can dialing calls be put on hold as well since they take up the
// foreground call slot?
- if (Call.State.ACTIVE == mState) {
+ if (Call.State.ACTIVE == mOriginalConnectionState) {
Log.v(this, "Holding active call");
try {
Phone phone = mOriginalConnection.getCall().getPhone();
@@ -104,13 +153,12 @@
} else {
Log.w(this, "Cannot put a call that is not currently active on hold.");
}
- super.onHold();
}
@Override
protected void onUnhold() {
- Log.d(this, "Attempting to release call from hold");
- if (Call.State.HOLDING == mState) {
+ Log.v(this, "onUnhold");
+ if (Call.State.HOLDING == mOriginalConnectionState) {
try {
// TODO: This doesn't handle multiple calls across connection services yet
mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
@@ -120,40 +168,98 @@
} else {
Log.w(this, "Cannot release a call that is not already on hold from hold.");
}
- super.onUnhold();
}
@Override
- public void onSetAudioState(CallAudioState audioState) {
- // TODO: update TTY mode.
- if (mOriginalConnection != null) {
- Call call = mOriginalConnection.getCall();
- if (call != null) {
- call.getPhone().setEchoSuppressionEnabled();
+ protected void onAnswer() {
+ Log.v(this, "onAnswer");
+ // TODO(santoscordon): Tons of hairy logic is missing here around multiple active calls on
+ // CDMA devices. See {@link CallManager.acceptCall}.
+
+ if (isValidRingingCall() && getPhone() != null) {
+ try {
+ getPhone().acceptCall();
+ } catch (CallStateException e) {
+ Log.e(this, e, "Failed to accept call.");
}
}
- super.onSetAudioState(audioState);
+ }
+
+ @Override
+ protected void onReject() {
+ Log.v(this, "onReject");
+ if (isValidRingingCall()) {
+ hangup(DisconnectCause.INCOMING_REJECTED);
+ }
+ super.onReject();
+ }
+
+ @Override
+ protected void onPostDialContinue(boolean proceed) {
+ Log.v(this, "onPostDialContinue, proceed: " + proceed);
+ if (mOriginalConnection != null) {
+ if (proceed) {
+ mOriginalConnection.proceedAfterWaitChar();
+ } else {
+ mOriginalConnection.cancelPostDial();
+ }
+ }
+ }
+
+ @Override
+ protected void onSwapWithBackgroundCall() {
+ Log.v(this, "onSwapWithBackgroundCall");
+ }
+
+ @Override
+ protected void onChildrenChanged(List<Connection> children) {
+ Log.v(this, "onChildrenChanged, children: " + children);
+ }
+
+ @Override
+ protected void onPhoneAccountClicked() {
+ Log.v(this, "onPhoneAccountClicked");
}
protected abstract int buildCallCapabilities();
- final void updateCallCapabilities() {
+ protected final void updateCallCapabilities(boolean force) {
int newCallCapabilities = buildCallCapabilities();
- if (getCallCapabilities() != newCallCapabilities) {
+ if (force || getCallCapabilities() != newCallCapabilities) {
setCallCapabilities(newCallCapabilities);
}
}
- final void onAddedToCallService() {
- updateCallCapabilities();
+ protected final void updateHandle(boolean force) {
if (mOriginalConnection != null) {
- setCallerDisplayName(
- mOriginalConnection.getCnapName(),
- mOriginalConnection.getCnapNamePresentation());
+ Uri handle = TelephonyConnectionService.getHandleFromAddress(
+ mOriginalConnection.getAddress());
+ int presentation = mOriginalConnection.getNumberPresentation();
+ if (force || !Objects.equals(handle, getHandle()) ||
+ presentation != getHandlePresentation()) {
+ Log.v(this, "updateHandle, handle changed");
+ setHandle(handle, presentation);
+ }
+
+ String name = mOriginalConnection.getCnapName();
+ int namePresentation = mOriginalConnection.getCnapNamePresentation();
+ if (force || !Objects.equals(name, getCallerDisplayName()) ||
+ namePresentation != getCallerDisplayNamePresentation()) {
+ Log.v(this, "updateHandle, caller display name changed");
+ setCallerDisplayName(name, namePresentation);
+ }
}
}
- protected void hangup(int disconnectCause) {
+ void onAddedToCallService() {
+ updateState(false);
+ }
+
+ void onRemovedFromCallService() {
+ // Subclass can override this to do cleanup.
+ }
+
+ private void hangup(int disconnectCause) {
if (mOriginalConnection != null) {
try {
Call call = mOriginalConnection.getCall();
@@ -172,17 +278,67 @@
close();
}
- private void updateState() {
+ com.android.internal.telephony.Connection getOriginalConnection() {
+ return mOriginalConnection;
+ }
+
+ protected Call getCall() {
+ if (mOriginalConnection != null) {
+ return mOriginalConnection.getCall();
+ }
+ return null;
+ }
+
+ Phone getPhone() {
+ Call call = getCall();
+ if (call != null) {
+ return call.getPhone();
+ }
+ return null;
+ }
+
+ private com.android.internal.telephony.Connection getForegroundConnection() {
+ if (getPhone() != null) {
+ return getPhone().getForegroundCall().getEarliestConnection();
+ }
+ return null;
+ }
+
+ /**
+ * Checks to see the original connection corresponds to an active incoming call. Returns false
+ * if there is no such actual call, or if the associated call is not incoming (See
+ * {@link Call.State#isRinging}).
+ */
+ private boolean isValidRingingCall() {
+ if (getPhone() == null) {
+ Log.v(this, "isValidRingingCall, phone is null");
+ return false;
+ }
+
+ Call ringingCall = getPhone().getRingingCall();
+ if (!ringingCall.getState().isRinging()) {
+ Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
+ return false;
+ }
+
+ if (ringingCall.getEarliestConnection() != mOriginalConnection) {
+ Log.v(this, "isValidRingingCall, ringing call connection does not match");
+ return false;
+ }
+
+ Log.v(this, "isValidRingingCall, returning true");
+ return true;
+ }
+
+ private void updateState(boolean force) {
if (mOriginalConnection == null) {
return;
}
Call.State newState = mOriginalConnection.getState();
- Log.v(this, "Update state from %s to %s for %s", mState, newState, this);
- if (mState != newState) {
- Log.d(this, "mOriginalConnection new state = %s", newState);
-
- mState = newState;
+ Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this);
+ if (force || mOriginalConnectionState != newState) {
+ mOriginalConnectionState = newState;
switch (newState) {
case IDLE:
break;
@@ -202,33 +358,23 @@
break;
case DISCONNECTED:
setDisconnected(mOriginalConnection.getDisconnectCause(), null);
+ close();
break;
case DISCONNECTING:
break;
}
- updateCallCapabilities();
}
+ updateCallCapabilities(force);
+ updateHandle(force);
}
private void close() {
- if (mOriginalConnection != null) {
- Call call = mOriginalConnection.getCall();
- if (call != null) {
- call.getPhone().unregisterForPreciseCallStateChanged(mHandler);
- }
- mOriginalConnection = null;
- setDestroyed();
+ Log.v(this, "close");
+ if (getPhone() != null) {
+ getPhone().unregisterForPreciseCallStateChanged(mHandler);
+ getPhone().unregisterForRingbackTone(mHandler);
}
- }
-
- private class StateHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case EVENT_PRECISE_CALL_STATE_CHANGED:
- updateState();
- break;
- }
- }
+ mOriginalConnection = null;
+ setDestroyed();
}
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 9a0d5a4..2d55165 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -16,240 +16,257 @@
package com.android.services.telephony;
+import android.content.ComponentName;
+import android.content.Context;
import android.net.Uri;
+import android.os.Debug;
import android.telephony.DisconnectCause;
import android.telephony.ServiceState;
import android.text.TextUtils;
-
-import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.Connection.PostDialListener;
-import com.android.internal.telephony.Phone;
-
+import android.telecomm.CallCapabilities;
import android.telecomm.Connection;
import android.telecomm.ConnectionRequest;
import android.telecomm.ConnectionService;
+import android.telecomm.PhoneAccount;
import android.telecomm.Response;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
-import java.util.HashSet;
-import java.util.Set;
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
/**
- * The parent class for Android's built-in connection services.
+ * Service for making GSM and CDMA connections.
*/
-public abstract class TelephonyConnectionService extends ConnectionService {
- private static final Set<com.android.internal.telephony.Connection> sKnownConnections
- = new HashSet<>();
+public class TelephonyConnectionService extends ConnectionService {
+ private static String SCHEME_TEL = "tel";
- /**
- * Initiates the underlying Telephony call, then creates a {@link TelephonyConnection}
- * by calling
- * {@link #createTelephonyConnection(
- * ConnectionRequest,Phone,com.android.internal.telephony.Connection)}
- * at the appropriate time. This method should be called by the subclass.
- */
- protected void startCallWithPhone(
- Phone phone,
- ConnectionRequest request,
- OutgoingCallResponse<Connection> response) {
- Log.d(this, "startCallWithPhone: %s.", request);
+ private EmergencyCallHelper mEmergencyCallHelper;
- if (phone == null) {
- respondWithError(
- request,
- response,
- DisconnectCause.ERROR_UNSPECIFIED, // Generic internal error
- "Phone is null");
+ static PhoneAccount getPhoneAccount(Context context) {
+ return new PhoneAccount(
+ new ComponentName(context, TelephonyConnectionService.class),
+ null /* id */,
+ null,
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+ }
+
+ @Override
+ protected void onCreateOutgoingConnection(
+ final ConnectionRequest request,
+ final CreateConnectionResponse<Connection> response) {
+ Log.v(this, "onCreateOutgoingConnection, request: " + request);
+
+ Uri handle = request.getHandle();
+ if (handle == null) {
+ Log.d(this, "onCreateOutgoingConnection, handle is null");
+ response.onFailure(request, DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, "Handle is null");
return;
}
- if (request.getHandle() == null) {
- respondWithError(
- request,
- response,
- DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
- "Handle is null");
+ if (!SCHEME_TEL.equals(handle.getScheme())) {
+ Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel",
+ handle.getScheme());
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
+ "Handle scheme is not type tel");
return;
}
- String number = request.getHandle().getSchemeSpecificPart();
+ final String number = handle.getSchemeSpecificPart();
if (TextUtils.isEmpty(number)) {
- respondWithError(
- request,
- response,
- DisconnectCause.INVALID_NUMBER,
- "Unable to parse number");
+ Log.d(this, "onCreateOutgoingConnection, unable to parse number");
+ response.onFailure(request, DisconnectCause.INVALID_NUMBER, "Unable to parse number");
return;
}
- if (!checkServiceStateForOutgoingCall(phone, request, response)) {
+ final Phone phone = PhoneFactory.getDefaultPhone();
+ if (phone == null) {
+ Log.d(this, "onCreateOutgoingConnection, phone is null");
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Phone is null");
return;
}
- com.android.internal.telephony.Connection connection;
- try {
- connection = phone.dial(number, request.getVideoState());
- } catch (CallStateException e) {
- Log.e(this, e, "Call to Phone.dial failed with exception");
- respondWithError(
- request,
- response,
- DisconnectCause.ERROR_UNSPECIFIED, // Generic internal error
- e.getMessage());
+ boolean isEmergencyNumber = PhoneNumberUtils.isPotentialEmergencyNumber(number);
+ if (!isEmergencyNumber) {
+ int state = phone.getServiceState().getState();
+ switch (state) {
+ case ServiceState.STATE_IN_SERVICE:
+ break;
+ case ServiceState.STATE_OUT_OF_SERVICE:
+ response.onFailure(request, DisconnectCause.OUT_OF_SERVICE,
+ "ServiceState.STATE_OUT_OF_SERVICE");
+ return;
+ case ServiceState.STATE_EMERGENCY_ONLY:
+ response.onFailure(request, DisconnectCause.EMERGENCY_ONLY,
+ "ServiceState.STATE_EMERGENCY_ONLY");
+ return;
+ case ServiceState.STATE_POWER_OFF:
+ response.onFailure(request, DisconnectCause.POWER_OFF,
+ "ServiceState.STATE_POWER_OFF");
+ return;
+ default:
+ Log.d(this, "onCreateOutgoingConnection, unkown service state: %d", state);
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
+ "Unkown service state " + state);
+ return;
+ }
+ }
+
+ if (isEmergencyNumber) {
+ Log.d(this, "onCreateOutgoingConnection, doing startTurnOnRadioSequence for " +
+ "emergency number");
+ if (mEmergencyCallHelper == null) {
+ mEmergencyCallHelper = new EmergencyCallHelper(this);
+ }
+ mEmergencyCallHelper.startTurnOnRadioSequence(phone,
+ new EmergencyCallHelper.Callback() {
+ @Override
+ public void onComplete(boolean isRadioReady) {
+ if (isRadioReady) {
+ startOutgoingCall(request, response, phone, number);
+ } else {
+ Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
+ response.onFailure(request, DisconnectCause.POWER_OFF,
+ "Failed to turn on radio.");
+ }
+ }
+ });
return;
}
- if (connection == null) {
- respondWithError(
- request,
- response,
- DisconnectCause.ERROR_UNSPECIFIED, // Generic internal error
- "Call to phone.dial failed");
- return;
- }
+ startOutgoingCall(request, response, phone, number);
+ }
- try {
- final TelephonyConnection telephonyConnection =
- createTelephonyConnection(request, phone, connection);
- respondWithResult(request, response, telephonyConnection);
-
- final com.android.internal.telephony.Connection connectionFinal = connection;
- PostDialListener postDialListener = new PostDialListener() {
- @Override
- public void onPostDialWait() {
- telephonyConnection.setPostDialWait(
- connectionFinal.getRemainingPostDialString());
- }
- };
- connection.addPostDialListener(postDialListener);
- } catch (Exception e) {
- Log.e(this, e, "Call to createConnection failed with exception");
- respondWithError(
- request,
- response,
- DisconnectCause.ERROR_UNSPECIFIED, // Generic internal error
- e.getMessage());
+ @Override
+ protected void onCreateConferenceConnection(
+ String token,
+ Connection connection,
+ Response<String, Connection> response) {
+ Log.v(this, "onCreateConferenceConnection, connection: " + connection);
+ if (connection instanceof GsmConnection || connection instanceof ConferenceConnection) {
+ if ((connection.getCallCapabilities() & CallCapabilities.MERGE_CALLS) != 0) {
+ response.onResult(token,
+ GsmConferenceController.createConferenceConnection(connection));
+ }
}
}
- private boolean checkServiceStateForOutgoingCall(
- Phone phone,
+ @Override
+ protected void onCreateIncomingConnection(
ConnectionRequest request,
- OutgoingCallResponse<Connection> response) {
- int state = phone.getServiceState().getState();
- switch (state) {
- case ServiceState.STATE_IN_SERVICE:
- return true;
- case ServiceState.STATE_OUT_OF_SERVICE:
- respondWithError(
- request,
- response,
- DisconnectCause.OUT_OF_SERVICE,
- null);
- break;
- case ServiceState.STATE_EMERGENCY_ONLY:
- respondWithError(
- request,
- response,
- DisconnectCause.EMERGENCY_ONLY,
- null);
- break;
- case ServiceState.STATE_POWER_OFF:
- respondWithError(
- request,
- response,
- DisconnectCause.POWER_OFF,
- null);
- break;
- default:
- // Internal error, but we pass it upwards and do not crash.
- Log.d(this, "Unrecognized service state %d", state);
- respondWithError(
- request,
- response,
- DisconnectCause.ERROR_UNSPECIFIED,
- "Unrecognized service state " + state);
- break;
+ CreateConnectionResponse<Connection> response) {
+ Log.v(this, "onCreateIncomingConnection, request: " + request);
+
+ Phone phone = PhoneFactory.getDefaultPhone();
+ Call call = phone.getRingingCall();
+ if (!call.getState().isRinging()) {
+ Log.v(this, "onCreateIncomingConnection, no ringing call");
+ response.onFailure(request, DisconnectCause.INCOMING_MISSED, "Found no ringing call");
+ return;
+ }
+
+ com.android.internal.telephony.Connection originalConnection = call.getEarliestConnection();
+ if (isOriginalConnectionKnown(originalConnection)) {
+ Log.v(this, "onCreateIncomingConnection, original connection already registered");
+ response.onCancel(request);
+ return;
+ }
+
+ Uri handle = getHandleFromAddress(originalConnection.getAddress());
+ ConnectionRequest telephonyRequest = new ConnectionRequest(
+ getPhoneAccount(this),
+ request.getCallId(),
+ handle,
+ originalConnection.getNumberPresentation(),
+ request.getExtras(),
+ request.getVideoState());
+
+ if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+ response.onSuccess(telephonyRequest, new GsmConnection(originalConnection));
+ } else if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+ response.onSuccess(telephonyRequest, new CdmaConnection(originalConnection));
+ } else {
+ response.onCancel(request);
+ }
+ }
+
+ @Override
+ protected void onConnectionAdded(Connection connection) {
+ Log.v(this, "onConnectionAdded, connection: " + connection);
+ if (connection instanceof TelephonyConnection) {
+ ((TelephonyConnection) connection).onAddedToCallService();
+ }
+ }
+
+ @Override
+ protected void onConnectionRemoved(Connection connection) {
+ Log.v(this, "onConnectionRemoved, connection: " + connection);
+ if (connection instanceof TelephonyConnection) {
+ ((TelephonyConnection) connection).onRemovedFromCallService();
+ }
+ }
+
+ private void startOutgoingCall(
+ ConnectionRequest request,
+ CreateConnectionResponse<Connection> response,
+ Phone phone,
+ String number) {
+ Log.v(this, "startOutgoingCall");
+
+ com.android.internal.telephony.Connection originalConnection;
+ try {
+ originalConnection = phone.dial(number, request.getVideoState());
+ } catch (CallStateException e) {
+ Log.e(this, e, "startOutgoingCall, phone.dial exception: " + e);
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, e.getMessage());
+ return;
+ }
+
+ if (originalConnection == null) {
+ Log.d(this, "startOutgoingCall, phone.dial returned null");
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Connection is null");
+ return;
+ }
+
+ ConnectionRequest telephonyRequest = new ConnectionRequest(
+ getPhoneAccount(this),
+ request.getCallId(),
+ request.getHandle(),
+ request.getHandlePresentation(),
+ request.getExtras(),
+ request.getVideoState());
+
+ if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+ response.onSuccess(telephonyRequest, new GsmConnection(originalConnection));
+ } else if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+ response.onSuccess(telephonyRequest, new CdmaConnection(originalConnection));
+ } else {
+ // TODO(ihab): Tear down 'originalConnection' here, or move recognition of
+ // getPhoneType() earlier in this method before we've already asked phone to dial()
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Invalid phone type");
+ }
+ }
+
+ private boolean isOriginalConnectionKnown(
+ com.android.internal.telephony.Connection originalConnection) {
+ for (Connection connection : getAllConnections()) {
+ TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
+ if (connection instanceof TelephonyConnection) {
+ if (telephonyConnection.getOriginalConnection() == originalConnection) {
+ return true;
+ }
+ }
}
return false;
}
- protected <REQUEST, RESULT> void respondWithError(
- REQUEST request,
- Response<REQUEST, RESULT> response,
- int errorCode,
- String errorMsg) {
- Log.d(this, "respondWithError %s: %d %s", request, errorCode, errorMsg);
- response.onError(request, errorCode, errorMsg);
+ static Uri getHandleFromAddress(String address) {
+ // Address can be null for blocked calls.
+ if (address == null) {
+ address = "";
+ }
+ return Uri.fromParts(SCHEME_TEL, address, null);
}
-
- protected void respondWithResult(
- ConnectionRequest request,
- Response<ConnectionRequest, Connection> response,
- Connection result) {
- Log.d(this, "respondWithResult %s -> %s", request, result);
- response.onResult(request, result);
- }
-
- protected void respondWithResult(
- ConnectionRequest request,
- OutgoingCallResponse<Connection> response,
- Connection result) {
- Log.d(this, "respondWithResult %s -> %s", request, result);
- response.onSuccess(request, result);
- }
-
- protected void respondWithError(
- ConnectionRequest request,
- OutgoingCallResponse<Connection> response,
- int errorCode,
- String errorMsg) {
- Log.d(this, "respondWithError %s: %d %s", request, errorCode, errorMsg);
- response.onFailure(request, errorCode, errorMsg);
- }
-
- protected final TelephonyConnection createTelephonyConnection(
- ConnectionRequest request,
- Phone phone,
- final com.android.internal.telephony.Connection connection) {
- final TelephonyConnection telephonyConnection =
- onCreateTelephonyConnection(request, phone, connection);
- sKnownConnections.add(connection);
- telephonyConnection.addConnectionListener(new Connection.Listener() {
- @Override
- public void onDestroyed(Connection c) {
- telephonyConnection.removeConnectionListener(this);
- sKnownConnections.remove(connection);
- }
- });
-
- return telephonyConnection;
- }
-
- protected static boolean isConnectionKnown(
- com.android.internal.telephony.Connection connection) {
- return sKnownConnections.contains(connection);
- }
-
- /**
- * Determine whether this {@link TelephonyConnectionService} can place a call
- * to the supplied handle (phone number).
- *
- * @param handle The proposed handle.
- * @return {@code true} if the handle can be called.
- */
- protected abstract boolean canCall(Uri handle);
-
- /**
- * Create a Telephony-specific {@link Connection} object.
- *
- * @param request A request for creating a {@link Connection}.
- * @param phone A {@code Phone} object to use.
- * @param connection An underlying Telephony {@link com.android.internal.telephony.Connection}
- * to use.
- * @return A new {@link TelephonyConnection}.
- */
- protected abstract TelephonyConnection onCreateTelephonyConnection(
- ConnectionRequest request,
- Phone phone,
- com.android.internal.telephony.Connection connection);
}