Merge remote-tracking branch 'goog/mirror-m-wireless-internal-release' into master_merge
Change-Id: I5d9ab761a1060a24680e69d1610ade206660e139
diff --git a/Android.mk b/Android.mk
index c5482b4..b2fbc16 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
include $(BUILD_PACKAGE)
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/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index e65d633..fe9ddd1 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -25,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,37 +46,9 @@
* and accepts call-related commands to perform on behalf of the BT device.
*/
public final class BluetoothPhoneServiceImpl {
- /**
- * 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();
- }
- }
- }
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;
@@ -113,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 = mCallsManager;
- 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) {
- mCallsManager.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(mContext)
- .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(mContext)
- .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.
*/
@@ -399,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;
+ }
}
};
@@ -414,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
+ }
+ }
}
}
};
@@ -430,9 +346,10 @@
private boolean mHeadsetUpdatedRecently = false;
- private Context mContext;
- private CallsManager mCallsManager;
- private PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final Context mContext;
+ private final TelecomSystem.SyncRoot mLock;
+ private final CallsManager mCallsManager;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
public IBinder getBinder() {
return mBinder;
@@ -440,11 +357,13 @@
public BluetoothPhoneServiceImpl(
Context context,
+ TelecomSystem.SyncRoot lock,
CallsManager callsManager,
PhoneAccountRegistrar phoneAccountRegistrar) {
Log.d(this, "onCreate");
mContext = context;
+ mLock = lock;
mCallsManager = callsManager;
mPhoneAccountRegistrar = phoneAccountRegistrar;
@@ -463,29 +382,28 @@
}
private boolean processChld(int chld) {
- CallsManager callsManager = mCallsManager;
- 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;
}
@@ -494,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) {
@@ -513,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;
}
}
@@ -527,35 +445,6 @@
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;
- }
-
private void sendListOfCalls(boolean shouldLog) {
Collection<Call> mCalls = mCallsManager.getCalls();
for (Call call : mCalls) {
@@ -670,9 +559,9 @@
*/
private void updateHeadsetWithCallState(boolean force) {
CallsManager callsManager = mCallsManager;
- Call activeCall = callsManager.getActiveCall();
- Call ringingCall = callsManager.getRingingCall();
- Call heldCall = callsManager.getHeldCall();
+ Call activeCall = mCallsManager.getActiveCall();
+ Call ringingCall = mCallsManager.getRingingCall();
+ Call heldCall = mCallsManager.getHeldCall();
int bluetoothCallState = getBluetoothCallStateForUpdate();
@@ -689,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
@@ -783,8 +672,8 @@
private int getBluetoothCallStateForUpdate() {
CallsManager callsManager = mCallsManager;
- Call ringingCall = callsManager.getRingingCall();
- Call dialingCall = callsManager.getDialingCall();
+ Call ringingCall = mCallsManager.getRingingCall();
+ Call dialingCall = mCallsManager.getDialingCall();
//
// !! WARNING !!
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 464975f..9e29fe1 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -94,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
@@ -300,6 +300,7 @@
private boolean mIsVoipAudioMode;
private StatusHints mStatusHints;
private final ConnectionServiceRepository mRepository;
+ private final ContactsAsyncHelper mContactsAsyncHelper;
private final Context mContext;
private final CallsManager mCallsManager;
@@ -327,10 +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,
@@ -341,6 +343,7 @@
mContext = context;
mCallsManager = callsManager;
mRepository = repository;
+ mContactsAsyncHelper = contactsAsyncHelper;
setHandle(handle);
setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
mGatewayInfo = gatewayInfo;
@@ -370,6 +373,7 @@
Context context,
CallsManager callsManager,
ConnectionServiceRepository repository,
+ ContactsAsyncHelper contactsAsyncHelper,
Uri handle,
GatewayInfo gatewayInfo,
PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -377,18 +381,18 @@
boolean isIncoming,
boolean isConference,
long connectTimeMillis) {
- this(context, callsManager, repository, handle, gatewayInfo,
+ 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);
}
@@ -445,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);
@@ -491,7 +495,7 @@
return mIsConference;
}
- Uri getHandle() {
+ public Uri getHandle() {
return mHandle;
}
@@ -504,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 ||
@@ -548,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;
}
@@ -564,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;
}
@@ -655,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;
}
@@ -1277,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,
@@ -1321,7 +1325,7 @@
if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
mCannedSmsResponsesLoadingStarted = true;
- TelecomSystem.getInstance().getRespondViaSmsManager().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 790528d..1fb682f 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -38,6 +38,7 @@
private final BluetoothManager mBluetoothManager;
private final WiredHeadsetManager mWiredHeadsetManager;
private final DockManager mDockManager;
+ private final CallsManager mCallsManager;
private AudioState mAudioState;
private int mAudioFocusStreamType;
@@ -47,12 +48,17 @@
private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL;
private Call mCallToSpeedUpMTAudio = null;
- CallAudioManager(Context context, StatusBarNotifier statusBarNotifier,
- WiredHeadsetManager wiredHeadsetManager, DockManager DockManager) {
+ 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);
mDockManager = DockManager;
mDockManager.addListener(this);
@@ -82,7 +88,7 @@
public void onCallRemoved(Call call) {
// If we didn't already have focus, there's nothing to do.
if (hasFocus()) {
- if (TelecomSystem.getInstance().getCallsManager().getCalls().isEmpty()) {
+ if (mCallsManager.getCalls().isEmpty()) {
Log.v(this, "all calls removed, reseting system audio to default state");
setInitialAudioState(null, false /* force */);
mWasSpeakerOn = false;
@@ -103,7 +109,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 = TelecomSystem.getInstance().getCallsManager().getCalls().size() == 1;
+ boolean isOnlyCall = mCallsManager.getCalls().size() == 1;
if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) {
mBluetoothManager.connectBluetoothAudio();
route = AudioState.ROUTE_BLUETOOTH;
@@ -206,7 +212,7 @@
Log.v(this, "mute, shouldMute: %b", shouldMute);
// Don't mute if there are any emergency calls.
- if (TelecomSystem.getInstance().getCallsManager().hasEmergencyCall()) {
+ if (mCallsManager.hasEmergencyCall()) {
shouldMute = false;
Log.v(this, "ignoring mute for emergency call");
}
@@ -264,8 +270,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;
@@ -360,7 +364,7 @@
}
if (!oldAudioState.equals(mAudioState)) {
- TelecomSystem.getInstance().getCallsManager()
+ mCallsManager
.onAudioStateChanged(oldAudioState, mAudioState);
updateAudioForForegroundCall();
}
@@ -395,9 +399,9 @@
requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
} else {
Call foregroundCall = getForegroundCall();
- Call waitingForAccountSelectionCall = TelecomSystem.getInstance().getCallsManager()
+ Call waitingForAccountSelectionCall = mCallsManager
.getFirstCallWithState(CallState.PRE_DIAL_WAIT);
- Call call = TelecomSystem.getInstance().getCallsManager().getForegroundCall();
+ Call call = mCallsManager.getForegroundCall();
if (foregroundCall == null && call != null && call == mCallToSpeedUpMTAudio) {
requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL,
AudioManager.MODE_IN_CALL);
@@ -544,7 +548,7 @@
}
private void updateAudioForForegroundCall() {
- Call call = TelecomSystem.getInstance().getCallsManager().getForegroundCall();
+ Call call = mCallsManager.getForegroundCall();
if (call != null && call.getConnectionService() != null) {
call.getConnectionService().onAudioStateChanged(call, mAudioState);
}
@@ -554,7 +558,7 @@
* Returns the current foreground call in order to properly set the audio mode.
*/
private Call getForegroundCall() {
- Call call = TelecomSystem.getInstance().getCallsManager().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}.
@@ -566,7 +570,7 @@
}
private boolean hasRingingForegroundCall() {
- Call call = TelecomSystem.getInstance().getCallsManager().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/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index e0e16a3..7ec83cb 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -17,16 +17,16 @@
import android.widget.Toast;
/**
- * 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 CallIntentProcessor} 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 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";
private final Context mContext;
private final CallsManager mCallsManager;
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 316beb4..df750c4 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -106,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
@@ -120,6 +121,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,29 +143,37 @@
/**
* Initializes the required Telecom components.
*/
- CallsManager(Context context, MissedCallNotifier missedCallNotifier,
- PhoneAccountRegistrar phoneAccountRegistrar,
- RespondViaSmsManager respondViaSmsManager) {
+ 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);
mDockManager = new DockManager(context);
mCallAudioManager = new CallAudioManager(
- context, statusBarNotifier, mWiredHeadsetManager, mDockManager);
+ 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);
@@ -175,8 +186,21 @@
mListeners.add(missedCallNotifier);
mListeners.add(mDtmfLocalTonePlayer);
mListeners.add(mHeadsetMediaButton);
- mListeners.add(respondViaSmsManager);
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
@@ -264,11 +288,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(
@@ -325,9 +352,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()));
@@ -403,6 +432,7 @@
mContext,
this,
mConnectionServiceRepository,
+ mContactsAsyncHelper,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -423,6 +453,7 @@
mContext,
this,
mConnectionServiceRepository,
+ mContactsAsyncHelper,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -460,6 +491,7 @@
mContext,
this,
mConnectionServiceRepository,
+ mContactsAsyncHelper,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -1048,6 +1080,7 @@
mContext,
this,
mConnectionServiceRepository,
+ mContactsAsyncHelper,
null /* handle */,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -1395,6 +1428,7 @@
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 5490c2d..a587b59 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -33,18 +33,28 @@
new HashMap<>();
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final Context mContext;
+ private final TelecomSystem.SyncRoot mLock;
+ private final CallsManager mCallsManager;
private final ServiceBinder.Listener<ConnectionServiceWrapper> mUnbindListener =
new ServiceBinder.Listener<ConnectionServiceWrapper>() {
@Override
public void onUnbind(ConnectionServiceWrapper service) {
- mServiceCache.remove(service.getComponentName());
+ synchronized (mLock) {
+ mServiceCache.remove(service.getComponentName());
+ }
}
};
- ConnectionServiceRepository(PhoneAccountRegistrar phoneAccountRegistrar, Context context) {
+ ConnectionServiceRepository(
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ Context context,
+ TelecomSystem.SyncRoot lock,
+ CallsManager callsManager) {
mPhoneAccountRegistrar = phoneAccountRegistrar;
mContext = context;
+ mLock = lock;
+ mCallsManager = callsManager;
}
ConnectionServiceWrapper getService(ComponentName componentName, UserHandle userHandle) {
@@ -55,7 +65,9 @@
componentName,
this,
mPhoneAccountRegistrar,
+ mCallsManager,
mContext,
+ mLock,
userHandle);
service.addListener(mUnbindListener);
mServiceCache.put(cacheKey, service);
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 2f125cf..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;
@@ -61,338 +58,6 @@
* {@link IConnectionService}.
*/
final class ConnectionServiceWrapper extends ServiceBinder {
- 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) {
- TelecomSystem.getInstance().getCallsManager().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) {
- TelecomSystem.getInstance().getCallsManager().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) {
- TelecomSystem.getInstance().getCallsManager().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) {
- TelecomSystem.getInstance().getCallsManager()
- .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) {
- TelecomSystem.getInstance().getCallsManager().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 = TelecomSystem.getInstance().getCallsManager().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()) {
- TelecomSystem.getInstance().getCallsManager().markCallAsDisconnected(
- call, new DisconnectCause(DisconnectCause.REMOTE));
- } else {
- TelecomSystem.getInstance().getCallsManager().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 = TelecomSystem.getInstance().getCallsManager()
- .createCallForExistingConnection(callId,
- connection);
- mCallIdMapper.addCall(existingCall, callId);
- existingCall.setConnectionService(ConnectionServiceWrapper.this);
- } finally {
- args.recycle();
- }
- }
- }
- }
- };
private final class Adapter extends IConnectionServiceAdapter.Stub {
@@ -401,221 +66,361 @@
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);
+ }
}
}
@@ -627,6 +432,7 @@
private IConnectionService mServiceInterface;
private final ConnectionServiceRepository mConnectionServiceRepository;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final CallsManager mCallsManager;
/**
* Creates a connection service.
@@ -634,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.
*/
@@ -641,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}. */
@@ -715,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);
@@ -732,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")) {
@@ -744,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")) {
@@ -756,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")) {
@@ -768,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")) {
@@ -780,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")) {
@@ -796,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")) {
@@ -808,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")) {
@@ -820,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")) {
@@ -933,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();
- TelecomSystem.getInstance().getCallsManager().handleConnectionServiceDeath(this);
+ mCallsManager.handleConnectionServiceDeath(this);
mServiceInterface = null;
} else {
mServiceInterface = IConnectionService.Stub.asInterface(binder);
@@ -1063,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 69010d2..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,6 +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 31114df..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;
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 b846243..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 (TelecomSystem.getInstance().getCallsManager().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,7 +350,6 @@
* @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);
@@ -357,8 +358,9 @@
try {
inCallService.setInCallAdapter(
new InCallAdapter(
- TelecomSystem.getInstance().getCallsManager(),
- mCallIdMapper));
+ mCallsManager,
+ mCallIdMapper,
+ mLock));
mInCallServices.put(componentName, inCallService);
} catch (RemoteException e) {
Log.e(this, e, "Failed to set the in-call adapter.");
@@ -367,25 +369,24 @@
}
// Upon successful connection, send the state of the world to the service.
- Collection<Call> calls = TelecomSystem.getInstance().getCallsManager().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,
- TelecomSystem.getInstance().getCallsManager().getAudioState());
- onCanAddCallChanged(TelecomSystem.getInstance().getCallsManager().canAddCall());
+ mCallsManager.getAudioState());
+ onCanAddCallChanged(mCallsManager.canAddCall());
} else {
unbind();
}
@@ -399,7 +400,6 @@
*/
private void onDisconnected(ComponentName disconnectedComponent) {
Log.i(this, "onDisconnected from %s", disconnectedComponent);
- ThreadUtil.checkOnMainThread();
if (mInCallServices.containsKey(disconnectedComponent)) {
mInCallServices.remove(disconnectedComponent);
@@ -411,7 +411,7 @@
// implementations.
if (disconnectedComponent.equals(mInCallComponentName)) {
Log.i(this, "In-call UI %s disconnected.", disconnectedComponent);
- TelecomSystem.getInstance().getCallsManager().disconnectAllCalls();
+ mCallsManager.disconnectAllCalls();
unbind();
} else {
Log.i(this, "In-Call Service %s suddenly disconnected", disconnectedComponent);
@@ -468,7 +468,7 @@
// If this is a single-SIM device, the "default SIM" will always be the only SIM.
boolean isDefaultSmsAccount =
- TelecomSystem.getInstance().getCallsManager().getPhoneAccountRegistrar()
+ mCallsManager.getPhoneAccountRegistrar()
.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
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/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 dbbc49e..52055cf 100644
--- a/src/com/android/server/telecom/MissedCallNotifier.java
+++ b/src/com/android/server/telecom/MissedCallNotifier.java
@@ -16,339 +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 CallsManager mCallsManager;
- 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();
- }
-
- void setCallsManager(CallsManager callsManager) {
- this.mCallsManager = callsManager;
- }
-
- /** {@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(
- 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.
- */
- 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, mCallsManager,
- 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 2cd5b5f..c1cf7f8 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;
@@ -157,8 +159,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 UserCallIntentProcessor#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/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index 1a1f427..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) {
@@ -44,7 +46,7 @@
public void onCallStateChanged(Call call, int oldState, int newState) {
if ((newState == CallState.DIALING || newState == CallState.ACTIVE
|| newState == CallState.ON_HOLD) &&
- !TelecomSystem.getInstance().getCallsManager().hasRingingCall()) {
+ !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.
@@ -64,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 = TelecomSystem.getInstance().getCallsManager();
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 d18e4ac..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 (TelecomSystem.getInstance().getCallsManager().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 (TelecomSystem.getInstance().getCallsManager().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 3a3432e..98ba3b4 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -41,30 +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 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 {
@@ -80,7 +65,10 @@
}
};
- public RespondViaSmsManager() {}
+ public RespondViaSmsManager(CallsManager callsManager, TelecomSystem.SyncRoot lock) {
+ mCallsManager = callsManager;
+ mLock = lock;
+ }
/**
* Read the (customizable) canned responses from SharedPreferences,
@@ -128,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();
}
@@ -139,9 +126,7 @@
@Override
public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
if (rejectWithMessage && call.getHandle() != null) {
- PhoneAccountRegistrar phoneAccountRegistrar =
- TelecomSystem.getInstance().getCallsManager().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 8baf3ec..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;
@@ -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/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index 3110d46..2b22ea9 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -20,27 +20,25 @@
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 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";
private final Context mContext;
+ private final CallsManager mCallsManager;
- public TelecomBroadcastIntentProcessor(Context context) {
+ public TelecomBroadcastIntentProcessor(Context context, CallsManager callsManager) {
mContext = context;
+ mCallsManager = callsManager;
}
public void processIntent(Intent intent) {
@@ -48,8 +46,7 @@
Log.v(this, "Action received: %s.", action);
- MissedCallNotifier missedCallNotifier = TelecomSystem.getInstance()
- .getCallsManager().getMissedCallNotifier();
+ MissedCallNotifier missedCallNotifier = mCallsManager.getMissedCallNotifier();
// Send an SMS from the missed call notification.
if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action)) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 476411c..b7b1500 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -57,359 +57,284 @@
private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION =
"android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION";
- /** 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:
- TelecomSystem.getInstance().getCallsManager().getRinger().silence();
- break;
- case MSG_SHOW_CALL_SCREEN:
- TelecomSystem.getInstance().getCallsManager().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:
- TelecomSystem.getInstance().getMissedCallNotifier().clearMissedCalls();
- break;
- case MSG_IS_TTY_SUPPORTED:
- result = TelecomSystem.getInstance().getCallsManager().isTtySupported();
- break;
- case MSG_GET_CURRENT_TTY_MODE:
- result = TelecomSystem.getInstance().getCallsManager().getCurrentTtyMode();
- break;
- case MSG_NEW_INCOMING_CALL:
- if (request.arg == null || !(request.arg instanceof Intent)) {
- Log.w(this, "Invalid new incoming call request");
- break;
- }
- CallIntentProcessor.processIncomingCallIntent(
- mCallsManager,
- (Intent) request.arg);
- break;
- }
-
- if (result != null) {
- request.result = result;
- synchronized(request) {
- request.notifyAll();
- }
- }
- }
- }
- }
-
- private static final String TAG = com.android.server.telecom.TelecomServiceImpl.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 AppOpsManager mAppOpsManager;
- private UserManager mUserManager;
- private PackageManager mPackageManager;
- private TelecomBinderImpl mBinderImpl;
- private CallsManager mCallsManager;
-
- public TelecomServiceImpl(Context context, CallsManager callsManager) {
- mContext = context;
- mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mBinderImpl = new TelecomBinderImpl();
-
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mPackageManager = mContext.getPackageManager();
-
- mCallsManager = callsManager;
- }
-
- public IBinder getBinder() {
- return mBinderImpl;
- }
-
- /**
- * Implementation of the ITelecomService interface.
- * TODO: Reorganize this inner class to top of file.
- */
- class TelecomBinderImpl extends ITelecomService.Stub {
+ private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
@Override
public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
- enforceReadPermission();
- long token = Binder.clearCallingIdentity();
- try {
- PhoneAccountHandle defaultOutgoingPhoneAccount =
- TelecomSystem.getInstance().getPhoneAccountRegistrar().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;
+ 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);
}
- return defaultOutgoingPhoneAccount;
- } catch (Exception e) {
- Log.e(this, e, "getDefaultOutgoingPhoneAccount");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
@Override
public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
- try {
- PhoneAccountHandle userSelectedOutgoingPhoneAccount =
- TelecomSystem.getInstance().getPhoneAccountRegistrar().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;
+ 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;
}
- return userSelectedOutgoingPhoneAccount;
- } catch (Exception e) {
- Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
- throw e;
}
}
@Override
public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
- enforceModifyPermission();
-
- try {
- TelecomSystem.getInstance().getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
- throw e;
+ synchronized (mLock) {
+ 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(
- TelecomSystem.getInstance().getPhoneAccountRegistrar().getCallCapablePhoneAccounts());
- } catch (Exception e) {
- Log.e(this, e, "getCallCapablePhoneAccounts");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
+ 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) {
- enforceReadPermission();
- long token = Binder.clearCallingIdentity();
- try {
- return filterForAccountsVisibleToCaller(
- TelecomSystem.getInstance().getPhoneAccountRegistrar().getCallCapablePhoneAccounts(uriScheme));
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
+ 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) {
- try {
- return filterForAccountsVisibleToCaller(
- TelecomSystem.getInstance().getPhoneAccountRegistrar().getPhoneAccountsForPackage(packageName));
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
- throw e;
+ 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) {
- try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
+ 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;
}
- return TelecomSystem.getInstance().getPhoneAccountRegistrar().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;
+ 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() {
- try {
- List<PhoneAccount> allPhoneAccounts = TelecomSystem.getInstance().getPhoneAccountRegistrar().getAllPhoneAccounts();
- List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(allPhoneAccounts.size());
- for (PhoneAccount phoneAccount : profilePhoneAccounts) {
- if (isVisibleToCaller(phoneAccount)) {
- profilePhoneAccounts.add(phoneAccount);
+ 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;
}
- return profilePhoneAccounts;
- } catch (Exception e) {
- Log.e(this, e, "getAllPhoneAccounts");
- throw e;
}
}
@Override
public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
- try {
- return filterForAccountsVisibleToCaller(
- TelecomSystem.getInstance().getPhoneAccountRegistrar().getAllPhoneAccountHandles());
- } catch (Exception e) {
- Log.e(this, e, "getAllPhoneAccounts");
- throw e;
+ synchronized (mLock) {
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getAllPhoneAccountHandles());
+ } catch (Exception e) {
+ Log.e(this, e, "getAllPhoneAccounts");
+ throw e;
+ }
}
}
@Override
public PhoneAccountHandle getSimCallManager() {
- try {
- PhoneAccountHandle accountHandle = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSimCallManager();
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
+ 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;
}
- return accountHandle;
- } catch (Exception e) {
- Log.e(this, e, "getSimCallManager");
- throw e;
}
}
@Override
public void setSimCallManager(PhoneAccountHandle accountHandle) {
- enforceModifyPermission();
-
- try {
- TelecomSystem.getInstance().getPhoneAccountRegistrar().setSimCallManager(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "setSimCallManager");
- throw e;
+ synchronized (mLock) {
+ 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(
- TelecomSystem.getInstance().getPhoneAccountRegistrar().getConnectionManagerPhoneAccounts());
- } catch (Exception e) {
- Log.e(this, e, "getSimCallManagers");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
+ 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) {
- 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());
+ 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());
- TelecomSystem.getInstance().getPhoneAccountRegistrar().registerPhoneAccount(account);
+ 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;
+ // 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) {
- try {
- enforcePhoneAccountModificationForPackage(
- accountHandle.getComponentName().getPackageName());
- enforceUserHandleMatchesCaller(accountHandle);
- TelecomSystem.getInstance().getPhoneAccountRegistrar().unregisterPhoneAccount(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
- throw e;
+ 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) {
- try {
- enforcePhoneAccountModificationForPackage(packageName);
- TelecomSystem.getInstance().getPhoneAccountRegistrar().clearAccounts(packageName, Binder.getCallingUserHandle());
- } catch (Exception e) {
- Log.e(this, e, "clearAccounts %s", packageName);
- throw e;
+ synchronized (mLock) {
+ try {
+ enforcePhoneAccountModificationForPackage(packageName);
+ mPhoneAccountRegistrar
+ .clearAccounts(packageName, Binder.getCallingUserHandle());
+ } catch (Exception e) {
+ Log.e(this, e, "clearAccounts %s", packageName);
+ throw e;
+ }
}
}
@@ -418,16 +343,18 @@
*/
@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;
+ 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;
}
- return TelecomSystem.getInstance().getPhoneAccountRegistrar().isVoiceMailNumber(accountHandle, number);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
}
}
@@ -436,21 +363,24 @@
*/
@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;
- }
+ 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 = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
+ 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;
}
- return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber(subId));
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
}
}
@@ -460,16 +390,19 @@
@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;
+ 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;
}
- int subId = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
- return getTelephonyManager().getLine1NumberForSubscriber(subId);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
}
}
@@ -478,9 +411,10 @@
*/
@Override
public void silenceRinger() {
- Log.d(this, "silenceRinger");
- enforceModifyPermission();
- sendRequestAsync(MSG_SILENCE_RINGER, 0);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ mCallsManager.getRinger().silence();
+ }
}
/**
@@ -488,6 +422,7 @@
*/
@Override
public ComponentName getDefaultPhoneApp() {
+ // No need to synchronize
Resources resources = mContext.getResources();
return new ComponentName(
resources.getString(R.string.ui_default_package),
@@ -499,12 +434,12 @@
*/
@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 = TelecomSystem.getInstance().getCallsManager().getCallState();
- return callState == TelephonyManager.CALL_STATE_OFFHOOK
- || callState == TelephonyManager.CALL_STATE_RINGING;
+ synchronized (mLock) {
+ enforceReadPermission();
+ final int callState = mCallsManager.getCallState();
+ return callState == TelephonyManager.CALL_STATE_OFFHOOK
+ || callState == TelephonyManager.CALL_STATE_RINGING;
+ }
}
/**
@@ -512,8 +447,10 @@
*/
@Override
public boolean isRinging() {
- enforceReadPermission();
- return TelecomSystem.getInstance().getCallsManager().getCallState() == TelephonyManager.CALL_STATE_RINGING;
+ synchronized (mLock) {
+ enforceReadPermission();
+ return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
+ }
}
/**
@@ -521,7 +458,9 @@
*/
@Override
public int getCallState() {
- return TelecomSystem.getInstance().getCallsManager().getCallState();
+ synchronized (mLock) {
+ return mCallsManager.getCallState();
+ }
}
/**
@@ -529,8 +468,10 @@
*/
@Override
public boolean endCall() {
- enforceModifyPermission();
- return (boolean) sendRequest(MSG_END_CALL);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ return endCallInternal();
+ }
}
/**
@@ -538,8 +479,10 @@
*/
@Override
public void acceptRingingCall() {
- enforceModifyPermission();
- sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
+ synchronized (mLock) {
+ enforceModifyPermission();
+ acceptRingingCallInternal();
+ }
}
/**
@@ -547,8 +490,10 @@
*/
@Override
public void showInCallScreen(boolean showDialpad) {
- enforceReadPermissionOrDefaultDialer();
- sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
+ synchronized (mLock) {
+ enforceReadPermissionOrDefaultDialer();
+ mCallsManager.getInCallController().bringToForeground(showDialpad);
+ }
}
/**
@@ -556,8 +501,10 @@
*/
@Override
public void cancelMissedCallsNotification() {
- enforceModifyPermissionOrDefaultDialer();
- sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
+ synchronized (mLock) {
+ enforceModifyPermissionOrDefaultDialer();
+ mCallsManager.getMissedCallNotifier().clearMissedCalls();
+ }
}
/**
@@ -565,44 +512,50 @@
*/
@Override
public boolean handlePinMmi(String dialString) {
- enforceModifyPermissionOrDefaultDialer();
+ 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);
+ // 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;
}
-
- return retval;
}
/**
* @see android.telecom.TelecomManager#handleMmi
*/
@Override
- public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle,
+ public boolean handlePinMmiForPhoneAccount(
+ PhoneAccountHandle accountHandle,
String dialString) {
- enforceModifyPermissionOrDefaultDialer();
+ synchronized (mLock) {
+ enforceModifyPermissionOrDefaultDialer();
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return false;
+ 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;
}
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- boolean retval = false;
- try {
- int subId = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
- retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return retval;
}
/**
@@ -610,24 +563,27 @@
*/
@Override
public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
- enforceModifyPermissionOrDefaultDialer();
+ synchronized (mLock) {
+ enforceModifyPermissionOrDefaultDialer();
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
+ 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);
}
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- String retval = "content://icc/adn/";
- try {
- long subId = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
- retval = retval + "subId/" + subId;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return Uri.parse(retval);
}
/**
@@ -635,8 +591,10 @@
*/
@Override
public boolean isTtySupported() {
- enforceReadPermission();
- return (boolean) sendRequest(MSG_IS_TTY_SUPPORTED);
+ synchronized (mLock) {
+ enforceReadPermission();
+ return mCallsManager.isTtySupported();
+ }
}
/**
@@ -644,8 +602,10 @@
*/
@Override
public int getCurrentTtyMode() {
- enforceReadPermission();
- return (int) sendRequest(MSG_GET_CURRENT_TTY_MODE);
+ synchronized (mLock) {
+ enforceReadPermission();
+ return mCallsManager.getCurrentTtyMode();
+ }
}
/**
@@ -653,28 +613,28 @@
*/
@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(),
+ 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);
+ 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");
}
- sendRequestAsync(MSG_NEW_INCOMING_CALL, 0, intent);
- } else {
- Log.w(this, "Null phoneAccountHandle. Ignoring request to add new incoming call");
}
}
@@ -683,24 +643,28 @@
*/
@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());
+ 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);
+ // 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.");
+ 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.");
+ }
}
}
@@ -723,18 +687,46 @@
}
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- if (TelecomSystem.getInstance().getCallsManager() != null) {
+ if (mCallsManager != null) {
pw.println("CallsManager: ");
pw.increaseIndent();
- TelecomSystem.getInstance().getCallsManager().dump(pw);
+ mCallsManager.dump(pw);
pw.decreaseIndent();
pw.println("PhoneAccountRegistrar: ");
pw.increaseIndent();
- TelecomSystem.getInstance().getPhoneAccountRegistrar().dump(pw);
+ 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;
}
//
@@ -746,7 +738,8 @@
return false;
}
- return isVisibleToCaller(TelecomSystem.getInstance().getPhoneAccountRegistrar().getPhoneAccountInternal(accountHandle));
+ return isVisibleToCaller(mPhoneAccountRegistrar
+ .getPhoneAccountInternal(accountHandle));
}
private boolean isVisibleToCaller(PhoneAccount account) {
@@ -825,7 +818,7 @@
}
private void acceptRingingCallInternal() {
- Call call = TelecomSystem.getInstance().getCallsManager().getFirstCallWithState(CallState.RINGING);
+ Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
if (call != null) {
call.answer(call.getVideoState());
}
@@ -834,9 +827,9 @@
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 = TelecomSystem.getInstance().getCallsManager().getForegroundCall();
+ Call call = mCallsManager.getForegroundCall();
if (call == null) {
- call = TelecomSystem.getInstance().getCallsManager().getFirstCallWithState(
+ call = mCallsManager.getFirstCallWithState(
CallState.ACTIVE,
CallState.DIALING,
CallState.RINGING,
@@ -975,41 +968,4 @@
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/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 3634b8a..22377c9 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -16,6 +16,8 @@
package com.android.server.telecom;
+import com.android.internal.annotations.VisibleForTesting;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -41,11 +43,20 @@
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;
@@ -55,6 +66,7 @@
private final CallIntentProcessor mCallIntentProcessor;
private final TelecomBroadcastIntentProcessor mTelecomBroadcastIntentProcessor;
private final TelecomServiceImpl mTelecomServiceImpl;
+ private final ContactsAsyncHelper mContactsAsyncHelper;
private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
@Override
@@ -77,43 +89,46 @@
INSTANCE = instance;
}
- public TelecomSystem(Context context) {
+ public TelecomSystem(
+ Context context,
+ MissedCallNotifier missedCallNotifier,
+ HeadsetMediaButtonFactory headsetMediaButtonFactory,
+ ProximitySensorManagerFactory proximitySensorManagerFactory,
+ InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
mContext = context.getApplicationContext();
- mMissedCallNotifier = new MissedCallNotifier(mContext);
+ mMissedCallNotifier = missedCallNotifier;
mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
-
- mRespondViaSmsManager = new RespondViaSmsManager();
+ mContactsAsyncHelper = new ContactsAsyncHelper(mLock);
mCallsManager = new CallsManager(
- mContext, mMissedCallNotifier, mPhoneAccountRegistrar, mRespondViaSmsManager);
- Log.i(this, "CallsManager initialized");
- mMissedCallNotifier.setCallsManager(mCallsManager);
+ 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(context, mCallsManager, mPhoneAccountRegistrar);
- mCallIntentProcessor = new CallIntentProcessor(context, mCallsManager);
- mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(context);
- mTelecomServiceImpl = new TelecomServiceImpl(context, mCallsManager);
+ mBluetoothPhoneServiceImpl = new BluetoothPhoneServiceImpl(
+ mContext, mLock, mCallsManager, mPhoneAccountRegistrar);
+ mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager);
+ mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
+ mContext, mCallsManager);
+ mTelecomServiceImpl = new TelecomServiceImpl(
+ mContext, mCallsManager, mPhoneAccountRegistrar, mLock);
}
- public MissedCallNotifier getMissedCallNotifier() {
- return mMissedCallNotifier;
- }
-
+ @VisibleForTesting
public PhoneAccountRegistrar getPhoneAccountRegistrar() {
return mPhoneAccountRegistrar;
}
- public CallsManager getCallsManager() {
- return mCallsManager;
- }
-
- public RespondViaSmsManager getRespondViaSmsManager() {
- return mRespondViaSmsManager;
- }
-
public BluetoothPhoneServiceImpl getBluetoothPhoneServiceImpl() {
return mBluetoothPhoneServiceImpl;
}
@@ -129,4 +144,8 @@
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
index 1f757d5..c5e195c 100644
--- a/src/com/android/server/telecom/components/BluetoothPhoneService.java
+++ b/src/com/android/server/telecom/components/BluetoothPhoneService.java
@@ -30,7 +30,9 @@
@Override
public IBinder onBind(Intent intent) {
- return getTelecomSystem().getBluetoothPhoneServiceImpl().getBinder();
+ synchronized (getTelecomSystem().getLock()) {
+ return getTelecomSystem().getBluetoothPhoneServiceImpl().getBinder();
+ }
}
@Override
diff --git a/src/com/android/server/telecom/components/PrimaryCallReceiver.java b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
index 8e1f72d..5198862 100644
--- a/src/com/android/server/telecom/components/PrimaryCallReceiver.java
+++ b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
@@ -1,7 +1,6 @@
package com.android.server.telecom.components;
import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.UserCallIntentProcessor;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -17,7 +16,9 @@
@Override
public void onReceive(Context context, Intent intent) {
- getTelecomSystem().getCallIntentProcessor().processIntent(intent);
+ synchronized (getTelecomSystem().getLock()) {
+ getTelecomSystem().getCallIntentProcessor().processIntent(intent);
+ }
}
@Override
diff --git a/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java b/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
index b4f525c..e0ca3c4 100644
--- a/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
+++ b/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
@@ -32,7 +32,9 @@
/** {@inheritDoc} */
@Override
public void onReceive(Context context, Intent intent) {
- getTelecomSystem().getTelecomBroadcastIntentProcessor().processIntent(intent);
+ synchronized (getTelecomSystem().getLock()) {
+ getTelecomSystem().getTelecomBroadcastIntentProcessor().processIntent(intent);
+ }
}
@Override
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 0b8238b..49b4aa4 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -18,11 +18,20 @@
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.
@@ -32,14 +41,54 @@
@Override
public IBinder onBind(Intent intent) {
Log.d(this, "onBind");
- // We are guaranteed that the TelecomService will be started before any other
- // components in this package because it is started and kept running by the system.
- TelecomSystem.setInstance(new TelecomSystem(this));
- // Start the BluetoothPhoneService
- if (BluetoothAdapter.getDefaultAdapter() != null) {
- startService(new Intent(this, BluetoothPhoneService.class));
+ initializeTelecomSystem(this);
+ synchronized (getTelecomSystem().getLock()) {
+ return getTelecomSystem().getTelecomServiceImpl().getBinder();
}
- 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
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index 3ea92bd..ccff468 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -18,7 +18,7 @@
import com.android.server.telecom.CallIntentProcessor;
import com.android.server.telecom.Log;
-import com.android.server.telecom.UserCallIntentProcessor;
+import com.android.server.telecom.TelecomSystem;
import android.app.Activity;
import android.content.Intent;
@@ -41,10 +41,10 @@
* {@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.
+ * {@link UserCallActivity}. Calling startActivity will continue to work on all non-emergency
+ * numbers just like it did pre-L.
*/
-public class UserCallActivity extends Activity {
+public class UserCallActivity extends Activity implements TelecomSystem.Component {
@Override
protected void onCreate(Bundle bundle) {
@@ -67,4 +67,9 @@
}
}
}
+
+ @Override
+ public TelecomSystem getTelecomSystem() {
+ return TelecomSystem.getInstance();
+ }
}
diff --git a/src/com/android/server/telecom/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
similarity index 95%
rename from src/com/android/server/telecom/UserCallIntentProcessor.java
rename to src/com/android/server/telecom/components/UserCallIntentProcessor.java
index 36d2126..97a3497 100644
--- a/src/com/android/server/telecom/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-package com.android.server.telecom;
+package com.android.server.telecom.components;
-import com.android.server.telecom.components.PrimaryCallReceiver;
+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;
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/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/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
+}