Merge "Check caller perm before clearing binder identity" into udc-dev
diff --git a/Android.bp b/Android.bp
index 1b422aa..c5141ca 100644
--- a/Android.bp
+++ b/Android.bp
@@ -28,6 +28,9 @@
static_libs: [
"androidx.annotation_annotation",
],
+ libs: [
+ "services",
+ ],
resource_dirs: ["res"],
proto: {
type: "nano",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 648b4a9..d42dcff 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -64,6 +64,7 @@
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
android:label="Broadcast the call type/duration information"
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 98115db..5ed2ebe 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -51,7 +51,7 @@
<string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"కాల్ చేయడానికి, చెల్లుబాటు అయ్యే నంబర్ను నమోదు చేయండి."</string>
<string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"ఈ సమయంలో కాల్ను జోడించడం సాధ్యపడదు."</string>
<string name="no_vm_number" msgid="2179959110602180844">"వాయిస్ మెయిల్ నంబర్ లేదు"</string>
- <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ నిల్వ చేయబడలేదు."</string>
+ <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ స్టోరేజ్ చేయబడలేదు."</string>
<string name="add_vm_number_str" msgid="5179510133063168998">"నంబర్ను జోడించండి"</string>
<string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g>ను మీ ఆటోమేటిక్ ఫోన్ యాప్గా చేయాలా?"</string>
<string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ఆటోమేటిక్గా సెట్ చేయండి"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 7033f36..afd8d0a 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -120,7 +120,7 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"کال مسدود کرنا غیر فعال ہو گیا ہے"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ہنگامی کال کی گئی"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ہنگامی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
- <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینو"</string>
+ <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینیو"</string>
<string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ہنگامی کال کے دوران کالز نہیں لی جائیں گی۔"</string>
<string name="cancel" msgid="6733466216239934756">"منسوخ کریں"</string>
<string name="back" msgid="6915955601805550206">"پیچھے"</string>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index e4dc6af..6e5826b 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -39,6 +39,7 @@
import android.provider.CallLog;
import android.provider.ContactsContract.Contacts;
import android.telecom.BluetoothCallQualityReport;
+import android.telecom.CallAttributes;
import android.telecom.CallAudioState;
import android.telecom.CallDiagnosticService;
import android.telecom.CallDiagnostics;
@@ -556,6 +557,25 @@
private boolean mIsSelfManaged = false;
private boolean mIsTransactionalCall = false;
+ private CallingPackageIdentity mCallingPackageIdentity = new CallingPackageIdentity();
+
+ /**
+ * CallingPackageIdentity is responsible for storing properties about the calling package that
+ * initiated the call. For example, if MyVoipApp requests to add a call with Telecom, we can
+ * store their UID and PID when we are still bound to that package.
+ */
+ public static class CallingPackageIdentity {
+ public int mCallingPackageUid = -1;
+ public int mCallingPackagePid = -1;
+
+ public CallingPackageIdentity() {
+ }
+
+ CallingPackageIdentity(Bundle extras) {
+ mCallingPackageUid = extras.getInt(CallAttributes.CALLER_UID_KEY, -1);
+ mCallingPackagePid = extras.getInt(CallAttributes.CALLER_PID_KEY, -1);
+ }
+ }
/**
* Indicates whether this call is streaming.
@@ -1831,6 +1851,17 @@
setConnectionProperties(getConnectionProperties());
}
+ public void setCallingPackageIdentity(Bundle extras) {
+ mCallingPackageIdentity = new CallingPackageIdentity(extras);
+ // These extras should NOT be propagated to Dialer and should be removed.
+ extras.remove(CallAttributes.CALLER_PID_KEY);
+ extras.remove(CallAttributes.CALLER_UID_KEY);
+ }
+
+ public CallingPackageIdentity getCallingPackageIdentity() {
+ return mCallingPackageIdentity;
+ }
+
public void setTransactionServiceWrapper(TransactionalServiceWrapper service) {
mTransactionalService = service;
}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index b9a83e5..9503c10 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -136,6 +136,7 @@
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.ToastFactory;
import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.voip.VoipCallMonitor;
import java.util.ArrayList;
import java.util.Arrays;
@@ -437,6 +438,7 @@
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final EmergencyCallHelper mEmergencyCallHelper;
private final RoleManagerAdapter mRoleManagerAdapter;
+ private final VoipCallMonitor mVoipCallMonitor;
private final CallEndpointController mCallEndpointController;
private final CallAnomalyWatchdog mCallAnomalyWatchdog;
@@ -642,6 +644,7 @@
mTransactionManager = transactionManager;
mBlockedNumbersAdapter = blockedNumbersAdapter;
mCallStreamingController = new CallStreamingController(mContext, mLock);
+ mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
mListeners.add(mInCallWakeLockController);
mListeners.add(statusBarNotifier);
@@ -662,6 +665,9 @@
// this needs to be after the mCallAudioManager
mListeners.add(mPhoneStateBroadcaster);
+ mListeners.add(mVoipCallMonitor);
+
+ mVoipCallMonitor.startMonitor();
// There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
final UserManager userManager = UserManager.get(mContext);
@@ -1399,6 +1405,7 @@
// set properties for transactional call
if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
call.setIsTransactionalCall(true);
+ call.setCallingPackageIdentity(extras);
call.setConnectionCapabilities(
extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
CallAttributes.SUPPORTS_SET_INACTIVE), true);
@@ -1709,6 +1716,7 @@
if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
call.setIsTransactionalCall(true);
+ call.setCallingPackageIdentity(extras);
call.setConnectionCapabilities(
extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
CallAttributes.SUPPORTS_SET_INACTIVE), true);
@@ -6278,6 +6286,10 @@
return mRinger;
}
+ @VisibleForTesting
+ public VoipCallMonitor getVoipCallMonitor() {
+ return mVoipCallMonitor;
+ }
/**
* This method should only be used for testing.
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 59a84f9..cc5932c 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -89,7 +89,6 @@
ConnectionServiceFocusManager.ConnectionServiceFocus {
private static final String TELECOM_ABBREVIATION = "cast";
-
private CompletableFuture<Pair<Integer, Location>> mQueryLocationFuture = null;
private @Nullable CancellationSignal mOngoingQueryLocationRequest = null;
private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor();
@@ -1432,7 +1431,11 @@
callback.send(0,
getQueryLocationErrorResult(QueryLocationException.ERROR_UNSPECIFIED));
}
+ //make sure we don't pass mock locations diretly, always reset() mock locations
if (result.second != null) {
+ if(result.second.isMock()) {
+ result.second.reset();
+ }
callback.send(1, getQueryLocationResult(result.second));
} else {
callback.send(0, getQueryLocationErrorResult(result.first));
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index a4a0242..3ce394e 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -265,7 +265,7 @@
if (packageName == null ||
Objects.equals(packageName, mCurrentDefaultDialerPerUser.get(userId))) {
String newDefaultDialer = refreshCacheForUser(userId);
- Log.i(LOG_TAG, "Refreshing default dialer for user %d: now %s",
+ Log.v(LOG_TAG, "Refreshing default dialer for user %d: now %s",
userId, newDefaultDialer);
}
}
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index ca82c49..f5a3450 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -1371,10 +1371,6 @@
* @return {@code True} if the phone account has permission.
*/
public boolean phoneAccountRequiresBindPermission(PhoneAccountHandle phoneAccountHandle) {
- if (hasTransactionalCallCapabilities(getPhoneAccountUnchecked(phoneAccountHandle))) {
- return false;
- }
-
List<ResolveInfo> resolveInfos = resolveComponent(phoneAccountHandle);
if (resolveInfos.isEmpty()) {
Log.w(this, "phoneAccount %s not found", phoneAccountHandle.getComponentName());
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 5ae37eb..99a8d3d 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -47,6 +47,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -193,16 +194,21 @@
enforcePhoneAccountIsRegisteredEnabled(handle, handle.getUserHandle());
enforceCallingPackage(callingPackage, "addCall");
+ // add extras about info used for FGS delegation
+ Bundle extras = new Bundle();
+ extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
+ extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());
+
VoipCallTransaction transaction = null;
// create transaction based on the call direction
switch (callAttributes.getDirection()) {
case DIRECTION_OUTGOING:
transaction = new OutgoingCallTransaction(callId, mContext, callAttributes,
- mCallsManager);
+ mCallsManager, extras);
break;
case DIRECTION_INCOMING:
transaction = new IncomingCallTransaction(callId, callAttributes,
- mCallsManager);
+ mCallsManager, extras);
break;
default:
throw new IllegalArgumentException(String.format("Invalid Call Direction. "
@@ -772,6 +778,9 @@
.build();
}
+ // Validate the profile boundary of the given image URI.
+ validateAccountIconUserBoundary(account.getIcon());
+
final long token = Binder.clearCallingIdentity();
try {
Log.i(this, "registerPhoneAccount: account=%s",
@@ -3129,4 +3138,22 @@
mContext.sendBroadcast(intent);
}
}
+
+ private void validateAccountIconUserBoundary(Icon icon) {
+ // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+ // incompatible types.
+ if (icon != null && (icon.getType() == Icon.TYPE_URI
+ || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+ String encodedUser = icon.getUri().getEncodedUserInfo();
+ // If there is no encoded user, the URI is calling into the calling user space
+ if (encodedUser != null) {
+ int userId = Integer.parseInt(encodedUser);
+ if (userId != UserHandle.getUserId(Binder.getCallingUid())) {
+ // If we are transcending the profile boundary, throw an error.
+ throw new IllegalArgumentException("Attempting to register a phone account with"
+ + " an image icon belonging to another user.");
+ }
+ }
+ }
+ }
}
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 1ba06ec..c5fdd4c 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -416,7 +416,7 @@
*/
public static long getNonVoipCallTransitoryStateTimeoutMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
- TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 5000L);
+ TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 10000L);
}
/**
@@ -426,7 +426,7 @@
*/
public static long getNonVoipEmergencyCallTransitoryStateTimeoutMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
- TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 5000L);
+ TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 10000L);
}
/**
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index efa9417..ef85fc7 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -214,7 +214,7 @@
.stopFlashNotificationSequence(context);
}
},
- Executors.newSingleThreadExecutor(),
+ Executors.newCachedThreadPool(),
new BlockedNumbersAdapter() {
@Override
public boolean shouldShowEmergencyCallNotification(Context
diff --git a/src/com/android/server/telecom/voip/IncomingCallTransaction.java b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
index 7413014..c0bb93d 100644
--- a/src/com/android/server/telecom/voip/IncomingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
@@ -20,7 +20,6 @@
import android.os.Bundle;
import android.telecom.CallAttributes;
-import android.telecom.CallControl;
import android.telecom.CallException;
import android.telecom.TelecomManager;
import android.util.Log;
@@ -37,15 +36,22 @@
private final String mCallId;
private final CallAttributes mCallAttributes;
private final CallsManager mCallsManager;
+ private final Bundle mExtras;
public IncomingCallTransaction(String callId, CallAttributes callAttributes,
- CallsManager callsManager) {
+ CallsManager callsManager, Bundle extras) {
super(callsManager.getLock());
+ mExtras = extras;
mCallId = callId;
mCallAttributes = callAttributes;
mCallsManager = callsManager;
}
+ public IncomingCallTransaction(String callId, CallAttributes callAttributes,
+ CallsManager callsManager) {
+ this(callId, callAttributes, callsManager, new Bundle());
+ }
+
@Override
public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
@@ -70,11 +76,10 @@
}
}
- private Bundle generateExtras(CallAttributes callAttributes){
- Bundle extras = new Bundle();
- extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
- extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
- extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
- return extras;
+ private Bundle generateExtras(CallAttributes callAttributes) {
+ mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+ mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+ mExtras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
+ return mExtras;
}
}
diff --git a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
index 124f5f3..0b17da2 100644
--- a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
@@ -43,15 +43,22 @@
private final String mCallingPackage;
private final CallAttributes mCallAttributes;
private final CallsManager mCallsManager;
+ private final Bundle mExtras;
public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
- CallsManager callsManager) {
+ CallsManager callsManager, Bundle extras) {
super(callsManager.getLock());
mCallId = callId;
mContext = context;
- mCallingPackage = mContext.getOpPackageName();
mCallAttributes = callAttributes;
mCallsManager = callsManager;
+ mExtras = extras;
+ mCallingPackage = mContext.getOpPackageName();
+ }
+
+ public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
+ CallsManager callsManager) {
+ this(callId, context, callAttributes, callsManager, new Bundle());
}
@Override
@@ -113,13 +120,12 @@
}
}
- private Bundle generateExtras(CallAttributes callAttributes){
- Bundle extras = new Bundle();
- extras.setDefusable(true);
- extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
- extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
- extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ private Bundle generateExtras(CallAttributes callAttributes) {
+ mExtras.setDefusable(true);
+ mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+ mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+ mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
callAttributes.getCallType());
- return extras;
+ return mExtras;
}
}
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/voip/VoipCallMonitor.java
new file mode 100644
index 0000000..67af11d
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2022 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.voip;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class VoipCallMonitor extends CallsManagerListenerBase {
+
+ private final List<Call> mPendingCalls;
+ private final Map<StatusBarNotification, Call> mNotifications;
+ private final Map<PhoneAccountHandle, Set<Call>> mPhoneAccountHandleListMap;
+ private ActivityManagerInternal mActivityManagerInternal;
+ private final Map<PhoneAccountHandle, ServiceConnection> mServices;
+ private NotificationListenerService mNotificationListener;
+ private final Object mLock = new Object();
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+ private final Context mContext;
+
+ public VoipCallMonitor(Context context, TelecomSystem.SyncRoot lock) {
+ mContext = context;
+ mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mPendingCalls = new ArrayList<>();
+ mNotifications = new HashMap<>();
+ mServices = new HashMap<>();
+ mPhoneAccountHandleListMap = new HashMap<>();
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+
+ mNotificationListener = new NotificationListenerService() {
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn) {
+ synchronized (mLock) {
+ if (mPendingCalls.isEmpty()) {
+ return;
+ }
+ if (sbn.getNotification().isStyle(Notification.CallStyle.class)) {
+ String packageName = sbn.getPackageName();
+ UserHandle userHandle = sbn.getUser();
+
+ for (Call call : mPendingCalls) {
+ if (packageName != null &&
+ packageName.equals(call.getTargetPhoneAccount()
+ .getComponentName().getPackageName())
+ && userHandle != null
+ && userHandle.equals(call.getInitiatingUser())) {
+ mPendingCalls.remove(call);
+ mNotifications.put(sbn, call);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn) {
+ synchronized (mLock) {
+ if (mNotifications.isEmpty()) {
+ return;
+ }
+ Call call = mNotifications.getOrDefault(sbn, null);
+ if (call != null) {
+ mNotifications.remove(sbn, call);
+ stopFGSDelegation(call.getTargetPhoneAccount());
+ }
+ }
+ }
+ };
+
+ }
+
+ public void startMonitor() {
+ try {
+ mNotificationListener.registerAsSystemService(mContext,
+ new ComponentName(this.getClass().getPackageName(),
+ this.getClass().getCanonicalName()), ActivityManager.getCurrentUser());
+ } catch (RemoteException e) {
+ Log.e(this, e, "Cannot register notification listener");
+ }
+ }
+
+ public void stopMonitor() {
+ try {
+ mNotificationListener.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ Log.e(this, e, "Cannot unregister notification listener");
+ }
+ }
+
+ @Override
+ public void onCallAdded(Call call) {
+ if (!call.isTransactionalCall()) {
+ return;
+ }
+
+ synchronized (mLock) {
+ PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+ Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
+ k -> new HashSet<>());
+ callList.add(call);
+
+ mHandler.post(
+ () -> startFGSDelegation(call.getCallingPackageIdentity().mCallingPackagePid,
+ call.getCallingPackageIdentity().mCallingPackageUid, call));
+ }
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ if (!call.isTransactionalCall()) {
+ return;
+ }
+
+ synchronized (mLock) {
+ stopMonitorWorks(call);
+ PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+ Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
+ k -> new HashSet<>());
+ callList.remove(call);
+
+ if (callList.isEmpty()) {
+ stopFGSDelegation(phoneAccountHandle);
+ }
+ }
+ }
+
+ private void startFGSDelegation(int pid, int uid, Call call) {
+ Log.i(this, "startFGSDelegation for call %s", call.getId());
+ if (mActivityManagerInternal != null) {
+ PhoneAccountHandle handle = call.getTargetPhoneAccount();
+ ForegroundServiceDelegationOptions options = new ForegroundServiceDelegationOptions(pid,
+ uid, handle.getComponentName().getPackageName(), null /* clientAppThread */,
+ false /* isSticky */, String.valueOf(handle.hashCode()),
+ 0 /* foregroundServiceType */,
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_PHONE_CALL);
+ ServiceConnection fgsConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mServices.put(handle, this);
+ startMonitorWorks(call);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mServices.remove(handle);
+ }
+ };
+ try {
+ mActivityManagerInternal.startForegroundServiceDelegate(options, fgsConnection);
+ } catch (Exception e) {
+ Log.i(this, "startForegroundServiceDelegate failed due to: " + e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public void stopFGSDelegation(PhoneAccountHandle handle) {
+ synchronized (mLock) {
+ Log.i(this, "stopFGSDelegation of handle %s", handle);
+ Set<Call> calls = mPhoneAccountHandleListMap.get(handle);
+ for (Call call : calls) {
+ stopMonitorWorks(call);
+ }
+ mPhoneAccountHandleListMap.remove(handle);
+
+ if (mActivityManagerInternal != null) {
+ ServiceConnection fgsConnection = mServices.get(handle);
+ if (fgsConnection != null) {
+ mActivityManagerInternal.stopForegroundServiceDelegate(fgsConnection);
+ }
+ }
+ }
+ }
+
+ private void startMonitorWorks(Call call) {
+ startMonitorNotification(call);
+ }
+
+ private void stopMonitorWorks(Call call) {
+ stopMonitorNotification(call);
+ }
+
+ private void startMonitorNotification(Call call) {
+ mPendingCalls.add(call);
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ if (mPendingCalls.contains(call)) {
+ stopFGSDelegation(call.getTargetPhoneAccount());
+ mPendingCalls.remove(call);
+ }
+ }
+ }, 5000L);
+ }
+
+ private void stopMonitorNotification(Call call) {
+ mPendingCalls.remove(call);
+ }
+
+ @VisibleForTesting
+ public void setActivityManagerInternal(ActivityManagerInternal ami) {
+ mActivityManagerInternal = ami;
+ }
+
+ @VisibleForTesting
+ public void setNotificationListenerService(NotificationListenerService listener) {
+ mNotificationListener = listener;
+ }
+}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index c84db3b..4ca6030 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -44,6 +44,9 @@
<!-- Used to access PlatformCompat APIs -->
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+
+ <!-- Used to register NotificationListenerService -->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<application android:label="@string/app_name"
android:debuggable="true">
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 6b817d8..997e7dd 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -26,16 +26,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.ComponentName;
import android.content.Intent;
@@ -43,7 +38,7 @@
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcel;
+import android.telecom.CallAttributes;
import android.telecom.CallerInfo;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
@@ -56,12 +51,10 @@
import android.telecom.VideoProfile;
import android.telephony.CallQuality;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
import android.widget.Toast;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.internal.telecom.IVideoProvider;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallIdMapper;
import com.android.server.telecom.CallState;
@@ -69,8 +62,6 @@
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.InCallController;
-import com.android.server.telecom.InCallController.InCallServiceInfo;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
@@ -640,8 +631,40 @@
verify(listener).onFailedUnknownCall(unknownCall);
}
+ /**
+ * ensure a Call object does not throw an NPE when the CallingPackageIdentity is not set and
+ * the correct values are returned when set
+ */
@Test
@SmallTest
+ public void testCallingPackageIdentity() {
+ final int packageUid = 123;
+ final int packagePid = 1;
+
+ Call call = createCall("1");
+
+ // assert default values for a Calls CallingPackageIdentity are -1 unless set via the setter
+ assertEquals(-1, call.getCallingPackageIdentity().mCallingPackageUid);
+ assertEquals(-1, call.getCallingPackageIdentity().mCallingPackagePid);
+
+ // set the Call objects CallingPackageIdentity via the setter and a bundle
+ Bundle extras = new Bundle();
+ extras.putInt(CallAttributes.CALLER_UID_KEY, packageUid);
+ extras.putInt(CallAttributes.CALLER_PID_KEY, packagePid);
+ // assert that the setter removed the extras
+ assertEquals(packageUid, extras.getInt(CallAttributes.CALLER_UID_KEY));
+ assertEquals(packagePid, extras.getInt(CallAttributes.CALLER_PID_KEY));
+ call.setCallingPackageIdentity(extras);
+ // assert that the setter removed the extras
+ assertEquals(0, extras.getInt(CallAttributes.CALLER_UID_KEY));
+ assertEquals(0, extras.getInt(CallAttributes.CALLER_PID_KEY));
+ // assert the properties are fetched correctly
+ assertEquals(packageUid, call.getCallingPackageIdentity().mCallingPackageUid);
+ assertEquals(packagePid, call.getCallingPackageIdentity().mCallingPackagePid);
+ }
+
+ @Test
+ @SmallTest
public void testOnConnectionEventNotifiesListener() {
Call.Listener listener = mock(Call.Listener.class);
Call call = createCall("1");
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 49ad6bb..cc22de2 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -59,7 +59,9 @@
import android.os.Bundle;
import android.os.DropBoxManager;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IInterface;
+import android.os.Looper;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
@@ -112,6 +114,7 @@
* property points to an application context implementing all the nontrivial functionality.
*/
public class ComponentContextFixture implements TestFixture<Context> {
+ private HandlerThread mHandlerThread;
public class FakeApplicationContext extends MockContext {
@Override
@@ -311,6 +314,15 @@
}
@Override
+ public Looper getMainLooper() {
+ if (mHandlerThread == null) {
+ mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+ mHandlerThread.start();
+ }
+ return mHandlerThread.getLooper();
+ }
+
+ @Override
public ContentResolver getContentResolver() {
return new ContentResolver(mApplicationContextSpy) {
@Override
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index a98da83..7b5afe6 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -33,6 +33,7 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -950,6 +951,26 @@
@SmallTest
@Test
+ public void testRegisterPhoneAccountImageIconCrossUser() throws RemoteException {
+ String packageNameToUse = "com.android.officialpackage";
+ PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+ packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+ Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+ PhoneAccount phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+ // This should fail; security exception will be thrown.
+ registerPhoneAccountTestHelper(phoneAccount, false);
+
+ icon = Icon.createWithContentUri("content://0@media/external/images/media/");
+ phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+ // This should succeed.
+ registerPhoneAccountTestHelper(phoneAccount, true);
+ }
+
+ @SmallTest
+ @Test
public void testUnregisterPhoneAccount() throws RemoteException {
String packageNameToUse = "com.android.officialpackage";
PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index dd5856d..d013fae 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -394,6 +394,7 @@
handlerThread.quitSafely();
}
handlerThreads.clear();
+ mTelecomSystem.getCallsManager().getVoipCallMonitor().stopMonitor();
}
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
waitForHandlerAction(mHandlerThread.getThreadHandler(), TEST_TIMEOUT);
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
new file mode 100644
index 0000000..346b3d8
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 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 static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.voip.VoipCallMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class VoipCallMonitorTest extends TelecomTestCase {
+ private VoipCallMonitor mMonitor;
+ private static final String PKG_NAME_1 = "telecom.voip.test1";
+ private static final String PKG_NAME_2 = "telecom.voip.test2";
+ private static final String CLS_NAME = "VoipActivity";
+ private static final String ID_1 = "id1";
+ private static final UserHandle USER_HANDLE_1 = new UserHandle(1);
+ private static final long TIMEOUT = 5000L;
+
+ @Mock private TelecomSystem.SyncRoot mLock;
+ @Mock private ActivityManagerInternal mActivityManagerInternal;
+ @Mock private NotificationListenerService mListenerService;
+
+ private final PhoneAccountHandle mHandle1User1 = new PhoneAccountHandle(
+ new ComponentName(PKG_NAME_1, CLS_NAME), ID_1, USER_HANDLE_1);
+ private final PhoneAccountHandle mHandle2User1 = new PhoneAccountHandle(
+ new ComponentName(PKG_NAME_2, CLS_NAME), ID_1, USER_HANDLE_1);
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mMonitor = new VoipCallMonitor(mContext, mLock);
+ mActivityManagerInternal = mock(ActivityManagerInternal.class);
+ mListenerService = mock(NotificationListenerService.class);
+ mMonitor.setActivityManagerInternal(mActivityManagerInternal);
+ mMonitor.setNotificationListenerService(mListenerService);
+ doNothing().when(mListenerService).registerAsSystemService(eq(mContext),
+ any(ComponentName.class), anyInt());
+ mMonitor.startMonitor();
+ }
+
+ @SmallTest
+ @Test
+ public void testStartMonitorForOneCall() {
+ Call call = createTestCall("testCall", mHandle1User1);
+ IBinder service = mock(IBinder.class);
+
+ ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call);
+ verify(mActivityManagerInternal, timeout(TIMEOUT)).startForegroundServiceDelegate(any(
+ ForegroundServiceDelegationOptions.class), captor.capture());
+ ServiceConnection conn = captor.getValue();
+ conn.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ mMonitor.onCallRemoved(call);
+ verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(eq(conn));
+ }
+
+ @SmallTest
+ @Test
+ public void testMonitorForTwoCallsOnSameHandle() {
+ Call call1 = createTestCall("testCall1", mHandle1User1);
+ Call call2 = createTestCall("testCall2", mHandle1User1);
+ IBinder service = mock(IBinder.class);
+
+ ArgumentCaptor<ServiceConnection> captor1 =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+ captor1.capture());
+ ServiceConnection conn1 = captor1.getValue();
+ conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ ArgumentCaptor<ServiceConnection> captor2 =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call2);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+ .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+ captor2.capture());
+ ServiceConnection conn2 = captor2.getValue();
+ conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ mMonitor.onCallRemoved(call1);
+ verify(mActivityManagerInternal, never()).stopForegroundServiceDelegate(
+ any(ServiceConnection.class));
+ mMonitor.onCallRemoved(call2);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .stopForegroundServiceDelegate(eq(conn2));
+ }
+
+ @SmallTest
+ @Test
+ public void testMonitorForTwoCallsOnDifferentHandle() {
+ Call call1 = createTestCall("testCall1", mHandle1User1);
+ Call call2 = createTestCall("testCall2", mHandle2User1);
+ IBinder service = mock(IBinder.class);
+
+ ArgumentCaptor<ServiceConnection> connCaptor1 = ArgumentCaptor.forClass(
+ ServiceConnection.class);
+ ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor1 =
+ ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+ mMonitor.onCallAdded(call1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .startForegroundServiceDelegate(optionsCaptor1.capture(), connCaptor1.capture());
+ ForegroundServiceDelegationOptions options1 = optionsCaptor1.getValue();
+ ServiceConnection conn1 = connCaptor1.getValue();
+ conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+ assertEquals(PKG_NAME_1, options1.getComponentName().getPackageName());
+
+ ArgumentCaptor<ServiceConnection> connCaptor2 = ArgumentCaptor.forClass(
+ ServiceConnection.class);
+ ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor2 =
+ ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+ mMonitor.onCallAdded(call2);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+ .startForegroundServiceDelegate(optionsCaptor2.capture(), connCaptor2.capture());
+ ForegroundServiceDelegationOptions options2 = optionsCaptor2.getValue();
+ ServiceConnection conn2 = connCaptor2.getValue();
+ conn2.onServiceConnected(mHandle2User1.getComponentName(), service);
+ assertEquals(PKG_NAME_2, options2.getComponentName().getPackageName());
+
+ mMonitor.onCallRemoved(call2);
+ verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn2));
+ mMonitor.onCallRemoved(call1);
+ verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn1));
+ }
+
+ @SmallTest
+ @Test
+ public void testStopDelegation() {
+ Call call1 = createTestCall("testCall1", mHandle1User1);
+ Call call2 = createTestCall("testCall2", mHandle1User1);
+ IBinder service = mock(IBinder.class);
+
+ ArgumentCaptor<ServiceConnection> captor1 =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+ captor1.capture());
+ ServiceConnection conn1 = captor1.getValue();
+ conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ ArgumentCaptor<ServiceConnection> captor2 =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call2);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+ .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+ captor2.capture());
+ ServiceConnection conn2 = captor2.getValue();
+ conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ mMonitor.stopFGSDelegation(mHandle1User1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .stopForegroundServiceDelegate(eq(conn2));
+ conn2.onServiceDisconnected(mHandle1User1.getComponentName());
+ mMonitor.onCallRemoved(call1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .stopForegroundServiceDelegate(any(ServiceConnection.class));
+ }
+
+ private Call createTestCall(String id, PhoneAccountHandle handle) {
+ Call call = mock(Call.class);
+ when(call.getTargetPhoneAccount()).thenReturn(handle);
+ when(call.isTransactionalCall()).thenReturn(true);
+ when(call.getExtras()).thenReturn(new Bundle());
+ when(call.getId()).thenReturn(id);
+ when(call.getCallingPackageIdentity()).thenReturn( new Call.CallingPackageIdentity() );
+ return call;
+ }
+}