Merge commit 'c289432' into manualmerge
Change-Id: If41bedc2b72823ec27f3d0d6536e9678e0f27561
diff --git a/Android.mk b/Android.mk
index 0917793..d3c3316 100644
--- a/Android.mk
+++ b/Android.mk
@@ -12,7 +12,7 @@
LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_PROGUARD_FLAGS := $(proguard.flags)
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
# Workaround for "local variable type mismatch" error.
LOCAL_DX_FLAGS += --no-locals
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a2c0b91..bbdb9a4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -38,6 +38,7 @@
<uses-permission android:name="android.permission.BIND_INCALL_SERVICE" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
<uses-permission android:name="android.permission.BROADCAST_CALLLOG_INFO" />
+ <uses-permission android:name="android.permission.BROADCAST_PHONE_ACCOUNT_REGISTRATION" />
<permission
android:name="android.permission.BROADCAST_CALLLOG_INFO"
@@ -49,6 +50,16 @@
android:label="Register to handle the broadcasted call type/duration information"
android:protectionLevel="signature|system"/>
+ <permission
+ android:name="android.permission.BROADCAST_PHONE_ACCOUNT_REGISTRATION"
+ android:label="Broadcast phone account registration"
+ android:protectionLevel="signature|system"/>
+
+ <permission
+ android:name="android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION"
+ android:label="Process phone account registration"
+ android:protectionLevel="signature|system"/>
+
<!-- Declare which SDK level this application was built against. This is needed so that IDEs
can check for incompatible APIs. -->
<uses-sdk android:minSdkVersion="19" />
@@ -75,7 +86,7 @@
contain contact information in the intent's data. CallActivity handles any data
URL with the schemes "tel", "sip", and "voicemail". It also handles URLs linked to
contacts provider entries. Any data not fitting the schema described is ignored. -->
- <activity android:name="CallActivity"
+ <activity android:name=".components.UserCallActivity"
android:theme="@style/Theme.Telecomm.Transparent"
android:permission="android.permission.CALL_PHONE"
android:excludeFromRecents="true"
@@ -114,7 +125,7 @@
processed. High priority of 1000 is used in all intent filters to prevent anything but
the system from processing this intent (b/8871505). -->
<activity-alias android:name="PrivilegedCallActivity"
- android:targetActivity="CallActivity"
+ android:targetActivity=".components.UserCallActivity"
android:permission="android.permission.CALL_PRIVILEGED"
android:process=":ui">
<intent-filter android:priority="1000">
@@ -149,7 +160,7 @@
<!-- TODO: Is there really a notion of an emergency SIP number? If not, can
that scheme be removed from this activity? -->
<activity-alias android:name="EmergencyCallActivity"
- android:targetActivity="CallActivity"
+ android:targetActivity=".components.UserCallActivity"
android:permission="android.permission.CALL_PRIVILEGED"
android:process=":ui">
<intent-filter android:priority="1000">
@@ -176,7 +187,7 @@
</intent-filter>
</activity-alias>
- <receiver android:name="TelecomBroadcastReceiver" android:exported="false"
+ <receiver android:name=".components.TelecomBroadcastReceiver" android:exported="false"
android:process="system">
<intent-filter>
<action android:name="com.android.server.telecom.ACTION_CALL_BACK_FROM_NOTIFICATION" />
@@ -185,7 +196,7 @@
</intent-filter>
</receiver>
- <receiver android:name="PhoneAccountBroadcastReceiver"
+ <receiver android:name=".components.PhoneAccountBroadcastReceiver"
android:process="system">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
@@ -203,7 +214,7 @@
</intent-filter>
</activity>
- <activity android:name=".ErrorDialogActivity"
+ <activity android:name=".components.ErrorDialogActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
@@ -211,13 +222,13 @@
android:process=":ui">
</activity>
- <receiver android:name=".CallReceiver"
+ <receiver android:name=".components.PrimaryCallReceiver"
android:exported="true"
android:permission="android.permission.MODIFY_PHONE_STATE"
android:process="system">
</receiver>
- <service android:name="BluetoothPhoneService"
+ <service android:name=".components.BluetoothPhoneService"
android:singleUser="true"
android:process="system">
<intent-filter>
@@ -225,7 +236,7 @@
</intent-filter>
</service>
- <service android:name=".TelecomService"
+ <service android:name=".components.TelecomService"
android:singleUser="true"
android:process="system">
<intent-filter>
diff --git a/proguard.flags b/proguard.flags
index e52ac20..357336b 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1,4 +1,8 @@
-verbose
-
-# Keep @VisibleForTesting elements
-keep @com.android.internal.annotations.VisibleForTesting class *
+-keep class com.android.server.telecom.TelecomSystem {
+ *;
+}
+-keep class com.android.server.telecom.Log {
+ *;
+}
diff --git a/src/com/android/server/telecom/BluetoothPhoneService.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
similarity index 66%
rename from src/com/android/server/telecom/BluetoothPhoneService.java
rename to src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 9a0cae1..fe9ddd1 100644
--- a/src/com/android/server/telecom/BluetoothPhoneService.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.telecom;
-import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
@@ -26,10 +25,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.telecom.CallState;
import android.telecom.Connection;
@@ -49,38 +45,10 @@
* Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
* and accepts call-related commands to perform on behalf of the BT device.
*/
-public final class BluetoothPhoneService extends Service {
- /**
- * Request object for performing synchronous requests to the main thread.
- */
- private static class MainThreadRequest {
- private static final Object RESULT_NOT_SET = new Object();
- Object result = RESULT_NOT_SET;
- int param;
-
- MainThreadRequest(int param) {
- this.param = param;
- }
-
- void setResult(Object value) {
- result = value;
- synchronized (this) {
- notifyAll();
- }
- }
- }
+public final class BluetoothPhoneServiceImpl {
private static final String TAG = "BluetoothPhoneService";
- private static final int MSG_ANSWER_CALL = 1;
- private static final int MSG_HANGUP_CALL = 2;
- private static final int MSG_SEND_DTMF = 3;
- private static final int MSG_PROCESS_CHLD = 4;
- private static final int MSG_GET_NETWORK_OPERATOR = 5;
- private static final int MSG_LIST_CURRENT_CALLS = 6;
- private static final int MSG_QUERY_PHONE_STATE = 7;
- private static final int MSG_GET_SUBSCRIBER_NUMBER = 8;
-
// match up with bthf_call_state_t of bt_hf.h
private static final int CALL_STATE_ACTIVE = 0;
private static final int CALL_STATE_HELD = 1;
@@ -114,206 +82,142 @@
private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
@Override
public boolean answerCall() throws RemoteException {
- enforceModifyPermission();
- Log.i(TAG, "BT - answering call");
- return sendSynchronousRequest(MSG_ANSWER_CALL);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ Log.i(TAG, "BT - answering call");
+ Call call = mCallsManager.getRingingCall();
+ if (call != null) {
+ mCallsManager.answerCall(call, 0);
+ return true;
+ }
+ return false;
+ }
}
@Override
public boolean hangupCall() throws RemoteException {
- enforceModifyPermission();
- Log.i(TAG, "BT - hanging up call");
- return sendSynchronousRequest(MSG_HANGUP_CALL);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ Log.i(TAG, "BT - hanging up call");
+ Call call = mCallsManager.getForegroundCall();
+ if (call != null) {
+ mCallsManager.disconnectCall(call);
+ return true;
+ }
+ return false;
+ }
}
@Override
public boolean sendDtmf(int dtmf) throws RemoteException {
- enforceModifyPermission();
- Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
- return sendSynchronousRequest(MSG_SEND_DTMF, dtmf);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
+ Call call = mCallsManager.getForegroundCall();
+ if (call != null) {
+ // TODO: Consider making this a queue instead of starting/stopping
+ // in quick succession.
+ mCallsManager.playDtmfTone(call, (char) dtmf);
+ mCallsManager.stopDtmfTone(call);
+ return true;
+ }
+ return false;
+ }
}
@Override
public String getNetworkOperator() throws RemoteException {
- Log.i(TAG, "getNetworkOperator");
- enforceModifyPermission();
- return sendSynchronousRequest(MSG_GET_NETWORK_OPERATOR);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ Log.i(TAG, "getNetworkOperator");
+ PhoneAccount account = getBestPhoneAccount();
+ if (account != null) {
+ return account.getLabel().toString();
+ } else {
+ // Finally, just get the network name from telephony.
+ return TelephonyManager.from(mContext)
+ .getNetworkOperatorName();
+ }
+ }
}
@Override
public String getSubscriberNumber() throws RemoteException {
- Log.i(TAG, "getSubscriberNumber");
- enforceModifyPermission();
- return sendSynchronousRequest(MSG_GET_SUBSCRIBER_NUMBER);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ Log.i(TAG, "getSubscriberNumber");
+ String address = null;
+ PhoneAccount account = getBestPhoneAccount();
+ if (account != null) {
+ Uri addressUri = account.getAddress();
+ if (addressUri != null) {
+ address = addressUri.getSchemeSpecificPart();
+ }
+ }
+ if (TextUtils.isEmpty(address)) {
+ address = TelephonyManager.from(mContext).getLine1Number();
+ }
+ return address;
+ }
}
@Override
public boolean listCurrentCalls() throws RemoteException {
- // only log if it is after we recently updated the headset state or else it can clog
- // the android log since this can be queried every second.
- boolean logQuery = mHeadsetUpdatedRecently;
- mHeadsetUpdatedRecently = false;
+ synchronized (mLock) {
+ enforceModifyPermission();
+ // only log if it is after we recently updated the headset state or else it can clog
+ // the android log since this can be queried every second.
+ boolean logQuery = mHeadsetUpdatedRecently;
+ mHeadsetUpdatedRecently = false;
- if (logQuery) {
- Log.i(TAG, "listcurrentCalls");
+ if (logQuery) {
+ Log.i(TAG, "listcurrentCalls");
+ }
+
+ sendListOfCalls(logQuery);
+ return true;
}
- enforceModifyPermission();
- return sendSynchronousRequest(MSG_LIST_CURRENT_CALLS, logQuery ? 1 : 0);
}
@Override
public boolean queryPhoneState() throws RemoteException {
- Log.i(TAG, "queryPhoneState");
- enforceModifyPermission();
- return sendSynchronousRequest(MSG_QUERY_PHONE_STATE);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ Log.i(TAG, "queryPhoneState");
+ updateHeadsetWithCallState(true /* force */);
+ return true;
+ }
}
@Override
public boolean processChld(int chld) throws RemoteException {
- Log.i(TAG, "processChld %d", chld);
- enforceModifyPermission();
- return sendSynchronousRequest(MSG_PROCESS_CHLD, chld);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ Log.i(TAG, "processChld %d", chld);
+ return BluetoothPhoneServiceImpl.this.processChld(chld);
+ }
}
@Override
public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
- Log.d(TAG, "RAT change");
+ Log.d(TAG, "RAT change - deprecated");
// deprecated
}
@Override
public void cdmaSetSecondCallState(boolean state) throws RemoteException {
- Log.d(TAG, "cdma 1");
+ Log.d(TAG, "cdma 1 - deprecated");
// deprecated
}
@Override
public void cdmaSwapSecondCallState() throws RemoteException {
- Log.d(TAG, "cdma 2");
+ Log.d(TAG, "cdma 2 - deprecated");
// deprecated
}
};
/**
- * Main-thread handler for BT commands. Since telecom logic runs on a single thread, commands
- * that are sent to it from the headset need to be moved over to the main thread before
- * executing. This handler exists for that reason.
- */
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- MainThreadRequest request = msg.obj instanceof MainThreadRequest ?
- (MainThreadRequest) msg.obj : null;
- CallsManager callsManager = getCallsManager();
- Call call = null;
-
- Log.d(TAG, "handleMessage(%d) w/ param %s",
- msg.what, request == null ? null : request.param);
-
- switch (msg.what) {
- case MSG_ANSWER_CALL:
- try {
- call = callsManager.getRingingCall();
- if (call != null) {
- getCallsManager().answerCall(call, 0);
- }
- } finally {
- request.setResult(call != null);
- }
- break;
-
- case MSG_HANGUP_CALL:
- try {
- call = callsManager.getForegroundCall();
- if (call != null) {
- callsManager.disconnectCall(call);
- }
- } finally {
- request.setResult(call != null);
- }
- break;
-
- case MSG_SEND_DTMF:
- try {
- call = callsManager.getForegroundCall();
- if (call != null) {
- // TODO: Consider making this a queue instead of starting/stopping
- // in quick succession.
- callsManager.playDtmfTone(call, (char) request.param);
- callsManager.stopDtmfTone(call);
- }
- } finally {
- request.setResult(call != null);
- }
- break;
-
- case MSG_PROCESS_CHLD:
- Boolean result = false;
- try {
- result = processChld(request.param);
- } finally {
- request.setResult(result);
- }
- break;
-
- case MSG_GET_SUBSCRIBER_NUMBER:
- String address = null;
- try {
- PhoneAccount account = getBestPhoneAccount();
- if (account != null) {
- Uri addressUri = account.getAddress();
- if (addressUri != null) {
- address = addressUri.getSchemeSpecificPart();
- }
- }
-
- if (TextUtils.isEmpty(address)) {
- address = TelephonyManager.from(BluetoothPhoneService.this)
- .getLine1Number();
- }
- } finally {
- request.setResult(address);
- }
- break;
-
- case MSG_GET_NETWORK_OPERATOR:
- String label = null;
- try {
- PhoneAccount account = getBestPhoneAccount();
- if (account != null) {
- label = account.getLabel().toString();
- } else {
- // Finally, just get the network name from telephony.
- label = TelephonyManager.from(BluetoothPhoneService.this)
- .getNetworkOperatorName();
- }
- } finally {
- request.setResult(label);
- }
- break;
-
- case MSG_LIST_CURRENT_CALLS:
- try {
- sendListOfCalls(request.param == 1);
- } finally {
- request.setResult(true);
- }
- break;
-
- case MSG_QUERY_PHONE_STATE:
- try {
- updateHeadsetWithCallState(true /* force */);
- } finally {
- if (request != null) {
- request.setResult(true);
- }
- }
- break;
- }
- }
- };
-
- /**
* Listens to call changes from the CallsManager and calls into methods to update the bluetooth
* headset with the new states.
*/
@@ -337,7 +241,7 @@
// When the call later transitions to DIALING/DISCONNECTED we will then send out the
// aggregated update.
if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
- for (Call otherCall : CallsManager.getInstance().getCalls()) {
+ for (Call otherCall : mCallsManager.getCalls()) {
if (otherCall.getState() == CallState.CONNECTING) {
return;
}
@@ -347,7 +251,7 @@
// To have an active call and another dialing at the same time is an invalid BT
// state. We can assume that the active call will be automatically held which will
// send another update at which point we will be in the right state.
- if (CallsManager.getInstance().getActiveCall() != null
+ if (mCallsManager.getActiveCall() != null
&& oldState == CallState.CONNECTING && newState == CallState.DIALING) {
return;
}
@@ -400,12 +304,16 @@
new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mBluetoothHeadset = (BluetoothHeadset) proxy;
+ synchronized (mLock) {
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ }
}
@Override
public void onServiceDisconnected(int profile) {
- mBluetoothHeadset = null;
+ synchronized (mLock) {
+ mBluetoothHeadset = null;
+ }
}
};
@@ -415,10 +323,17 @@
private final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
- Log.d(TAG, "Bluetooth Adapter state: %d", state);
- if (state == BluetoothAdapter.STATE_ON) {
- mHandler.sendEmptyMessage(MSG_QUERY_PHONE_STATE);
+ synchronized (mLock) {
+ int state = intent
+ .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+ Log.d(TAG, "Bluetooth Adapter state: %d", state);
+ if (state == BluetoothAdapter.STATE_ON) {
+ try {
+ mBinder.queryPhoneState();
+ } catch (RemoteException e) {
+ // Remote exception not expected
+ }
+ }
}
}
};
@@ -431,71 +346,64 @@
private boolean mHeadsetUpdatedRecently = false;
- public BluetoothPhoneService() {
- Log.v(TAG, "Constructor");
- }
+ private final Context mContext;
+ private final TelecomSystem.SyncRoot mLock;
+ private final CallsManager mCallsManager;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
- public static final void start(Context context) {
- if (BluetoothAdapter.getDefaultAdapter() != null) {
- context.startService(new Intent(context, BluetoothPhoneService.class));
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- Log.d(TAG, "Binding service");
+ public IBinder getBinder() {
return mBinder;
}
- @Override
- public void onCreate() {
- Log.d(TAG, "onCreate");
+ public BluetoothPhoneServiceImpl(
+ Context context,
+ TelecomSystem.SyncRoot lock,
+ CallsManager callsManager,
+ PhoneAccountRegistrar phoneAccountRegistrar) {
+ Log.d(this, "onCreate");
+
+ mContext = context;
+ mLock = lock;
+ mCallsManager = callsManager;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
- Log.d(TAG, "BluetoothPhoneService shutting down, no BT Adapter found.");
+ Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
return;
}
- mBluetoothAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
+ mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
- registerReceiver(mBluetoothAdapterReceiver, intentFilter);
+ context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
- CallsManager.getInstance().addListener(mCallsManagerListener);
+ mCallsManager.addListener(mCallsManagerListener);
updateHeadsetWithCallState(false /* force */);
}
- @Override
- public void onDestroy() {
- Log.d(TAG, "onDestroy");
- CallsManager.getInstance().removeListener(mCallsManagerListener);
- super.onDestroy();
- }
-
private boolean processChld(int chld) {
- CallsManager callsManager = CallsManager.getInstance();
- Call activeCall = callsManager.getActiveCall();
- Call ringingCall = callsManager.getRingingCall();
- Call heldCall = callsManager.getHeldCall();
+ Call activeCall = mCallsManager.getActiveCall();
+ Call ringingCall = mCallsManager.getRingingCall();
+ Call heldCall = mCallsManager.getHeldCall();
// TODO: Keeping as Log.i for now. Move to Log.d after L release if BT proves stable.
Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
if (chld == CHLD_TYPE_RELEASEHELD) {
if (ringingCall != null) {
- callsManager.rejectCall(ringingCall, false, null);
+ mCallsManager.rejectCall(ringingCall, false, null);
return true;
} else if (heldCall != null) {
- callsManager.disconnectCall(heldCall);
+ mCallsManager.disconnectCall(heldCall);
return true;
}
} else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
if (activeCall != null) {
- callsManager.disconnectCall(activeCall);
+ mCallsManager.disconnectCall(activeCall);
if (ringingCall != null) {
- callsManager.answerCall(ringingCall, 0);
+ mCallsManager.answerCall(ringingCall, 0);
} else if (heldCall != null) {
- callsManager.unholdCall(heldCall);
+ mCallsManager.unholdCall(heldCall);
}
return true;
}
@@ -504,15 +412,15 @@
activeCall.swapConference();
return true;
} else if (ringingCall != null) {
- callsManager.answerCall(ringingCall, 0);
+ mCallsManager.answerCall(ringingCall, 0);
return true;
} else if (heldCall != null) {
// CallsManager will hold any active calls when unhold() is called on a
// currently-held call.
- callsManager.unholdCall(heldCall);
+ mCallsManager.unholdCall(heldCall);
return true;
} else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
- callsManager.holdCall(activeCall);
+ mCallsManager.holdCall(activeCall);
return true;
}
} else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
@@ -523,7 +431,7 @@
} else {
List<Call> conferenceable = activeCall.getConferenceableCalls();
if (!conferenceable.isEmpty()) {
- callsManager.conference(activeCall, conferenceable.get(0));
+ mCallsManager.conference(activeCall, conferenceable.get(0));
return true;
}
}
@@ -533,40 +441,12 @@
}
private void enforceModifyPermission() {
- enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
- }
-
- private <T> T sendSynchronousRequest(int message) {
- return sendSynchronousRequest(message, 0);
- }
-
- private <T> T sendSynchronousRequest(int message, int param) {
- if (Looper.myLooper() == mHandler.getLooper()) {
- Log.w(TAG, "This method will deadlock if called from the main thread.");
- }
-
- MainThreadRequest request = new MainThreadRequest(param);
- mHandler.obtainMessage(message, request).sendToTarget();
- synchronized (request) {
- while (request.result == MainThreadRequest.RESULT_NOT_SET) {
- try {
- request.wait();
- } catch (InterruptedException e) {
- // Do nothing, go back and wait until the request is complete.
- Log.e(TAG, e, "InterruptedException");
- }
- }
- }
- if (request.result != null) {
- @SuppressWarnings("unchecked")
- T retval = (T) request.result;
- return retval;
- }
- return null;
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_PHONE_STATE, null);
}
private void sendListOfCalls(boolean shouldLog) {
- Collection<Call> mCalls = getCallsManager().getCalls();
+ Collection<Call> mCalls = mCallsManager.getCalls();
for (Call call : mCalls) {
// We don't send the parent conference call to the bluetooth device.
if (!call.isConference()) {
@@ -580,7 +460,7 @@
* Sends a single clcc (C* List Current Calls) event for the specified call.
*/
private void sendClccForCall(Call call, boolean shouldLog) {
- boolean isForeground = getCallsManager().getForegroundCall() == call;
+ boolean isForeground = mCallsManager.getForegroundCall() == call;
int state = convertCallState(call.getState(), isForeground);
boolean isPartOfConference = false;
@@ -678,10 +558,10 @@
* changed.
*/
private void updateHeadsetWithCallState(boolean force) {
- CallsManager callsManager = getCallsManager();
- Call activeCall = callsManager.getActiveCall();
- Call ringingCall = callsManager.getRingingCall();
- Call heldCall = callsManager.getHeldCall();
+ CallsManager callsManager = mCallsManager;
+ Call activeCall = mCallsManager.getActiveCall();
+ Call ringingCall = mCallsManager.getRingingCall();
+ Call heldCall = mCallsManager.getHeldCall();
int bluetoothCallState = getBluetoothCallStateForUpdate();
@@ -698,7 +578,7 @@
}
int numActiveCalls = activeCall == null ? 0 : 1;
- int numHeldCalls = callsManager.getNumHeldCalls();
+ int numHeldCalls = mCallsManager.getNumHeldCalls();
// For conference calls which support swapping the active call within the conference
// (namely CDMA calls) we need to expose that as a held call in order for the BT device
@@ -791,9 +671,9 @@
}
private int getBluetoothCallStateForUpdate() {
- CallsManager callsManager = getCallsManager();
- Call ringingCall = callsManager.getRingingCall();
- Call dialingCall = callsManager.getDialingCall();
+ CallsManager callsManager = mCallsManager;
+ Call ringingCall = mCallsManager.getRingingCall();
+ Call dialingCall = mCallsManager.getDialingCall();
//
// !! WARNING !!
@@ -848,33 +728,28 @@
return CALL_STATE_IDLE;
}
- private CallsManager getCallsManager() {
- return CallsManager.getInstance();
- }
-
/**
* Returns the best phone account to use for the given state of all calls.
* First, tries to return the phone account for the foreground call, second the default
* phone account for PhoneAccount.SCHEME_TEL.
*/
private PhoneAccount getBestPhoneAccount() {
- PhoneAccountRegistrar registry = TelecomGlobals.getInstance().getPhoneAccountRegistrar();
- if (registry == null) {
+ if (mPhoneAccountRegistrar == null) {
return null;
}
- Call call = getCallsManager().getForegroundCall();
+ Call call = mCallsManager.getForegroundCall();
PhoneAccount account = null;
if (call != null) {
// First try to get the network name of the foreground call.
- account = registry.getPhoneAccount(call.getTargetPhoneAccount());
+ account = mPhoneAccountRegistrar.getPhoneAccount(call.getTargetPhoneAccount());
}
if (account == null) {
// Second, Try to get the label for the default Phone Account.
- account = registry.getPhoneAccount(
- registry.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL));
+ account = mPhoneAccountRegistrar.getPhoneAccount(
+ mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL));
}
return account;
}
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 8a85f2c..9e29fe1 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -38,6 +38,7 @@
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.CallerInfoAsyncQuery;
@@ -60,7 +61,8 @@
* from the time the call intent was received by Telecom (vs. the time the call was
* connected etc).
*/
-final class Call implements CreateConnectionResponse {
+@VisibleForTesting
+final public class Call implements CreateConnectionResponse {
/**
* Listener for events on the call.
*/
@@ -92,7 +94,7 @@
boolean onCanceledViaNewOutgoingCallBroadcast(Call call);
}
- abstract static class ListenerBase implements Listener {
+ public abstract static class ListenerBase implements Listener {
@Override
public void onSuccessfulOutgoingCall(Call call, int callState) {}
@Override
@@ -298,7 +300,9 @@
private boolean mIsVoipAudioMode;
private StatusHints mStatusHints;
private final ConnectionServiceRepository mRepository;
+ private final ContactsAsyncHelper mContactsAsyncHelper;
private final Context mContext;
+ private final CallsManager mCallsManager;
private boolean mWasConferencePreviouslyMerged = false;
@@ -324,9 +328,11 @@
* one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
* @param isIncoming True if this is an incoming call.
*/
- Call(
+ public Call(
Context context,
+ CallsManager callsManager,
ConnectionServiceRepository repository,
+ ContactsAsyncHelper contactsAsyncHelper,
Uri handle,
GatewayInfo gatewayInfo,
PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -335,7 +341,9 @@
boolean isConference) {
mState = isConference ? CallState.ACTIVE : CallState.NEW;
mContext = context;
+ mCallsManager = callsManager;
mRepository = repository;
+ mContactsAsyncHelper = contactsAsyncHelper;
setHandle(handle);
setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
mGatewayInfo = gatewayInfo;
@@ -363,7 +371,9 @@
*/
Call(
Context context,
+ CallsManager callsManager,
ConnectionServiceRepository repository,
+ ContactsAsyncHelper contactsAsyncHelper,
Uri handle,
GatewayInfo gatewayInfo,
PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -371,17 +381,18 @@
boolean isIncoming,
boolean isConference,
long connectTimeMillis) {
- this(context, repository, handle, gatewayInfo, connectionManagerPhoneAccountHandle,
- targetPhoneAccountHandle, isIncoming, isConference);
+ this(context, callsManager, repository, contactsAsyncHelper, handle, gatewayInfo,
+ connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, isIncoming,
+ isConference);
mConnectTimeMillis = connectTimeMillis;
}
- void addListener(Listener listener) {
+ public void addListener(Listener listener) {
mListeners.add(listener);
}
- void removeListener(Listener listener) {
+ public void removeListener(Listener listener) {
if (listener != null) {
mListeners.remove(listener);
}
@@ -438,7 +449,7 @@
* misbehave and they do this very often. The result is that we do not enforce state transitions
* and instead keep the code resilient to unexpected state changes.
*/
- void setState(int newState) {
+ public void setState(int newState) {
if (mState != newState) {
Log.v(this, "setState %s -> %s", mState, newState);
@@ -484,7 +495,7 @@
return mIsConference;
}
- Uri getHandle() {
+ public Uri getHandle() {
return mHandle;
}
@@ -497,7 +508,7 @@
setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
}
- void setHandle(Uri handle, int presentation) {
+ public void setHandle(Uri handle, int presentation) {
if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
mHandlePresentation = presentation;
if (mHandlePresentation == TelecomManager.PRESENTATION_RESTRICTED ||
@@ -541,15 +552,15 @@
}
}
- String getName() {
+ public String getName() {
return mCallerInfo == null ? null : mCallerInfo.name;
}
- Bitmap getPhotoIcon() {
+ public Bitmap getPhotoIcon() {
return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
}
- Drawable getPhoto() {
+ public Drawable getPhoto() {
return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
}
@@ -557,13 +568,13 @@
* @param disconnectCause The reason for the disconnection, represented by
* {@link android.telecom.DisconnectCause}.
*/
- void setDisconnectCause(DisconnectCause disconnectCause) {
+ public void setDisconnectCause(DisconnectCause disconnectCause) {
// TODO: Consider combining this method with a setDisconnected() method that is totally
// separate from setState.
mDisconnectCause = disconnectCause;
}
- DisconnectCause getDisconnectCause() {
+ public DisconnectCause getDisconnectCause() {
return mDisconnectCause;
}
@@ -648,11 +659,11 @@
* @return The time when this call object was created and added to the set of pending outgoing
* calls.
*/
- long getCreationTimeMillis() {
+ public long getCreationTimeMillis() {
return mCreationTimeMillis;
}
- void setCreationTimeMillis(long time) {
+ public void setCreationTimeMillis(long time) {
mCreationTimeMillis = time;
}
@@ -820,7 +831,7 @@
public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
clearConnectionService();
setDisconnectCause(disconnectCause);
- CallsManager.getInstance().markCallAsDisconnected(this, disconnectCause);
+ mCallsManager.markCallAsDisconnected(this, disconnectCause);
if (mIsUnknown) {
for (Listener listener : mListeners) {
@@ -1270,7 +1281,7 @@
if (mCallerInfo.contactDisplayPhotoUri != null) {
Log.d(this, "Searching person uri %s for call %s",
mCallerInfo.contactDisplayPhotoUri, this);
- ContactsAsyncHelper.startObtainPhotoAsync(
+ mContactsAsyncHelper.startObtainPhotoAsync(
token,
mContext,
mCallerInfo.contactDisplayPhotoUri,
@@ -1314,7 +1325,7 @@
if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
mCannedSmsResponsesLoadingStarted = true;
- RespondViaSmsManager.getInstance().loadCannedTextMessages(
+ mCallsManager.getRespondViaSmsManager().loadCannedTextMessages(
new Response<Void, List<String>>() {
@Override
public void onResult(Void request, List<String>... result) {
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index b555f85..8d1144d 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -37,6 +37,7 @@
private final AudioManager mAudioManager;
private final BluetoothManager mBluetoothManager;
private final WiredHeadsetManager mWiredHeadsetManager;
+ private final CallsManager mCallsManager;
private AudioState mAudioState;
private int mAudioFocusStreamType;
@@ -46,12 +47,17 @@
private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL;
private Call mCallToSpeedUpMTAudio = null;
- CallAudioManager(Context context, StatusBarNotifier statusBarNotifier,
- WiredHeadsetManager wiredHeadsetManager) {
+ CallAudioManager(
+ Context context,
+ StatusBarNotifier statusBarNotifier,
+ WiredHeadsetManager wiredHeadsetManager,
+ CallsManager callsManager) {
mStatusBarNotifier = statusBarNotifier;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mBluetoothManager = new BluetoothManager(context, this);
mWiredHeadsetManager = wiredHeadsetManager;
+ mCallsManager = callsManager;
+
mWiredHeadsetManager.addListener(this);
saveAudioState(getInitialAudioState(null));
@@ -79,7 +85,7 @@
public void onCallRemoved(Call call) {
// If we didn't already have focus, there's nothing to do.
if (hasFocus()) {
- if (CallsManager.getInstance().getCalls().isEmpty()) {
+ if (mCallsManager.getCalls().isEmpty()) {
Log.v(this, "all calls removed, reseting system audio to default state");
setInitialAudioState(null, false /* force */);
mWasSpeakerOn = false;
@@ -100,7 +106,7 @@
// We do two things:
// (1) If this is the first call, then we can to turn on bluetooth if available.
// (2) Unmute the audio for the new incoming call.
- boolean isOnlyCall = CallsManager.getInstance().getCalls().size() == 1;
+ boolean isOnlyCall = mCallsManager.getCalls().size() == 1;
if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) {
mBluetoothManager.connectBluetoothAudio();
route = AudioState.ROUTE_BLUETOOTH;
@@ -182,7 +188,7 @@
Log.v(this, "mute, shouldMute: %b", shouldMute);
// Don't mute if there are any emergency calls.
- if (CallsManager.getInstance().hasEmergencyCall()) {
+ if (mCallsManager.hasEmergencyCall()) {
shouldMute = false;
Log.v(this, "ignoring mute for emergency call");
}
@@ -240,8 +246,6 @@
* @param isPlayingNew The status to set.
*/
void setIsTonePlaying(boolean isPlayingNew) {
- ThreadUtil.checkOnMainThread();
-
if (mIsTonePlaying != isPlayingNew) {
Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
mIsTonePlaying = isPlayingNew;
@@ -336,7 +340,8 @@
}
if (!oldAudioState.equals(mAudioState)) {
- CallsManager.getInstance().onAudioStateChanged(oldAudioState, mAudioState);
+ mCallsManager
+ .onAudioStateChanged(oldAudioState, mAudioState);
updateAudioForForegroundCall();
}
}
@@ -370,9 +375,9 @@
requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
} else {
Call foregroundCall = getForegroundCall();
- Call waitingForAccountSelectionCall =
- CallsManager.getInstance().getFirstCallWithState(CallState.PRE_DIAL_WAIT);
- Call call = CallsManager.getInstance().getForegroundCall();
+ Call waitingForAccountSelectionCall = mCallsManager
+ .getFirstCallWithState(CallState.PRE_DIAL_WAIT);
+ Call call = mCallsManager.getForegroundCall();
if (foregroundCall == null && call != null && call == mCallToSpeedUpMTAudio) {
requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL,
AudioManager.MODE_IN_CALL);
@@ -519,7 +524,7 @@
}
private void updateAudioForForegroundCall() {
- Call call = CallsManager.getInstance().getForegroundCall();
+ Call call = mCallsManager.getForegroundCall();
if (call != null && call.getConnectionService() != null) {
call.getConnectionService().onAudioStateChanged(call, mAudioState);
}
@@ -529,7 +534,7 @@
* Returns the current foreground call in order to properly set the audio mode.
*/
private Call getForegroundCall() {
- Call call = CallsManager.getInstance().getForegroundCall();
+ Call call = mCallsManager.getForegroundCall();
// We ignore any foreground call that is in the ringing state because we deal with ringing
// calls exclusively through the mIsRinging variable set by {@link Ringer}.
@@ -541,7 +546,7 @@
}
private boolean hasRingingForegroundCall() {
- Call call = CallsManager.getInstance().getForegroundCall();
+ Call call = mCallsManager.getForegroundCall();
return call != null && call.getState() == CallState.RINGING;
}
diff --git a/src/com/android/server/telecom/CallIdMapper.java b/src/com/android/server/telecom/CallIdMapper.java
index 729db0a..8199dfa 100644
--- a/src/com/android/server/telecom/CallIdMapper.java
+++ b/src/com/android/server/telecom/CallIdMapper.java
@@ -79,13 +79,10 @@
private static int sIdCount;
CallIdMapper(String callIdPrefix) {
- ThreadUtil.checkOnMainThread();
mCallIdPrefix = callIdPrefix + "@";
}
void replaceCall(Call newCall, Call callToReplace) {
- ThreadUtil.checkOnMainThread();
-
// Use the old call's ID for the new call.
String callId = getCallId(callToReplace);
mCalls.put(callId, newCall);
@@ -95,12 +92,10 @@
if (call == null) {
return;
}
- ThreadUtil.checkOnMainThread();
mCalls.put(id, call);
}
void addCall(Call call) {
- ThreadUtil.checkOnMainThread();
addCall(call, getNewId());
}
@@ -108,12 +103,10 @@
if (call == null) {
return;
}
- ThreadUtil.checkOnMainThread();
mCalls.removeValue(call);
}
void removeCall(String callId) {
- ThreadUtil.checkOnMainThread();
mCalls.remove(callId);
}
@@ -121,13 +114,10 @@
if (call == null) {
return null;
}
- ThreadUtil.checkOnMainThread();
return mCalls.getKey(call);
}
Call getCall(Object objId) {
- ThreadUtil.checkOnMainThread();
-
String callId = null;
if (objId instanceof String) {
callId = (String) objId;
diff --git a/src/com/android/server/telecom/CallReceiver.java b/src/com/android/server/telecom/CallIntentProcessor.java
similarity index 68%
rename from src/com/android/server/telecom/CallReceiver.java
rename to src/com/android/server/telecom/CallIntentProcessor.java
index 27d4f51..1a82498 100644
--- a/src/com/android/server/telecom/CallReceiver.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -1,6 +1,7 @@
package com.android.server.telecom;
-import android.content.BroadcastReceiver;
+import com.android.server.telecom.components.ErrorDialogActivity;
+
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -16,40 +17,49 @@
import android.widget.Toast;
/**
- * Single point of entry for all outgoing and incoming calls. {@link CallActivity} serves as a
- * trampoline activity that captures call intents for individual users and forwards it to
- * the {@link CallReceiver} which interacts with the rest of Telecom, both of which run only as
- * the primary user.
+ * Single point of entry for all outgoing and incoming calls.
+ * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
+ * captures call intents for individual users and forwards it to the {@link CallIntentProcessor}
+ * which interacts with the rest of Telecom, both of which run only as the primary user.
*/
-public class CallReceiver extends BroadcastReceiver {
- private static final String TAG = CallReceiver.class.getName();
+public class CallIntentProcessor {
- static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
- static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
- static final String KEY_IS_DEFAULT_DIALER =
- "is_default_dialer";
+ public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
+ public static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
+ public static final String KEY_IS_DEFAULT_DIALER = "is_default_dialer";
- @Override
- public void onReceive(Context context, Intent intent) {
+ private final Context mContext;
+ private final CallsManager mCallsManager;
+
+ public CallIntentProcessor(Context context, CallsManager callsManager) {
+ this.mContext = context;
+ this.mCallsManager = callsManager;
+ }
+
+ public void processIntent(Intent intent) {
final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
Trace.beginSection("processNewCallCallIntent");
if (isUnknownCall) {
- processUnknownCallIntent(intent);
+ processUnknownCallIntent(mCallsManager, intent);
} else {
- processOutgoingCallIntent(context, intent);
+ processOutgoingCallIntent(mContext, mCallsManager, intent);
}
Trace.endSection();
}
+
/**
* Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
*
* @param intent Call intent containing data about the handle to call.
*/
- static void processOutgoingCallIntent(Context context, Intent intent) {
- if (shouldPreventDuplicateVideoCall(context, intent)) {
+ static void processOutgoingCallIntent(
+ Context context,
+ CallsManager callsManager,
+ Intent intent) {
+ if (shouldPreventDuplicateVideoCall(context, callsManager, intent)) {
return;
}
@@ -76,7 +86,7 @@
final boolean isDefaultDialer = intent.getBooleanExtra(KEY_IS_DEFAULT_DIALER, false);
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
- Call call = getCallsManager().startOutgoingCall(handle, phoneAccountHandle, clientExtras);
+ Call call = callsManager.startOutgoingCall(handle, phoneAccountHandle, clientExtras);
if (call != null) {
// Asynchronous calls should not usually be made inside a BroadcastReceiver because once
@@ -85,7 +95,7 @@
// process will be running throughout the duration of the phone call and should never
// be killed.
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
- context, getCallsManager(), call, intent, isDefaultDialer);
+ context, callsManager, call, intent, isDefaultDialer);
final int result = broadcaster.processIntent();
final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
@@ -95,16 +105,18 @@
}
}
- static void processIncomingCallIntent(Intent intent) {
+ static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
if (phoneAccountHandle == null) {
- Log.w(TAG, "Rejecting incoming call due to null phone account");
+ Log.w(CallIntentProcessor.class,
+ "Rejecting incoming call due to null phone account");
return;
}
if (phoneAccountHandle.getComponentName() == null) {
- Log.w(TAG, "Rejecting incoming call due to null component name");
+ Log.w(CallIntentProcessor.class,
+ "Rejecting incoming call due to null component name");
return;
}
@@ -116,29 +128,26 @@
clientExtras = Bundle.EMPTY;
}
- Log.d(TAG, "Processing incoming call from connection service [%s]",
+ Log.d(CallIntentProcessor.class,
+ "Processing incoming call from connection service [%s]",
phoneAccountHandle.getComponentName());
- getCallsManager().processIncomingCallIntent(phoneAccountHandle, clientExtras);
+ callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
}
- private void processUnknownCallIntent(Intent intent) {
+ private static void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
if (phoneAccountHandle == null) {
- Log.w(this, "Rejecting unknown call due to null phone account");
+ Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account");
return;
}
if (phoneAccountHandle.getComponentName() == null) {
- Log.w(this, "Rejecting unknown call due to null component name");
+ Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
return;
}
- getCallsManager().addNewUnknownCall(phoneAccountHandle, intent.getExtras());
- }
-
- static CallsManager getCallsManager() {
- return CallsManager.getInstance();
+ callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
}
private static void disconnectCallAndShowErrorDialog(
@@ -167,11 +176,14 @@
* @return {@code true} if the outgoing call is a video call and should be prevented from going
* out, {@code false} otherwise.
*/
- private static boolean shouldPreventDuplicateVideoCall(Context context, Intent intent) {
+ private static boolean shouldPreventDuplicateVideoCall(
+ Context context,
+ CallsManager callsManager,
+ Intent intent) {
int intentVideoState = intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.VideoState.AUDIO_ONLY);
if (intentVideoState == VideoProfile.VideoState.AUDIO_ONLY
- || !getCallsManager().hasVideoCall()) {
+ || !callsManager.hasVideoCall()) {
return false;
} else {
// Display an error toast to the user.
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 3732a4f..aaa075a 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -37,6 +37,7 @@
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.util.Collection;
@@ -76,11 +77,6 @@
void onCanAddCallChanged(boolean canAddCall);
}
- /**
- * Singleton instance of the {@link CallsManager}, initialized from {@link TelecomService}.
- */
- private static CallsManager sInstance = null;
-
private static final String TAG = "CallsManager";
private static final int MAXIMUM_LIVE_CALLS = 1;
@@ -110,6 +106,7 @@
private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
private final InCallController mInCallController;
private final CallAudioManager mCallAudioManager;
+ private RespondViaSmsManager mRespondViaSmsManager;
private final Ringer mRinger;
private final InCallWakeLockController mInCallWakeLockController;
// For this set initial table size to 16 because we add 13 listeners in
@@ -123,6 +120,8 @@
private final PhoneStateBroadcaster mPhoneStateBroadcaster;
private final CallLogManager mCallLogManager;
private final Context mContext;
+ private final TelecomSystem.SyncRoot mLock;
+ private final ContactsAsyncHelper mContactsAsyncHelper;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final MissedCallNotifier mMissedCallNotifier;
private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
@@ -140,43 +139,39 @@
private Runnable mStopTone;
- /** Singleton accessor. */
- static CallsManager getInstance() {
- return sInstance;
- }
-
- /**
- * Sets the static singleton instance.
- *
- * @param instance The instance to set.
- */
- static void initialize(CallsManager instance) {
- sInstance = instance;
- }
-
/**
* Initializes the required Telecom components.
*/
- CallsManager(Context context, MissedCallNotifier missedCallNotifier,
- PhoneAccountRegistrar phoneAccountRegistrar) {
+ CallsManager(
+ Context context,
+ TelecomSystem.SyncRoot lock,
+ ContactsAsyncHelper contactsAsyncHelper,
+ MissedCallNotifier missedCallNotifier,
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ HeadsetMediaButtonFactory headsetMediaButtonFactory,
+ ProximitySensorManagerFactory proximitySensorManagerFactory,
+ InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
mContext = context;
+ mLock = lock;
+ mContactsAsyncHelper = contactsAsyncHelper;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mMissedCallNotifier = missedCallNotifier;
StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
mWiredHeadsetManager = new WiredHeadsetManager(context);
- mCallAudioManager = new CallAudioManager(context, statusBarNotifier, mWiredHeadsetManager);
+ mCallAudioManager = new CallAudioManager(
+ context, statusBarNotifier, mWiredHeadsetManager, this);
InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager);
mRinger = new Ringer(mCallAudioManager, this, playerFactory, context);
- mHeadsetMediaButton = new HeadsetMediaButton(context, this);
+ mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this);
mTtyManager = new TtyManager(context, mWiredHeadsetManager);
- mProximitySensorManager = new ProximitySensorManager(context);
- mPhoneStateBroadcaster = new PhoneStateBroadcaster();
+ mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
+ mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
mCallLogManager = new CallLogManager(context);
- mInCallController = new InCallController(context);
+ mInCallController = new InCallController(context, mLock, this);
mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
- mConnectionServiceRepository = new ConnectionServiceRepository(mPhoneAccountRegistrar,
- context);
- mInCallWakeLockController = new InCallWakeLockController(context, this);
+ mConnectionServiceRepository =
+ new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
+ mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
mListeners.add(statusBarNotifier);
mListeners.add(mCallLogManager);
@@ -189,8 +184,21 @@
mListeners.add(missedCallNotifier);
mListeners.add(mDtmfLocalTonePlayer);
mListeners.add(mHeadsetMediaButton);
- mListeners.add(RespondViaSmsManager.getInstance());
mListeners.add(mProximitySensorManager);
+
+ mMissedCallNotifier.updateOnStartup(mLock, this, mContactsAsyncHelper);
+ }
+
+ public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) {
+ if (mRespondViaSmsManager != null) {
+ mListeners.remove(mRespondViaSmsManager);
+ }
+ mRespondViaSmsManager = respondViaSmsManager;
+ mListeners.add(respondViaSmsManager);
+ }
+
+ public RespondViaSmsManager getRespondViaSmsManager() {
+ return mRespondViaSmsManager;
}
@Override
@@ -278,11 +286,14 @@
mDtmfLocalTonePlayer.playTone(call, nextChar);
+ // TODO: Create a LockedRunnable class that does the synchronization automatically.
mStopTone = new Runnable() {
@Override
public void run() {
- // Set a timeout to stop the tone in case there isn't another tone to follow.
- mDtmfLocalTonePlayer.stopTone(call);
+ synchronized (mLock) {
+ // Set a timeout to stop the tone in case there isn't another tone to follow.
+ mDtmfLocalTonePlayer.stopTone(call);
+ }
}
};
mHandler.postDelayed(
@@ -339,9 +350,11 @@
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- if (mPendingCallsToDisconnect.remove(call)) {
- Log.i(this, "Delayed disconnection of call: %s", call);
- call.disconnect();
+ synchronized (mLock) {
+ if (mPendingCallsToDisconnect.remove(call)) {
+ Log.i(this, "Delayed disconnection of call: %s", call);
+ call.disconnect();
+ }
}
}
}, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
@@ -415,7 +428,9 @@
Uri handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
Call call = new Call(
mContext,
+ this,
mConnectionServiceRepository,
+ mContactsAsyncHelper,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -434,7 +449,9 @@
Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle));
Call call = new Call(
mContext,
+ this,
mConnectionServiceRepository,
+ mContactsAsyncHelper,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -470,7 +487,9 @@
// to a connection service, but in most cases will remain the same.
return new Call(
mContext,
+ this,
mConnectionServiceRepository,
+ mContactsAsyncHelper,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -871,7 +890,7 @@
* Marks the specified call as STATE_DISCONNECTED and notifies the in-call app. If this was the
* last live call, then also disconnect from the in-call controller.
*
- * @param disconnectCause The disconnect cause, see {@link android.telecomm.DisconnectCause}.
+ * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
*/
void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
call.setDisconnectCause(disconnectCause);
@@ -975,7 +994,8 @@
return true;
}
- Call getRingingCall() {
+ @VisibleForTesting
+ public Call getRingingCall() {
return getFirstCallWithState(CallState.RINGING);
}
@@ -1050,7 +1070,9 @@
Call call = new Call(
mContext,
+ this,
mConnectionServiceRepository,
+ mContactsAsyncHelper,
null /* handle */,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -1396,7 +1418,9 @@
Call createCallForExistingConnection(String callId, ParcelableConnection connection) {
Call call = new Call(
mContext,
+ this,
mConnectionServiceRepository,
+ mContactsAsyncHelper,
connection.getHandle() /* handle */,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index ffc5947..6b54709 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -21,7 +21,7 @@
/**
* Provides a default implementation for listeners of CallsManager.
*/
-class CallsManagerListenerBase implements CallsManager.CallsManagerListener {
+public class CallsManagerListenerBase implements CallsManager.CallsManagerListener {
@Override
public void onCallAdded(Call call) {
}
diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java
index 0d73371..a587b59 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -28,16 +28,33 @@
/**
* Searches for and returns connection services.
*/
-final class ConnectionServiceRepository
- implements ServiceBinder.Listener<ConnectionServiceWrapper> {
+final class ConnectionServiceRepository {
private final HashMap<Pair<ComponentName, UserHandle>, ConnectionServiceWrapper> mServiceCache =
new HashMap<>();
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final Context mContext;
+ private final TelecomSystem.SyncRoot mLock;
+ private final CallsManager mCallsManager;
- ConnectionServiceRepository(PhoneAccountRegistrar phoneAccountRegistrar, Context context) {
+ private final ServiceBinder.Listener<ConnectionServiceWrapper> mUnbindListener =
+ new ServiceBinder.Listener<ConnectionServiceWrapper>() {
+ @Override
+ public void onUnbind(ConnectionServiceWrapper service) {
+ synchronized (mLock) {
+ mServiceCache.remove(service.getComponentName());
+ }
+ }
+ };
+
+ ConnectionServiceRepository(
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ Context context,
+ TelecomSystem.SyncRoot lock,
+ CallsManager callsManager) {
mPhoneAccountRegistrar = phoneAccountRegistrar;
mContext = context;
+ mLock = lock;
+ mCallsManager = callsManager;
}
ConnectionServiceWrapper getService(ComponentName componentName, UserHandle userHandle) {
@@ -48,25 +65,17 @@
componentName,
this,
mPhoneAccountRegistrar,
+ mCallsManager,
mContext,
+ mLock,
userHandle);
- service.addListener(this);
+ service.addListener(mUnbindListener);
mServiceCache.put(cacheKey, service);
}
return service;
}
/**
- * Removes the specified service from the cache when the service unbinds.
- *
- * {@inheritDoc}
- */
- @Override
- public void onUnbind(ConnectionServiceWrapper service) {
- mServiceCache.remove(service.getComponentName());
- }
-
- /**
* Dumps the state of the {@link ConnectionServiceRepository}.
*
* @param pw The {@code IndentingPrintWriter} to write the state to.
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index d470737..1aea31d 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -20,9 +20,7 @@
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.telecom.AudioState;
@@ -39,7 +37,6 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
-import com.android.internal.os.SomeArgs;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.IVideoProvider;
@@ -60,337 +57,7 @@
* {@link IConnectionService} directly and instead should use this class to invoke methods of
* {@link IConnectionService}.
*/
-final class ConnectionServiceWrapper extends ServiceBinder<IConnectionService> {
- private static final int MSG_HANDLE_CREATE_CONNECTION_COMPLETE = 1;
- private static final int MSG_SET_ACTIVE = 2;
- private static final int MSG_SET_RINGING = 3;
- private static final int MSG_SET_DIALING = 4;
- private static final int MSG_SET_DISCONNECTED = 5;
- private static final int MSG_SET_ON_HOLD = 6;
- private static final int MSG_SET_RINGBACK_REQUESTED = 7;
- private static final int MSG_SET_CONNECTION_CAPABILITIES = 8;
- private static final int MSG_SET_IS_CONFERENCED = 9;
- private static final int MSG_ADD_CONFERENCE_CALL = 10;
- private static final int MSG_REMOVE_CALL = 11;
- private static final int MSG_ON_POST_DIAL_WAIT = 12;
- private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 13;
- private static final int MSG_SET_VIDEO_PROVIDER = 14;
- private static final int MSG_SET_IS_VOIP_AUDIO_MODE = 15;
- private static final int MSG_SET_STATUS_HINTS = 16;
- private static final int MSG_SET_ADDRESS = 17;
- private static final int MSG_SET_CALLER_DISPLAY_NAME = 18;
- private static final int MSG_SET_VIDEO_STATE = 19;
- private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 20;
- private static final int MSG_ADD_EXISTING_CONNECTION = 21;
- private static final int MSG_ON_POST_DIAL_CHAR = 22;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- Call call;
- switch (msg.what) {
- case MSG_HANDLE_CREATE_CONNECTION_COMPLETE: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- String callId = (String) args.arg1;
- ConnectionRequest request = (ConnectionRequest) args.arg2;
- ParcelableConnection connection = (ParcelableConnection) args.arg3;
- handleCreateConnectionComplete(callId, request, connection);
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_SET_ACTIVE:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.markCallAsActive(call);
- } else {
- //Log.w(this, "setActive, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_SET_RINGING:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.markCallAsRinging(call);
- } else {
- //Log.w(this, "setRinging, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_SET_DIALING:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.markCallAsDialing(call);
- } else {
- //Log.w(this, "setDialing, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_SET_DISCONNECTED: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- DisconnectCause disconnectCause = (DisconnectCause) args.arg2;
- Log.d(this, "disconnect call %s %s", disconnectCause, call);
- if (call != null) {
- mCallsManager.markCallAsDisconnected(call, disconnectCause);
- } else {
- //Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_SET_ON_HOLD:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.markCallAsOnHold(call);
- } else {
- //Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_SET_RINGBACK_REQUESTED: {
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- call.setRingbackRequested(msg.arg1 == 1);
- } else {
- //Log.w(this, "setRingback, unknown call id: %s", args.arg1);
- }
- break;
- }
- case MSG_SET_CONNECTION_CAPABILITIES: {
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- call.setConnectionCapabilities(msg.arg1);
- } else {
- //Log.w(ConnectionServiceWrapper.this,
- // "setConnectionCapabilities, unknown call id: %s", msg.obj);
- }
- break;
- }
- case MSG_SET_IS_CONFERENCED: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- Call childCall = mCallIdMapper.getCall(args.arg1);
- Log.d(this, "SET_IS_CONFERENCE: %s %s", args.arg1, args.arg2);
- if (childCall != null) {
- String conferenceCallId = (String) args.arg2;
- if (conferenceCallId == null) {
- Log.d(this, "unsetting parent: %s", args.arg1);
- childCall.setParentCall(null);
- } else {
- Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
- childCall.setParentCall(conferenceCall);
- }
- } else {
- //Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_ADD_CONFERENCE_CALL: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- String id = (String) args.arg1;
- if (mCallIdMapper.getCall(id) != null) {
- Log.w(this, "Attempting to add a conference call using an existing " +
- "call id %s", id);
- break;
- }
- ParcelableConference parcelableConference =
- (ParcelableConference) args.arg2;
-
- // Make sure that there's at least one valid call. For remote connections
- // we'll get a add conference msg from both the remote connection service
- // and from the real connection service.
- boolean hasValidCalls = false;
- for (String callId : parcelableConference.getConnectionIds()) {
- if (mCallIdMapper.getCall(callId) != null) {
- hasValidCalls = true;
- }
- }
- // But don't bail out if the connection count is 0, because that is a valid
- // IMS conference state.
- if (!hasValidCalls && parcelableConference.getConnectionIds().size() > 0) {
- Log.d(this, "Attempting to add a conference with no valid calls");
- break;
- }
-
- // need to create a new Call
- PhoneAccountHandle phAcc = null;
- if (parcelableConference != null &&
- parcelableConference.getPhoneAccount() != null) {
- phAcc = parcelableConference.getPhoneAccount();
- }
- Call conferenceCall = mCallsManager.createConferenceCall(
- phAcc, parcelableConference);
- mCallIdMapper.addCall(conferenceCall, id);
- conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
-
- Log.d(this, "adding children to conference %s phAcc %s",
- parcelableConference.getConnectionIds(), phAcc);
- for (String callId : parcelableConference.getConnectionIds()) {
- Call childCall = mCallIdMapper.getCall(callId);
- Log.d(this, "found child: %s", callId);
- if (childCall != null) {
- childCall.setParentCall(conferenceCall);
- }
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_REMOVE_CALL: {
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- if (call.isAlive()) {
- mCallsManager.markCallAsDisconnected(
- call, new DisconnectCause(DisconnectCause.REMOTE));
- } else {
- mCallsManager.markCallAsRemoved(call);
- }
- }
- break;
- }
- case MSG_ON_POST_DIAL_WAIT: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- if (call != null) {
- String remaining = (String) args.arg2;
- call.onPostDialWait(remaining);
- } else {
- //Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_ON_POST_DIAL_CHAR: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- if (call != null) {
- char nextChar = (char) args.argi1;
- call.onPostDialChar(nextChar);
- } else {
- //Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_QUERY_REMOTE_CALL_SERVICES: {
- queryRemoteConnectionServices((RemoteServiceCallback) msg.obj);
- break;
- }
- case MSG_SET_VIDEO_PROVIDER: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- IVideoProvider videoProvider = (IVideoProvider) args.arg2;
- if (call != null) {
- call.setVideoProvider(videoProvider);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_SET_IS_VOIP_AUDIO_MODE: {
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- call.setIsVoipAudioMode(msg.arg1 == 1);
- }
- break;
- }
- case MSG_SET_STATUS_HINTS: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- StatusHints statusHints = (StatusHints) args.arg2;
- if (call != null) {
- call.setStatusHints(statusHints);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_SET_ADDRESS: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- if (call != null) {
- call.setHandle((Uri) args.arg2, args.argi1);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_SET_CALLER_DISPLAY_NAME: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- if (call != null) {
- call.setCallerDisplayName((String) args.arg2, args.argi1);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_SET_VIDEO_STATE: {
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- call.setVideoState(msg.arg1);
- }
- break;
- }
- case MSG_SET_CONFERENCEABLE_CONNECTIONS: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- if (call != null ){
- @SuppressWarnings("unchecked")
- List<String> conferenceableIds = (List<String>) args.arg2;
- List<Call> conferenceableCalls =
- new ArrayList<>(conferenceableIds.size());
- for (String otherId : (List<String>) args.arg2) {
- Call otherCall = mCallIdMapper.getCall(otherId);
- if (otherCall != null && otherCall != call) {
- conferenceableCalls.add(otherCall);
- }
- }
- call.setConferenceableCalls(conferenceableCalls);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_ADD_EXISTING_CONNECTION: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- String callId = (String)args.arg1;
- ParcelableConnection connection = (ParcelableConnection)args.arg2;
- Call existingCall = mCallsManager.createCallForExistingConnection(callId,
- connection);
- mCallIdMapper.addCall(existingCall, callId);
- existingCall.setConnectionService(ConnectionServiceWrapper.this);
- } finally {
- args.recycle();
- }
- }
- }
- }
- };
+final class ConnectionServiceWrapper extends ServiceBinder {
private final class Adapter extends IConnectionServiceAdapter.Stub {
@@ -399,232 +66,365 @@
String callId,
ConnectionRequest request,
ParcelableConnection connection) {
- logIncoming("handleCreateConnectionComplete %s", request);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = request;
- args.arg3 = connection;
- mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_COMPLETE, args)
- .sendToTarget();
+ synchronized (mLock) {
+ logIncoming("handleCreateConnectionComplete %s", callId);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ ConnectionServiceWrapper.this
+ .handleCreateConnectionComplete(callId, request, connection);
+ }
}
}
@Override
public void setActive(String callId) {
- logIncoming("setActive %s", callId);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
- mHandler.obtainMessage(MSG_SET_ACTIVE, callId).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setActive %s", callId);
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+ .isValidConferenceId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.markCallAsActive(call);
+ } else {
+ //Log.w(this, "setActive, unknown call id: %s", msg.obj);
+ }
+ }
}
}
@Override
public void setRinging(String callId) {
- logIncoming("setRinging %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_SET_RINGING, callId).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setRinging %s", callId);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.markCallAsRinging(call);
+ } else {
+ //Log.w(this, "setRinging, unknown call id: %s", msg.obj);
+ }
+ }
}
}
@Override
public void setVideoProvider(String callId, IVideoProvider videoProvider) {
- logIncoming("setVideoProvider %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = videoProvider;
- mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setVideoProvider %s", callId);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setVideoProvider(videoProvider);
+ }
+ }
}
}
@Override
public void setDialing(String callId) {
- logIncoming("setDialing %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_SET_DIALING, callId).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setDialing %s", callId);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.markCallAsDialing(call);
+ } else {
+ //Log.w(this, "setDialing, unknown call id: %s", msg.obj);
+ }
+ }
}
}
@Override
public void setDisconnected(String callId, DisconnectCause disconnectCause) {
- logIncoming("setDisconnected %s %s", callId, disconnectCause);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
- Log.d(this, "disconnect call %s", callId);
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = disconnectCause;
- mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setDisconnected %s %s", callId, disconnectCause);
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+ .isValidConferenceId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ Log.d(this, "disconnect call %s %s", disconnectCause, call);
+ if (call != null) {
+ mCallsManager.markCallAsDisconnected(call, disconnectCause);
+ } else {
+ //Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
+ }
+ }
}
}
@Override
public void setOnHold(String callId) {
- logIncoming("setOnHold %s", callId);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
- mHandler.obtainMessage(MSG_SET_ON_HOLD, callId).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setOnHold %s", callId);
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+ .isValidConferenceId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.markCallAsOnHold(call);
+ } else {
+ //Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
+ }
+ }
}
}
@Override
public void setRingbackRequested(String callId, boolean ringback) {
- logIncoming("setRingbackRequested %s %b", callId, ringback);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_SET_RINGBACK_REQUESTED, ringback ? 1 : 0, 0, callId)
- .sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setRingbackRequested %s %b", callId, ringback);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setRingbackRequested(ringback);
+ } else {
+ //Log.w(this, "setRingback, unknown call id: %s", args.arg1);
+ }
+ }
}
}
@Override
public void removeCall(String callId) {
- logIncoming("removeCall %s", callId);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
- mHandler.obtainMessage(MSG_REMOVE_CALL, callId).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("removeCall %s", callId);
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+ .isValidConferenceId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ if (call.isAlive()) {
+ mCallsManager.markCallAsDisconnected(
+ call, new DisconnectCause(DisconnectCause.REMOTE));
+ } else {
+ mCallsManager.markCallAsRemoved(call);
+ }
+ }
+ }
}
}
@Override
public void setConnectionCapabilities(String callId, int connectionCapabilities) {
- logIncoming("setConnectionCapabilities %s %d", callId, connectionCapabilities);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
- mHandler.obtainMessage(MSG_SET_CONNECTION_CAPABILITIES, connectionCapabilities, 0, callId)
- .sendToTarget();
- } else {
- Log.w(this, "ID not valid for setCallCapabilities");
+ synchronized (mLock) {
+ logIncoming("setConnectionCapabilities %s %d", callId, connectionCapabilities);
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+ .isValidConferenceId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setConnectionCapabilities(connectionCapabilities);
+ } else {
+ //Log.w(ConnectionServiceWrapper.this,
+ // "setConnectionCapabilities, unknown call id: %s", msg.obj);
+ }
+ }
}
}
@Override
public void setIsConferenced(String callId, String conferenceCallId) {
- logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = conferenceCallId;
- mHandler.obtainMessage(MSG_SET_IS_CONFERENCED, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
+ Call childCall = mCallIdMapper.getCall(callId);
+ if (childCall != null) {
+ if (conferenceCallId == null) {
+ Log.d(this, "unsetting parent: %s", conferenceCallId);
+ childCall.setParentCall(null);
+ } else {
+ Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
+ childCall.setParentCall(conferenceCall);
+ }
+ } else {
+ //Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
+ }
+ }
}
@Override
public void addConferenceCall(String callId, ParcelableConference parcelableConference) {
- logIncoming("addConferenceCall %s %s", callId, parcelableConference);
- // We do not check call Ids here because we do not yet know the call ID for new
- // conference calls.
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = parcelableConference;
- mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, args).sendToTarget();
+ synchronized (mLock) {
+ if (mCallIdMapper.getCall(callId) != null) {
+ Log.w(this, "Attempting to add a conference call using an existing " +
+ "call id %s", callId);
+ return;
+ }
+
+ // Make sure that there's at least one valid call. For remote connections
+ // we'll get a add conference msg from both the remote connection service
+ // and from the real connection service.
+ boolean hasValidCalls = false;
+ for (String connId : parcelableConference.getConnectionIds()) {
+ if (mCallIdMapper.getCall(connId) != null) {
+ hasValidCalls = true;
+ }
+ }
+ // But don't bail out if the connection count is 0, because that is a valid
+ // IMS conference state.
+ if (!hasValidCalls && parcelableConference.getConnectionIds().size() > 0) {
+ Log.d(this, "Attempting to add a conference with no valid calls");
+ return;
+ }
+
+ // need to create a new Call
+ PhoneAccountHandle phAcc = null;
+ if (parcelableConference != null &&
+ parcelableConference.getPhoneAccount() != null) {
+ phAcc = parcelableConference.getPhoneAccount();
+ }
+ Call conferenceCall = mCallsManager.createConferenceCall(
+ phAcc, parcelableConference);
+ mCallIdMapper.addCall(conferenceCall, callId);
+ conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
+
+ Log.d(this, "adding children to conference %s phAcc %s",
+ parcelableConference.getConnectionIds(), phAcc);
+ for (String connId : parcelableConference.getConnectionIds()) {
+ Call childCall = mCallIdMapper.getCall(connId);
+ Log.d(this, "found child: %s", connId);
+ if (childCall != null) {
+ childCall.setParentCall(conferenceCall);
+ }
+ }
+ }
}
@Override
public void onPostDialWait(String callId, String remaining) throws RemoteException {
- logIncoming("onPostDialWait %s %s", callId, remaining);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = remaining;
- mHandler.obtainMessage(MSG_ON_POST_DIAL_WAIT, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("onPostDialWait %s %s", callId, remaining);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.onPostDialWait(remaining);
+ } else {
+ //Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
+ }
+ }
}
}
@Override
public void onPostDialChar(String callId, char nextChar) throws RemoteException {
- logIncoming("onPostDialChar %s %s", callId, nextChar);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.argi1 = nextChar;
- mHandler.obtainMessage(MSG_ON_POST_DIAL_CHAR, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("onPostDialChar %s %s", callId, nextChar);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.onPostDialChar(nextChar);
+ } else {
+ //Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1);
+ }
+ }
}
}
@Override
public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
- logIncoming("queryRemoteCSs");
- mHandler.obtainMessage(MSG_QUERY_REMOTE_CALL_SERVICES, callback).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("queryRemoteConnectionServices %s", callback);
+ ConnectionServiceWrapper.this.queryRemoteConnectionServices(callback);
+ }
}
@Override
public void setVideoState(String callId, int videoState) {
- logIncoming("setVideoState %s %d", callId, videoState);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState, 0, callId).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setVideoState %s %d", callId, videoState);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setVideoState(videoState);
+ }
+ }
}
}
@Override
public void setIsVoipAudioMode(String callId, boolean isVoip) {
- logIncoming("setIsVoipAudioMode %s %b", callId, isVoip);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_SET_IS_VOIP_AUDIO_MODE, isVoip ? 1 : 0, 0,
- callId).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setIsVoipAudioMode %s %b", callId, isVoip);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setIsVoipAudioMode(isVoip);
+ }
+ }
}
}
@Override
public void setStatusHints(String callId, StatusHints statusHints) {
- logIncoming("setStatusHints %s %s", callId, statusHints);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = statusHints;
- mHandler.obtainMessage(MSG_SET_STATUS_HINTS, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setStatusHints %s %s", callId, statusHints);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setStatusHints(statusHints);
+ }
+ }
}
}
@Override
public void setAddress(String callId, Uri address, int presentation) {
- logIncoming("setAddress %s %s %d", callId, address, presentation);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = address;
- args.argi1 = presentation;
- mHandler.obtainMessage(MSG_SET_ADDRESS, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setAddress %s %s %d", callId, address, presentation);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setHandle(address, presentation);
+ }
+ }
}
}
@Override
public void setCallerDisplayName(
String callId, String callerDisplayName, int presentation) {
- logIncoming("setCallerDisplayName %s %s %d", callId, callerDisplayName, presentation);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = callerDisplayName;
- args.argi1 = presentation;
- mHandler.obtainMessage(MSG_SET_CALLER_DISPLAY_NAME, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setCallerDisplayName %s %s %d", callId, callerDisplayName, presentation);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setCallerDisplayName(callerDisplayName, presentation);
+ }
+ }
}
}
@Override
public void setConferenceableConnections(
String callId, List<String> conferenceableCallIds) {
- logIncoming("setConferenceableConnections %s %s", callId, conferenceableCallIds);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = conferenceableCallIds;
- mHandler.obtainMessage(MSG_SET_CONFERENCEABLE_CONNECTIONS, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("setConferenceableConnections %s %s", callId, conferenceableCallIds);
+ if (mCallIdMapper.isValidCallId(callId) ||
+ mCallIdMapper.isValidConferenceId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null ){
+ List<Call> conferenceableCalls =
+ new ArrayList<>(conferenceableCallIds.size());
+ for (String otherId : conferenceableCallIds) {
+ Call otherCall = mCallIdMapper.getCall(otherId);
+ if (otherCall != null && otherCall != call) {
+ conferenceableCalls.add(otherCall);
+ }
+ }
+ call.setConferenceableCalls(conferenceableCalls);
+ }
+ }
}
}
@Override
public void addExistingConnection(String callId, ParcelableConnection connection) {
- logIncoming("addExistingConnection %s %s", callId, connection);
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = connection;
- mHandler.obtainMessage(MSG_ADD_EXISTING_CONNECTION, args).sendToTarget();
+ synchronized (mLock) {
+ logIncoming("addExistingConnection %s %s", callId, connection);
+ Call existingCall = mCallsManager
+ .createCallForExistingConnection(callId, connection);
+ mCallIdMapper.addCall(existingCall, callId);
+ existingCall.setConnectionService(ConnectionServiceWrapper.this);
+ }
}
}
private final Adapter mAdapter = new Adapter();
- private final CallsManager mCallsManager = CallsManager.getInstance();
- /**
- * 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 Set<Call> mPendingConferenceCalls = Collections.newSetFromMap(
- new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
private final CallIdMapper mCallIdMapper = new CallIdMapper("ConnectionService");
private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();
@@ -632,6 +432,7 @@
private IConnectionService mServiceInterface;
private final ConnectionServiceRepository mConnectionServiceRepository;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final CallsManager mCallsManager;
/**
* Creates a connection service.
@@ -639,6 +440,7 @@
* @param componentName The component name of the service with which to bind.
* @param connectionServiceRepository Connection service repository.
* @param phoneAccountRegistrar Phone account registrar
+ * @param callsManager Calls manager
* @param context The context.
* @param userHandle The {@link UserHandle} to use when binding.
*/
@@ -646,15 +448,18 @@
ComponentName componentName,
ConnectionServiceRepository connectionServiceRepository,
PhoneAccountRegistrar phoneAccountRegistrar,
+ CallsManager callsManager,
Context context,
+ TelecomSystem.SyncRoot lock,
UserHandle userHandle) {
- super(ConnectionService.SERVICE_INTERFACE, componentName, context, userHandle);
+ super(ConnectionService.SERVICE_INTERFACE, componentName, context, lock, userHandle);
mConnectionServiceRepository = connectionServiceRepository;
phoneAccountRegistrar.addListener(new PhoneAccountRegistrar.Listener() {
// TODO -- Upon changes to PhoneAccountRegistrar, need to re-wire connections
// To do this, we must proxy remote ConnectionService objects
});
mPhoneAccountRegistrar = phoneAccountRegistrar;
+ mCallsManager = callsManager;
}
/** See {@link IConnectionService#addConnectionServiceAdapter}. */
@@ -720,7 +525,7 @@
mBinder.bind(callback);
}
- /** @see ConnectionService#abort(String) */
+ /** @see IConnectionService#abort(String) */
void abort(Call call) {
// Clear out any pending outgoing call data
final String callId = mCallIdMapper.getCallId(call);
@@ -737,7 +542,7 @@
removeCall(call, new DisconnectCause(DisconnectCause.LOCAL));
}
- /** @see ConnectionService#hold(String) */
+ /** @see IConnectionService#hold(String) */
void hold(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("hold")) {
@@ -749,7 +554,7 @@
}
}
- /** @see ConnectionService#unhold(String) */
+ /** @see IConnectionService#unhold(String) */
void unhold(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("unhold")) {
@@ -761,7 +566,7 @@
}
}
- /** @see ConnectionService#onAudioStateChanged(String,AudioState) */
+ /** @see IConnectionService#onAudioStateChanged(String,AudioState) */
void onAudioStateChanged(Call activeCall, AudioState audioState) {
final String callId = mCallIdMapper.getCallId(activeCall);
if (callId != null && isServiceValid("onAudioStateChanged")) {
@@ -773,7 +578,7 @@
}
}
- /** @see ConnectionService#disconnect(String) */
+ /** @see IConnectionService#disconnect(String) */
void disconnect(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("disconnect")) {
@@ -785,7 +590,7 @@
}
}
- /** @see ConnectionService#answer(String,int) */
+ /** @see IConnectionService#answer(String) */
void answer(Call call, int videoState) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("answer")) {
@@ -801,7 +606,7 @@
}
}
- /** @see ConnectionService#reject(String) */
+ /** @see IConnectionService#reject(String) */
void reject(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("reject")) {
@@ -813,7 +618,7 @@
}
}
- /** @see ConnectionService#playDtmfTone(String,char) */
+ /** @see IConnectionService#playDtmfTone(String,char) */
void playDtmfTone(Call call, char digit) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("playDtmfTone")) {
@@ -825,7 +630,7 @@
}
}
- /** @see ConnectionService#stopDtmfTone(String) */
+ /** @see IConnectionService#stopDtmfTone(String) */
void stopDtmfTone(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("stopDtmfTone")) {
@@ -938,7 +743,7 @@
// outgoing calls to try the next service. This needs to happen before CallsManager
// tries to clean up any calls still associated with this service.
handleConnectionServiceDeath();
- CallsManager.getInstance().handleConnectionServiceDeath(this);
+ mCallsManager.handleConnectionServiceDeath(this);
mServiceInterface = null;
} else {
mServiceInterface = IConnectionService.Stub.asInterface(binder);
@@ -1068,10 +873,6 @@
}
private void noRemoteServices(RemoteServiceCallback callback) {
- try {
- callback.onResult(Collections.EMPTY_LIST, Collections.EMPTY_LIST);
- } catch (RemoteException e) {
- Log.e(this, e, "Contacting ConnectionService %s", this.getComponentName());
- }
+ setRemoteServices(callback, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
}
}
diff --git a/src/com/android/server/telecom/ContactsAsyncHelper.java b/src/com/android/server/telecom/ContactsAsyncHelper.java
index 1fbf5ee..ef7ea4c 100644
--- a/src/com/android/server/telecom/ContactsAsyncHelper.java
+++ b/src/com/android/server/telecom/ContactsAsyncHelper.java
@@ -60,36 +60,14 @@
// constants
private static final int EVENT_LOAD_IMAGE = 1;
- private static final Handler sResultHandler = new Handler(Looper.getMainLooper()) {
- /** Called when loading is done. */
- @Override
- public void handleMessage(Message msg) {
- WorkerArgs args = (WorkerArgs) msg.obj;
- switch (msg.arg1) {
- case EVENT_LOAD_IMAGE:
- if (args.listener != null) {
- Log.d(this, "Notifying listener: " + args.listener.toString() +
- " image: " + args.displayPhotoUri + " completed");
- args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon,
- args.cookie);
- }
- break;
- default:
- }
- }
- };
-
/** Handler run on a worker thread to load photo asynchronously. */
- private static final Handler sThreadHandler;
+ private Handler mThreadHandler;
+ private final TelecomSystem.SyncRoot mLock;
- static {
- HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
- thread.start();
- sThreadHandler = new WorkerHandler(thread.getLooper());
+ public ContactsAsyncHelper(TelecomSystem.SyncRoot lock) {
+ mLock = lock;
}
- private ContactsAsyncHelper() {}
-
private static final class WorkerArgs {
public Context context;
public Uri displayPhotoUri;
@@ -103,7 +81,7 @@
* Thread worker class that handles the task of opening the stream and loading
* the images.
*/
- private static class WorkerHandler extends Handler {
+ private class WorkerHandler extends Handler {
public WorkerHandler(Looper looper) {
super(looper);
}
@@ -149,15 +127,16 @@
}
}
}
+ synchronized (mLock) {
+ Log.d(this, "Notifying listener: " + args.listener.toString() +
+ " image: " + args.displayPhotoUri + " completed");
+ args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon,
+ args.cookie);
+ }
break;
default:
+ break;
}
-
- // send the reply to the enclosing class.
- Message reply = sResultHandler.obtainMessage(msg.what);
- reply.arg1 = msg.arg1;
- reply.obj = msg.obj;
- reply.sendToTarget();
}
/**
@@ -212,9 +191,9 @@
* fourth argument of {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable,
* Bitmap, Object)}. Can be null, at which the callback will also has null for the argument.
*/
- public static final void startObtainPhotoAsync(int token, Context context, Uri displayPhotoUri,
+ public final void startObtainPhotoAsync(int token, Context context, Uri displayPhotoUri,
OnImageLoadCompleteListener listener, Object cookie) {
- ThreadUtil.checkOnMainThread();
+ ensureAsyncHandlerStarted();
// in case the source caller info is null, the URI will be null as well.
// just update using the placeholder image in this case.
@@ -234,7 +213,7 @@
args.listener = listener;
// setup message arguments
- Message msg = sThreadHandler.obtainMessage(token);
+ Message msg = mThreadHandler.obtainMessage(token);
msg.arg1 = EVENT_LOAD_IMAGE;
msg.obj = args;
@@ -242,8 +221,14 @@
", displaying default image for now.");
// notify the thread to begin working
- sThreadHandler.sendMessage(msg);
+ mThreadHandler.sendMessage(msg);
}
-
+ private void ensureAsyncHandlerStarted() {
+ if (mThreadHandler == null) {
+ HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
+ thread.start();
+ mThreadHandler = new WorkerHandler(thread.getLooper());
+ }
+ }
}
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index eec1427..1d8ef28 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -102,6 +102,7 @@
CreateConnectionProcessor(
Call call, ConnectionServiceRepository repository, CreateConnectionResponse response,
PhoneAccountRegistrar phoneAccountRegistrar, Context context) {
+ Log.v(this, "CreateConnectionProcessor created for Call = %s", call);
mCall = call;
mRepository = repository;
mResponse = response;
@@ -267,6 +268,10 @@
// Connection managers are only allowed to manage SIM subscriptions.
PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar.getPhoneAccount(
targetPhoneAccountHandle);
+ if (targetPhoneAccount == null) {
+ Log.d(this, "shouldSetConnectionManager, phone account not found");
+ return false;
+ }
boolean isSimSubscription = (targetPhoneAccount.getCapabilities() &
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0;
if (!isSimSubscription) {
@@ -326,7 +331,8 @@
if (mShouldUseConnectionManager && callManagerHandle != null) {
PhoneAccount callManager = mPhoneAccountRegistrar
.getPhoneAccount(callManagerHandle);
- if (callManager.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
+ if (callManager != null && callManager.hasCapabilities(
+ PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle,
mPhoneAccountRegistrar.
getDefaultOutgoingPhoneAccount(mCall.getHandle().getScheme())
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index f0ea1e9..8c1488c 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -25,7 +25,7 @@
/**
* Static class to handle listening to the headset media buttons.
*/
-final class HeadsetMediaButton extends CallsManagerListenerBase {
+public class HeadsetMediaButton extends CallsManagerListenerBase {
// Types of media button presses
static final int SHORT_PRESS = 1;
@@ -54,7 +54,7 @@
private final MediaSession mSession;
- HeadsetMediaButton(Context context, CallsManager callsManager) {
+ public HeadsetMediaButton(Context context, CallsManager callsManager) {
mCallsManager = callsManager;
// Create a MediaSession but don't enable it yet. This is a
diff --git a/src/com/android/server/telecom/HeadsetMediaButtonFactory.java b/src/com/android/server/telecom/HeadsetMediaButtonFactory.java
new file mode 100644
index 0000000..becabbf
--- /dev/null
+++ b/src/com/android/server/telecom/HeadsetMediaButtonFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.content.Context;
+
+/**
+ * This is a TEMPORARY fix to make the {@link HeadsetMediaButton} object injectable for testing.
+ * Class {@link HeadsetMediaButton} itself is not testable because it grabs lots of special stuff
+ * from its {@code Context} that cannot be conveniently mocked.
+ *
+ * TODO: Replace with a better design.
+ */
+public interface HeadsetMediaButtonFactory {
+
+ HeadsetMediaButton create(Context context, CallsManager callsManager);
+}
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index e39b5a5..d84c87e 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -29,320 +29,234 @@
* binding to it. This adapter can receive commands and updates until the in-call app is unbound.
*/
class InCallAdapter extends IInCallAdapter.Stub {
- private static final int MSG_ANSWER_CALL = 0;
- private static final int MSG_REJECT_CALL = 1;
- private static final int MSG_PLAY_DTMF_TONE = 2;
- private static final int MSG_STOP_DTMF_TONE = 3;
- private static final int MSG_POST_DIAL_CONTINUE = 4;
- private static final int MSG_DISCONNECT_CALL = 5;
- private static final int MSG_HOLD_CALL = 6;
- private static final int MSG_UNHOLD_CALL = 7;
- private static final int MSG_MUTE = 8;
- private static final int MSG_SET_AUDIO_ROUTE = 9;
- private static final int MSG_CONFERENCE = 10;
- private static final int MSG_SPLIT_FROM_CONFERENCE = 11;
- private static final int MSG_SWAP_WITH_BACKGROUND_CALL = 12;
- private static final int MSG_PHONE_ACCOUNT_SELECTED = 13;
- private static final int MSG_TURN_ON_PROXIMITY_SENSOR = 14;
- private static final int MSG_TURN_OFF_PROXIMITY_SENSOR = 15;
- private static final int MSG_MERGE_CONFERENCE = 16;
- private static final int MSG_SWAP_CONFERENCE = 17;
-
- private final class InCallAdapterHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- Call call;
- switch (msg.what) {
- case MSG_ANSWER_CALL: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- int videoState = (int) args.arg2;
- if (call != null) {
- mCallsManager.answerCall(call, videoState);
- } else {
- Log.w(this, "answerCall, unknown call id: %s", msg.obj);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_REJECT_CALL: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- boolean rejectWithMessage = args.argi1 == 1;
- String textMessage = (String) args.arg2;
- if (call != null) {
- mCallsManager.rejectCall(call, rejectWithMessage, textMessage);
- } else {
- Log.w(this, "setRingback, unknown call id: %s", args.arg1);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_PLAY_DTMF_TONE:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.playDtmfTone(call, (char) msg.arg1);
- } else {
- Log.w(this, "playDtmfTone, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_STOP_DTMF_TONE:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.stopDtmfTone(call);
- } else {
- Log.w(this, "stopDtmfTone, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_POST_DIAL_CONTINUE:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.postDialContinue(call, msg.arg1 == 1);
- } else {
- Log.w(this, "postDialContinue, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_DISCONNECT_CALL:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.disconnectCall(call);
- } else {
- Log.w(this, "disconnectCall, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_HOLD_CALL:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.holdCall(call);
- } else {
- Log.w(this, "holdCall, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_UNHOLD_CALL:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- mCallsManager.unholdCall(call);
- } else {
- Log.w(this, "unholdCall, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_PHONE_ACCOUNT_SELECTED: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- if (call != null) {
- mCallsManager.phoneAccountSelected(call,
- (PhoneAccountHandle) args.arg2, args.argi1 == 1);
- } else {
- Log.w(this, "phoneAccountSelected, unknown call id: %s", args.arg1);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_MUTE:
- mCallsManager.mute(msg.arg1 == 1);
- break;
- case MSG_SET_AUDIO_ROUTE:
- mCallsManager.setAudioRoute(msg.arg1);
- break;
- case MSG_CONFERENCE: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- call = mCallIdMapper.getCall(args.arg1);
- Call otherCall = mCallIdMapper.getCall(args.arg2);
- if (call != null && otherCall != null) {
- mCallsManager.conference(call, otherCall);
- } else {
- Log.w(this, "conference, unknown call id: %s", msg.obj);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_SPLIT_FROM_CONFERENCE:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- call.splitFromConference();
- } else {
- Log.w(this, "splitFromConference, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_TURN_ON_PROXIMITY_SENSOR:
- mCallsManager.turnOnProximitySensor();
- break;
- case MSG_TURN_OFF_PROXIMITY_SENSOR:
- mCallsManager.turnOffProximitySensor((boolean) msg.obj);
- break;
- case MSG_MERGE_CONFERENCE:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- call.mergeConference();
- } else {
- Log.w(this, "mergeConference, unknown call id: %s", msg.obj);
- }
- break;
- case MSG_SWAP_CONFERENCE:
- call = mCallIdMapper.getCall(msg.obj);
- if (call != null) {
- call.swapConference();
- } else {
- Log.w(this, "swapConference, unknown call id: %s", msg.obj);
- }
- break;
- }
- }
- }
-
private final CallsManager mCallsManager;
- private final Handler mHandler = new InCallAdapterHandler();
private final CallIdMapper mCallIdMapper;
+ private final TelecomSystem.SyncRoot mLock;
/** Persists the specified parameters. */
- public InCallAdapter(CallsManager callsManager, CallIdMapper callIdMapper) {
- ThreadUtil.checkOnMainThread();
+ public InCallAdapter(CallsManager callsManager, CallIdMapper callIdMapper, TelecomSystem.SyncRoot lock) {
mCallsManager = callsManager;
mCallIdMapper = callIdMapper;
+ mLock = lock;
}
@Override
public void answerCall(String callId, int videoState) {
- Log.d(this, "answerCall(%s,%d)", callId, videoState);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = videoState;
- mHandler.obtainMessage(MSG_ANSWER_CALL, args).sendToTarget();
+ synchronized (mLock) {
+ Log.d(this, "answerCall(%s,%d)", callId, videoState);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.answerCall(call, videoState);
+ } else {
+ Log.w(this, "answerCall, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
- Log.d(this, "rejectCall(%s,%b,%s)", callId, rejectWithMessage, textMessage);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.argi1 = rejectWithMessage ? 1 : 0;
- args.arg2 = textMessage;
- mHandler.obtainMessage(MSG_REJECT_CALL, args).sendToTarget();
+ synchronized (this) {
+ Log.d(this, "rejectCall(%s,%b,%s)", callId, rejectWithMessage, textMessage);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.rejectCall(call, rejectWithMessage, textMessage);
+ } else {
+ Log.w(this, "setRingback, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void playDtmfTone(String callId, char digit) {
- Log.d(this, "playDtmfTone(%s,%c)", callId, digit);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, (int) digit, 0, callId).sendToTarget();
+ synchronized (mLock) {
+ Log.d(this, "playDtmfTone(%s,%c)", callId, digit);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.playDtmfTone(call, digit);
+ } else {
+ Log.w(this, "playDtmfTone, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void stopDtmfTone(String callId) {
- Log.d(this, "stopDtmfTone(%s)", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
+ synchronized (mLock) {
+ Log.d(this, "stopDtmfTone(%s)", callId);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.stopDtmfTone(call);
+ } else {
+ Log.w(this, "stopDtmfTone, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void postDialContinue(String callId, boolean proceed) {
- Log.d(this, "postDialContinue(%s)", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_POST_DIAL_CONTINUE, proceed ? 1 : 0, 0, callId).sendToTarget();
+ synchronized (mLock) {
+ Log.d(this, "postDialContinue(%s)", callId);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.postDialContinue(call, proceed);
+ } else {
+ Log.w(this, "postDialContinue, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void disconnectCall(String callId) {
- Log.v(this, "disconnectCall: %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_DISCONNECT_CALL, callId).sendToTarget();
+ synchronized (mLock) {
+ Log.v(this, "disconnectCall: %s", callId);
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.disconnectCall(call);
+ } else {
+ Log.w(this, "disconnectCall, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void holdCall(String callId) {
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_HOLD_CALL, callId).sendToTarget();
+ synchronized (mLock) {
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.holdCall(call);
+ } else {
+ Log.w(this, "holdCall, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void unholdCall(String callId) {
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_UNHOLD_CALL, callId).sendToTarget();
+ synchronized (mLock) {
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.unholdCall(call);
+ } else {
+ Log.w(this, "unholdCall, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle,
boolean setDefault) {
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = accountHandle;
- args.argi1 = setDefault? 1 : 0;
- mHandler.obtainMessage(MSG_PHONE_ACCOUNT_SELECTED, args).sendToTarget();
+ synchronized (mLock) {
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.phoneAccountSelected(call, accountHandle, setDefault);
+ } else {
+ Log.w(this, "phoneAccountSelected, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void mute(boolean shouldMute) {
- mHandler.obtainMessage(MSG_MUTE, shouldMute ? 1 : 0, 0).sendToTarget();
+ synchronized (mLock) {
+ mCallsManager.mute(shouldMute);
+ }
}
@Override
public void setAudioRoute(int route) {
- mHandler.obtainMessage(MSG_SET_AUDIO_ROUTE, route, 0).sendToTarget();
+ synchronized (mLock) {
+ mCallsManager.setAudioRoute(route);
+ }
}
@Override
public void conference(String callId, String otherCallId) {
- if (mCallIdMapper.isValidCallId(callId) &&
- mCallIdMapper.isValidCallId(otherCallId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = otherCallId;
- mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
+ synchronized (mLock) {
+ if (mCallIdMapper.isValidCallId(callId) &&
+ mCallIdMapper.isValidCallId(otherCallId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ Call otherCall = mCallIdMapper.getCall(otherCallId);
+ if (call != null && otherCall != null) {
+ mCallsManager.conference(call, otherCall);
+ } else {
+ Log.w(this, "conference, unknown call id: %s or %s", callId, otherCallId);
+ }
+
+ }
}
}
@Override
public void splitFromConference(String callId) {
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
+ synchronized (mLock) {
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.splitFromConference();
+ } else {
+ Log.w(this, "splitFromConference, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void mergeConference(String callId) {
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
+ synchronized (mLock) {
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.mergeConference();
+ } else {
+ Log.w(this, "mergeConference, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void swapConference(String callId) {
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
+ synchronized (mLock) {
+ if (mCallIdMapper.isValidCallId(callId)) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.swapConference();
+ } else {
+ Log.w(this, "swapConference, unknown call id: %s", callId);
+ }
+ }
}
}
@Override
public void turnOnProximitySensor() {
- mHandler.obtainMessage(MSG_TURN_ON_PROXIMITY_SENSOR).sendToTarget();
+ synchronized (mLock) {
+ mCallsManager.turnOnProximitySensor();
+ }
}
@Override
public void turnOffProximitySensor(boolean screenOnImmediately) {
- mHandler.obtainMessage(MSG_TURN_OFF_PROXIMITY_SENSOR, screenOnImmediately).sendToTarget();
+ synchronized (mLock) {
+ mCallsManager.turnOffProximitySensor(screenOnImmediately);
+ }
}
}
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index a5da3a3..e959bd7 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -139,9 +139,13 @@
private final ComponentName mInCallComponentName;
private final Context mContext;
+ private final TelecomSystem.SyncRoot mLock;
+ private final CallsManager mCallsManager;
- public InCallController(Context context) {
+ public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager) {
mContext = context;
+ mLock = lock;
+ mCallsManager = callsManager;
Resources resources = mContext.getResources();
mInCallComponentName = new ComponentName(
@@ -175,7 +179,7 @@
@Override
public void onCallRemoved(Call call) {
Log.i(this, "onCallRemoved: %s", call);
- if (CallsManager.getInstance().getCalls().isEmpty()) {
+ if (mCallsManager.getCalls().isEmpty()) {
// TODO: Wait for all messages to be delivered to the service before unbinding.
unbind();
}
@@ -258,7 +262,6 @@
* Unbinds an existing bound connection to the in-call app.
*/
private void unbind() {
- ThreadUtil.checkOnMainThread();
Iterator<Map.Entry<ComponentName, InCallServiceConnection>> iterator =
mServiceConnections.entrySet().iterator();
while (iterator.hasNext()) {
@@ -276,7 +279,6 @@
* @param call The newly added call that triggered the binding to the in-call services.
*/
private void bind(Call call) {
- ThreadUtil.checkOnMainThread();
if (mInCallServices.isEmpty()) {
PackageManager packageManager = mContext.getPackageManager();
Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
@@ -348,15 +350,17 @@
* @param service The {@link IInCallService} implementation.
*/
private void onConnected(ComponentName componentName, IBinder service) {
- ThreadUtil.checkOnMainThread();
Trace.beginSection("onConnected: " + componentName);
Log.i(this, "onConnected to %s", componentName);
IInCallService inCallService = IInCallService.Stub.asInterface(service);
try {
- inCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
- mCallIdMapper));
+ inCallService.setInCallAdapter(
+ new InCallAdapter(
+ mCallsManager,
+ mCallIdMapper,
+ mLock));
mInCallServices.put(componentName, inCallService);
} catch (RemoteException e) {
Log.e(this, e, "Failed to set the in-call adapter.");
@@ -365,23 +369,24 @@
}
// Upon successful connection, send the state of the world to the service.
- Collection<Call> calls = CallsManager.getInstance().getCalls();
+ Collection<Call> calls = mCallsManager.getCalls();
if (!calls.isEmpty()) {
Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(),
componentName);
for (Call call : calls) {
try {
// Track the call if we don't already know about it.
- Log.i(this, "addCall after binding: %s", call);
addCall(call);
-
- inCallService.addCall(toParcelableCall(call,
+ inCallService.addCall(toParcelableCall(
+ call,
componentName.equals(mInCallComponentName) /* includeVideoProvider */));
} catch (RemoteException ignored) {
}
}
- onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
- onCanAddCallChanged(CallsManager.getInstance().canAddCall());
+ onAudioStateChanged(
+ null,
+ mCallsManager.getAudioState());
+ onCanAddCallChanged(mCallsManager.canAddCall());
} else {
unbind();
}
@@ -395,7 +400,6 @@
*/
private void onDisconnected(ComponentName disconnectedComponent) {
Log.i(this, "onDisconnected from %s", disconnectedComponent);
- ThreadUtil.checkOnMainThread();
if (mInCallServices.containsKey(disconnectedComponent)) {
mInCallServices.remove(disconnectedComponent);
@@ -407,7 +411,7 @@
// implementations.
if (disconnectedComponent.equals(mInCallComponentName)) {
Log.i(this, "In-call UI %s disconnected.", disconnectedComponent);
- CallsManager.getInstance().disconnectAllCalls();
+ mCallsManager.disconnectAllCalls();
unbind();
} else {
Log.i(this, "In-Call Service %s suddenly disconnected", disconnectedComponent);
@@ -464,8 +468,8 @@
// If this is a single-SIM device, the "default SIM" will always be the only SIM.
boolean isDefaultSmsAccount =
- CallsManager.getInstance().getPhoneAccountRegistrar().isUserSelectedSmsPhoneAccount(
- call.getTargetPhoneAccount());
+ mCallsManager.getPhoneAccountRegistrar()
+ .isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
}
@@ -577,9 +581,21 @@
Connection.CAPABILITY_MANAGE_CONFERENCE,
android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE,
+ Connection.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
+ android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
+
+ Connection.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
+ android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
+
Connection.CAPABILITY_SUPPORTS_VT_LOCAL,
android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL,
+ Connection.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
+ android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
+
+ Connection.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
+ android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
+
Connection.CAPABILITY_SUPPORTS_VT_REMOTE,
android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE,
@@ -596,7 +612,10 @@
android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
Connection.CAPABILITY_GENERIC_CONFERENCE,
- android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE
+ android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE,
+
+ Connection.CAPABILITY_SHOW_CALLBACK_NUMBER,
+ android.telecom.Call.Details.CAPABILITY_SHOW_CALLBACK_NUMBER
};
private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 5afc67f..3b4380e 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -222,8 +222,6 @@
}
void startTone() {
- ThreadUtil.checkOnMainThread();
-
sTonesPlaying++;
if (sTonesPlaying == 1) {
mCallAudioManager.setIsTonePlaying(true);
diff --git a/src/com/android/server/telecom/InCallWakeLockController.java b/src/com/android/server/telecom/InCallWakeLockController.java
index d97e171..b6d3820 100644
--- a/src/com/android/server/telecom/InCallWakeLockController.java
+++ b/src/com/android/server/telecom/InCallWakeLockController.java
@@ -16,14 +16,16 @@
package com.android.server.telecom;
+import com.android.internal.annotations.VisibleForTesting;
+
import android.content.Context;
import android.os.PowerManager;
-import android.telecom.CallState;
/**
* Handles acquisition and release of wake locks relating to call state.
*/
-class InCallWakeLockController extends CallsManagerListenerBase {
+@VisibleForTesting
+public class InCallWakeLockController extends CallsManagerListenerBase {
private static final String TAG = "InCallWakeLockContoller";
@@ -31,7 +33,8 @@
private final PowerManager.WakeLock mFullWakeLock;
private final CallsManager mCallsManager;
- InCallWakeLockController(Context context, CallsManager callsManager) {
+ @VisibleForTesting
+ public InCallWakeLockController(Context context, CallsManager callsManager) {
mContext = context;
mCallsManager = callsManager;
diff --git a/src/com/android/server/telecom/InCallWakeLockControllerFactory.java b/src/com/android/server/telecom/InCallWakeLockControllerFactory.java
new file mode 100644
index 0000000..86335ba
--- /dev/null
+++ b/src/com/android/server/telecom/InCallWakeLockControllerFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.content.Context;
+
+/**
+ * This is a TEMPORARY fix to make the {@link InCallWakeLockController} object injectable for
+ * testing. Class {@link InCallWakeLockController} itself is not testable because it grabs lots of
+ * special stuff from its {@code Context} that cannot be conveniently mocked.
+ *
+ * TODO: Replace with a better design.
+ */
+public interface InCallWakeLockControllerFactory {
+
+ InCallWakeLockController create(Context context, CallsManager callsManager);
+}
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index 3ec8267..451e86d 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -25,13 +25,16 @@
import java.util.IllegalFormatException;
import java.util.Locale;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Manages logging for the entire module.
*/
+@VisibleForTesting
public class Log {
// Generic tag for all In Call logging
- private static final String TAG = "Telecom";
+ private static String TAG = "Telecom";
public static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
public static final boolean SYSTRACE_DEBUG = false; /* STOP SHIP if true */
@@ -43,6 +46,11 @@
private Log() {}
+ @VisibleForTesting
+ public static void setTag(String tag) {
+ TAG = tag;
+ }
+
public static boolean isLoggable(int level) {
return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level);
}
diff --git a/src/com/android/server/telecom/MissedCallNotifier.java b/src/com/android/server/telecom/MissedCallNotifier.java
index 2c1e78b..52055cf 100644
--- a/src/com/android/server/telecom/MissedCallNotifier.java
+++ b/src/com/android/server/telecom/MissedCallNotifier.java
@@ -16,333 +16,17 @@
package com.android.server.telecom;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.TaskStackBuilder;
-import android.content.AsyncQueryHandler;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.UserHandle;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
-import android.telecom.CallState;
-import android.telecom.DisconnectCause;
-import android.telecom.PhoneAccount;
-import android.telephony.PhoneNumberUtils;
-import android.text.BidiFormatter;
-import android.text.TextDirectionHeuristics;
-import android.text.TextUtils;
-
-// TODO: Needed for move to system service: import com.android.internal.R;
-
/**
* Creates a notification for calls that the user missed (neither answered nor rejected).
- * TODO: Make TelephonyManager.clearMissedCalls call into this class.
*/
-class MissedCallNotifier extends CallsManagerListenerBase {
+public interface MissedCallNotifier extends CallsManager.CallsManagerListener {
- private static final String[] CALL_LOG_PROJECTION = new String[] {
- Calls._ID,
- Calls.NUMBER,
- Calls.NUMBER_PRESENTATION,
- Calls.DATE,
- Calls.DURATION,
- Calls.TYPE,
- };
+ void clearMissedCalls();
- private static final int CALL_LOG_COLUMN_ID = 0;
- private static final int CALL_LOG_COLUMN_NUMBER = 1;
- private static final int CALL_LOG_COLUMN_NUMBER_PRESENTATION = 2;
- private static final int CALL_LOG_COLUMN_DATE = 3;
- private static final int CALL_LOG_COLUMN_DURATION = 4;
- private static final int CALL_LOG_COLUMN_TYPE = 5;
+ void showMissedCallNotification(Call call);
- private static final int MISSED_CALL_NOTIFICATION_ID = 1;
-
- private final Context mContext;
- private final NotificationManager mNotificationManager;
-
- // Used to track the number of missed calls.
- private int mMissedCallCount = 0;
-
- MissedCallNotifier(Context context) {
- mContext = context;
- mNotificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-
- updateOnStartup();
- }
-
- /** {@inheritDoc} */
- @Override
- public void onCallStateChanged(Call call, int oldState, int newState) {
- if (oldState == CallState.RINGING && newState == CallState.DISCONNECTED &&
- call.getDisconnectCause().getCode() == DisconnectCause.MISSED) {
- showMissedCallNotification(call);
- }
- }
-
- /** Clears missed call notification and marks the call log's missed calls as read. */
- void clearMissedCalls() {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- // Clear the list of new missed calls from the call log.
- ContentValues values = new ContentValues();
- values.put(Calls.NEW, 0);
- values.put(Calls.IS_READ, 1);
- StringBuilder where = new StringBuilder();
- where.append(Calls.NEW);
- where.append(" = 1 AND ");
- where.append(Calls.TYPE);
- where.append(" = ?");
- mContext.getContentResolver().update(Calls.CONTENT_URI, values, where.toString(),
- new String[]{ Integer.toString(Calls.MISSED_TYPE) });
- }
- });
- cancelMissedCallNotification();
- }
-
- /**
- * Create a system notification for the missed call.
- *
- * @param call The missed call.
- */
- void showMissedCallNotification(Call call) {
- mMissedCallCount++;
-
- final int titleResId;
- final String expandedText; // The text in the notification's line 1 and 2.
-
- // Display the first line of the notification:
- // 1 missed call: <caller name || handle>
- // More than 1 missed call: <number of calls> + "missed calls"
- if (mMissedCallCount == 1) {
- titleResId = R.string.notification_missedCallTitle;
- expandedText = getNameForCall(call);
- } else {
- titleResId = R.string.notification_missedCallsTitle;
- expandedText =
- mContext.getString(R.string.notification_missedCallsMsg, mMissedCallCount);
- }
-
- // Create the notification.
- Notification.Builder builder = new Notification.Builder(mContext);
- builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
- .setColor(mContext.getResources().getColor(R.color.theme_color))
- .setWhen(call.getCreationTimeMillis())
- .setContentTitle(mContext.getText(titleResId))
- .setContentText(expandedText)
- .setContentIntent(createCallLogPendingIntent())
- .setAutoCancel(true)
- .setDeleteIntent(createClearMissedCallsPendingIntent());
-
- Uri handleUri = call.getHandle();
- String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();
-
- // Add additional actions when there is only 1 missed call, like call-back and SMS.
- if (mMissedCallCount == 1) {
- Log.d(this, "Add actions with number %s.", Log.piiHandle(handle));
-
- if (!TextUtils.isEmpty(handle)
- && !TextUtils.equals(handle, mContext.getString(R.string.handle_restricted))) {
- builder.addAction(R.drawable.stat_sys_phone_call,
- mContext.getString(R.string.notification_missedCall_call_back),
- createCallBackPendingIntent(handleUri));
-
- builder.addAction(R.drawable.ic_text_holo_dark,
- mContext.getString(R.string.notification_missedCall_message),
- createSendSmsFromNotificationPendingIntent(handleUri));
- }
-
- Bitmap photoIcon = call.getPhotoIcon();
- if (photoIcon != null) {
- builder.setLargeIcon(photoIcon);
- } else {
- Drawable photo = call.getPhoto();
- if (photo != null && photo instanceof BitmapDrawable) {
- builder.setLargeIcon(((BitmapDrawable) photo).getBitmap());
- }
- }
- } else {
- Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle),
- mMissedCallCount);
- }
-
- Notification notification = builder.build();
- configureLedOnNotification(notification);
-
- Log.i(this, "Adding missed call notification for %s.", call);
- mNotificationManager.notifyAsUser(
- null /* tag */ , MISSED_CALL_NOTIFICATION_ID, notification, UserHandle.CURRENT);
- }
-
- /** Cancels the "missed call" notification. */
- private void cancelMissedCallNotification() {
- // Reset the number of missed calls to 0.
- mMissedCallCount = 0;
- mNotificationManager.cancel(MISSED_CALL_NOTIFICATION_ID);
- }
-
- /**
- * Returns the name to use in the missed call notification.
- */
- private String getNameForCall(Call call) {
- String handle = call.getHandle() == null ? null : call.getHandle().getSchemeSpecificPart();
- String name = call.getName();
-
- if (!TextUtils.isEmpty(name) && TextUtils.isGraphic(name)) {
- return name;
- } else if (!TextUtils.isEmpty(handle)) {
- // A handle should always be displayed LTR using {@link BidiFormatter} regardless of the
- // content of the rest of the notification.
- // TODO: Does this apply to SIP addresses?
- BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- return bidiFormatter.unicodeWrap(handle, TextDirectionHeuristics.LTR);
- } else {
- // Use "unknown" if the call is unidentifiable.
- return mContext.getString(R.string.unknown);
- }
- }
-
- /**
- * Creates a new pending intent that sends the user to the call log.
- *
- * @return The pending intent.
- */
- private PendingIntent createCallLogPendingIntent() {
- Intent intent = new Intent(Intent.ACTION_VIEW, null);
- intent.setType(CallLog.Calls.CONTENT_TYPE);
-
- TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext);
- taskStackBuilder.addNextIntent(intent);
-
- return taskStackBuilder.getPendingIntent(0, 0);
- }
-
- /**
- * Creates an intent to be invoked when the missed call notification is cleared.
- */
- private PendingIntent createClearMissedCallsPendingIntent() {
- return createTelecomPendingIntent(
- TelecomBroadcastReceiver.ACTION_CLEAR_MISSED_CALLS, null);
- }
-
- /**
- * Creates an intent to be invoked when the user opts to "call back" from the missed call
- * notification.
- *
- * @param handle The handle to call back.
- */
- private PendingIntent createCallBackPendingIntent(Uri handle) {
- return createTelecomPendingIntent(
- TelecomBroadcastReceiver.ACTION_CALL_BACK_FROM_NOTIFICATION, handle);
- }
-
- /**
- * Creates an intent to be invoked when the user opts to "send sms" from the missed call
- * notification.
- */
- private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle) {
- return createTelecomPendingIntent(
- TelecomBroadcastReceiver.ACTION_SEND_SMS_FROM_NOTIFICATION,
- Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null));
- }
-
- /**
- * Creates generic pending intent from the specified parameters to be received by
- * {@link TelecomBroadcastReceiver}.
- *
- * @param action The intent action.
- * @param data The intent data.
- */
- private PendingIntent createTelecomPendingIntent(String action, Uri data) {
- Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
- return PendingIntent.getBroadcast(mContext, 0, intent, 0);
- }
-
- /**
- * Configures a notification to emit the blinky notification light.
- */
- private void configureLedOnNotification(Notification notification) {
- notification.flags |= Notification.FLAG_SHOW_LIGHTS;
- notification.defaults |= Notification.DEFAULT_LIGHTS;
- }
-
- /**
- * Adds the missed call notification on startup if there are unread missed calls.
- */
- private void updateOnStartup() {
- Log.d(this, "updateOnStartup()...");
-
- // instantiate query handler
- AsyncQueryHandler queryHandler = new AsyncQueryHandler(mContext.getContentResolver()) {
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- Log.d(MissedCallNotifier.this, "onQueryComplete()...");
- if (cursor != null) {
- try {
- while (cursor.moveToNext()) {
- // Get data about the missed call from the cursor
- final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER);
- final int presentation =
- cursor.getInt(CALL_LOG_COLUMN_NUMBER_PRESENTATION);
- final long date = cursor.getLong(CALL_LOG_COLUMN_DATE);
-
- final Uri handle;
- if (presentation != Calls.PRESENTATION_ALLOWED
- || TextUtils.isEmpty(handleString)) {
- handle = null;
- } else {
- handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(handleString) ?
- PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
- handleString, null);
- }
-
- // Convert the data to a call object
- Call call = new Call(mContext, null, null, null, null, null, true,
- false);
- call.setDisconnectCause(new DisconnectCause(DisconnectCause.MISSED));
- call.setState(CallState.DISCONNECTED);
- call.setCreationTimeMillis(date);
-
- // Listen for the update to the caller information before posting the
- // notification so that we have the contact info and photo.
- call.addListener(new Call.ListenerBase() {
- @Override
- public void onCallerInfoChanged(Call call) {
- call.removeListener(this); // No longer need to listen to call
- // changes after the contact info
- // is retrieved.
- showMissedCallNotification(call);
- }
- });
- // Set the handle here because that is what triggers the contact info
- // query.
- call.setHandle(handle, presentation);
- }
- } finally {
- cursor.close();
- }
- }
- }
- };
-
- // setup query spec, look for all Missed calls that are new.
- StringBuilder where = new StringBuilder("type=");
- where.append(Calls.MISSED_TYPE);
- where.append(" AND new=1");
-
- // start the query
- queryHandler.startQuery(0, null, Calls.CONTENT_URI, CALL_LOG_PROJECTION,
- where.toString(), null, Calls.DEFAULT_SORT_ORDER);
- }
+ void updateOnStartup(
+ TelecomSystem.SyncRoot lock,
+ CallsManager callsManager,
+ ContactsAsyncHelper contactsAsyncHelper);
}
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index c52f2bb..a2441ce 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -16,6 +16,8 @@
package com.android.server.telecom;
+import com.android.server.telecom.components.UserCallIntentProcessor;
+
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -156,8 +158,8 @@
* - CALL_PRIVILEGED (intent launched by system apps e.g. system Dialer, voice Dialer)
* - CALL_EMERGENCY (intent launched by lock screen emergency dialer)
*
- * @return {@link CallActivity#OUTGOING_CALL_SUCCEEDED} if the call succeeded, and an
- * appropriate {@link DisconnectCause} if the call did not, describing why it failed.
+ * @return {@link DisconnectCause#NOT_DISCONNECTED} if the call succeeded, and an appropriate
+ * {@link DisconnectCause} if the call did not, describing why it failed.
*/
int processIntent() {
Log.v(this, "Processing call intent in OutgoingCallIntentBroadcaster.");
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index eb3248e..759a31d 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -27,6 +27,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
+import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -36,11 +37,13 @@
import android.telecom.PhoneAccountHandle;
import android.telephony.PhoneNumberUtils;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AtomicFile;
import android.util.Base64;
import android.util.Xml;
+
// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
@@ -63,6 +66,7 @@
import java.lang.SecurityException;
import java.lang.String;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
@@ -136,15 +140,15 @@
*/
public int getSubscriptionIdForPhoneAccount(PhoneAccountHandle accountHandle) {
PhoneAccount account = getPhoneAccountInternal(accountHandle);
- if (account == null
- || !account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
- || !TextUtils.isDigitsOnly(accountHandle.getId())
- || !isVisibleForUser(accountHandle)) {
- // Since no decimals or negative numbers can be valid subscription ids, only a string of
- // numbers can be subscription id
- return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+ if (account != null
+ && account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+ && isVisibleForUser(accountHandle)) {
+ TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ return tm.getSubIdForPhoneAccount(account);
}
- return Integer.parseInt(accountHandle.getId());
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
/**
@@ -296,8 +300,16 @@
mContext.getResources().getString(R.string.default_connection_manager_component);
if (!TextUtils.isEmpty(defaultConnectionMgr)) {
ComponentName componentName = ComponentName.unflattenFromString(defaultConnectionMgr);
+ if (componentName == null) {
+ return null;
+ }
+
// Make sure that the component can be resolved.
List<ResolveInfo> resolveInfos = resolveComponent(componentName, null);
+ if (resolveInfos.isEmpty()) {
+ resolveInfos = resolveComponent(componentName, Binder.getCallingUserHandle());
+ }
+
if (!resolveInfos.isEmpty()) {
// See if there is registered PhoneAccount by this component.
List<PhoneAccountHandle> handles = getAllPhoneAccountHandles();
@@ -379,16 +391,23 @@
return true;
}
+ if (phoneAccountUserHandle.equals(Binder.getCallingUserHandle())) {
+ return true;
+ }
+
// Unlike in TelecomServiceImpl, we only care about *profiles* here. We want to make sure
// that we don't resolve PhoneAccount across *users*, but resolving across *profiles* is
// fine.
- List<UserInfo> profileUsers = mUserManager.getProfiles(mCurrentUserHandle.getIdentifier());
-
- for (UserInfo profileInfo : profileUsers) {
- if (profileInfo.getUserHandle().equals(phoneAccountUserHandle)) {
- return true;
+ if (UserHandle.getCallingUserId() == UserHandle.USER_OWNER) {
+ List<UserInfo> profileUsers =
+ mUserManager.getProfiles(mCurrentUserHandle.getIdentifier());
+ for (UserInfo profileInfo : profileUsers) {
+ if (profileInfo.getUserHandle().equals(phoneAccountUserHandle)) {
+ return true;
+ }
}
}
+
return false;
}
@@ -402,10 +421,15 @@
PackageManager pm = mContext.getPackageManager();
Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
intent.setComponent(componentName);
- if (userHandle != null) {
- return pm.queryIntentServicesAsUser(intent, 0, userHandle.getIdentifier());
- } else {
- return pm.queryIntentServices(intent, 0);
+ try {
+ if (userHandle != null) {
+ return pm.queryIntentServicesAsUser(intent, 0, userHandle.getIdentifier());
+ } else {
+ return pm.queryIntentServices(intent, 0);
+ }
+ } catch (SecurityException e) {
+ Log.v(this, "%s is not visible for the calling user", componentName);
+ return Collections.EMPTY_LIST;
}
}
@@ -612,17 +636,19 @@
* @return {@code True} if the phone account has permission.
*/
public boolean phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle) {
- PackageManager packageManager = mContext.getPackageManager();
- try {
- ServiceInfo serviceInfo = packageManager.getServiceInfo(
- phoneAccountHandle.getComponentName(), 0);
-
- return serviceInfo.permission != null &&
- serviceInfo.permission.equals(Manifest.permission.BIND_CONNECTION_SERVICE);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(this, "Name not found %s", e);
+ List<ResolveInfo> resolveInfos = resolveComponent(phoneAccountHandle);
+ if (resolveInfos.isEmpty()) {
+ Log.w(this, "phoneAccount %s not found", phoneAccountHandle.getComponentName());
return false;
}
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (serviceInfo == null || !Objects.equals(serviceInfo.permission,
+ Manifest.permission.BIND_CONNECTION_SERVICE)) {
+ return false;
+ }
+ }
+ return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index b96eb02..bf0d3b8 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -29,10 +29,12 @@
*/
final class PhoneStateBroadcaster extends CallsManagerListenerBase {
+ private final CallsManager mCallsManager;
private final ITelephonyRegistry mRegistry;
private int mCurrentState = TelephonyManager.CALL_STATE_IDLE;
- public PhoneStateBroadcaster() {
+ public PhoneStateBroadcaster(CallsManager callsManager) {
+ mCallsManager = callsManager;
mRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
if (mRegistry == null) {
@@ -43,7 +45,8 @@
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
if ((newState == CallState.DIALING || newState == CallState.ACTIVE
- || newState == CallState.ON_HOLD) && !CallsManager.getInstance().hasRingingCall()) {
+ || newState == CallState.ON_HOLD) &&
+ !mCallsManager.hasRingingCall()) {
/*
* EXTRA_STATE_RINGING takes precedence over EXTRA_STATE_OFFHOOK, so if there is
* already a ringing call, don't broadcast EXTRA_STATE_OFFHOOK.
@@ -63,11 +66,10 @@
public void onCallRemoved(Call call) {
// Recalculate the current phone state based on the consolidated state of the remaining
// calls in the call list.
- final CallsManager callsManager = CallsManager.getInstance();
int callState = TelephonyManager.CALL_STATE_IDLE;
- if (callsManager.hasRingingCall()) {
+ if (mCallsManager.hasRingingCall()) {
callState = TelephonyManager.CALL_STATE_RINGING;
- } else if (callsManager.getFirstCallWithState(CallState.DIALING, CallState.ACTIVE,
+ } else if (mCallsManager.getFirstCallWithState(CallState.DIALING, CallState.ACTIVE,
CallState.ON_HOLD) != null) {
callState = TelephonyManager.CALL_STATE_OFFHOOK;
}
diff --git a/src/com/android/server/telecom/ProximitySensorManager.java b/src/com/android/server/telecom/ProximitySensorManager.java
index 5b82c43..5fddb89 100644
--- a/src/com/android/server/telecom/ProximitySensorManager.java
+++ b/src/com/android/server/telecom/ProximitySensorManager.java
@@ -26,8 +26,9 @@
private static final String TAG = ProximitySensorManager.class.getSimpleName();
private final PowerManager.WakeLock mProximityWakeLock;
+ private final CallsManager mCallsManager;
- public ProximitySensorManager(Context context) {
+ public ProximitySensorManager(Context context, CallsManager callsManager) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
@@ -36,12 +37,14 @@
} else {
mProximityWakeLock = null;
}
+
+ mCallsManager = callsManager;
Log.d(this, "onCreate: mProximityWakeLock: ", mProximityWakeLock);
}
@Override
public void onCallRemoved(Call call) {
- if (CallsManager.getInstance().getCalls().isEmpty()) {
+ if (mCallsManager.getCalls().isEmpty()) {
Log.i(this, "All calls removed, resetting proximity sensor to default state");
turnOff(true);
}
@@ -52,7 +55,7 @@
* Turn the proximity sensor on.
*/
void turnOn() {
- if (CallsManager.getInstance().getCalls().isEmpty()) {
+ if (mCallsManager.getCalls().isEmpty()) {
Log.w(this, "Asking to turn on prox sensor without a call? I don't think so.");
return;
}
diff --git a/src/com/android/server/telecom/ProximitySensorManagerFactory.java b/src/com/android/server/telecom/ProximitySensorManagerFactory.java
new file mode 100644
index 0000000..b73636f
--- /dev/null
+++ b/src/com/android/server/telecom/ProximitySensorManagerFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.content.Context;
+
+/**
+ * This is a TEMPORARY fix to make the {@link ProximitySensorManager} object injectable for testing.
+ * Class {@link ProximitySensorManager} itself is not testable because it grabs lots of special
+ * stuff from its {@code Context} that cannot be conveniently mocked.
+ *
+ * TODO: Replace with a better design.
+ */
+public interface ProximitySensorManagerFactory {
+
+ ProximitySensorManager create(Context context, CallsManager callsManager);
+
+}
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index ebedf9f..98ba3b4 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -41,32 +41,15 @@
* Helper class to manage the "Respond via Message" feature for incoming calls.
*/
public class RespondViaSmsManager extends CallsManagerListenerBase {
- private static final int MSG_CANNED_TEXT_MESSAGES_READY = 1;
private static final int MSG_SHOW_SENT_TOAST = 2;
- private static final RespondViaSmsManager sInstance = new RespondViaSmsManager();
+ private final CallsManager mCallsManager;
+ private final TelecomSystem.SyncRoot mLock;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_CANNED_TEXT_MESSAGES_READY: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- Response<Void, List<String>> response =
- (Response<Void, List<String>>) args.arg1;
- List<String> textMessages =
- (List<String>) args.arg2;
- if (textMessages != null) {
- response.onResult(null, textMessages);
- } else {
- response.onError(null, 0, null);
- }
- } finally {
- args.recycle();
- }
- break;
- }
case MSG_SHOW_SENT_TOAST: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -82,9 +65,10 @@
}
};
- public static RespondViaSmsManager getInstance() { return sInstance; }
-
- private RespondViaSmsManager() {}
+ public RespondViaSmsManager(CallsManager callsManager, TelecomSystem.SyncRoot lock) {
+ mCallsManager = callsManager;
+ mLock = lock;
+ }
/**
* Read the (customizable) canned responses from SharedPreferences,
@@ -132,10 +116,9 @@
"loadCannedResponses() completed, found responses: %s",
textMessages.toString());
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = response;
- args.arg2 = textMessages;
- mHandler.obtainMessage(MSG_CANNED_TEXT_MESSAGES_READY, args).sendToTarget();
+ synchronized (mLock) {
+ response.onResult(null, textMessages);
+ }
}
}.start();
}
@@ -143,9 +126,7 @@
@Override
public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
if (rejectWithMessage && call.getHandle() != null) {
- PhoneAccountRegistrar phoneAccountRegistrar =
- CallsManager.getInstance().getPhoneAccountRegistrar();
- int subId = phoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
+ int subId = mCallsManager.getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(
call.getTargetPhoneAccount());
rejectCallWithMessage(call.getContext(), call.getHandle().getSchemeSpecificPart(),
textMessage, subId);
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index e6d703c..6c27388 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -89,7 +89,6 @@
*/
private void startRingbackForCall(Call call) {
Preconditions.checkState(call.getState() == CallState.DIALING);
- ThreadUtil.checkOnMainThread();
if (mCall == call) {
Log.w(this, "Ignoring duplicate requests to ring for %s.", call);
@@ -116,8 +115,6 @@
* @param call The call for which to stop ringback.
*/
private void stopRingbackForCall(Call call) {
- ThreadUtil.checkOnMainThread();
-
if (mCall == call) {
// The foreground call is no longer dialing or is no longer the foreground call. In
// either case, stop the ringback tone.
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index 9a5ad03..05882ab 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -21,8 +21,6 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -38,7 +36,7 @@
* Subclasses supply the service intent and component name and this class will invoke protected
* methods when the class is bound, unbound, or upon failure.
*/
-abstract class ServiceBinder<ServiceInterface extends IInterface> {
+abstract class ServiceBinder {
/**
* Callback to notify after a binding succeeds or fails.
@@ -51,7 +49,7 @@
/**
* Listener for bind events on ServiceBinder.
*/
- interface Listener<ServiceBinderClass extends ServiceBinder<?>> {
+ interface Listener<ServiceBinderClass extends ServiceBinder> {
void onUnbind(ServiceBinderClass serviceBinder);
}
@@ -66,7 +64,6 @@
* @param callback The callback to notify of the binding's success or failure.
*/
void bind(BindCallback callback) {
- ThreadUtil.checkOnMainThread();
Log.d(ServiceBinder.this, "bind()");
// Reset any abort request if we're asked to bind again.
@@ -107,37 +104,43 @@
private final class ServiceBinderConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
- ThreadUtil.checkOnMainThread();
- Log.i(this, "Service bound %s", componentName);
+ synchronized (mLock) {
+ Log.i(this, "Service bound %s", componentName);
- // Unbind request was queued so unbind immediately.
- if (mIsBindingAborted) {
- clearAbort();
- logServiceDisconnected("onServiceConnected");
- mContext.unbindService(this);
- handleFailedConnection();
- return;
+ // Unbind request was queued so unbind immediately.
+ if (mIsBindingAborted) {
+ clearAbort();
+ logServiceDisconnected("onServiceConnected");
+ mContext.unbindService(this);
+ handleFailedConnection();
+ return;
+ }
+
+ mServiceConnection = this;
+ setBinder(binder);
+ handleSuccessfulConnection();
}
-
- mServiceConnection = this;
- setBinder(binder);
- handleSuccessfulConnection();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
- logServiceDisconnected("onServiceDisconnected");
+ synchronized (mLock) {
+ logServiceDisconnected("onServiceDisconnected");
- mServiceConnection = null;
- clearAbort();
+ mServiceConnection = null;
+ clearAbort();
- handleServiceDisconnected();
+ handleServiceDisconnected();
+ }
}
}
/** The application context. */
private final Context mContext;
+ /** The Telecom lock object. */
+ protected final TelecomSystem.SyncRoot mLock;
+
/** The intent action to use when binding through {@link Context#bindService}. */
private final String mServiceAction;
@@ -182,11 +185,12 @@
* @param userHandle The {@link UserHandle} to use for binding.
*/
protected ServiceBinder(String serviceAction, ComponentName componentName, Context context,
- UserHandle userHandle) {
+ TelecomSystem.SyncRoot lock, UserHandle userHandle) {
Preconditions.checkState(!TextUtils.isEmpty(serviceAction));
Preconditions.checkNotNull(componentName);
mContext = context;
+ mLock = lock;
mServiceAction = serviceAction;
mComponentName = componentName;
mUserHandle = userHandle;
@@ -221,8 +225,6 @@
* Unbinds from the service if already bound, no-op otherwise.
*/
final void unbind() {
- ThreadUtil.checkOnMainThread();
-
if (mServiceConnection == null) {
// We're not yet bound, so queue up an abort request.
mIsBindingAborted = true;
diff --git a/src/com/android/server/telecom/TelecomBroadcastReceiver.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
similarity index 76%
rename from src/com/android/server/telecom/TelecomBroadcastReceiver.java
rename to src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index cc5116d..2b22ea9 100644
--- a/src/com/android/server/telecom/TelecomBroadcastReceiver.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -16,57 +16,58 @@
package com.android.server.telecom;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
-/**
- * Handles miscellaneous Telecom broadcast intents. This should be visible from outside, but
- * should not be in the "exported" state.
- */
-public final class TelecomBroadcastReceiver extends BroadcastReceiver {
+public final class TelecomBroadcastIntentProcessor {
/** The action used to send SMS response for the missed call notification. */
- static final String ACTION_SEND_SMS_FROM_NOTIFICATION =
+ public static final String ACTION_SEND_SMS_FROM_NOTIFICATION =
"com.android.server.telecom.ACTION_SEND_SMS_FROM_NOTIFICATION";
/** The action used to call a handle back for the missed call notification. */
- static final String ACTION_CALL_BACK_FROM_NOTIFICATION =
+ public static final String ACTION_CALL_BACK_FROM_NOTIFICATION =
"com.android.server.telecom.ACTION_CALL_BACK_FROM_NOTIFICATION";
/** The action used to clear missed calls. */
- static final String ACTION_CLEAR_MISSED_CALLS =
+ public static final String ACTION_CLEAR_MISSED_CALLS =
"com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS";
- /** {@inheritDoc} */
- @Override
- public void onReceive(Context context, Intent intent) {
+ private final Context mContext;
+ private final CallsManager mCallsManager;
+
+ public TelecomBroadcastIntentProcessor(Context context, CallsManager callsManager) {
+ mContext = context;
+ mCallsManager = callsManager;
+ }
+
+ public void processIntent(Intent intent) {
String action = intent.getAction();
Log.v(this, "Action received: %s.", action);
- MissedCallNotifier missedCallNotifier = CallsManager.getInstance().getMissedCallNotifier();
+ MissedCallNotifier missedCallNotifier = mCallsManager.getMissedCallNotifier();
// Send an SMS from the missed call notification.
if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action)) {
// Close the notification shade and the notification itself.
- closeSystemDialogs(context);
+ closeSystemDialogs(mContext);
missedCallNotifier.clearMissedCalls();
Intent callIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(callIntent);
+ mContext.startActivity(callIntent);
// Call back recent caller from the missed call notification.
} else if (ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
// Close the notification shade and the notification itself.
- closeSystemDialogs(context);
+ closeSystemDialogs(mContext);
missedCallNotifier.clearMissedCalls();
Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
callIntent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- context.startActivity(callIntent);
+ mContext.startActivity(callIntent);
// Clear the missed call notification and call log entries.
} else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
diff --git a/src/com/android/server/telecom/TelecomGlobals.java b/src/com/android/server/telecom/TelecomGlobals.java
deleted file mode 100644
index cf0936c..0000000
--- a/src/com/android/server/telecom/TelecomGlobals.java
+++ /dev/null
@@ -1,109 +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.server.telecom;
-
-import android.app.Application;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-
-/**
- * Top-level Application class for Telecom.
- */
-public final class TelecomGlobals {
- private static final String TAG = TelecomGlobals.class.getSimpleName();
-
- private static final IntentFilter USER_SWITCHED_FILTER =
- new IntentFilter(Intent.ACTION_USER_SWITCHED);
-
- private static final TelecomGlobals INSTANCE = new TelecomGlobals();
-
- /**
- * The Telecom service implementation.
- */
- private TelecomService mTelecomService;
-
- /**
- * Missed call notifier. Exists here so that the instance can be shared with
- * {@link TelecomBroadcastReceiver}.
- */
- private MissedCallNotifier mMissedCallNotifier;
-
- /**
- * Maintains the list of registered {@link android.telecom.PhoneAccountHandle}s.
- */
- private PhoneAccountRegistrar mPhoneAccountRegistrar;
-
- /**
- * The calls manager for the Telecom service.
- */
- private CallsManager mCallsManager;
-
- /**
- * The application context.
- */
- private Context mContext;
-
- private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- UserHandle currentUserHandle = new UserHandle(userHandleId);
- mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
- }
- };
-
- static TelecomGlobals getInstance() {
- return INSTANCE;
- }
-
- void initialize(Context context) {
- if (mContext != null) {
- Log.e(TAG, new Exception(), "Attempting to intialize TelecomGlobals a second time.");
- return;
- } else {
- Log.i(TAG, "TelecomGlobals initializing");
- }
- mContext = context.getApplicationContext();
-
- mMissedCallNotifier = new MissedCallNotifier(mContext);
- mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
-
- mCallsManager = new CallsManager(mContext, mMissedCallNotifier, mPhoneAccountRegistrar);
- CallsManager.initialize(mCallsManager);
- Log.i(this, "CallsManager initialized");
-
- // Start the BluetoothPhoneService
- BluetoothPhoneService.start(mContext);
-
- mContext.registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
- }
-
- MissedCallNotifier getMissedCallNotifier() {
- return mMissedCallNotifier;
- }
-
- PhoneAccountRegistrar getPhoneAccountRegistrar() {
- return mPhoneAccountRegistrar;
- }
-
- CallsManager getCallsManager() {
- return mCallsManager;
- }
-}
diff --git a/src/com/android/server/telecom/TelecomService.java b/src/com/android/server/telecom/TelecomService.java
deleted file mode 100644
index e914f32..0000000
--- a/src/com/android/server/telecom/TelecomService.java
+++ /dev/null
@@ -1,1014 +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.server.telecom;
-
-import android.Manifest;
-import android.annotation.SdkConstant;
-import android.app.AppOpsManager;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.telecom.CallState;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-// TODO: Needed for move to system service: import com.android.internal.R;
-import com.android.internal.telecom.ITelecomService;
-import com.android.internal.util.IndentingPrintWriter;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Implementation of the ITelecom interface.
- */
-public class TelecomService extends Service {
- /**
- * The {@link Intent} that must be declared as handled by the service.
- */
- @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
- public static final String SERVICE_INTERFACE = "android.telecom.ITelecomService";
-
- /** The context. */
- private Context mContext;
-
- /**
- * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
- * request after sending. The main thread will notify the request when it is complete.
- */
- private static final class MainThreadRequest {
- /** The result of the request that is run on the main thread */
- public Object result;
- /** Object that can be used to store non-integer arguments */
- public Object arg;
- }
-
- /**
- * A handler that processes messages on the main thread. Since many of the method calls are not
- * thread safe this is needed to shuttle the requests from the inbound binder threads to the
- * main thread.
- */
- private final class MainThreadHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- if (msg.obj instanceof MainThreadRequest) {
- MainThreadRequest request = (MainThreadRequest) msg.obj;
- Object result = null;
- switch (msg.what) {
- case MSG_SILENCE_RINGER:
- mCallsManager.getRinger().silence();
- break;
- case MSG_SHOW_CALL_SCREEN:
- mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
- break;
- case MSG_END_CALL:
- result = endCallInternal();
- break;
- case MSG_ACCEPT_RINGING_CALL:
- acceptRingingCallInternal();
- break;
- case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
- mMissedCallNotifier.clearMissedCalls();
- break;
- case MSG_IS_TTY_SUPPORTED:
- result = mCallsManager.isTtySupported();
- break;
- case MSG_GET_CURRENT_TTY_MODE:
- result = mCallsManager.getCurrentTtyMode();
- break;
- case MSG_NEW_INCOMING_CALL:
- if (request.arg == null || !(request.arg instanceof Intent)) {
- Log.w(this, "Invalid new incoming call request");
- break;
- }
- CallReceiver.processIncomingCallIntent((Intent) request.arg);
- break;
- }
-
- if (result != null) {
- request.result = result;
- synchronized(request) {
- request.notifyAll();
- }
- }
- }
- }
- }
-
- private static final String TAG = TelecomService.class.getSimpleName();
-
- private static final String SERVICE_NAME = "telecom";
-
- private static final int MSG_SILENCE_RINGER = 1;
- private static final int MSG_SHOW_CALL_SCREEN = 2;
- private static final int MSG_END_CALL = 3;
- private static final int MSG_ACCEPT_RINGING_CALL = 4;
- private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 5;
- private static final int MSG_IS_TTY_SUPPORTED = 6;
- private static final int MSG_GET_CURRENT_TTY_MODE = 7;
- private static final int MSG_NEW_INCOMING_CALL = 8;
-
- private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
-
- private CallsManager mCallsManager;
- private MissedCallNotifier mMissedCallNotifier;
- private PhoneAccountRegistrar mPhoneAccountRegistrar;
- private AppOpsManager mAppOpsManager;
- private UserManager mUserManager;
- private PackageManager mPackageManager;
- private TelecomServiceImpl mServiceImpl;
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- Log.d(this, "onCreate");
- mContext = this;
- mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mServiceImpl = new TelecomServiceImpl();
-
- TelecomGlobals globals = TelecomGlobals.getInstance();
- globals.initialize(this);
-
- mMissedCallNotifier = globals.getMissedCallNotifier();
- mPhoneAccountRegistrar = globals.getPhoneAccountRegistrar();
- mCallsManager = globals.getCallsManager();
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mPackageManager = mContext.getPackageManager();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- Log.d(this, "onBind");
- return mServiceImpl;
- }
-
- /**
- * Implementation of the ITelecomService interface.
- * TODO: Reorganize this inner class to top of file.
- */
- class TelecomServiceImpl extends ITelecomService.Stub {
- @Override
- public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
- enforceReadPermission();
- long token = Binder.clearCallingIdentity();
- try {
- PhoneAccountHandle defaultOutgoingPhoneAccount =
- mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(uriScheme);
- // Make sure that the calling user can see this phone account.
- if (defaultOutgoingPhoneAccount != null
- && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
- Log.w(this, "No account found for the calling user");
- return null;
- }
- return defaultOutgoingPhoneAccount;
- } catch (Exception e) {
- Log.e(this, e, "getDefaultOutgoingPhoneAccount");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
- try {
- PhoneAccountHandle userSelectedOutgoingPhoneAccount =
- mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount();
- // Make sure that the calling user can see this phone account.
- if (!isVisibleToCaller(userSelectedOutgoingPhoneAccount)) {
- Log.w(this, "No account found for the calling user");
- return null;
- }
- return userSelectedOutgoingPhoneAccount;
- } catch (Exception e) {
- Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
- throw e;
- }
- }
-
- @Override
- public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
- enforceModifyPermission();
-
- try {
- mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getCallCapablePhoneAccounts() {
- enforceReadPermission();
- long token = Binder.clearCallingIdentity();
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getCallCapablePhoneAccounts());
- } catch (Exception e) {
- Log.e(this, e, "getCallCapablePhoneAccounts");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) {
- enforceReadPermission();
- long token = Binder.clearCallingIdentity();
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme));
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName));
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
- throw e;
- }
- }
-
- @Override
- public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
- try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
- }
- return mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccount %s", accountHandle);
- throw e;
- }
- }
-
- @Override
- public int getAllPhoneAccountsCount() {
- try {
- // This list is pre-filtered for the calling user.
- return getAllPhoneAccounts().size();
- } catch (Exception e) {
- Log.e(this, e, "getAllPhoneAccountsCount");
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccount> getAllPhoneAccounts() {
- try {
- List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts();
- List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(allPhoneAccounts.size());
- for (PhoneAccount phoneAccount : profilePhoneAccounts) {
- if (isVisibleToCaller(phoneAccount)) {
- profilePhoneAccounts.add(phoneAccount);
- }
- }
- return profilePhoneAccounts;
- } catch (Exception e) {
- Log.e(this, e, "getAllPhoneAccounts");
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getAllPhoneAccountHandles());
- } catch (Exception e) {
- Log.e(this, e, "getAllPhoneAccounts");
- throw e;
- }
- }
-
- @Override
- public PhoneAccountHandle getSimCallManager() {
- try {
- PhoneAccountHandle accountHandle = mPhoneAccountRegistrar.getSimCallManager();
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
- }
- return accountHandle;
- } catch (Exception e) {
- Log.e(this, e, "getSimCallManager");
- throw e;
- }
- }
-
- @Override
- public void setSimCallManager(PhoneAccountHandle accountHandle) {
- enforceModifyPermission();
-
- try {
- mPhoneAccountRegistrar.setSimCallManager(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "setSimCallManager");
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getSimCallManagers() {
- enforceReadPermission();
- long token = Binder.clearCallingIdentity();
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getConnectionManagerPhoneAccounts());
- } catch (Exception e) {
- Log.e(this, e, "getSimCallManagers");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void registerPhoneAccount(PhoneAccount account) {
- try {
- enforcePhoneAccountModificationForPackage(
- account.getAccountHandle().getComponentName().getPackageName());
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
- enforceRegisterCallProviderPermission();
- }
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
- enforceRegisterSimSubscriptionPermission();
- }
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
- enforceRegisterConnectionManagerPermission();
- }
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
- enforceRegisterMultiUser();
- }
- enforceUserHandleMatchesCaller(account.getAccountHandle());
-
- mPhoneAccountRegistrar.registerPhoneAccount(account);
- } catch (Exception e) {
- Log.e(this, e, "registerPhoneAccount %s", account);
- throw e;
- }
- }
-
- @Override
- public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
- try {
- enforcePhoneAccountModificationForPackage(
- accountHandle.getComponentName().getPackageName());
- enforceUserHandleMatchesCaller(accountHandle);
- mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
- throw e;
- }
- }
-
- @Override
- public void clearAccounts(String packageName) {
- try {
- enforcePhoneAccountModificationForPackage(packageName);
- mPhoneAccountRegistrar.clearAccounts(packageName, Binder.getCallingUserHandle());
- } catch (Exception e) {
- Log.e(this, e, "clearAccounts %s", packageName);
- throw e;
- }
- }
-
- /**
- * @see android.telecom.TelecomManager#isVoiceMailNumber
- */
- @Override
- public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) {
- enforceReadPermissionOrDefaultDialer();
- try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return false;
- }
- return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
- }
- }
-
- /**
- * @see android.telecom.TelecomManager#hasVoiceMailNumber
- */
- @Override
- public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) {
- enforceReadPermissionOrDefaultDialer();
- try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return false;
- }
-
- int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
- return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber(subId));
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
- }
- }
-
- /**
- * @see android.telecom.TelecomManager#getLine1Number
- */
- @Override
- public String getLine1Number(PhoneAccountHandle accountHandle) {
- enforceReadPermissionOrDefaultDialer();
- try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
- }
- int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
- return getTelephonyManager().getLine1NumberForSubscriber(subId);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
- }
- }
-
- /**
- * @see android.telecom.TelecomManager#silenceRinger
- */
- @Override
- public void silenceRinger() {
- Log.d(this, "silenceRinger");
- enforceModifyPermission();
- sendRequestAsync(MSG_SILENCE_RINGER, 0);
- }
-
- /**
- * @see android.telecom.TelecomManager#getDefaultPhoneApp
- */
- @Override
- public ComponentName getDefaultPhoneApp() {
- Resources resources = mContext.getResources();
- return new ComponentName(
- resources.getString(R.string.ui_default_package),
- resources.getString(R.string.dialer_default_class));
- }
-
- /**
- * @see android.telecom.TelecomManager#isInCall
- */
- @Override
- public boolean isInCall() {
- enforceReadPermission();
- // Do not use sendRequest() with this method since it could cause a deadlock with
- // audio service, which we call into from the main thread: AudioManager.setMode().
- final int callState = mCallsManager.getCallState();
- return callState == TelephonyManager.CALL_STATE_OFFHOOK
- || callState == TelephonyManager.CALL_STATE_RINGING;
- }
-
- /**
- * @see android.telecom.TelecomManager#isRinging
- */
- @Override
- public boolean isRinging() {
- enforceReadPermission();
- return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
- }
-
- /**
- * @see TelecomManager#getCallState
- */
- @Override
- public int getCallState() {
- return mCallsManager.getCallState();
- }
-
- /**
- * @see android.telecom.TelecomManager#endCall
- */
- @Override
- public boolean endCall() {
- enforceModifyPermission();
- return (boolean) sendRequest(MSG_END_CALL);
- }
-
- /**
- * @see android.telecom.TelecomManager#acceptRingingCall
- */
- @Override
- public void acceptRingingCall() {
- enforceModifyPermission();
- sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
- }
-
- /**
- * @see android.telecom.TelecomManager#showInCallScreen
- */
- @Override
- public void showInCallScreen(boolean showDialpad) {
- enforceReadPermissionOrDefaultDialer();
- sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
- }
-
- /**
- * @see android.telecom.TelecomManager#cancelMissedCallsNotification
- */
- @Override
- public void cancelMissedCallsNotification() {
- enforceModifyPermissionOrDefaultDialer();
- sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
- }
-
- /**
- * @see android.telecom.TelecomManager#handleMmi
- */
- @Override
- public boolean handlePinMmi(String dialString) {
- enforceModifyPermissionOrDefaultDialer();
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- boolean retval = false;
- try {
- retval = getTelephonyManager().handlePinMmi(dialString);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return retval;
- }
-
- /**
- * @see android.telecom.TelecomManager#handleMmi
- */
- @Override
- public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle,
- String dialString) {
- enforceModifyPermissionOrDefaultDialer();
-
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return false;
- }
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- boolean retval = false;
- try {
- int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
- retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return retval;
- }
-
- /**
- * @see android.telecom.TelecomManager#getAdnUriForPhoneAccount
- */
- @Override
- public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
- enforceModifyPermissionOrDefaultDialer();
-
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
- }
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- String retval = "content://icc/adn/";
- try {
- long subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
- retval = retval + "subId/" + subId;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return Uri.parse(retval);
- }
-
- /**
- * @see android.telecom.TelecomManager#isTtySupported
- */
- @Override
- public boolean isTtySupported() {
- enforceReadPermission();
- return (boolean) sendRequest(MSG_IS_TTY_SUPPORTED);
- }
-
- /**
- * @see android.telecom.TelecomManager#getCurrentTtyMode
- */
- @Override
- public int getCurrentTtyMode() {
- enforceReadPermission();
- return (int) sendRequest(MSG_GET_CURRENT_TTY_MODE);
- }
-
- /**
- * @see android.telecom.TelecomManager#addNewIncomingCall
- */
- @Override
- public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- Log.i(this, "Adding new incoming call with phoneAccountHandle %s", phoneAccountHandle);
- if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
- // TODO(sail): Add unit tests for adding incoming calls from a SIM call manager.
- if (isCallerSimCallManager() && TelephonyUtil.isPstnComponentName(
- phoneAccountHandle.getComponentName())) {
- Log.v(this, "Allowing call manager to add incoming call with PSTN handle");
- } else {
- mAppOpsManager.checkPackage(Binder.getCallingUid(),
- phoneAccountHandle.getComponentName().getPackageName());
- // Make sure it doesn't cross the UserHandle boundary
- enforceUserHandleMatchesCaller(phoneAccountHandle);
- }
-
- Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
- intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
- intent.putExtra(CallReceiver.KEY_IS_INCOMING_CALL, true);
- if (extras != null) {
- intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
- }
- sendRequestAsync(MSG_NEW_INCOMING_CALL, 0, intent);
- } else {
- Log.w(this, "Null phoneAccountHandle. Ignoring request to add new incoming call");
- }
- }
-
- /**
- * @see android.telecom.TelecomManager#addNewUnknownCall
- */
- @Override
- public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null &&
- TelephonyUtil.isPstnComponentName(phoneAccountHandle.getComponentName())) {
- mAppOpsManager.checkPackage(
- Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
-
- // Make sure it doesn't cross the UserHandle boundary
- enforceUserHandleMatchesCaller(phoneAccountHandle);
-
- Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
- intent.setClass(mContext, CallReceiver.class);
- intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtras(extras);
- intent.putExtra(CallReceiver.KEY_IS_UNKNOWN_CALL, true);
- intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
- mContext.sendBroadcastAsUser(intent, phoneAccountHandle.getUserHandle());
- } else {
- Log.i(this, "Null phoneAccountHandle or not initiated by Telephony. Ignoring request"
- + " to add new unknown call.");
- }
- }
-
- /**
- * Dumps the current state of the TelecomService. Used when generating problem reports.
- *
- * @param fd The file descriptor.
- * @param writer The print writer to dump the state to.
- * @param args Optional dump arguments.
- */
- @Override
- protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.DUMP)
- != PackageManager.PERMISSION_GRANTED) {
- writer.println("Permission Denial: can't dump TelecomService " +
- "from from pid=" + Binder.getCallingPid() + ", uid=" +
- Binder.getCallingUid());
- return;
- }
-
- final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- if (mCallsManager != null) {
- pw.println("mCallsManager: ");
- pw.increaseIndent();
- mCallsManager.dump(pw);
- pw.decreaseIndent();
-
- pw.println("mPhoneAccountRegistrar: ");
- pw.increaseIndent();
- mPhoneAccountRegistrar.dump(pw);
- pw.decreaseIndent();
- }
- }
- }
-
- //
- // Supporting methods for the ITelecomService interface implementation.
- //
-
- private boolean isVisibleToCaller(PhoneAccountHandle accountHandle) {
- if (accountHandle == null) {
- return false;
- }
-
- return isVisibleToCaller(mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle));
- }
-
- private boolean isVisibleToCaller(PhoneAccount account) {
- if (account == null) {
- return false;
- }
-
- // If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and
- // all profiles. Only Telephony and SIP accounts should have this capability.
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
- return true;
- }
-
- UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle();
- if (phoneAccountUserHandle == null) {
- return false;
- }
-
- List<UserHandle> profileUserHandles;
- if (isCallerSystemApp()) {
- // If the caller lives in /system/priv-app, it can see PhoneAccounts for all of the
- // *profiles* that the calling user owns, but not for any other *users*.
- profileUserHandles = mUserManager.getUserProfiles();
- } else {
- // Otherwise, it has to be owned by the current caller's profile.
- profileUserHandles = new ArrayList<>(1);
- profileUserHandles.add(Binder.getCallingUserHandle());
- }
-
- return profileUserHandles.contains(phoneAccountUserHandle);
- }
-
- /**
- * Given a list of {@link PhoneAccountHandle}s, filter them to the ones that the calling
- * user can see.
- *
- * @param phoneAccountHandles Unfiltered list of account handles.
- *
- * @return {@link PhoneAccountHandle}s visible to the calling user and its profiles.
- */
- private List<PhoneAccountHandle> filterForAccountsVisibleToCaller(
- List<PhoneAccountHandle> phoneAccountHandles) {
- List<PhoneAccountHandle> profilePhoneAccountHandles =
- new ArrayList<>(phoneAccountHandles.size());
- for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) {
- if (isVisibleToCaller(phoneAccountHandle)) {
- profilePhoneAccountHandles.add(phoneAccountHandle);
- }
- }
- return profilePhoneAccountHandles;
- }
-
- private boolean isCallerSystemApp() {
- int uid = Binder.getCallingUid();
- String[] packages = mPackageManager.getPackagesForUid(uid);
- for (String packageName : packages) {
- if (isPackageSystemApp(packageName)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean isPackageSystemApp(String packageName) {
- try {
- ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName,
- PackageManager.GET_META_DATA);
- if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- }
- return false;
- }
-
- private void acceptRingingCallInternal() {
- Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
- if (call != null) {
- call.answer(call.getVideoState());
- }
- }
-
- private boolean endCallInternal() {
- // Always operate on the foreground call if one exists, otherwise get the first call in
- // priority order by call-state.
- Call call = mCallsManager.getForegroundCall();
- if (call == null) {
- call = mCallsManager.getFirstCallWithState(
- CallState.ACTIVE,
- CallState.DIALING,
- CallState.RINGING,
- CallState.ON_HOLD);
- }
-
- if (call != null) {
- if (call.getState() == CallState.RINGING) {
- call.reject(false /* rejectWithMessage */, null);
- } else {
- call.disconnect();
- }
- return true;
- }
-
- return false;
- }
-
- private void enforcePhoneAccountModificationForPackage(String packageName) {
- // TODO: Use a new telecomm permission for this instead of reusing modify.
-
- int result = mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
-
- // Callers with MODIFY_PHONE_STATE can use the PhoneAccount mechanism to implement
- // built-in behavior even when PhoneAccounts are not exposed as a third-part API. They
- // may also modify PhoneAccounts on behalf of any 'packageName'.
-
- if (result != PackageManager.PERMISSION_GRANTED) {
- // Other callers are only allowed to modify PhoneAccounts if the relevant system
- // feature is enabled ...
- enforceConnectionServiceFeature();
- // ... and the PhoneAccounts they refer to are for their own package.
- enforceCallingPackage(packageName);
- }
- }
-
- private void enforceReadPermissionOrDefaultDialer() {
- if (!isDefaultDialerCalling()) {
- enforceReadPermission();
- }
- }
-
- private void enforceModifyPermissionOrDefaultDialer() {
- if (!isDefaultDialerCalling()) {
- enforceModifyPermission();
- }
- }
-
- private void enforceCallingPackage(String packageName) {
- mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
- }
-
- private void enforceConnectionServiceFeature() {
- enforceFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
- }
-
- private void enforceRegisterCallProviderPermission() {
- enforcePermission(android.Manifest.permission.REGISTER_CALL_PROVIDER);
- }
-
- private void enforceRegisterSimSubscriptionPermission() {
- enforcePermission(android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION);
- }
-
- private void enforceRegisterConnectionManagerPermission() {
- enforcePermission(android.Manifest.permission.REGISTER_CONNECTION_MANAGER);
- }
-
- private void enforceReadPermission() {
- enforcePermission(Manifest.permission.READ_PHONE_STATE);
- }
-
- private void enforceModifyPermission() {
- enforcePermission(Manifest.permission.MODIFY_PHONE_STATE);
- }
-
- private void enforcePermission(String permission) {
- mContext.enforceCallingOrSelfPermission(permission, null);
- }
-
- private void enforceRegisterMultiUser() {
- if (!isCallerSystemApp()) {
- throw new SecurityException("CAPABILITY_MULTI_USER is only available to system apps.");
- }
- }
-
- private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) {
- if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) {
- throw new SecurityException("Calling UserHandle does not match PhoneAccountHandle's");
- }
- }
-
- private void enforceFeature(String feature) {
- PackageManager pm = mContext.getPackageManager();
- if (!pm.hasSystemFeature(feature)) {
- throw new UnsupportedOperationException(
- "System does not support feature " + feature);
- }
- }
-
- private boolean isCallerSimCallManager() {
- PhoneAccountHandle accountHandle = mPhoneAccountRegistrar.getSimCallManager();
- if (accountHandle != null) {
- try {
- mAppOpsManager.checkPackage(
- Binder.getCallingUid(), accountHandle.getComponentName().getPackageName());
- return true;
- } catch (SecurityException e) {
- }
- }
- return false;
- }
-
- private boolean isDefaultDialerCalling() {
- ComponentName defaultDialerComponent = getDefaultPhoneAppInternal();
- if (defaultDialerComponent != null) {
- try {
- mAppOpsManager.checkPackage(
- Binder.getCallingUid(), defaultDialerComponent.getPackageName());
- return true;
- } catch (SecurityException e) {
- Log.e(TAG, e, "Could not get default dialer.");
- }
- }
- return false;
- }
-
- private ComponentName getDefaultPhoneAppInternal() {
- Resources resources = mContext.getResources();
- return new ComponentName(
- resources.getString(R.string.ui_default_package),
- resources.getString(R.string.dialer_default_class));
- }
-
- private TelephonyManager getTelephonyManager() {
- return (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
- }
-
- private MainThreadRequest sendRequestAsync(int command, int arg1) {
- return sendRequestAsync(command, arg1, null);
- }
-
- private MainThreadRequest sendRequestAsync(int command, int arg1, Object arg) {
- MainThreadRequest request = new MainThreadRequest();
- request.arg = arg;
- mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
- return request;
- }
-
- /**
- * Posts the specified command to be executed on the main thread, waits for the request to
- * complete, and returns the result.
- */
- private Object sendRequest(int command) {
- if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
- MainThreadRequest request = new MainThreadRequest();
- mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));
- return request.result;
- } else {
- MainThreadRequest request = sendRequestAsync(command, 0);
-
- // Wait for the request to complete
- synchronized (request) {
- while (request.result == null) {
- try {
- request.wait();
- } catch (InterruptedException e) {
- // Do nothing, go back and wait until the request is complete
- }
- }
- }
- return request.result;
- }
- }
-}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
new file mode 100644
index 0000000..b7b1500
--- /dev/null
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -0,0 +1,971 @@
+/*
+ * 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.server.telecom;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telecom.CallState;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+// TODO: Needed for move to system service: import com.android.internal.R;
+import com.android.internal.telecom.ITelecomService;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the ITelecom interface.
+ */
+public class TelecomServiceImpl {
+ private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION =
+ "android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION";
+
+ private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
+ @Override
+ public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
+ synchronized (mLock) {
+ enforceReadPermission();
+ long token = Binder.clearCallingIdentity();
+ try {
+ PhoneAccountHandle defaultOutgoingPhoneAccount =
+ mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(uriScheme);
+ // Make sure that the calling user can see this phone account.
+ if (defaultOutgoingPhoneAccount != null
+ && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
+ Log.w(this, "No account found for the calling user");
+ return null;
+ }
+ return defaultOutgoingPhoneAccount;
+ } catch (Exception e) {
+ Log.e(this, e, "getDefaultOutgoingPhoneAccount");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
+ synchronized (mLock) {
+ try {
+ PhoneAccountHandle userSelectedOutgoingPhoneAccount =
+ mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount();
+ // Make sure that the calling user can see this phone account.
+ if (!isVisibleToCaller(userSelectedOutgoingPhoneAccount)) {
+ Log.w(this, "No account found for the calling user");
+ return null;
+ }
+ return userSelectedOutgoingPhoneAccount;
+ } catch (Exception e) {
+ Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
+ synchronized (mLock) {
+ enforceModifyPermission();
+ try {
+ mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(accountHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getCallCapablePhoneAccounts() {
+ synchronized (mLock) {
+ enforceReadPermission();
+ long token = Binder.clearCallingIdentity();
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getCallCapablePhoneAccounts());
+ } catch (Exception e) {
+ Log.e(this, e, "getCallCapablePhoneAccounts");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) {
+ synchronized (mLock) {
+ enforceReadPermission();
+ long token = Binder.clearCallingIdentity();
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme));
+ } catch (Exception e) {
+ Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
+ synchronized (mLock) {
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName));
+ } catch (Exception e) {
+ Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
+ synchronized (mLock) {
+ try {
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return null;
+ }
+ return mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "getPhoneAccount %s", accountHandle);
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public int getAllPhoneAccountsCount() {
+ synchronized (mLock) {
+ try {
+ // This list is pre-filtered for the calling user.
+ return getAllPhoneAccounts().size();
+ } catch (Exception e) {
+ Log.e(this, e, "getAllPhoneAccountsCount");
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public List<PhoneAccount> getAllPhoneAccounts() {
+ synchronized (mLock) {
+ try {
+ List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar
+ .getAllPhoneAccounts();
+ List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(
+ allPhoneAccounts.size());
+ for (PhoneAccount phoneAccount : profilePhoneAccounts) {
+ if (isVisibleToCaller(phoneAccount)) {
+ profilePhoneAccounts.add(phoneAccount);
+ }
+ }
+ return profilePhoneAccounts;
+ } catch (Exception e) {
+ Log.e(this, e, "getAllPhoneAccounts");
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
+ synchronized (mLock) {
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getAllPhoneAccountHandles());
+ } catch (Exception e) {
+ Log.e(this, e, "getAllPhoneAccounts");
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public PhoneAccountHandle getSimCallManager() {
+ synchronized (mLock) {
+ try {
+ PhoneAccountHandle accountHandle = mPhoneAccountRegistrar.getSimCallManager();
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return null;
+ }
+ return accountHandle;
+ } catch (Exception e) {
+ Log.e(this, e, "getSimCallManager");
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public void setSimCallManager(PhoneAccountHandle accountHandle) {
+ synchronized (mLock) {
+ enforceModifyPermission();
+ try {
+ mPhoneAccountRegistrar.setSimCallManager(accountHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "setSimCallManager");
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getSimCallManagers() {
+ synchronized (mLock) {
+ enforceReadPermission();
+ long token = Binder.clearCallingIdentity();
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getConnectionManagerPhoneAccounts());
+ } catch (Exception e) {
+ Log.e(this, e, "getSimCallManagers");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public void registerPhoneAccount(PhoneAccount account) {
+ synchronized (mLock) {
+ try {
+ enforcePhoneAccountModificationForPackage(
+ account.getAccountHandle().getComponentName().getPackageName());
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
+ enforceRegisterCallProviderPermission();
+ }
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+ enforceRegisterSimSubscriptionPermission();
+ }
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
+ enforceRegisterConnectionManagerPermission();
+ }
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+ enforceRegisterMultiUser();
+ }
+ enforceUserHandleMatchesCaller(account.getAccountHandle());
+
+ mPhoneAccountRegistrar.registerPhoneAccount(account);
+
+ // Broadcast an intent indicating the phone account which was registered.
+ long token = Binder.clearCallingIdentity();
+ Intent intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ account.getAccountHandle());
+ Log.i(this, "Sending phone-account intent as user");
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+ PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
+ Binder.restoreCallingIdentity(token);
+ } catch (Exception e) {
+ Log.e(this, e, "registerPhoneAccount %s", account);
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
+ synchronized (mLock) {
+ try {
+ enforcePhoneAccountModificationForPackage(
+ accountHandle.getComponentName().getPackageName());
+ enforceUserHandleMatchesCaller(accountHandle);
+ mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public void clearAccounts(String packageName) {
+ synchronized (mLock) {
+ try {
+ enforcePhoneAccountModificationForPackage(packageName);
+ mPhoneAccountRegistrar
+ .clearAccounts(packageName, Binder.getCallingUserHandle());
+ } catch (Exception e) {
+ Log.e(this, e, "clearAccounts %s", packageName);
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#isVoiceMailNumber
+ */
+ @Override
+ public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) {
+ synchronized (mLock) {
+ enforceReadPermissionOrDefaultDialer();
+ try {
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return false;
+ }
+ return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#hasVoiceMailNumber
+ */
+ @Override
+ public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) {
+ synchronized (mLock) {
+ enforceReadPermissionOrDefaultDialer();
+ try {
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return false;
+ }
+
+ int subId = SubscriptionManager.getDefaultVoiceSubId();
+ if (accountHandle != null) {
+ subId = mPhoneAccountRegistrar
+ .getSubscriptionIdForPhoneAccount(accountHandle);
+ }
+ return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber(subId));
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#getLine1Number
+ */
+ @Override
+ public String getLine1Number(PhoneAccountHandle accountHandle) {
+ enforceReadPermissionOrDefaultDialer();
+ synchronized (mLock) {
+ try {
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return null;
+ }
+ int subId =
+ mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+ return getTelephonyManager().getLine1NumberForSubscriber(subId);
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#silenceRinger
+ */
+ @Override
+ public void silenceRinger() {
+ synchronized (mLock) {
+ enforceModifyPermission();
+ mCallsManager.getRinger().silence();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#getDefaultPhoneApp
+ */
+ @Override
+ public ComponentName getDefaultPhoneApp() {
+ // No need to synchronize
+ Resources resources = mContext.getResources();
+ return new ComponentName(
+ resources.getString(R.string.ui_default_package),
+ resources.getString(R.string.dialer_default_class));
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#isInCall
+ */
+ @Override
+ public boolean isInCall() {
+ synchronized (mLock) {
+ enforceReadPermission();
+ final int callState = mCallsManager.getCallState();
+ return callState == TelephonyManager.CALL_STATE_OFFHOOK
+ || callState == TelephonyManager.CALL_STATE_RINGING;
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#isRinging
+ */
+ @Override
+ public boolean isRinging() {
+ synchronized (mLock) {
+ enforceReadPermission();
+ return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
+ }
+ }
+
+ /**
+ * @see TelecomManager#getCallState
+ */
+ @Override
+ public int getCallState() {
+ synchronized (mLock) {
+ return mCallsManager.getCallState();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#endCall
+ */
+ @Override
+ public boolean endCall() {
+ synchronized (mLock) {
+ enforceModifyPermission();
+ return endCallInternal();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#acceptRingingCall
+ */
+ @Override
+ public void acceptRingingCall() {
+ synchronized (mLock) {
+ enforceModifyPermission();
+ acceptRingingCallInternal();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#showInCallScreen
+ */
+ @Override
+ public void showInCallScreen(boolean showDialpad) {
+ synchronized (mLock) {
+ enforceReadPermissionOrDefaultDialer();
+ mCallsManager.getInCallController().bringToForeground(showDialpad);
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#cancelMissedCallsNotification
+ */
+ @Override
+ public void cancelMissedCallsNotification() {
+ synchronized (mLock) {
+ enforceModifyPermissionOrDefaultDialer();
+ mCallsManager.getMissedCallNotifier().clearMissedCalls();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#handleMmi
+ */
+ @Override
+ public boolean handlePinMmi(String dialString) {
+ synchronized (mLock) {
+ enforceModifyPermissionOrDefaultDialer();
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+ long token = Binder.clearCallingIdentity();
+ boolean retval = false;
+ try {
+ retval = getTelephonyManager().handlePinMmi(dialString);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return retval;
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#handleMmi
+ */
+ @Override
+ public boolean handlePinMmiForPhoneAccount(
+ PhoneAccountHandle accountHandle,
+ String dialString) {
+ synchronized (mLock) {
+ enforceModifyPermissionOrDefaultDialer();
+
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return false;
+ }
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+ long token = Binder.clearCallingIdentity();
+ boolean retval = false;
+ try {
+ int subId = mPhoneAccountRegistrar
+ .getSubscriptionIdForPhoneAccount(accountHandle);
+ retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return retval;
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#getAdnUriForPhoneAccount
+ */
+ @Override
+ public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
+ synchronized (mLock) {
+ enforceModifyPermissionOrDefaultDialer();
+
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return null;
+ }
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+ long token = Binder.clearCallingIdentity();
+ String retval = "content://icc/adn/";
+ try {
+ long subId = mPhoneAccountRegistrar
+ .getSubscriptionIdForPhoneAccount(accountHandle);
+ retval = retval + "subId/" + subId;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return Uri.parse(retval);
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#isTtySupported
+ */
+ @Override
+ public boolean isTtySupported() {
+ synchronized (mLock) {
+ enforceReadPermission();
+ return mCallsManager.isTtySupported();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#getCurrentTtyMode
+ */
+ @Override
+ public int getCurrentTtyMode() {
+ synchronized (mLock) {
+ enforceReadPermission();
+ return mCallsManager.getCurrentTtyMode();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#addNewIncomingCall
+ */
+ @Override
+ public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ synchronized (mLock) {
+ Log.i(this, "Adding new incoming call with phoneAccountHandle %s",
+ phoneAccountHandle);
+ if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(),
+ phoneAccountHandle.getComponentName().getPackageName());
+
+ // Make sure it doesn't cross the UserHandle boundary
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
+
+ Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+ intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, true);
+ if (extras != null) {
+ intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
+ }
+ CallIntentProcessor.processIncomingCallIntent(mCallsManager, intent);
+ } else {
+ Log.w(this,
+ "Null phoneAccountHandle. Ignoring request to add new incoming call");
+ }
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#addNewUnknownCall
+ */
+ @Override
+ public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ synchronized (mLock) {
+ if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null &&
+ TelephonyUtil.isPstnComponentName(phoneAccountHandle.getComponentName())) {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(),
+ phoneAccountHandle.getComponentName().getPackageName());
+
+ // Make sure it doesn't cross the UserHandle boundary
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
+
+ Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
+ intent.setClass(mContext, CallIntentProcessor.class);
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtras(extras);
+ intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+ mContext.sendBroadcastAsUser(intent, phoneAccountHandle.getUserHandle());
+ } else {
+ Log.i(this,
+ "Null phoneAccountHandle or not initiated by Telephony. " +
+ "Ignoring request to add new unknown call.");
+ }
+ }
+ }
+
+ /**
+ * Dumps the current state of the TelecomService. Used when generating problem reports.
+ *
+ * @param fd The file descriptor.
+ * @param writer The print writer to dump the state to.
+ * @param args Optional dump arguments.
+ */
+ @Override
+ protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ writer.println("Permission Denial: can't dump TelecomService " +
+ "from from pid=" + Binder.getCallingPid() + ", uid=" +
+ Binder.getCallingUid());
+ return;
+ }
+
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ if (mCallsManager != null) {
+ pw.println("CallsManager: ");
+ pw.increaseIndent();
+ mCallsManager.dump(pw);
+ pw.decreaseIndent();
+
+ pw.println("PhoneAccountRegistrar: ");
+ pw.increaseIndent();
+ mPhoneAccountRegistrar.dump(pw);
+ pw.decreaseIndent();
+ }
+ }
+ };
+
+ private Context mContext;
+ private AppOpsManager mAppOpsManager;
+ private UserManager mUserManager;
+ private PackageManager mPackageManager;
+ private CallsManager mCallsManager;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final TelecomSystem.SyncRoot mLock;
+
+ public TelecomServiceImpl(
+ Context context,
+ CallsManager callsManager,
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ TelecomSystem.SyncRoot lock) {
+ mContext = context;
+ mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mPackageManager = mContext.getPackageManager();
+
+ mCallsManager = callsManager;
+ mLock = lock;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
+ }
+
+ public IBinder getBinder() {
+ return mBinderImpl;
+ }
+
+ //
+ // Supporting methods for the ITelecomService interface implementation.
+ //
+
+ private boolean isVisibleToCaller(PhoneAccountHandle accountHandle) {
+ if (accountHandle == null) {
+ return false;
+ }
+
+ return isVisibleToCaller(mPhoneAccountRegistrar
+ .getPhoneAccountInternal(accountHandle));
+ }
+
+ private boolean isVisibleToCaller(PhoneAccount account) {
+ if (account == null) {
+ return false;
+ }
+
+ // If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and
+ // all profiles. Only Telephony and SIP accounts should have this capability.
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+ return true;
+ }
+
+ UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle();
+ if (phoneAccountUserHandle == null) {
+ return false;
+ }
+
+ if (phoneAccountUserHandle.equals(Binder.getCallingUserHandle())) {
+ return true;
+ }
+
+ List<UserHandle> profileUserHandles;
+ if (UserHandle.getCallingUserId() == UserHandle.USER_OWNER) {
+ profileUserHandles = mUserManager.getUserProfiles();
+ } else {
+ // Otherwise, it has to be owned by the current caller's profile.
+ profileUserHandles = new ArrayList<>(1);
+ profileUserHandles.add(Binder.getCallingUserHandle());
+ }
+
+ return profileUserHandles.contains(phoneAccountUserHandle);
+ }
+
+ /**
+ * Given a list of {@link PhoneAccountHandle}s, filter them to the ones that the calling
+ * user can see.
+ *
+ * @param phoneAccountHandles Unfiltered list of account handles.
+ *
+ * @return {@link PhoneAccountHandle}s visible to the calling user and its profiles.
+ */
+ private List<PhoneAccountHandle> filterForAccountsVisibleToCaller(
+ List<PhoneAccountHandle> phoneAccountHandles) {
+ List<PhoneAccountHandle> profilePhoneAccountHandles =
+ new ArrayList<>(phoneAccountHandles.size());
+ for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) {
+ if (isVisibleToCaller(phoneAccountHandle)) {
+ profilePhoneAccountHandles.add(phoneAccountHandle);
+ }
+ }
+ return profilePhoneAccountHandles;
+ }
+
+ private boolean isCallerSystemApp() {
+ int uid = Binder.getCallingUid();
+ String[] packages = mPackageManager.getPackagesForUid(uid);
+ for (String packageName : packages) {
+ if (isPackageSystemApp(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isPackageSystemApp(String packageName) {
+ try {
+ ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName,
+ PackageManager.GET_META_DATA);
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ return false;
+ }
+
+ private void acceptRingingCallInternal() {
+ Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
+ if (call != null) {
+ call.answer(call.getVideoState());
+ }
+ }
+
+ private boolean endCallInternal() {
+ // Always operate on the foreground call if one exists, otherwise get the first call in
+ // priority order by call-state.
+ Call call = mCallsManager.getForegroundCall();
+ if (call == null) {
+ call = mCallsManager.getFirstCallWithState(
+ CallState.ACTIVE,
+ CallState.DIALING,
+ CallState.RINGING,
+ CallState.ON_HOLD);
+ }
+
+ if (call != null) {
+ if (call.getState() == CallState.RINGING) {
+ call.reject(false /* rejectWithMessage */, null);
+ } else {
+ call.disconnect();
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private void enforcePhoneAccountModificationForPackage(String packageName) {
+ // TODO: Use a new telecomm permission for this instead of reusing modify.
+
+ int result = mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+
+ // Callers with MODIFY_PHONE_STATE can use the PhoneAccount mechanism to implement
+ // built-in behavior even when PhoneAccounts are not exposed as a third-part API. They
+ // may also modify PhoneAccounts on behalf of any 'packageName'.
+
+ if (result != PackageManager.PERMISSION_GRANTED) {
+ // Other callers are only allowed to modify PhoneAccounts if the relevant system
+ // feature is enabled ...
+ enforceConnectionServiceFeature();
+ // ... and the PhoneAccounts they refer to are for their own package.
+ enforceCallingPackage(packageName);
+ }
+ }
+
+ private void enforceReadPermissionOrDefaultDialer() {
+ if (!isDefaultDialerCalling()) {
+ enforceReadPermission();
+ }
+ }
+
+ private void enforceModifyPermissionOrDefaultDialer() {
+ if (!isDefaultDialerCalling()) {
+ enforceModifyPermission();
+ }
+ }
+
+ private void enforceCallingPackage(String packageName) {
+ mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
+ }
+
+ private void enforceConnectionServiceFeature() {
+ enforceFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
+ }
+
+ private void enforceRegisterCallProviderPermission() {
+ enforcePermission(android.Manifest.permission.REGISTER_CALL_PROVIDER);
+ }
+
+ private void enforceRegisterSimSubscriptionPermission() {
+ enforcePermission(android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION);
+ }
+
+ private void enforceRegisterConnectionManagerPermission() {
+ enforcePermission(android.Manifest.permission.REGISTER_CONNECTION_MANAGER);
+ }
+
+ private void enforceReadPermission() {
+ enforcePermission(Manifest.permission.READ_PHONE_STATE);
+ }
+
+ private void enforceModifyPermission() {
+ enforcePermission(Manifest.permission.MODIFY_PHONE_STATE);
+ }
+
+ private void enforcePermission(String permission) {
+ mContext.enforceCallingOrSelfPermission(permission, null);
+ }
+
+ private void enforceRegisterMultiUser() {
+ if (!isCallerSystemApp()) {
+ throw new SecurityException("CAPABILITY_MULTI_USER is only available to system apps.");
+ }
+ }
+
+ private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) {
+ if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) {
+ throw new SecurityException("Calling UserHandle does not match PhoneAccountHandle's");
+ }
+ }
+
+ private void enforceFeature(String feature) {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(feature)) {
+ throw new UnsupportedOperationException(
+ "System does not support feature " + feature);
+ }
+ }
+
+ private boolean isCallerSimCallManager() {
+ PhoneAccountHandle accountHandle = TelecomSystem.getInstance().getPhoneAccountRegistrar()
+ .getSimCallManager();
+ if (accountHandle != null) {
+ try {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(), accountHandle.getComponentName().getPackageName());
+ return true;
+ } catch (SecurityException e) {
+ }
+ }
+ return false;
+ }
+
+ private boolean isDefaultDialerCalling() {
+ ComponentName defaultDialerComponent = getDefaultPhoneAppInternal();
+ if (defaultDialerComponent != null) {
+ try {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(), defaultDialerComponent.getPackageName());
+ return true;
+ } catch (SecurityException e) {
+ Log.e(this, e, "Could not get default dialer.");
+ }
+ }
+ return false;
+ }
+
+ private ComponentName getDefaultPhoneAppInternal() {
+ Resources resources = mContext.getResources();
+ return new ComponentName(
+ resources.getString(R.string.ui_default_package),
+ resources.getString(R.string.dialer_default_class));
+ }
+
+ private TelephonyManager getTelephonyManager() {
+ return (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ }
+}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
new file mode 100644
index 0000000..22377c9
--- /dev/null
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -0,0 +1,151 @@
+/*
+ * 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.server.telecom;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+
+/**
+ * Top-level Application class for Telecom.
+ */
+public final class TelecomSystem {
+
+ /**
+ * This interface is implemented by system-instantiated components (e.g., Services and
+ * Activity-s) that wish to use the TelecomSystem but would like to be testable. Such a
+ * component should implement the getTelecomSystem() method to return the global singleton,
+ * and use its own method. Tests can subclass the component to return a non-singleton.
+ *
+ * A refactoring goal for Telecom is to limit use of the TelecomSystem singleton to those
+ * system-instantiated components, and have all other parts of the system just take all their
+ * dependencies as explicit arguments to their constructor or other methods.
+ */
+ public interface Component {
+ TelecomSystem getTelecomSystem();
+ }
+
+
+ /**
+ * Tagging interface for the object used for synchronizing multi-threaded operations in
+ * the Telecom system.
+ */
+ public interface SyncRoot {
+ }
+
+ private static final IntentFilter USER_SWITCHED_FILTER =
+ new IntentFilter(Intent.ACTION_USER_SWITCHED);
+
+ private static TelecomSystem INSTANCE = null;
+
+ private final SyncRoot mLock = new SyncRoot() { };
+ private final MissedCallNotifier mMissedCallNotifier;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final CallsManager mCallsManager;
+ private final RespondViaSmsManager mRespondViaSmsManager;
+ private final Context mContext;
+ private final BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
+ private final CallIntentProcessor mCallIntentProcessor;
+ private final TelecomBroadcastIntentProcessor mTelecomBroadcastIntentProcessor;
+ private final TelecomServiceImpl mTelecomServiceImpl;
+ private final ContactsAsyncHelper mContactsAsyncHelper;
+
+ private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ UserHandle currentUserHandle = new UserHandle(userHandleId);
+ mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
+ }
+ };
+
+ public static TelecomSystem getInstance() {
+ return INSTANCE;
+ }
+
+ public static void setInstance(TelecomSystem instance) {
+ if (INSTANCE != null) {
+ throw new RuntimeException("Attempt to set TelecomSystem.INSTANCE twice");
+ }
+ Log.i(TelecomSystem.class, "TelecomSystem.INSTANCE being set");
+ INSTANCE = instance;
+ }
+
+ public TelecomSystem(
+ Context context,
+ MissedCallNotifier missedCallNotifier,
+ HeadsetMediaButtonFactory headsetMediaButtonFactory,
+ ProximitySensorManagerFactory proximitySensorManagerFactory,
+ InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
+ mContext = context.getApplicationContext();
+
+ mMissedCallNotifier = missedCallNotifier;
+ mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
+ mContactsAsyncHelper = new ContactsAsyncHelper(mLock);
+
+ mCallsManager = new CallsManager(
+ mContext,
+ mLock,
+ mContactsAsyncHelper,
+ mMissedCallNotifier,
+ mPhoneAccountRegistrar,
+ headsetMediaButtonFactory,
+ proximitySensorManagerFactory,
+ inCallWakeLockControllerFactory);
+
+ mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
+ mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
+
+ mContext.registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
+ mBluetoothPhoneServiceImpl = new BluetoothPhoneServiceImpl(
+ mContext, mLock, mCallsManager, mPhoneAccountRegistrar);
+ mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager);
+ mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
+ mContext, mCallsManager);
+ mTelecomServiceImpl = new TelecomServiceImpl(
+ mContext, mCallsManager, mPhoneAccountRegistrar, mLock);
+ }
+
+ @VisibleForTesting
+ public PhoneAccountRegistrar getPhoneAccountRegistrar() {
+ return mPhoneAccountRegistrar;
+ }
+
+ public BluetoothPhoneServiceImpl getBluetoothPhoneServiceImpl() {
+ return mBluetoothPhoneServiceImpl;
+ }
+
+ public CallIntentProcessor getCallIntentProcessor() {
+ return mCallIntentProcessor;
+ }
+
+ public TelecomBroadcastIntentProcessor getTelecomBroadcastIntentProcessor() {
+ return mTelecomBroadcastIntentProcessor;
+ }
+
+ public TelecomServiceImpl getTelecomServiceImpl() {
+ return mTelecomServiceImpl;
+ }
+
+ public Object getLock() {
+ return mLock;
+ }
+}
diff --git a/src/com/android/server/telecom/TelephonyUtil.java b/src/com/android/server/telecom/TelephonyUtil.java
index a130522..04f08f9 100644
--- a/src/com/android/server/telecom/TelephonyUtil.java
+++ b/src/com/android/server/telecom/TelephonyUtil.java
@@ -60,7 +60,7 @@
return pstnComponentName.equals(componentName);
}
- static boolean shouldProcessAsEmergency(Context context, Uri handle) {
+ public static boolean shouldProcessAsEmergency(Context context, Uri handle) {
return handle != null && PhoneNumberUtils.isPotentialLocalEmergencyNumber(
context, handle.getSchemeSpecificPart());
}
diff --git a/src/com/android/server/telecom/components/BluetoothPhoneService.java b/src/com/android/server/telecom/components/BluetoothPhoneService.java
new file mode 100644
index 0000000..c5e195c
--- /dev/null
+++ b/src/com/android/server/telecom/components/BluetoothPhoneService.java
@@ -0,0 +1,42 @@
+/*
+ * 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.server.telecom.components;
+
+import com.android.server.telecom.TelecomSystem;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
+ * and accepts call-related commands to perform on behalf of the BT device.
+ */
+public final class BluetoothPhoneService extends Service implements TelecomSystem.Component {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ synchronized (getTelecomSystem().getLock()) {
+ return getTelecomSystem().getBluetoothPhoneServiceImpl().getBinder();
+ }
+ }
+
+ @Override
+ public TelecomSystem getTelecomSystem() {
+ return TelecomSystem.getInstance();
+ }
+}
diff --git a/src/com/android/server/telecom/ErrorDialogActivity.java b/src/com/android/server/telecom/components/ErrorDialogActivity.java
similarity index 97%
rename from src/com/android/server/telecom/ErrorDialogActivity.java
rename to src/com/android/server/telecom/components/ErrorDialogActivity.java
index a669f10..858682e 100644
--- a/src/com/android/server/telecom/ErrorDialogActivity.java
+++ b/src/com/android/server/telecom/components/ErrorDialogActivity.java
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.server.telecom;
+package com.android.server.telecom.components;
+
+import com.android.server.telecom.Log;
+import com.android.server.telecom.R;
import android.app.Activity;
import android.app.AlertDialog;
diff --git a/src/com/android/server/telecom/PhoneAccountBroadcastReceiver.java b/src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
similarity index 96%
rename from src/com/android/server/telecom/PhoneAccountBroadcastReceiver.java
rename to src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
index 9634eda..7737cd8 100644
--- a/src/com/android/server/telecom/PhoneAccountBroadcastReceiver.java
+++ b/src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
@@ -14,7 +14,9 @@
* limitations under the License
*/
-package com.android.server.telecom;
+package com.android.server.telecom.components;
+
+import com.android.server.telecom.PhoneAccountRegistrar;
import android.content.BroadcastReceiver;
import android.content.Context;
diff --git a/src/com/android/server/telecom/components/PrimaryCallReceiver.java b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
new file mode 100644
index 0000000..5198862
--- /dev/null
+++ b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
@@ -0,0 +1,28 @@
+package com.android.server.telecom.components;
+
+import com.android.server.telecom.TelecomSystem;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Single point of entry for all outgoing and incoming calls. {@link UserCallIntentProcessor} serves
+ * as a trampoline that captures call intents for individual users and forwards it to
+ * the {@link PrimaryCallReceiver} which interacts with the rest of Telecom, both of which run only as
+ * the primary user.
+ */
+public class PrimaryCallReceiver extends BroadcastReceiver implements TelecomSystem.Component {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (getTelecomSystem().getLock()) {
+ getTelecomSystem().getCallIntentProcessor().processIntent(intent);
+ }
+ }
+
+ @Override
+ public TelecomSystem getTelecomSystem() {
+ return TelecomSystem.getInstance();
+ }
+}
diff --git a/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java b/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
new file mode 100644
index 0000000..e0ca3c4
--- /dev/null
+++ b/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.telecom.components;
+
+import com.android.server.telecom.TelecomSystem;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Handles miscellaneous Telecom broadcast intents. This should be visible from outside, but
+ * should not be in the "exported" state.
+ */
+public final class TelecomBroadcastReceiver
+ extends BroadcastReceiver implements TelecomSystem.Component {
+
+ /** {@inheritDoc} */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (getTelecomSystem().getLock()) {
+ getTelecomSystem().getTelecomBroadcastIntentProcessor().processIntent(intent);
+ }
+ }
+
+ @Override
+ public TelecomSystem getTelecomSystem() {
+ return TelecomSystem.getInstance();
+ }
+
+}
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
new file mode 100644
index 0000000..49b4aa4
--- /dev/null
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -0,0 +1,98 @@
+/*
+ * 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.server.telecom.components;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.HeadsetMediaButton;
+import com.android.server.telecom.HeadsetMediaButtonFactory;
+import com.android.server.telecom.InCallWakeLockControllerFactory;
+import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.InCallWakeLockController;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.ProximitySensorManager;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.ui.MissedCallNotifierImpl;
+
+/**
+ * Implementation of the ITelecom interface.
+ */
+public class TelecomService extends Service implements TelecomSystem.Component {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(this, "onBind");
+ initializeTelecomSystem(this);
+ synchronized (getTelecomSystem().getLock()) {
+ return getTelecomSystem().getTelecomServiceImpl().getBinder();
+ }
+ }
+
+ /**
+ * This method is to be called by components (Activitys, Services, ...) to initialize the
+ * Telecom singleton. It should only be called on the main thread. As such, it is atomic
+ * and needs no synchronization -- it will either perform its initialization, after which
+ * the {@link TelecomSystem#getInstance()} will be initialized, or some other invocation of
+ * this method on the main thread will have happened strictly prior to it, and this method
+ * will be a benign no-op.
+ *
+ * @param context
+ */
+ static void initializeTelecomSystem(Context context) {
+ if (TelecomSystem.getInstance() == null) {
+ TelecomSystem.setInstance(
+ new TelecomSystem(
+ context,
+ new MissedCallNotifierImpl(context.getApplicationContext()),
+ new HeadsetMediaButtonFactory() {
+ @Override
+ public HeadsetMediaButton create(Context context,
+ CallsManager callsManager) {
+ return new HeadsetMediaButton(context, callsManager);
+ }
+ },
+ new ProximitySensorManagerFactory() {
+ @Override
+ public ProximitySensorManager create(
+ Context context,
+ CallsManager callsManager) {
+ return new ProximitySensorManager(context, callsManager);
+ }
+ },
+ new InCallWakeLockControllerFactory() {
+ @Override
+ public InCallWakeLockController create(Context context,
+ CallsManager callsManager) {
+ return new InCallWakeLockController(context, callsManager);
+ }
+ }));
+ }
+ if (BluetoothAdapter.getDefaultAdapter() != null) {
+ context.startService(new Intent(context, BluetoothPhoneService.class));
+ }
+ }
+
+ @Override
+ public TelecomSystem getTelecomSystem() {
+ return TelecomSystem.getInstance();
+ }
+}
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
new file mode 100644
index 0000000..ccff468
--- /dev/null
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 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.server.telecom.components;
+
+import com.android.server.telecom.CallIntentProcessor;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.TelecomSystem;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.TelecomManager;
+
+// TODO: Needed for move to system service: import com.android.internal.R;
+
+/**
+ * Activity that handles system CALL actions and forwards them to {@link CallIntentProcessor}.
+ * Handles all three CALL action types: CALL, CALL_PRIVILEGED, and CALL_EMERGENCY.
+ *
+ * Pre-L, the only way apps were were allowed to make outgoing emergency calls was the
+ * ACTION_CALL_PRIVILEGED action (which requires the system only CALL_PRIVILEGED permission).
+ *
+ * In L, any app that has the CALL_PRIVILEGED permission can continue to make outgoing emergency
+ * calls via ACTION_CALL_PRIVILEGED.
+ *
+ * In addition, the default dialer (identified via
+ * {@link TelecomManager#getDefaultPhoneApp()} will also be granted the ability to
+ * make emergency outgoing calls using the CALL action. In order to do this, it must call
+ * startActivityForResult on the CALL intent to allow its package name to be passed to
+ * {@link UserCallActivity}. Calling startActivity will continue to work on all non-emergency
+ * numbers just like it did pre-L.
+ */
+public class UserCallActivity extends Activity implements TelecomSystem.Component {
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ // TODO: Figure out if there is something to restore from bundle.
+ // See OutgoingCallBroadcaster in services/Telephony for more.
+ Intent intent = getIntent();
+ verifyCallAction(intent);
+ new UserCallIntentProcessor(this).processIntent(getIntent(), getCallingPackage());
+ finish();
+ }
+
+ private void verifyCallAction(Intent intent) {
+ if (getClass().getName().equals(intent.getComponent().getClassName())) {
+ // If we were launched directly from the CallActivity, not one of its more privileged
+ // aliases, then make sure that only the non-privileged actions are allowed.
+ if (!Intent.ACTION_CALL.equals(intent.getAction())) {
+ Log.w(this, "Attempt to deliver non-CALL action; forcing to CALL");
+ intent.setAction(Intent.ACTION_CALL);
+ }
+ }
+ }
+
+ @Override
+ public TelecomSystem getTelecomSystem() {
+ return TelecomSystem.getInstance();
+ }
+}
diff --git a/src/com/android/server/telecom/CallActivity.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
similarity index 62%
rename from src/com/android/server/telecom/CallActivity.java
rename to src/com/android/server/telecom/components/UserCallIntentProcessor.java
index 37e24f6..97a3497 100644
--- a/src/com/android/server/telecom/CallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -14,21 +14,21 @@
* limitations under the License.
*/
-package com.android.server.telecom;
+package com.android.server.telecom.components;
-import android.app.Activity;
+import com.android.server.telecom.CallIntentProcessor;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelephonyUtil;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.os.Bundle;
-import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.widget.Toast;
@@ -36,7 +36,7 @@
// TODO: Needed for move to system service: import com.android.internal.R;
/**
- * Activity that handles system CALL actions and forwards them to {@link CallReceiver}.
+ * Handles system CALL actions and forwards them to {@link CallIntentProcessor}.
* Handles all three CALL action types: CALL, CALL_PRIVILEGED, and CALL_EMERGENCY.
*
* Pre-L, the only way apps were were allowed to make outgoing emergency calls was the
@@ -49,23 +49,15 @@
* {@link android.telecom.TelecomManager#getDefaultPhoneApp()} will also be granted the ability to
* make emergency outgoing calls using the CALL action. In order to do this, it must call
* startActivityForResult on the CALL intent to allow its package name to be passed to
- * {@link CallActivity}. Calling startActivity will continue to work on all non-emergency numbers
- * just like it did pre-L.
+ * {@link UserCallIntentProcessor}. Calling startActivity will continue to work on all
+ * non-emergency numbers just like it did pre-L.
*/
-public class CallActivity extends Activity {
+public class UserCallIntentProcessor {
- @Override
- protected void onCreate(Bundle bundle) {
- super.onCreate(bundle);
+ private final Context mContext;
- // TODO: Figure out if there is something to restore from bundle.
- // See OutgoingCallBroadcaster in services/Telephony for more.
-
- processIntent(getIntent());
-
- // This activity does not have associated UI, so close.
- finish();
- Log.d(this, "onCreate: end");
+ public UserCallIntentProcessor(Context context) {
+ mContext = context;
}
/**
@@ -73,34 +65,22 @@
*
* @param intent The intent.
*/
- private void processIntent(Intent intent) {
+ public void processIntent(Intent intent, String callingPackageName) {
// Ensure call intents are not processed on devices that are not capable of calling.
if (!isVoiceCapable()) {
return;
}
- verifyCallAction(intent);
String action = intent.getAction();
if (Intent.ACTION_CALL.equals(action) ||
Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
Intent.ACTION_CALL_EMERGENCY.equals(action)) {
- processOutgoingCallIntent(intent);
+ processOutgoingCallIntent(intent, callingPackageName);
}
}
- private void verifyCallAction(Intent intent) {
- if (CallActivity.class.getName().equals(intent.getComponent().getClassName())) {
- // If we were launched directly from the CallActivity, not one of its more privileged
- // aliases, then make sure that only the non-privileged actions are allowed.
- if (!Intent.ACTION_CALL.equals(intent.getAction())) {
- Log.w(this, "Attempt to deliver non-CALL action; forcing to CALL");
- intent.setAction(Intent.ACTION_CALL);
- }
- }
- }
-
- private void processOutgoingCallIntent(Intent intent) {
+ private void processOutgoingCallIntent(Intent intent, String callingPackageName) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
@@ -110,33 +90,34 @@
PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
}
- UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+ UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS)
- && !TelephonyUtil.shouldProcessAsEmergency(this, handle)) {
+ && !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
// Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
// restriction.
- Toast.makeText(this, getResources().getString(R.string.outgoing_call_not_allowed),
+ Toast.makeText(
+ mContext,
+ mContext.getResources().getString(R.string.outgoing_call_not_allowed),
Toast.LENGTH_SHORT).show();
Log.d(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
+ "restriction");
return;
}
- intent.putExtra(CallReceiver.KEY_IS_DEFAULT_DIALER, isDefaultDialer());
+ intent.putExtra(CallIntentProcessor.KEY_IS_DEFAULT_DIALER, isDefaultDialer(callingPackageName));
sendBroadcastToReceiver(intent);
}
- private boolean isDefaultDialer() {
- final String packageName = getCallingPackage();
- if (TextUtils.isEmpty(packageName)) {
+ private boolean isDefaultDialer(String callingPackageName) {
+ if (TextUtils.isEmpty(callingPackageName)) {
return false;
}
final TelecomManager telecomManager =
- (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+ (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
final ComponentName defaultPhoneApp = telecomManager.getDefaultPhoneApp();
return (defaultPhoneApp != null
- && TextUtils.equals(defaultPhoneApp.getPackageName(), packageName));
+ && TextUtils.equals(defaultPhoneApp.getPackageName(), callingPackageName));
}
/**
@@ -145,7 +126,7 @@
* @return {@code True} if the device is voice-capable.
*/
private boolean isVoiceCapable() {
- return getApplicationContext().getResources().getBoolean(
+ return mContext.getApplicationContext().getResources().getBoolean(
com.android.internal.R.bool.config_voice_capable);
}
@@ -153,11 +134,11 @@
* Trampolines the intent to the broadcast receiver that runs only as the primary user.
*/
private boolean sendBroadcastToReceiver(Intent intent) {
- intent.putExtra(CallReceiver.KEY_IS_INCOMING_CALL, false);
+ intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.setClass(this, CallReceiver.class);
+ intent.setClass(mContext, PrimaryCallReceiver.class);
Log.d(this, "Sending broadcast as user to CallReceiver");
- sendBroadcastAsUser(intent, UserHandle.OWNER);
+ mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
return true;
}
}
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
new file mode 100644
index 0000000..9022d27
--- /dev/null
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright 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.server.telecom.ui;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.Constants;
+import com.android.server.telecom.ContactsAsyncHelper;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.MissedCallNotifier;
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.TelecomSystem;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.AsyncQueryHandler;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.UserHandle;
+import android.provider.CallLog.Calls;
+import android.telecom.CallState;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telephony.PhoneNumberUtils;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+
+// TODO: Needed for move to system service: import com.android.internal.R;
+
+/**
+ * Creates a notification for calls that the user missed (neither answered nor rejected).
+ *
+ * TODO: Make TelephonyManager.clearMissedCalls call into this class.
+ *
+ * TODO: Reduce dependencies in this implementation; remove the need to create a new Call
+ * simply to look up caller metadata, and if possible, make it unnecessary to get a
+ * direct reference to the CallsManager. Try to make this class simply handle the UI
+ * and Android-framework entanglements of missed call notification.
+ */
+public class MissedCallNotifierImpl extends CallsManagerListenerBase implements MissedCallNotifier {
+
+ private static final String[] CALL_LOG_PROJECTION = new String[] {
+ Calls._ID,
+ Calls.NUMBER,
+ Calls.NUMBER_PRESENTATION,
+ Calls.DATE,
+ Calls.DURATION,
+ Calls.TYPE,
+ };
+
+ private static final int CALL_LOG_COLUMN_ID = 0;
+ private static final int CALL_LOG_COLUMN_NUMBER = 1;
+ private static final int CALL_LOG_COLUMN_NUMBER_PRESENTATION = 2;
+ private static final int CALL_LOG_COLUMN_DATE = 3;
+ private static final int CALL_LOG_COLUMN_DURATION = 4;
+ private static final int CALL_LOG_COLUMN_TYPE = 5;
+
+ private static final int MISSED_CALL_NOTIFICATION_ID = 1;
+
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+
+ // Used to track the number of missed calls.
+ private int mMissedCallCount = 0;
+
+ public MissedCallNotifierImpl(Context context) {
+ mContext = context;
+ mNotificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+ if (oldState == CallState.RINGING && newState == CallState.DISCONNECTED &&
+ call.getDisconnectCause().getCode() == DisconnectCause.MISSED) {
+ showMissedCallNotification(call);
+ }
+ }
+
+ /** Clears missed call notification and marks the call log's missed calls as read. */
+ public void clearMissedCalls() {
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ // Clear the list of new missed calls from the call log.
+ ContentValues values = new ContentValues();
+ values.put(Calls.NEW, 0);
+ values.put(Calls.IS_READ, 1);
+ StringBuilder where = new StringBuilder();
+ where.append(Calls.NEW);
+ where.append(" = 1 AND ");
+ where.append(Calls.TYPE);
+ where.append(" = ?");
+ mContext.getContentResolver().update(Calls.CONTENT_URI, values, where.toString(),
+ new String[]{ Integer.toString(Calls.MISSED_TYPE) });
+ }
+ });
+ cancelMissedCallNotification();
+ }
+
+ /**
+ * Create a system notification for the missed call.
+ *
+ * @param call The missed call.
+ */
+ public void showMissedCallNotification(Call call) {
+ mMissedCallCount++;
+
+ final int titleResId;
+ final String expandedText; // The text in the notification's line 1 and 2.
+
+ // Display the first line of the notification:
+ // 1 missed call: <caller name || handle>
+ // More than 1 missed call: <number of calls> + "missed calls"
+ if (mMissedCallCount == 1) {
+ titleResId = R.string.notification_missedCallTitle;
+ expandedText = getNameForCall(call);
+ } else {
+ titleResId = R.string.notification_missedCallsTitle;
+ expandedText =
+ mContext.getString(R.string.notification_missedCallsMsg, mMissedCallCount);
+ }
+
+ // Create the notification.
+ Notification.Builder builder = new Notification.Builder(mContext);
+ builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
+ .setColor(mContext.getResources().getColor(R.color.theme_color))
+ .setWhen(call.getCreationTimeMillis())
+ .setContentTitle(mContext.getText(titleResId))
+ .setContentText(expandedText)
+ .setContentIntent(createCallLogPendingIntent())
+ .setAutoCancel(true)
+ .setDeleteIntent(createClearMissedCallsPendingIntent());
+
+ Uri handleUri = call.getHandle();
+ String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();
+
+ // Add additional actions when there is only 1 missed call, like call-back and SMS.
+ if (mMissedCallCount == 1) {
+ Log.d(this, "Add actions with number %s.", Log.piiHandle(handle));
+
+ if (!TextUtils.isEmpty(handle)
+ && !TextUtils.equals(handle, mContext.getString(R.string.handle_restricted))) {
+ builder.addAction(R.drawable.stat_sys_phone_call,
+ mContext.getString(R.string.notification_missedCall_call_back),
+ createCallBackPendingIntent(handleUri));
+
+ builder.addAction(R.drawable.ic_text_holo_dark,
+ mContext.getString(R.string.notification_missedCall_message),
+ createSendSmsFromNotificationPendingIntent(handleUri));
+ }
+
+ Bitmap photoIcon = call.getPhotoIcon();
+ if (photoIcon != null) {
+ builder.setLargeIcon(photoIcon);
+ } else {
+ Drawable photo = call.getPhoto();
+ if (photo != null && photo instanceof BitmapDrawable) {
+ builder.setLargeIcon(((BitmapDrawable) photo).getBitmap());
+ }
+ }
+ } else {
+ Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle),
+ mMissedCallCount);
+ }
+
+ Notification notification = builder.build();
+ configureLedOnNotification(notification);
+
+ Log.i(this, "Adding missed call notification for %s.", call);
+ mNotificationManager.notifyAsUser(
+ null /* tag */ , MISSED_CALL_NOTIFICATION_ID, notification, UserHandle.CURRENT);
+ }
+
+ /** Cancels the "missed call" notification. */
+ private void cancelMissedCallNotification() {
+ // Reset the number of missed calls to 0.
+ mMissedCallCount = 0;
+ mNotificationManager.cancel(MISSED_CALL_NOTIFICATION_ID);
+ }
+
+ /**
+ * Returns the name to use in the missed call notification.
+ */
+ private String getNameForCall(Call call) {
+ String handle = call.getHandle() == null ? null : call.getHandle().getSchemeSpecificPart();
+ String name = call.getName();
+
+ if (!TextUtils.isEmpty(name) && TextUtils.isGraphic(name)) {
+ return name;
+ } else if (!TextUtils.isEmpty(handle)) {
+ // A handle should always be displayed LTR using {@link BidiFormatter} regardless of the
+ // content of the rest of the notification.
+ // TODO: Does this apply to SIP addresses?
+ BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ return bidiFormatter.unicodeWrap(handle, TextDirectionHeuristics.LTR);
+ } else {
+ // Use "unknown" if the call is unidentifiable.
+ return mContext.getString(R.string.unknown);
+ }
+ }
+
+ /**
+ * Creates a new pending intent that sends the user to the call log.
+ *
+ * @return The pending intent.
+ */
+ private PendingIntent createCallLogPendingIntent() {
+ Intent intent = new Intent(Intent.ACTION_VIEW, null);
+ intent.setType(Calls.CONTENT_TYPE);
+
+ TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext);
+ taskStackBuilder.addNextIntent(intent);
+
+ return taskStackBuilder.getPendingIntent(0, 0);
+ }
+
+ /**
+ * Creates an intent to be invoked when the missed call notification is cleared.
+ */
+ private PendingIntent createClearMissedCallsPendingIntent() {
+ return createTelecomPendingIntent(
+ TelecomBroadcastIntentProcessor.ACTION_CLEAR_MISSED_CALLS, null);
+ }
+
+ /**
+ * Creates an intent to be invoked when the user opts to "call back" from the missed call
+ * notification.
+ *
+ * @param handle The handle to call back.
+ */
+ private PendingIntent createCallBackPendingIntent(Uri handle) {
+ return createTelecomPendingIntent(
+ TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, handle);
+ }
+
+ /**
+ * Creates an intent to be invoked when the user opts to "send sms" from the missed call
+ * notification.
+ */
+ private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle) {
+ return createTelecomPendingIntent(
+ TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION,
+ Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null));
+ }
+
+ /**
+ * Creates generic pending intent from the specified parameters to be received by
+ * {@link TelecomBroadcastIntentProcessor}.
+ *
+ * @param action The intent action.
+ * @param data The intent data.
+ */
+ private PendingIntent createTelecomPendingIntent(String action, Uri data) {
+ Intent intent = new Intent(action, data, mContext, TelecomBroadcastIntentProcessor.class);
+ return PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ }
+
+ /**
+ * Configures a notification to emit the blinky notification light.
+ */
+ private void configureLedOnNotification(Notification notification) {
+ notification.flags |= Notification.FLAG_SHOW_LIGHTS;
+ notification.defaults |= Notification.DEFAULT_LIGHTS;
+ }
+
+ /**
+ * Adds the missed call notification on startup if there are unread missed calls.
+ */
+ @Override
+ public void updateOnStartup(
+ final TelecomSystem.SyncRoot lock,
+ final CallsManager callsManager,
+ final ContactsAsyncHelper contactsAsyncHelper) {
+ Log.d(this, "updateOnStartup()...");
+
+ // instantiate query handler
+ AsyncQueryHandler queryHandler = new AsyncQueryHandler(mContext.getContentResolver()) {
+ @Override
+ protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ Log.d(MissedCallNotifierImpl.this, "onQueryComplete()...");
+ if (cursor != null) {
+ try {
+ while (cursor.moveToNext()) {
+ // Get data about the missed call from the cursor
+ final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER);
+ final int presentation =
+ cursor.getInt(CALL_LOG_COLUMN_NUMBER_PRESENTATION);
+ final long date = cursor.getLong(CALL_LOG_COLUMN_DATE);
+
+ final Uri handle;
+ if (presentation != Calls.PRESENTATION_ALLOWED
+ || TextUtils.isEmpty(handleString)) {
+ handle = null;
+ } else {
+ handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(handleString) ?
+ PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
+ handleString, null);
+ }
+
+ synchronized (lock) {
+
+ // Convert the data to a call object
+ Call call = new Call(mContext, callsManager,
+ null, contactsAsyncHelper, null, null, null, null, true,
+ false);
+ call.setDisconnectCause(
+ new DisconnectCause(DisconnectCause.MISSED));
+ call.setState(CallState.DISCONNECTED);
+ call.setCreationTimeMillis(date);
+
+ // Listen for the update to the caller information before posting
+ // the notification so that we have the contact info and photo.
+ call.addListener(new Call.ListenerBase() {
+ @Override
+ public void onCallerInfoChanged(Call call) {
+ call.removeListener(
+ this); // No longer need to listen to call
+ // changes after the contact info
+ // is retrieved.
+ showMissedCallNotification(call);
+ }
+ });
+ // Set the handle here because that is what triggers the contact
+ // info query.
+ call.setHandle(handle, presentation);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ };
+
+ // setup query spec, look for all Missed calls that are new.
+ StringBuilder where = new StringBuilder("type=");
+ where.append(Calls.MISSED_TYPE);
+ where.append(" AND new=1");
+
+ // start the query
+ queryHandler.startQuery(0, null, Calls.CONTENT_URI, CALL_LOG_PROJECTION,
+ where.toString(), null, Calls.DEFAULT_SORT_ORDER);
+ }
+}
diff --git a/tests/src/com/android/server/telecom/testapps/CallServiceNotifier.java b/tests/src/com/android/server/telecom/testapps/CallServiceNotifier.java
index 23e8222..d25cc2d 100644
--- a/tests/src/com/android/server/telecom/testapps/CallServiceNotifier.java
+++ b/tests/src/com/android/server/telecom/testapps/CallServiceNotifier.java
@@ -109,7 +109,8 @@
"TelecomTestApp Call Provider")
.setAddress(Uri.parse("tel:555-TEST"))
.setSubscriptionAddress(Uri.parse("tel:555-TEST"))
- .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING)
.setIcon(context, R.drawable.stat_sys_phone_call, Color.RED)
.setHighlightColor(Color.RED)
.setShortDescription("a short description for the call provider")
@@ -124,7 +125,8 @@
.setAddress(Uri.parse("tel:555-TSIM"))
.setSubscriptionAddress(Uri.parse("tel:555-TSIM"))
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
- PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING)
.setIcon(context, R.drawable.stat_sys_phone_call, Color.GREEN)
.setHighlightColor(Color.GREEN)
.setShortDescription("a short description for the sim subscription")
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextHolder.java b/tests/src/com/android/server/telecom/tests/ComponentContextHolder.java
new file mode 100644
index 0000000..692da37
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextHolder.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telecom.IInCallService;
+
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.os.IInterface;
+import android.os.UserHandle;
+import android.telecom.ConnectionService;
+import android.telecom.InCallService;
+import android.telecom.PhoneAccount;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContext;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+/**
+ * Controls a test {@link Context} as would be provided by the Android framework to an
+ * {@code Activity}, {@code Service} or other system-instantiated component.
+ *
+ * The {@link Context} created by this object is "hollow" but its {@code applicationContext}
+ * property points to an application context implementing all the nontrivial functionality.
+ */
+public class ComponentContextHolder implements TestDoubleHolder<Context> {
+
+ public class TestApplicationContext extends MockContext {
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public File getFilesDir() {
+ try {
+ return File.createTempFile("temp", "temp").getParentFile();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean bindServiceAsUser(
+ Intent serviceIntent,
+ ServiceConnection connection,
+ int flags,
+ UserHandle userHandle) {
+ // TODO: Implement "as user" functionality
+ return bindService(serviceIntent, connection, flags);
+ }
+
+ @Override
+ public boolean bindService(
+ Intent serviceIntent,
+ ServiceConnection connection,
+ int flags) {
+ if (mServiceByServiceConnection.containsKey(connection)) {
+ throw new RuntimeException("ServiceConnection already bound: " + connection);
+ }
+ IInterface service = mServiceByComponentName.get(serviceIntent.getComponent());
+ if (service == null) {
+ throw new RuntimeException("ServiceConnection not found: "
+ + serviceIntent.getComponent());
+ }
+ mServiceByServiceConnection.put(connection, service);
+ connection.onServiceConnected(serviceIntent.getComponent(), service.asBinder());
+ return true;
+ }
+
+ @Override
+ public void unbindService(
+ ServiceConnection connection) {
+ IInterface service = mServiceByServiceConnection.remove(connection);
+ if (service == null) {
+ throw new RuntimeException("ServiceConnection not found: " + connection);
+ }
+ connection.onServiceDisconnected(mComponentNameByService.get(service));
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ switch (name) {
+ case Context.AUDIO_SERVICE:
+ return mAudioManager;
+ case Context.TELEPHONY_SERVICE:
+ return mTelephonyManager;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public Resources getResources() {
+ return mResources;
+ }
+
+ @Override
+ public String getOpPackageName() {
+ return "test";
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return new ContentResolver(this) {
+ @Override
+ protected IContentProvider acquireProvider(Context c, String name) {
+ return null;
+ }
+
+ @Override
+ public boolean releaseProvider(IContentProvider icp) {
+ return false;
+ }
+
+ @Override
+ protected IContentProvider acquireUnstableProvider(Context c, String name) {
+ return null;
+ }
+
+ @Override
+ public boolean releaseUnstableProvider(IContentProvider icp) {
+ return false;
+ }
+
+ @Override
+ public void unstableProviderDied(IContentProvider icp) {
+
+ }
+ };
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ // TODO -- this is called by WiredHeadsetManager!!!
+ return null;
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ // TODO -- need to ensure this is captured
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission) {
+ // TODO -- need to ensure this is captured
+ }
+ };
+
+ private final Multimap<String, ComponentName> mComponentNamesByAction =
+ ArrayListMultimap.create();
+ private final Map<ComponentName, IInterface> mServiceByComponentName = new HashMap<>();
+ private final Map<ComponentName, ServiceInfo> mServiceInfoByComponentName = new HashMap<>();
+ private final Map<IInterface, ComponentName> mComponentNameByService = new HashMap<>();
+ private final Map<ServiceConnection, IInterface> mServiceByServiceConnection = new HashMap<>();
+
+ private final Context mContext = new MockContext() {
+ @Override
+ public Context getApplicationContext() {
+ return mApplicationContextSpy;
+ }
+ };
+
+ // The application context is the most important object this class provides to the system
+ // under test.
+ private final Context mApplicationContext = new TestApplicationContext();
+
+ // We then create a spy on the application context allowing standard Mockito-style
+ // when(...) logic to be used to add specific little responses where needed.
+
+ private final Context mApplicationContextSpy = Mockito.spy(mApplicationContext);
+ private final PackageManager mPackageManager = Mockito.mock(PackageManager.class);
+ private final AudioManager mAudioManager = Mockito.mock(AudioManager.class);
+ private final TelephonyManager mTelephonyManager = Mockito.mock(TelephonyManager.class);
+ private final Resources mResources = Mockito.mock(Resources.class);
+ private final Configuration mResourceConfiguration = new Configuration();
+
+ public ComponentContextHolder() {
+ MockitoAnnotations.initMocks(this);
+ when(mResources.getConfiguration()).thenReturn(mResourceConfiguration);
+ mResourceConfiguration.setLocale(Locale.TAIWAN);
+
+ // TODO: Move into actual tests
+ when(mAudioManager.isWiredHeadsetOn()).thenReturn(false);
+
+ doAnswer(new Answer<List<ResolveInfo>>() {
+ @Override
+ public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
+ return doQueryIntentServices(
+ (Intent) invocation.getArguments()[0],
+ (Integer) invocation.getArguments()[1]);
+ }
+ }).when(mPackageManager).queryIntentServices((Intent) any(), anyInt());
+
+ doAnswer(new Answer<List<ResolveInfo>>() {
+ @Override
+ public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
+ return doQueryIntentServices(
+ (Intent) invocation.getArguments()[0],
+ (Integer) invocation.getArguments()[1]);
+ }
+ }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt());
+
+ when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(1);
+ }
+
+ @Override
+ public Context getTestDouble() {
+ return mContext;
+ }
+
+ public void addConnectionService(
+ ComponentName componentName,
+ IConnectionService service)
+ throws Exception {
+ addService(ConnectionService.SERVICE_INTERFACE, componentName, service);
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.permission = android.Manifest.permission.BIND_CONNECTION_SERVICE;
+ serviceInfo.packageName = componentName.getPackageName();
+ serviceInfo.name = componentName.getClassName();
+ mServiceInfoByComponentName.put(componentName, serviceInfo);
+ }
+
+ public void addInCallService(
+ ComponentName componentName,
+ IInCallService service)
+ throws Exception {
+ addService(InCallService.SERVICE_INTERFACE, componentName, service);
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.permission = android.Manifest.permission.BIND_INCALL_SERVICE;
+ serviceInfo.packageName = componentName.getPackageName();
+ serviceInfo.name = componentName.getClassName();
+ mServiceInfoByComponentName.put(componentName, serviceInfo);
+ }
+
+ public void putResource(int id, String value) {
+ when(mResources.getString(eq(id))).thenReturn(value);
+ }
+
+ private void addService(String action, ComponentName name, IInterface service) {
+ mComponentNamesByAction.put(action, name);
+ mServiceByComponentName.put(name, service);
+ mComponentNameByService.put(service, name);
+ }
+
+ private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
+ List<ResolveInfo> result = new ArrayList<>();
+ for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = mServiceInfoByComponentName.get(componentName);
+ result.add(resolveInfo);
+ }
+ return result;
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceHolder.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceHolder.java
new file mode 100644
index 0000000..668d6c2
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceHolder.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telecom.IConnectionServiceAdapter;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telecom.AudioState;
+import android.telecom.ConnectionRequest;
+import android.telecom.PhoneAccountHandle;
+
+/**
+ * Controls a test {@link IConnectionService} as would be provided by a source of connectivity
+ * to the Telecom framework.
+ */
+public class ConnectionServiceHolder implements TestDoubleHolder<IConnectionService> {
+
+ private final IConnectionService mConnectionService = new IConnectionService.Stub() {
+ @Override
+ public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter)
+ throws RemoteException {
+ }
+
+ @Override
+ public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void createConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+ String callId,
+ ConnectionRequest request, boolean isIncoming, boolean isUnknown)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void abort(String callId) throws RemoteException {
+
+ }
+
+ @Override
+ public void answerVideo(String callId, int videoState) throws RemoteException {
+
+ }
+
+ @Override
+ public void answer(String callId) throws RemoteException {
+
+ }
+
+ @Override
+ public void reject(String callId) throws RemoteException {
+
+ }
+
+ @Override
+ public void disconnect(String callId) throws RemoteException {
+
+ }
+
+ @Override
+ public void hold(String callId) throws RemoteException {
+
+ }
+
+ @Override
+ public void unhold(String callId) throws RemoteException {
+
+ }
+
+ @Override
+ public void onAudioStateChanged(String activeCallId, AudioState audioState)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void playDtmfTone(String callId, char digit) throws RemoteException {
+
+ }
+
+ @Override
+ public void stopDtmfTone(String callId) throws RemoteException {
+
+ }
+
+ @Override
+ public void conference(String conferenceCallId, String callId) throws RemoteException {
+
+ }
+
+ @Override
+ public void splitFromConference(String callId) throws RemoteException {
+
+ }
+
+ @Override
+ public void mergeConference(String conferenceCallId) throws RemoteException {
+
+ }
+
+ @Override
+ public void swapConference(String conferenceCallId) throws RemoteException {
+
+ }
+
+ @Override
+ public void onPostDialContinue(String callId, boolean proceed) throws RemoteException {
+
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+
+ @Override
+ public IConnectionService getTestDouble() {
+ return mConnectionService;
+ }
+
+}
diff --git a/tests/src/com/android/server/telecom/tests/MockitoHelper.java b/tests/src/com/android/server/telecom/tests/MockitoHelper.java
new file mode 100644
index 0000000..32b91f9
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MockitoHelper.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * Helper for Mockito-based test cases.
+ */
+public final class MockitoHelper {
+ private static final String TAG = "MockitoHelper";
+ private static final String DEXCACHE = "dexmaker.dexcache";
+
+ private ClassLoader mOriginalClassLoader;
+ private Thread mContextThread;
+
+ /**
+ * Creates a new helper, which in turn will set the context classloader so
+ * it can load Mockito resources.
+ *
+ * @param packageClass test case class
+ */
+ public void setUp(Context context, Class<?> packageClass) throws Exception {
+ // makes a copy of the context classloader
+ mContextThread = Thread.currentThread();
+ mOriginalClassLoader = mContextThread.getContextClassLoader();
+ ClassLoader newClassLoader = packageClass.getClassLoader();
+ Log.v(TAG, "Changing context classloader from " + mOriginalClassLoader
+ + " to " + newClassLoader);
+ mContextThread.setContextClassLoader(newClassLoader);
+ System.setProperty(DEXCACHE, context.getCacheDir().toString());
+ }
+
+ /**
+ * Restores the context classloader to the previous value.
+ */
+ public void tearDown() throws Exception {
+ Log.v(TAG, "Restoring context classloader to " + mOriginalClassLoader);
+ mContextThread.setContextClassLoader(mOriginalClassLoader);
+ System.clearProperty(DEXCACHE);
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/SimpleTelecomTest.java b/tests/src/com/android/server/telecom/tests/SimpleTelecomTest.java
new file mode 100644
index 0000000..0b5843d
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/SimpleTelecomTest.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telecom.IConnectionServiceAdapter;
+import com.android.internal.telecom.IInCallAdapter;
+import com.android.internal.telecom.IInCallService;
+import com.android.internal.telecom.IVideoProvider;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.HeadsetMediaButton;
+import com.android.server.telecom.HeadsetMediaButtonFactory;
+import com.android.server.telecom.InCallWakeLockControllerFactory;
+import com.android.server.telecom.MissedCallNotifier;
+import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.InCallWakeLockController;
+import com.android.server.telecom.ProximitySensorManager;
+import com.android.server.telecom.TelecomSystem;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.CallState;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.DisconnectCause;
+import android.telecom.ParcelableCall;
+import android.telecom.ParcelableConnection;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.StatusHints;
+import android.telecom.TelecomManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SimpleTelecomTest extends AndroidTestCase {
+
+ private static final String TAG = "Telecom-TEST";
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Telecom specific mock objects
+
+ @Mock MissedCallNotifier mMissedCallNotifier;
+ @Mock HeadsetMediaButtonFactory mHeadsetMediaButtonFactory;
+ @Mock ProximitySensorManagerFactory mProximitySensorManagerFactory;
+ @Mock InCallWakeLockControllerFactory mInCallWakeLockControllerFactory;
+ @Mock HeadsetMediaButton mHeadsetMediaButton;
+ @Mock ProximitySensorManager mProximitySensorManager;
+ @Mock InCallWakeLockController mInCallWakeLockController;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Connection service
+
+ PhoneAccount mTestPhoneAccount = PhoneAccount.builder(
+ new PhoneAccountHandle(
+ new ComponentName("connection-service-package", "connection-service-class"),
+ "test-account-id"),
+ "test phone account")
+ .addSupportedUriScheme("tel")
+ .setCapabilities(
+ PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+ .build();
+ @Mock IConnectionService.Stub mConnectionService;
+ IConnectionServiceAdapter mConnectionServiceAdapter;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // In-Call service
+
+ ComponentName mIncallComponentName = new ComponentName("incall-package", "incall-class");
+ @Mock IInCallService.Stub mInCallService;
+ IInCallAdapter mIInCallAdapter;
+
+ private ComponentContextHolder mContextHolder;
+ private TelecomSystem mSystem;
+
+ private ConnectionRequest mConnectionRequest;
+ private String mConnectionId;
+
+ private ParcelableCall mParcelableCall;
+
+ private Looper mMainLooper;
+ private Looper mTestLooper;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Captured values for outgoing call processing
+
+ Intent mNewOutgoingCallIntent;
+ BroadcastReceiver mNewOutgoingCallReceiver;
+
+ private MockitoHelper mMockitoHelper = new MockitoHelper();
+
+ @Override
+ public void setUp() throws Exception {
+ mMockitoHelper.setUp(getContext(), getClass());
+
+ mMainLooper = Looper.getMainLooper();
+ mTestLooper = Looper.myLooper();
+
+ mContextHolder = new ComponentContextHolder();
+ MockitoAnnotations.initMocks(this);
+
+ mContextHolder.putResource(
+ com.android.server.telecom.R.string.ui_default_package,
+ mIncallComponentName.getPackageName());
+ mContextHolder.putResource(
+ com.android.server.telecom.R.string.incall_default_class,
+ mIncallComponentName.getClassName());
+
+ com.android.server.telecom.Log.setTag(TAG);
+
+ when(mHeadsetMediaButtonFactory.create(
+ any(Context.class),
+ any(CallsManager.class)))
+ .thenReturn(mHeadsetMediaButton);
+
+ when(mInCallWakeLockControllerFactory.create(
+ any(Context.class),
+ any(CallsManager.class)))
+ .thenReturn(mInCallWakeLockController);
+
+ when(mProximitySensorManagerFactory.create((Context) any(), (CallsManager) any()))
+ .thenReturn(mProximitySensorManager);
+
+ // Set up connection service
+
+ mContextHolder.addConnectionService(
+ mTestPhoneAccount.getAccountHandle().getComponentName(),
+ mConnectionService);
+ when(mConnectionService.asBinder()).thenReturn(mConnectionService);
+ when(mConnectionService.queryLocalInterface(anyString()))
+ .thenReturn(mConnectionService);
+
+ // Set up in-call service
+
+ mContextHolder.addInCallService(
+ mIncallComponentName,
+ mInCallService);
+ when(mInCallService.asBinder()).thenReturn(mInCallService);
+ when(mInCallService.queryLocalInterface(anyString()))
+ .thenReturn(mInCallService);
+
+ mSystem = new TelecomSystem(
+ mContextHolder.getTestDouble(),
+ mMissedCallNotifier,
+ mHeadsetMediaButtonFactory,
+ mProximitySensorManagerFactory,
+ mInCallWakeLockControllerFactory);
+ mSystem.getPhoneAccountRegistrar().registerPhoneAccount(mTestPhoneAccount);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mMockitoHelper.tearDown();
+ mSystem = null;
+ }
+
+ public void testSimpleOutgoingCall() throws Exception {
+
+ // Arrange to receive the first set of notifications when Telecom receives an Intent
+ // to make an outgoing call
+ doAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(final InvocationOnMock invocation) {
+ mIInCallAdapter = (IInCallAdapter) invocation.getArguments()[0];
+ return null;
+ }
+ }).when(mInCallService).setInCallAdapter((IInCallAdapter) any());
+ verify(mInCallService, never()).addCall((ParcelableCall) any());
+
+ doAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(final InvocationOnMock invocation) {
+ mNewOutgoingCallIntent = (Intent) invocation.getArguments()[0];
+ mNewOutgoingCallReceiver = (BroadcastReceiver) invocation.getArguments()[3];
+ return null;
+ }
+ }).when(mContextHolder.getTestDouble().getApplicationContext())
+ .sendOrderedBroadcastAsUser(
+ any(Intent.class),
+ any(UserHandle.class),
+ anyString(),
+ any(BroadcastReceiver.class),
+ any(Handler.class),
+ anyInt(),
+ anyString(),
+ any(Bundle.class));
+
+ doAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(final InvocationOnMock invocation) {
+ mParcelableCall = (ParcelableCall) invocation.getArguments()[0];
+ return null;
+ }
+ }).when(mInCallService).addCall((ParcelableCall) any());
+
+ doAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(final InvocationOnMock invocation) {
+ mParcelableCall = (ParcelableCall) invocation.getArguments()[0];
+ return null;
+ }
+ }).when(mInCallService).updateCall((ParcelableCall) any());
+
+ // Start an outgoing phone call
+ String number = "650-555-1212";
+ Intent actionCallIntent = new Intent();
+ actionCallIntent.setData(Uri.parse("tel:" + number));
+ actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
+ actionCallIntent.putExtra(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ mTestPhoneAccount.getAccountHandle());
+ actionCallIntent.setAction(Intent.ACTION_CALL);
+ mSystem.getCallIntentProcessor().processIntent(actionCallIntent);
+
+ // Sanity check that the in-call adapter is now set
+ assertNotNull(mIInCallAdapter);
+ assertNotNull(mNewOutgoingCallIntent);
+ assertNotNull(mNewOutgoingCallReceiver);
+
+ // Arrange to receive the Connection Service adapter
+ doAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ mConnectionServiceAdapter = (IConnectionServiceAdapter) invocation
+ .getArguments()[0];
+ return null;
+ }
+ }).when(mConnectionService).addConnectionServiceAdapter((IConnectionServiceAdapter) any());
+
+ doAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ mConnectionId = (String) invocation.getArguments()[1];
+ mConnectionRequest = (ConnectionRequest) invocation.getArguments()[2];
+ return null;
+ }
+ }).when(mConnectionService).createConnection(
+ any(PhoneAccountHandle.class),
+ anyString(),
+ any(ConnectionRequest.class),
+ anyBoolean(),
+ anyBoolean());
+
+ // Pass on the new outgoing call Intent
+ // Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
+ mNewOutgoingCallReceiver.setPendingResult(
+ new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0));
+ mNewOutgoingCallReceiver.setResultData(
+ mNewOutgoingCallIntent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+ mNewOutgoingCallReceiver.onReceive(
+ mContextHolder.getTestDouble(),
+ mNewOutgoingCallIntent);
+
+ assertNotNull(mConnectionServiceAdapter);
+ assertNotNull(mConnectionRequest);
+ assertNotNull(mConnectionId);
+
+ mConnectionServiceAdapter.handleCreateConnectionComplete(
+ mConnectionId,
+ mConnectionRequest,
+ new ParcelableConnection(
+ mConnectionRequest.getAccountHandle(),
+ Connection.STATE_DIALING,
+ 0,
+ (Uri) null,
+ 0,
+ "caller display name",
+ 0,
+ (IVideoProvider) null,
+ 0,
+ false,
+ false,
+ (StatusHints) null,
+ (DisconnectCause) null,
+ (List<String>) Collections.EMPTY_LIST));
+ mConnectionServiceAdapter.setDialing(mConnectionId);
+ mConnectionServiceAdapter.setActive(mConnectionId);
+
+ assertNotNull(mParcelableCall);
+ assertEquals(CallState.ACTIVE, mParcelableCall.getState());
+
+ try {
+ mIInCallAdapter.disconnectCall(mParcelableCall.getId());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ assertNotNull(mParcelableCall);
+ assertEquals(CallState.ACTIVE, mParcelableCall.getState());
+ try {
+ verify(mConnectionService).disconnect(mConnectionId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ mConnectionServiceAdapter.setDisconnected(
+ mConnectionId,
+ new DisconnectCause(DisconnectCause.LOCAL));
+
+ assertEquals(CallState.DISCONNECTED, mParcelableCall.getState());
+ }
+
+ private String exceptionToString(Throwable t) {
+ StringWriter sw = new StringWriter();
+ t.printStackTrace(new PrintWriter(sw));
+ return sw.toString();
+ }
+
+ private void log(String msg) {
+ Log.i(TAG, getClass().getSimpleName() + " - " + msg);
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/MockConnectionService.java b/tests/src/com/android/server/telecom/tests/TestDoubleHolder.java
similarity index 63%
rename from tests/src/com/android/server/telecom/tests/MockConnectionService.java
rename to tests/src/com/android/server/telecom/tests/TestDoubleHolder.java
index 62448cd..7c34f84 100644
--- a/tests/src/com/android/server/telecom/tests/MockConnectionService.java
+++ b/tests/src/com/android/server/telecom/tests/TestDoubleHolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,17 @@
package com.android.server.telecom.tests;
-import android.telecom.ConnectionService;
-
/**
- * A non-functional {@link android.telecom.ConnectionService} to use for unit tests.
+ * An object that provides a control interface for configuring a test double.
+ *
+ * TODO: Come up with a better name for this.
*/
-public class MockConnectionService extends ConnectionService {
+public interface TestDoubleHolder <T> {
+ /**
+ * Obtain the actual test double provided by this holder.
+ *
+ * @return the test double.
+ */
+ T getTestDouble();
}
diff --git a/tests/src/com/android/server/telecom/tests/unit/InCallWakeLockControllerTest.java b/tests/src/com/android/server/telecom/tests/unit/InCallWakeLockControllerTest.java
index 5af9440..f617723 100644
--- a/tests/src/com/android/server/telecom/tests/unit/InCallWakeLockControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/unit/InCallWakeLockControllerTest.java
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package com.android.server.telecom;
+package com.android.server.telecom.tests.unit;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.never;
diff --git a/tests/src/com/android/server/telecom/tests/unit/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/unit/PhoneAccountRegistrarTest.java
index e63e79f..0224566 100644
--- a/tests/src/com/android/server/telecom/tests/unit/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/unit/PhoneAccountRegistrarTest.java
@@ -357,4 +357,4 @@
s.simCallManager = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id1");
return s;
}
-}
\ No newline at end of file
+}