Dynamic InCallService switching.
Be able to switch between in-call services UIs. Created a tree data structure to handle switching between InCallServices depending on the state of the system. Tree looks like this: CarModeSwitchingConnection | | +------+ +-------+ | | CarModeConnection EmergencyConnection | | DefaultDialerConnection Bug: 24571147 Change-Id: I0999fad4185321d5211172aed2f1d60fe8f5fe3a
This commit is contained in:
parent
d9d8fb6508
commit
501b9b3775
|
@ -12,7 +12,8 @@
|
|||
* 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.server.telecom;
|
||||
*/
|
||||
package com.android.server.telecom;
|
||||
|
||||
import com.android.internal.telephony.CallerInfoAsyncQuery;
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ import com.android.internal.telephony.AsyncEmergencyContactNotifier;
|
|||
import com.android.internal.telephony.PhoneConstants;
|
||||
import com.android.internal.telephony.TelephonyProperties;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
|
||||
import com.android.server.telecom.components.ErrorDialogActivity;
|
||||
|
||||
import java.util.Collection;
|
||||
|
@ -200,7 +201,8 @@ public class CallsManager extends Call.ListenerBase
|
|||
CallAudioManager.AudioServiceFactory audioServiceFactory,
|
||||
BluetoothManager bluetoothManager,
|
||||
WiredHeadsetManager wiredHeadsetManager,
|
||||
SystemStateProvider systemStateProvider) {
|
||||
SystemStateProvider systemStateProvider,
|
||||
DefaultDialerManagerAdapter defaultDialerAdapter) {
|
||||
mContext = context;
|
||||
mLock = lock;
|
||||
mContactsAsyncHelper = contactsAsyncHelper;
|
||||
|
@ -238,7 +240,8 @@ public class CallsManager extends Call.ListenerBase
|
|||
RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context);
|
||||
SystemVibrator systemVibrator = new SystemVibrator(context);
|
||||
AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
|
||||
mInCallController = new InCallController(context, mLock, this, systemStateProvider);
|
||||
mInCallController = new InCallController(
|
||||
context, mLock, this, systemStateProvider, defaultDialerAdapter);
|
||||
mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
|
||||
ringtoneFactory, systemVibrator, mInCallController);
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ import com.android.internal.annotations.VisibleForTesting;
|
|||
import com.android.internal.telecom.IInCallService;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.telecom.SystemStateProvider.SystemStateListener;
|
||||
import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -62,25 +63,358 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
* a binding to the {@link IInCallService} (implemented by the in-call app).
|
||||
*/
|
||||
public final class InCallController extends CallsManagerListenerBase {
|
||||
/**
|
||||
* Used to bind to the in-call app and triggers the start of communication between
|
||||
* this class and in-call app.
|
||||
*/
|
||||
private class InCallServiceConnection implements ServiceConnection {
|
||||
/** {@inheritDoc} */
|
||||
@Override public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
Log.startSession("ICSC.oSC");
|
||||
Log.d(this, "onServiceConnected: %s", name);
|
||||
onConnected(name, service);
|
||||
Log.endSession();
|
||||
|
||||
public class InCallServiceConnection {
|
||||
public class Listener {
|
||||
public void onDisconnect(InCallServiceConnection conn) {}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override public void onServiceDisconnected(ComponentName name) {
|
||||
Log.startSession("ICSC.oSD");
|
||||
Log.d(this, "onDisconnected: %s", name);
|
||||
onDisconnected(name);
|
||||
Log.endSession();
|
||||
protected Listener mListener;
|
||||
|
||||
public boolean connect(Call call) { return false; }
|
||||
public void disconnect() {}
|
||||
public void setHasEmergency(boolean hasEmergency) {}
|
||||
public void setListener(Listener l) {
|
||||
mListener = l;
|
||||
}
|
||||
public void dump(IndentingPrintWriter pw) {}
|
||||
}
|
||||
|
||||
private class InCallServiceBindingConnection extends InCallServiceConnection {
|
||||
|
||||
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
Log.startSession("ICSBC.oSC");
|
||||
try {
|
||||
Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected);
|
||||
mIsBound = true;
|
||||
if (mIsConnected) {
|
||||
// Only proceed if we are supposed to be connected.
|
||||
onConnected(service);
|
||||
}
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Log.startSession("ICSBC.oSD");
|
||||
try {
|
||||
Log.d(this, "onDisconnected: %s", name);
|
||||
mIsBound = false;
|
||||
onDisconnected();
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final ComponentName mComponentName;
|
||||
private boolean mIsConnected = false;
|
||||
private boolean mIsBound = false;
|
||||
|
||||
public InCallServiceBindingConnection(ComponentName componentName) {
|
||||
mComponentName = componentName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect(Call call) {
|
||||
if (mIsConnected) {
|
||||
Log.event(call, Log.Events.INFO, "Already connected, ignoring request.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
|
||||
intent.setComponent(mComponentName);
|
||||
if (call != null && !call.isIncoming()){
|
||||
intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
|
||||
call.getIntentExtras());
|
||||
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
|
||||
call.getTargetPhoneAccount());
|
||||
}
|
||||
|
||||
Log.i(this, "Attempting to bind to InCall %s, with %s", mComponentName, intent);
|
||||
if (mContext.bindServiceAsUser(intent, mServiceConnection,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
|
||||
UserHandle.CURRENT)) {
|
||||
Log.i(this, "It's connecting!");
|
||||
mIsConnected = true;
|
||||
}
|
||||
|
||||
return mIsConnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (mIsConnected) {
|
||||
mContext.unbindService(mServiceConnection);
|
||||
mIsConnected = false;
|
||||
} else {
|
||||
Log.event(null, Log.Events.INFO, "Already disconnected, ignoring request.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
pw.append("BindingConnection [");
|
||||
pw.append(mIsConnected ? "" : "not ").append("connected, ");
|
||||
pw.append(mIsBound ? "" : "not ").append("bound]\n");
|
||||
}
|
||||
|
||||
protected void onConnected(IBinder service) {
|
||||
boolean shouldRemainConnected =
|
||||
InCallController.this.onConnected(mComponentName, service);
|
||||
if (!shouldRemainConnected) {
|
||||
// Sometimes we can opt to disconnect for certain reasons, like if the
|
||||
// InCallService rejected our intialization step, or the calls went away
|
||||
// in the time it took us to bind to the InCallService. In such cases, we go
|
||||
// ahead and disconnect ourselves.
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onDisconnected() {
|
||||
InCallController.this.onDisconnected(mComponentName);
|
||||
disconnect(); // Unbind explicitly if we get disconnected.
|
||||
if (mListener != null) {
|
||||
mListener.onDisconnect(InCallServiceBindingConnection.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of the InCallServiceBindingConnection that proxies all calls to a secondary
|
||||
* connection until it finds an emergency call, or the other connection dies. When one of those
|
||||
* two things happen, this class instance will take over the connection.
|
||||
*/
|
||||
private class EmergencyInCallServiceConnection extends InCallServiceBindingConnection {
|
||||
private boolean mIsProxying = true;
|
||||
private boolean mIsConnected = false;
|
||||
private final InCallServiceConnection mSubConnection;
|
||||
|
||||
private Listener mSubListener = new Listener() {
|
||||
@Override
|
||||
public void onDisconnect(InCallServiceConnection subConnection) {
|
||||
if (subConnection == mSubConnection) {
|
||||
if (mIsConnected && mIsProxying) {
|
||||
// At this point we know that we need to be connected to the InCallService
|
||||
// and we are proxying to the sub connection. However, the sub-connection
|
||||
// just died so we need to stop proxying and connect to the system in-call
|
||||
// service instead.
|
||||
mIsProxying = false;
|
||||
connect(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public EmergencyInCallServiceConnection(
|
||||
ComponentName componentName, InCallServiceConnection subConnection) {
|
||||
super(componentName);
|
||||
mSubConnection = subConnection;
|
||||
if (mSubConnection != null) {
|
||||
mSubConnection.setListener(mSubListener);
|
||||
}
|
||||
mIsProxying = (mSubConnection != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect(Call call) {
|
||||
mIsConnected = true;
|
||||
if (mIsProxying) {
|
||||
if (mSubConnection.connect(call)) {
|
||||
return true;
|
||||
}
|
||||
// Could not connect to child, stop proxying.
|
||||
mIsProxying = false;
|
||||
}
|
||||
|
||||
// If we are here, we didn't or could not connect to child. So lets connect ourselves.
|
||||
return super.connect(call);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
Log.i(this, "Disconnect forced!");
|
||||
if (mIsProxying) {
|
||||
mSubConnection.disconnect();
|
||||
} else {
|
||||
super.disconnect();
|
||||
}
|
||||
mIsConnected = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHasEmergency(boolean hasEmergency) {
|
||||
if (hasEmergency) {
|
||||
takeControl();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisconnected() {
|
||||
// Save this here because super.onDisconnected() could force us to explicitly
|
||||
// disconnect() as a cleanup step and that sets mIsConnected to false.
|
||||
boolean shouldReconnect = mIsConnected;
|
||||
super.onDisconnected();
|
||||
// We just disconnected. Check if we are expected to be connected, and reconnect.
|
||||
if (shouldReconnect && !mIsProxying) {
|
||||
connect(null); // reconnect
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
pw.println("Emergency ICS Connection");
|
||||
pw.increaseIndent();
|
||||
pw.print("Emergency: ");
|
||||
super.dump(pw);
|
||||
if (mSubConnection != null) {
|
||||
pw.print("Default-Dialer: ");
|
||||
mSubConnection.dump(pw);
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the connection to take control from it's subConnection.
|
||||
*/
|
||||
private void takeControl() {
|
||||
if (mIsProxying) {
|
||||
mIsProxying = false;
|
||||
if (mIsConnected) {
|
||||
mSubConnection.disconnect();
|
||||
super.connect(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of InCallServiceConnection which switches UI between two separate sub-instances of
|
||||
* InCallServicesConnections.
|
||||
*/
|
||||
private class CarSwappingInCallServiceConnection extends InCallServiceConnection {
|
||||
private final InCallServiceConnection mDialerConnection;
|
||||
private final InCallServiceConnection mCarModeConnection;
|
||||
private InCallServiceConnection mCurrentConnection;
|
||||
private boolean mIsCarMode = false;
|
||||
private boolean mIsConnected = false;
|
||||
|
||||
public CarSwappingInCallServiceConnection(
|
||||
InCallServiceConnection dialerConnection,
|
||||
InCallServiceConnection carModeConnection) {
|
||||
mDialerConnection = dialerConnection;
|
||||
mCarModeConnection = carModeConnection;
|
||||
mCurrentConnection = getCurrentConnection();
|
||||
}
|
||||
|
||||
public synchronized void setCarMode(boolean isCarMode) {
|
||||
Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode);
|
||||
if (isCarMode != mIsCarMode) {
|
||||
mIsCarMode = isCarMode;
|
||||
InCallServiceConnection newConnection = getCurrentConnection();
|
||||
if (newConnection != mCurrentConnection) {
|
||||
if (mIsConnected) {
|
||||
mCurrentConnection.disconnect();
|
||||
newConnection.connect(null);
|
||||
}
|
||||
mCurrentConnection = newConnection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect(Call call) {
|
||||
if (mIsConnected) {
|
||||
Log.i(this, "already connected");
|
||||
return true;
|
||||
} else {
|
||||
if (mCurrentConnection.connect(call)) {
|
||||
mIsConnected = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (mIsConnected) {
|
||||
mCurrentConnection.disconnect();
|
||||
mIsConnected = false;
|
||||
} else {
|
||||
Log.i(this, "already disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHasEmergency(boolean hasEmergency) {
|
||||
if (mDialerConnection != null) {
|
||||
mDialerConnection.setHasEmergency(hasEmergency);
|
||||
}
|
||||
if (mCarModeConnection != null) {
|
||||
mCarModeConnection.setHasEmergency(hasEmergency);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
pw.println("Car Swapping ICS");
|
||||
pw.increaseIndent();
|
||||
if (mDialerConnection != null) {
|
||||
pw.print("Dialer: ");
|
||||
mDialerConnection.dump(pw);
|
||||
}
|
||||
if (mCarModeConnection != null) {
|
||||
pw.print("Car Mode: ");
|
||||
mCarModeConnection.dump(pw);
|
||||
}
|
||||
}
|
||||
|
||||
private InCallServiceConnection getCurrentConnection() {
|
||||
if (mIsCarMode && mCarModeConnection != null) {
|
||||
return mCarModeConnection;
|
||||
} else {
|
||||
return mDialerConnection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NonUIInCallServiceConnectionCollection extends InCallServiceConnection {
|
||||
private final List<InCallServiceBindingConnection> mSubConnections;
|
||||
|
||||
public NonUIInCallServiceConnectionCollection(
|
||||
List<InCallServiceBindingConnection> subConnections) {
|
||||
mSubConnections = subConnections;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect(Call call) {
|
||||
for (InCallServiceBindingConnection subConnection : mSubConnections) {
|
||||
subConnection.connect(call);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
for (InCallServiceBindingConnection subConnection : mSubConnections) {
|
||||
subConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
pw.println("Non-UI Connections:");
|
||||
pw.increaseIndent();
|
||||
for (InCallServiceBindingConnection subConnection : mSubConnections) {
|
||||
subConnection.dump(pw);
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,7 +514,9 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
private final SystemStateListener mSystemStateListener = new SystemStateListener() {
|
||||
@Override
|
||||
public void onCarModeChanged(boolean isCarMode) {
|
||||
// Do something when the car mode changes.
|
||||
if (mInCallServiceConnection != null) {
|
||||
mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -190,15 +526,6 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
|
||||
private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
|
||||
|
||||
/**
|
||||
* Maintains a binding connection to the in-call app(s).
|
||||
* ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
|
||||
* load factor before resizing, 1 means we only expect a single thread to
|
||||
* access the map so make only a single shard
|
||||
*/
|
||||
private final Map<ComponentName, InCallServiceConnection> mServiceConnections =
|
||||
new ConcurrentHashMap<ComponentName, InCallServiceConnection>(8, 0.9f, 1);
|
||||
|
||||
/** The in-call app implementations, see {@link IInCallService}. */
|
||||
private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>();
|
||||
|
||||
|
@ -216,13 +543,18 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
private final TelecomSystem.SyncRoot mLock;
|
||||
private final CallsManager mCallsManager;
|
||||
private final SystemStateProvider mSystemStateProvider;
|
||||
private final DefaultDialerManagerAdapter mDefaultDialerAdapter;
|
||||
private CarSwappingInCallServiceConnection mInCallServiceConnection;
|
||||
private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
|
||||
|
||||
public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
|
||||
SystemStateProvider systemStateProvider) {
|
||||
SystemStateProvider systemStateProvider,
|
||||
DefaultDialerManagerAdapter defaultDialerAdapter) {
|
||||
mContext = context;
|
||||
mLock = lock;
|
||||
mCallsManager = callsManager;
|
||||
mSystemStateProvider = systemStateProvider;
|
||||
mDefaultDialerAdapter = defaultDialerAdapter;
|
||||
|
||||
Resources resources = mContext.getResources();
|
||||
mSystemInCallComponentName = new ComponentName(
|
||||
|
@ -379,19 +711,12 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
* Unbinds an existing bound connection to the in-call app.
|
||||
*/
|
||||
private void unbindFromServices() {
|
||||
Iterator<Map.Entry<ComponentName, InCallServiceConnection>> iterator =
|
||||
mServiceConnections.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final Map.Entry<ComponentName, InCallServiceConnection> entry = iterator.next();
|
||||
Log.i(this, "Unbinding from InCallService %s", entry.getKey());
|
||||
try {
|
||||
mContext.unbindService(entry.getValue());
|
||||
} catch (Exception e) {
|
||||
Log.e(this, e, "Exception while unbinding from InCallService");
|
||||
}
|
||||
iterator.remove();
|
||||
if (isBoundToServices()) {
|
||||
mInCallServiceConnection.disconnect();
|
||||
mInCallServiceConnection = null;
|
||||
mNonUIInCallServiceConnections.disconnect();
|
||||
mNonUIInCallServiceConnections = null;
|
||||
}
|
||||
mInCallServices.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -402,13 +727,72 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
*/
|
||||
@VisibleForTesting
|
||||
public void bindToServices(Call call) {
|
||||
ComponentName inCallUIService = null;
|
||||
ComponentName carModeInCallUIService = null;
|
||||
List<ComponentName> nonUIInCallServices = new LinkedList<>();
|
||||
InCallServiceConnection dialerInCall = null;
|
||||
ComponentName defaultDialerComponent = getDefaultDialerComponent();
|
||||
Log.i(this, "defaultDialer: " + defaultDialerComponent);
|
||||
if (defaultDialerComponent != null &&
|
||||
!defaultDialerComponent.equals(mSystemInCallComponentName)) {
|
||||
dialerInCall = new InCallServiceBindingConnection(defaultDialerComponent);
|
||||
}
|
||||
Log.i(this, "defaultDialer: " + dialerInCall);
|
||||
|
||||
EmergencyInCallServiceConnection systemInCall =
|
||||
new EmergencyInCallServiceConnection(mSystemInCallComponentName, dialerInCall);
|
||||
systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
|
||||
|
||||
InCallServiceConnection carModeInCall = null;
|
||||
ComponentName carModeComponent = getCarModeComponent();
|
||||
if (carModeComponent != null &&
|
||||
!carModeComponent.equals(mSystemInCallComponentName)) {
|
||||
carModeInCall = new InCallServiceBindingConnection(carModeComponent);
|
||||
}
|
||||
|
||||
mInCallServiceConnection =
|
||||
new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
|
||||
mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
|
||||
mInCallServiceConnection.connect(call);
|
||||
|
||||
|
||||
List<ComponentName> nonUIInCallComponents =
|
||||
getInCallServiceComponents(null, IN_CALL_SERVICE_TYPE_NON_UI);
|
||||
List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
|
||||
for (ComponentName componentName : nonUIInCallComponents) {
|
||||
nonUIInCalls.add(new InCallServiceBindingConnection(componentName));
|
||||
}
|
||||
mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
|
||||
mNonUIInCallServiceConnections.connect(call);
|
||||
}
|
||||
|
||||
private ComponentName getDefaultDialerComponent() {
|
||||
String packageName = mDefaultDialerAdapter.getDefaultDialerApplication(
|
||||
mContext, mCallsManager.getCurrentUserHandle().getIdentifier());
|
||||
Log.d(this, "Default Dialer package: " + packageName);
|
||||
|
||||
return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
|
||||
}
|
||||
|
||||
private ComponentName getCarModeComponent() {
|
||||
return getInCallServiceComponent(null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
|
||||
}
|
||||
|
||||
private ComponentName getInCallServiceComponent(String packageName, int type) {
|
||||
List<ComponentName> list = getInCallServiceComponents(packageName, type);
|
||||
if (list != null && !list.isEmpty()) {
|
||||
return list.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<ComponentName> getInCallServiceComponents(String packageName, int type) {
|
||||
List<ComponentName> retval = new LinkedList<>();
|
||||
|
||||
// Loop through all the InCallService implementations that exist in the devices;
|
||||
PackageManager packageManager = mContext.getPackageManager();
|
||||
Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
|
||||
if (packageName != null) {
|
||||
serviceIntent.setPackage(packageName);
|
||||
}
|
||||
|
||||
|
||||
PackageManager packageManager = mContext.getPackageManager();
|
||||
for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
|
||||
serviceIntent,
|
||||
PackageManager.GET_META_DATA,
|
||||
|
@ -416,107 +800,13 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
ServiceInfo serviceInfo = entry.serviceInfo;
|
||||
|
||||
if (serviceInfo != null) {
|
||||
ComponentName componentName =
|
||||
new ComponentName(serviceInfo.packageName, serviceInfo.name);
|
||||
Log.v(this, "ICS: " + componentName + ", user: " + entry.targetUserId);
|
||||
|
||||
switch (getInCallServiceType(entry.serviceInfo, packageManager)) {
|
||||
case IN_CALL_SERVICE_TYPE_DIALER_UI:
|
||||
if (inCallUIService == null ||
|
||||
inCallUIService.compareTo(componentName) > 0) {
|
||||
inCallUIService = componentName;
|
||||
}
|
||||
break;
|
||||
|
||||
case IN_CALL_SERVICE_TYPE_SYSTEM_UI:
|
||||
// skip, will be added manually
|
||||
break;
|
||||
|
||||
case IN_CALL_SERVICE_TYPE_CAR_MODE_UI:
|
||||
if (carModeInCallUIService == null ||
|
||||
carModeInCallUIService.compareTo(componentName) > 0) {
|
||||
carModeInCallUIService = componentName;
|
||||
}
|
||||
break;
|
||||
|
||||
case IN_CALL_SERVICE_TYPE_NON_UI:
|
||||
nonUIInCallServices.add(componentName);
|
||||
break;
|
||||
|
||||
case IN_CALL_SERVICE_TYPE_INVALID:
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.w(this, "unexpected in-call service type");
|
||||
break;
|
||||
if (type == 0 || type == getInCallServiceType(entry.serviceInfo, packageManager)) {
|
||||
retval.add(new ComponentName(serviceInfo.packageName, serviceInfo.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(this, "Car mode InCallService: %s", carModeInCallUIService);
|
||||
Log.i(this, "Dialer InCallService: %s", inCallUIService);
|
||||
|
||||
// Adding the in-call services in order:
|
||||
// (1) The carmode in-call if carmode is on.
|
||||
// (2) The default-dialer in-call if not an emergency call
|
||||
// (3) The system-provided in-call
|
||||
List<ComponentName> orderedInCallUIServices = new LinkedList<>();
|
||||
if (shouldUseCarModeUI() && carModeInCallUIService != null) {
|
||||
orderedInCallUIServices.add(carModeInCallUIService);
|
||||
}
|
||||
if (!mCallsManager.hasEmergencyCall() && inCallUIService != null) {
|
||||
orderedInCallUIServices.add(inCallUIService);
|
||||
}
|
||||
orderedInCallUIServices.add(mSystemInCallComponentName);
|
||||
|
||||
// TODO: Need to implement the fall-back logic in case the main UI in-call service rejects
|
||||
// the binding request.
|
||||
ComponentName inCallUIServiceToBind = orderedInCallUIServices.get(0);
|
||||
if (!bindToInCallService(inCallUIServiceToBind, call, "ui")) {
|
||||
Log.event(call, Log.Events.ERROR_LOG,
|
||||
"InCallService system UI failed binding: " + inCallUIService);
|
||||
}
|
||||
mInCallUIComponentName = inCallUIServiceToBind;
|
||||
|
||||
// Bind to the control InCallServices
|
||||
for (ComponentName componentName : nonUIInCallServices) {
|
||||
bindToInCallService(componentName, call, "control");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds to the specified InCallService.
|
||||
*/
|
||||
private boolean bindToInCallService(ComponentName componentName, Call call, String tag) {
|
||||
if (mInCallServices.containsKey(componentName)) {
|
||||
Log.i(this, "An InCallService already exists: %s", componentName);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mServiceConnections.containsKey(componentName)) {
|
||||
Log.w(this, "The service is already bound for this component %s", componentName);
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
|
||||
intent.setComponent(componentName);
|
||||
if (call != null && !call.isIncoming()){
|
||||
intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
|
||||
call.getIntentExtras());
|
||||
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
|
||||
call.getTargetPhoneAccount());
|
||||
}
|
||||
|
||||
Log.i(this, "Attempting to bind to [%s] InCall %s, with %s", tag, componentName, intent);
|
||||
InCallServiceConnection inCallServiceConnection = new InCallServiceConnection();
|
||||
if (mContext.bindServiceAsUser(intent, inCallServiceConnection,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
|
||||
UserHandle.CURRENT)) {
|
||||
mServiceConnections.put(componentName, inCallServiceConnection);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return retval;
|
||||
}
|
||||
|
||||
private boolean shouldUseCarModeUI() {
|
||||
|
@ -560,7 +850,7 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
|
||||
// Check to see that it is the default dialer package
|
||||
boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
|
||||
DefaultDialerManager.getDefaultDialerApplication(
|
||||
mDefaultDialerAdapter.getDefaultDialerApplication(
|
||||
mContext, mCallsManager.getCurrentUserHandle().getIdentifier()));
|
||||
boolean isUIService = serviceInfo.metaData != null &&
|
||||
serviceInfo.metaData.getBoolean(
|
||||
|
@ -583,13 +873,10 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
}
|
||||
|
||||
private void adjustServiceBindingsForEmergency() {
|
||||
if (!Objects.equals(mInCallUIComponentName, mSystemInCallComponentName)) {
|
||||
// The connected UI is not the system UI, so lets check if we should switch them
|
||||
// if there exists an emergency number.
|
||||
if (mCallsManager.hasEmergencyCall()) {
|
||||
// Lets fake a failure here in order to trigger the switch to the system UI.
|
||||
onInCallServiceFailure(mInCallUIComponentName, "emergency adjust");
|
||||
}
|
||||
// The connected UI is not the system UI, so lets check if we should switch them
|
||||
// if there exists an emergency number.
|
||||
if (mCallsManager.hasEmergencyCall()) {
|
||||
mInCallServiceConnection.setHasEmergency(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -600,8 +887,9 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
*
|
||||
* @param componentName The service {@link ComponentName}.
|
||||
* @param service The {@link IInCallService} implementation.
|
||||
* @return True if we successfully connected.
|
||||
*/
|
||||
private void onConnected(ComponentName componentName, IBinder service) {
|
||||
private boolean onConnected(ComponentName componentName, IBinder service) {
|
||||
Trace.beginSection("onConnected: " + componentName);
|
||||
Log.i(this, "onConnected to %s", componentName);
|
||||
|
||||
|
@ -618,8 +906,7 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
} catch (RemoteException e) {
|
||||
Log.e(this, e, "Failed to set the in-call adapter.");
|
||||
Trace.endSection();
|
||||
onInCallServiceFailure(componentName, "setInCallAdapter");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upon successful connection, send the state of the world to the service.
|
||||
|
@ -644,9 +931,10 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
} catch (RemoteException ignored) {
|
||||
}
|
||||
} else {
|
||||
unbindFromServices();
|
||||
return false;
|
||||
}
|
||||
Trace.endSection();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -658,40 +946,6 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
Log.i(this, "onDisconnected from %s", disconnectedComponent);
|
||||
|
||||
mInCallServices.remove(disconnectedComponent);
|
||||
if (mServiceConnections.containsKey(disconnectedComponent)) {
|
||||
// One of the services that we were bound to has unexpectedly disconnected.
|
||||
onInCallServiceFailure(disconnectedComponent, "onDisconnect");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles non-recoverable failures by the InCallService. This method performs cleanup and
|
||||
* special handling when the failure is to the UI InCallService.
|
||||
*/
|
||||
private void onInCallServiceFailure(ComponentName componentName, String tag) {
|
||||
Log.i(this, "Cleaning up a failed InCallService [%s]: %s", tag, componentName);
|
||||
|
||||
// We always clean up the connections here. Even in the case where we rebind to the UI
|
||||
// because binding is count based and we could end up double-bound.
|
||||
mInCallServices.remove(componentName);
|
||||
InCallServiceConnection serviceConnection = mServiceConnections.remove(componentName);
|
||||
if (serviceConnection != null) {
|
||||
// We still need to call unbind even though it disconnected.
|
||||
mContext.unbindService(serviceConnection);
|
||||
}
|
||||
|
||||
if (Objects.equals(mInCallUIComponentName, componentName)) {
|
||||
if (!mCallsManager.hasAnyCalls()) {
|
||||
// No calls are left anyway. Lets just disconnect all of them.
|
||||
unbindFromServices();
|
||||
return;
|
||||
}
|
||||
|
||||
// Whenever the UI crashes, we automatically revert to the System UI for the
|
||||
// remainder of the active calls.
|
||||
mInCallUIComponentName = mSystemInCallComponentName;
|
||||
bindToInCallService(mInCallUIComponentName, null, "reconnecting");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -743,7 +997,7 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
}
|
||||
|
||||
private boolean isBoundToServices() {
|
||||
return !mInCallServices.isEmpty();
|
||||
return mInCallServiceConnection != null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -759,10 +1013,10 @@ public final class InCallController extends CallsManagerListenerBase {
|
|||
}
|
||||
pw.decreaseIndent();
|
||||
|
||||
pw.println("mServiceConnections (InCalls bound):");
|
||||
pw.println("ServiceConnections (InCalls bound):");
|
||||
pw.increaseIndent();
|
||||
for (ComponentName componentName : mServiceConnections.keySet()) {
|
||||
pw.println(componentName);
|
||||
if (mInCallServiceConnection != null) {
|
||||
mInCallServiceConnection.dump(pw);
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
|
|
@ -104,6 +104,7 @@ public class Log {
|
|||
public static final String REMOTELY_HELD = "REMOTELY_HELD";
|
||||
public static final String REMOTELY_UNHELD = "REMOTELY_UNHELD";
|
||||
public static final String PULL = "PULL";
|
||||
public static final String INFO = "INFO";
|
||||
|
||||
/**
|
||||
* Maps from a request to a response. The same event could be listed as the
|
||||
|
|
|
@ -67,6 +67,7 @@ import java.util.List;
|
|||
public class TelecomServiceImpl {
|
||||
public interface DefaultDialerManagerAdapter {
|
||||
String getDefaultDialerApplication(Context context);
|
||||
String getDefaultDialerApplication(Context context, int userId);
|
||||
boolean setDefaultDialerApplication(Context context, String packageName);
|
||||
boolean isDefaultOrSystemDialer(Context context, String packageName);
|
||||
}
|
||||
|
@ -77,6 +78,11 @@ public class TelecomServiceImpl {
|
|||
return DefaultDialerManager.getDefaultDialerApplication(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultDialerApplication(Context context, int userId) {
|
||||
return DefaultDialerManager.getDefaultDialerApplication(context, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setDefaultDialerApplication(Context context, String packageName) {
|
||||
return DefaultDialerManager.setDefaultDialerApplication(context, packageName);
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.android.server.telecom.components.UserCallIntentProcessorFactory;
|
|||
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
|
||||
import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
|
||||
import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
|
||||
import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.BroadcastReceiver;
|
||||
|
@ -173,6 +174,9 @@ public final class TelecomSystem {
|
|||
mMissedCallNotifier = missedCallNotifierImplFactory
|
||||
.makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar);
|
||||
|
||||
DefaultDialerManagerAdapter defaultDialerAdapter =
|
||||
new TelecomServiceImpl.DefaultDialerManagerAdapterImpl();
|
||||
|
||||
mCallsManager = new CallsManager(
|
||||
mContext,
|
||||
mLock,
|
||||
|
@ -186,7 +190,8 @@ public final class TelecomSystem {
|
|||
audioServiceFactory,
|
||||
bluetoothManager,
|
||||
wiredHeadsetManager,
|
||||
systemStateProvider);
|
||||
systemStateProvider,
|
||||
defaultDialerAdapter);
|
||||
|
||||
mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
|
||||
mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
|
||||
|
@ -214,7 +219,7 @@ public final class TelecomSystem {
|
|||
return new UserCallIntentProcessor(context, userHandle);
|
||||
}
|
||||
},
|
||||
new TelecomServiceImpl.DefaultDialerManagerAdapterImpl(),
|
||||
defaultDialerAdapter,
|
||||
new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
|
||||
mLock);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
<service android:name="com.android.server.telecom.testapps.TestInCallServiceImpl"
|
||||
android:process="com.android.server.telecom.testapps.TestInCallService"
|
||||
android:permission="android.permission.BIND_INCALL_SERVICE" >
|
||||
<meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI" android:value="true"/>
|
||||
<meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true"/>
|
||||
<intent-filter>
|
||||
<action android:name="android.telecom.InCallService"/>
|
||||
</intent-filter>
|
||||
|
|
|
@ -148,7 +148,10 @@ public class TestCallList extends Call.Listener {
|
|||
}
|
||||
|
||||
public void clearCalls() {
|
||||
mCalls.clear();
|
||||
for (Call call : new LinkedList<Call>(mCalls)) {
|
||||
removeCall(call);
|
||||
}
|
||||
|
||||
for (Call call : mVideoCallListeners.keySet()) {
|
||||
if (call.getVideoCall() != null) {
|
||||
call.getVideoCall().destroy();
|
||||
|
|
|
@ -16,14 +16,17 @@
|
|||
|
||||
package com.android.server.telecom.tests;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.UserHandle;
|
||||
import android.telecom.ConnectionService;
|
||||
import android.telecom.InCallService;
|
||||
|
@ -32,6 +35,8 @@ import android.telecom.TelecomManager;
|
|||
import android.test.mock.MockContext;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
|
||||
import com.android.internal.telecom.IInCallAdapter;
|
||||
import com.android.internal.telecom.IInCallService;
|
||||
import com.android.server.telecom.BluetoothHeadsetProxy;
|
||||
import com.android.server.telecom.Call;
|
||||
import com.android.server.telecom.CallsManager;
|
||||
|
@ -39,6 +44,7 @@ import com.android.server.telecom.InCallController;
|
|||
import com.android.server.telecom.PhoneAccountRegistrar;
|
||||
import com.android.server.telecom.R;
|
||||
import com.android.server.telecom.SystemStateProvider;
|
||||
import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
|
||||
import com.android.server.telecom.TelecomSystem;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
@ -72,10 +78,13 @@ public class InCallControllerTests extends TelecomTestCase {
|
|||
@Mock Call mMockCall;
|
||||
@Mock Resources mMockResources;
|
||||
@Mock MockContext mMockContext;
|
||||
@Mock DefaultDialerManagerAdapter mMockDefaultDialerAdapter;
|
||||
|
||||
private static final int CURRENT_USER_ID = 900973;
|
||||
private static final String DEF_PKG = "defpkg";
|
||||
private static final String DEF_CLASS = "defcls";
|
||||
private static final String SYS_PKG = "syspkg";
|
||||
private static final String SYS_CLASS = "syscls";
|
||||
private static final PhoneAccountHandle PA_HANDLE =
|
||||
new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id");
|
||||
|
||||
|
@ -88,10 +97,10 @@ public class InCallControllerTests extends TelecomTestCase {
|
|||
super.setUp();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doReturn(mMockResources).when(mMockContext).getResources();
|
||||
doReturn(DEF_PKG).when(mMockResources).getString(R.string.ui_default_package);
|
||||
doReturn(DEF_CLASS).when(mMockResources).getString(R.string.incall_default_class);
|
||||
doReturn(SYS_PKG).when(mMockResources).getString(R.string.ui_default_package);
|
||||
doReturn(SYS_CLASS).when(mMockResources).getString(R.string.incall_default_class);
|
||||
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
|
||||
mMockSystemStateProvider);
|
||||
mMockSystemStateProvider, mMockDefaultDialerAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -121,8 +130,8 @@ public class InCallControllerTests extends TelecomTestCase {
|
|||
|
||||
Intent bindIntent = bindIntentCaptor.getValue();
|
||||
assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
|
||||
assertEquals(DEF_PKG, bindIntent.getComponent().getPackageName());
|
||||
assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName());
|
||||
assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
|
||||
assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
|
||||
assertNull(bindIntent.getExtras());
|
||||
}
|
||||
|
||||
|
@ -151,6 +160,74 @@ public class InCallControllerTests extends TelecomTestCase {
|
|||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(UserHandle.CURRENT));
|
||||
|
||||
Intent bindIntent = bindIntentCaptor.getValue();
|
||||
assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
|
||||
assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
|
||||
assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
|
||||
assertEquals(PA_HANDLE, bindIntent.getExtras().getParcelable(
|
||||
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
|
||||
assertEquals(callExtras, bindIntent.getExtras().getParcelable(
|
||||
TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS));
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testBindToService_DefaultDialer_NoEmergency() throws Exception {
|
||||
Bundle callExtras = new Bundle();
|
||||
callExtras.putBoolean("whatever", true);
|
||||
|
||||
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
|
||||
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
|
||||
when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
|
||||
when(mMockCall.isIncoming()).thenReturn(false);
|
||||
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
|
||||
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
|
||||
when(mMockDefaultDialerAdapter.getDefaultDialerApplication(mMockContext, CURRENT_USER_ID))
|
||||
.thenReturn(DEF_PKG);
|
||||
when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
|
||||
anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
|
||||
|
||||
Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
|
||||
when(mMockPackageManager.queryIntentServicesAsUser(
|
||||
any(Intent.class), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)))
|
||||
.thenReturn(new LinkedList<ResolveInfo>() {{
|
||||
add(new ResolveInfo() {{
|
||||
serviceInfo = new ServiceInfo();
|
||||
serviceInfo.packageName = DEF_PKG;
|
||||
serviceInfo.name = DEF_CLASS;
|
||||
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
|
||||
serviceInfo.metaData = new Bundle();
|
||||
serviceInfo.metaData.putBoolean(
|
||||
TelecomManager.METADATA_IN_CALL_SERVICE_UI, true);
|
||||
}});
|
||||
add(new ResolveInfo() {{
|
||||
serviceInfo = new ServiceInfo();
|
||||
serviceInfo.packageName = SYS_PKG;
|
||||
serviceInfo.name = SYS_CLASS;
|
||||
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
|
||||
}});
|
||||
}});
|
||||
mInCallController.bindToServices(mMockCall);
|
||||
|
||||
// Query for the different InCallServices
|
||||
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockPackageManager, times(3)).queryIntentServicesAsUser(
|
||||
queryIntentCaptor.capture(),
|
||||
eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
|
||||
|
||||
// Verify call for default dialer InCallService
|
||||
assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
|
||||
// Verify call for car-mode InCallService
|
||||
assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage());
|
||||
// Verify call for non-UI InCallServices
|
||||
assertEquals(null, queryIntentCaptor.getAllValues().get(2).getPackage());
|
||||
|
||||
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockContext, times(1)).bindServiceAsUser(
|
||||
bindIntentCaptor.capture(),
|
||||
any(ServiceConnection.class),
|
||||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(UserHandle.CURRENT));
|
||||
|
||||
Intent bindIntent = bindIntentCaptor.getValue();
|
||||
assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
|
||||
assertEquals(DEF_PKG, bindIntent.getComponent().getPackageName());
|
||||
|
@ -160,4 +237,171 @@ public class InCallControllerTests extends TelecomTestCase {
|
|||
assertEquals(callExtras, bindIntent.getExtras().getParcelable(
|
||||
TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS));
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testBindToService_SystemDialer_Emergency() throws Exception {
|
||||
Bundle callExtras = new Bundle();
|
||||
callExtras.putBoolean("whatever", true);
|
||||
|
||||
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
|
||||
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
|
||||
when(mMockCallsManager.hasEmergencyCall()).thenReturn(true);
|
||||
when(mMockCall.isIncoming()).thenReturn(false);
|
||||
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
|
||||
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
|
||||
when(mMockDefaultDialerAdapter.getDefaultDialerApplication(mMockContext, CURRENT_USER_ID))
|
||||
.thenReturn(DEF_PKG);
|
||||
when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
|
||||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(UserHandle.CURRENT))).thenReturn(true);
|
||||
|
||||
Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
|
||||
when(mMockPackageManager.queryIntentServicesAsUser(
|
||||
any(Intent.class), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)))
|
||||
.thenReturn(new LinkedList<ResolveInfo>() {{
|
||||
add(new ResolveInfo() {{
|
||||
serviceInfo = new ServiceInfo();
|
||||
serviceInfo.packageName = DEF_PKG;
|
||||
serviceInfo.name = DEF_CLASS;
|
||||
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
|
||||
serviceInfo.metaData = new Bundle();
|
||||
serviceInfo.metaData.putBoolean(
|
||||
TelecomManager.METADATA_IN_CALL_SERVICE_UI, true);
|
||||
}});
|
||||
add(new ResolveInfo() {{
|
||||
serviceInfo = new ServiceInfo();
|
||||
serviceInfo.packageName = SYS_PKG;
|
||||
serviceInfo.name = SYS_CLASS;
|
||||
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
|
||||
}});
|
||||
}});
|
||||
mInCallController.bindToServices(mMockCall);
|
||||
|
||||
// Query for the different InCallServices
|
||||
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockPackageManager, times(3)).queryIntentServicesAsUser(
|
||||
queryIntentCaptor.capture(),
|
||||
eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
|
||||
|
||||
// Verify call for default dialer InCallService
|
||||
assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
|
||||
// Verify call for car-mode InCallService
|
||||
assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage());
|
||||
// Verify call for non-UI InCallServices
|
||||
assertEquals(null, queryIntentCaptor.getAllValues().get(2).getPackage());
|
||||
|
||||
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockContext, times(1)).bindServiceAsUser(
|
||||
bindIntentCaptor.capture(),
|
||||
any(ServiceConnection.class),
|
||||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(UserHandle.CURRENT));
|
||||
|
||||
Intent bindIntent = bindIntentCaptor.getValue();
|
||||
assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
|
||||
assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
|
||||
assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
|
||||
assertEquals(PA_HANDLE, bindIntent.getExtras().getParcelable(
|
||||
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
|
||||
assertEquals(callExtras, bindIntent.getExtras().getParcelable(
|
||||
TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS));
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testBindToService_DefaultDialer_FallBackToSystem() throws Exception {
|
||||
Bundle callExtras = new Bundle();
|
||||
callExtras.putBoolean("whatever", true);
|
||||
|
||||
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
|
||||
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
|
||||
when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
|
||||
when(mMockCall.isIncoming()).thenReturn(false);
|
||||
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
|
||||
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
|
||||
when(mMockDefaultDialerAdapter.getDefaultDialerApplication(mMockContext, CURRENT_USER_ID))
|
||||
.thenReturn(DEF_PKG);
|
||||
when(mMockContext.bindServiceAsUser(
|
||||
any(Intent.class), any(ServiceConnection.class), anyInt(), any(UserHandle.class)))
|
||||
.thenReturn(true);
|
||||
|
||||
Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
|
||||
when(mMockPackageManager.queryIntentServicesAsUser(
|
||||
any(Intent.class), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)))
|
||||
.thenReturn(new LinkedList<ResolveInfo>() {{
|
||||
add(new ResolveInfo() {{
|
||||
serviceInfo = new ServiceInfo();
|
||||
serviceInfo.packageName = DEF_PKG;
|
||||
serviceInfo.name = DEF_CLASS;
|
||||
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
|
||||
serviceInfo.metaData = new Bundle();
|
||||
serviceInfo.metaData.putBoolean(
|
||||
TelecomManager.METADATA_IN_CALL_SERVICE_UI, true);
|
||||
}});
|
||||
add(new ResolveInfo() {{
|
||||
serviceInfo = new ServiceInfo();
|
||||
serviceInfo.packageName = SYS_PKG;
|
||||
serviceInfo.name = SYS_CLASS;
|
||||
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
|
||||
}});
|
||||
}});
|
||||
mInCallController.bindToServices(mMockCall);
|
||||
|
||||
// Query for the different InCallServices
|
||||
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockPackageManager, times(3)).queryIntentServicesAsUser(
|
||||
queryIntentCaptor.capture(),
|
||||
eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
|
||||
|
||||
// Verify call for default dialer InCallService
|
||||
assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
|
||||
// Verify call for car-mode InCallService
|
||||
assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage());
|
||||
// Verify call for non-UI InCallServices
|
||||
assertEquals(null, queryIntentCaptor.getAllValues().get(2).getPackage());
|
||||
|
||||
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
|
||||
ArgumentCaptor.forClass(ServiceConnection.class);
|
||||
verify(mMockContext, times(1)).bindServiceAsUser(
|
||||
bindIntentCaptor.capture(),
|
||||
serviceConnectionCaptor.capture(),
|
||||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(UserHandle.CURRENT));
|
||||
|
||||
Intent bindIntent = bindIntentCaptor.getValue();
|
||||
assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
|
||||
assertEquals(DEF_PKG, bindIntent.getComponent().getPackageName());
|
||||
assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName());
|
||||
assertEquals(PA_HANDLE, bindIntent.getExtras().getParcelable(
|
||||
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
|
||||
assertEquals(callExtras, bindIntent.getExtras().getParcelable(
|
||||
TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS));
|
||||
|
||||
// We have a ServiceConnection for the default dialer, lets start the connection, and then
|
||||
// simulate a crash so that we fallback to system.
|
||||
ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
|
||||
ComponentName defDialerComponentName = new ComponentName(DEF_PKG, DEF_CLASS);
|
||||
IBinder mockBinder = mock(IBinder.class);
|
||||
IInCallService mockInCallService = mock(IInCallService.class);
|
||||
when(mockBinder.queryLocalInterface(anyString())).thenReturn(mockInCallService);
|
||||
|
||||
|
||||
// Start the connection with IInCallService
|
||||
serviceConnection.onServiceConnected(defDialerComponentName, mockBinder);
|
||||
verify(mockInCallService).setInCallAdapter(any(IInCallAdapter.class));
|
||||
|
||||
// Now crash the damn thing!
|
||||
serviceConnection.onServiceDisconnected(defDialerComponentName);
|
||||
|
||||
ArgumentCaptor<Intent> bindIntentCaptor2 = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockContext, times(2)).bindServiceAsUser(
|
||||
bindIntentCaptor2.capture(),
|
||||
any(ServiceConnection.class),
|
||||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(UserHandle.CURRENT));
|
||||
|
||||
bindIntent = bindIntentCaptor2.getValue();
|
||||
assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
|
||||
assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,11 @@ public class TelecomServiceImplTest extends TelecomTestCase {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultDialerApplication(Context context, int userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setDefaultDialerApplication(Context context, String packageName) {
|
||||
return false;
|
||||
|
|
Loading…
Reference in New Issue