Merge rvc-qpr-dev-plus-aosp-without-vendor@6881855
Bug: 172690556
Merged-In: Idd3ecd217229d37bfef5bed50c55cd1a837ce852
Change-Id: I9b4c72aa8dd0f16c7568e846374e3b837d580c8a
diff --git a/res/xml/layout_blocked_number.xml b/res/xml/layout_blocked_number.xml
index 3cdd771..9dbfc2f 100644
--- a/res/xml/layout_blocked_number.xml
+++ b/res/xml/layout_blocked_number.xml
@@ -28,9 +28,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
+ android:layout_toStartOf="@+id/delete_blocked_number"
android:paddingTop="@dimen/blocked_numbers_delete_icon_padding"
- android:layout_alignParentLeft="true"
- android:layout_toLeftOf="@+id/delete_blocked_number"
android:textDirection="ltr" />
<ImageView
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 9c227a6..8b7c37d 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -208,6 +208,9 @@
public void setCallSource(int callSource) {
}
+
+ public void setMissedReason(long missedReason) {
+ }
}
/**
@@ -242,6 +245,7 @@
public List<TelecomLogClass.InCallServiceInfo> inCallServiceInfos;
public int callProperties = 0;
public int callSource = CALL_SOURCE_UNSPECIFIED;
+ public long missedReason;
private long mTimeOfLastVideoEvent = -1;
@@ -254,6 +258,7 @@
connectionService = "";
videoEvents = new LinkedList<>();
inCallServiceInfos = new LinkedList<>();
+ missedReason = 0;
}
CallInfoImpl(CallInfoImpl other) {
@@ -272,6 +277,7 @@
this.videoEvents = other.videoEvents;
this.callProperties = other.callProperties;
this.callSource = other.callSource;
+ this.missedReason = other.missedReason;
if (other.callTerminationReason != null) {
this.callTerminationReason = new DisconnectCause(
@@ -342,6 +348,13 @@
}
@Override
+ public void setMissedReason(long missedReason) {
+ Log.d(TAG, "setting missedReason for call " + callId + ": "
+ + missedReason);
+ this.missedReason = missedReason;
+ }
+
+ @Override
public void setCallEvents(EventManager.EventRecord records) {
this.callEvents = records;
}
@@ -399,6 +412,7 @@
+ " isEmergency: " + isEmergency + '\n'
+ " callTechnologies: " + getCallTechnologiesAsString() + '\n'
+ " callTerminationReason: " + getCallDisconnectReasonString() + '\n'
+ + " missedReason: " + getMissedReasonString() + '\n'
+ " connectionService: " + connectionService + '\n'
+ " isVideoCall: " + isVideo + '\n'
+ " inCallServices: " + getInCallServicesString() + '\n'
@@ -526,6 +540,11 @@
}
}
+ private String getMissedReasonString() {
+ //TODO: Implement this
+ return null;
+ }
+
private String getInCallServicesString() {
StringBuilder s = new StringBuilder();
s.append("[\n");
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 6af999e..fb4f58d 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -16,6 +16,8 @@
package com.android.server.telecom;
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -33,6 +35,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.CallLog;
import android.provider.ContactsContract.Contacts;
import android.telecom.CallAudioState;
import android.telecom.CallerInfo;
@@ -588,7 +591,7 @@
/**
* Indicates whether this call is using one of the
- * {@link com.android.server.telecom.callfiltering.IncomingCallFilter.CallFilter} modules.
+ * {@link com.android.server.telecom.callfiltering.CallFilter} modules.
*/
private boolean mIsUsingCallFiltering = false;
@@ -611,6 +614,16 @@
private String mPostCallPackageName;
/**
+ * Call missed information code.
+ */
+ @CallLog.Calls.MissedReason private long mMissedReason;
+
+ /**
+ * Time that this call start ringing or simulated ringing.
+ */
+ private long mStartRingTime;
+
+ /**
* Persists the specified parameters and initializes the new instance.
* @param context The context.
* @param repository The connection service repository.
@@ -692,6 +705,8 @@
mClockProxy = clockProxy;
mToastFactory = toastFactory;
mCreationTimeMillis = mClockProxy.currentTimeMillis();
+ mMissedReason = MISSED_REASON_NOT_MISSED;
+ mStartRingTime = 0;
}
/**
@@ -3870,4 +3885,20 @@
public String getPostCallPackageName() {
return mPostCallPackageName;
}
+
+ public long getMissedReason() {
+ return mMissedReason;
+ }
+
+ public void setMissedReason(long missedReason) {
+ mMissedReason = missedReason;
+ }
+
+ public long getStartRingTime() {
+ return mStartRingTime;
+ }
+
+ public void setStartRingTime(long startRingTime) {
+ mStartRingTime = startRingTime;
+ }
}
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index b19e269..cedd41c 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -16,6 +16,7 @@
package com.android.server.telecom;
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
import static android.telephony.CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL;
import android.annotation.Nullable;
@@ -84,7 +85,8 @@
int features, PhoneAccountHandle accountHandle, long creationDate,
long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead,
@Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason,
- CharSequence callScreeningAppName, String callScreeningComponentName) {
+ CharSequence callScreeningAppName, String callScreeningComponentName,
+ long missedReason) {
this.context = context;
this.callerInfo = callerInfo;
this.number = number;
@@ -103,6 +105,7 @@
this.callBockReason = callBlockReason;
this.callScreeningAppName = callScreeningAppName;
this.callScreeningComponentName = callScreeningComponentName;
+ this.missedReason = missedReason;
}
// Since the members are accessed directly, we don't use the
// mXxxx notation.
@@ -127,6 +130,7 @@
public final int callBockReason;
public final CharSequence callScreeningAppName;
public final String callScreeningComponentName;
+ public final long missedReason;
}
private static final String TAG = CallLogManager.class.getSimpleName();
@@ -353,20 +357,25 @@
call.wasEverRttCall(),
call.wasVolte());
- if (callLogType == Calls.BLOCKED_TYPE) {
+ if (result == null) {
+ // Call auto missed before filtered
+ result = new CallFilteringResult.Builder().build();
+ }
+
+ if (callLogType == Calls.BLOCKED_TYPE || callLogType == Calls.MISSED_TYPE) {
logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
creationTime, age, callDataUsage, call.isEmergencyCall(),
call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
result.mCallBlockReason, result.mCallScreeningAppName,
- result.mCallScreeningComponentName);
+ result.mCallScreeningComponentName, call.getMissedReason());
} else {
logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
creationTime, age, callDataUsage, call.isEmergencyCall(),
call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
Calls.BLOCK_REASON_NOT_BLOCKED, null /*callScreeningAppName*/,
- null /*callScreeningComponentName*/);
+ null /*callScreeningComponentName*/, call.getMissedReason());
}
}
@@ -390,6 +399,7 @@
* @param callBlockReason The reason why the call is blocked.
* @param callScreeningAppName The call screening application name which block the call.
* @param callScreeningComponentName The call screening component name which block the call.
+ * @param missedReason The encoded information about reasons for missed call.
*/
private void logCall(
CallerInfo callerInfo,
@@ -409,7 +419,8 @@
@Nullable LogCallCompletedListener logCallCompletedListener,
int callBlockReason,
CharSequence callScreeningAppName,
- String callScreeningComponentName) {
+ String callScreeningComponentName,
+ long missedReason) {
// On some devices, to avoid accidental redialing of emergency numbers, we *never* log
// emergency calls to the Call Log. (This behavior is set on a per-product basis, based
@@ -443,7 +454,7 @@
AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
viaNumber, presentation, callType, features, accountHandle, start, duration,
dataUsage, initiatingUser, isRead, logCallCompletedListener, callBlockReason,
- callScreeningAppName, callScreeningComponentName);
+ callScreeningAppName, callScreeningComponentName, missedReason);
logCallAsync(args);
} else {
Log.d(TAG, "Not adding emergency call to call log.");
@@ -596,7 +607,7 @@
c.presentation, c.callType, c.features, c.accountHandle, c.timestamp,
c.durationInSec, c.dataUsage, userToBeInserted == null,
userToBeInserted, c.isRead, c.callBockReason, c.callScreeningAppName,
- c.callScreeningComponentName);
+ c.callScreeningComponentName, c.missedReason);
}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index c1fdadd..6b99633 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,6 +16,7 @@
package com.android.server.telecom;
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
import static android.telecom.TelecomManager.ACTION_POST_CALL;
import static android.telecom.TelecomManager.DURATION_LONG;
import static android.telecom.TelecomManager.DURATION_MEDIUM;
@@ -27,6 +28,9 @@
import static android.telecom.TelecomManager.MEDIUM_CALL_TIME_MS;
import static android.telecom.TelecomManager.SHORT_CALL_TIME_MS;
import static android.telecom.TelecomManager.VERY_SHORT_CALL_TIME_MS;
+import static android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
import android.Manifest;
import android.annotation.NonNull;
@@ -739,6 +743,7 @@
// Only set the incoming call as ringing if it isn't already disconnected. It is possible
// that the connection service disconnected the call before it was even added to Telecom, in
// which case it makes no sense to set it back to a ringing state.
+ Log.i(this, "onCallFilteringComplete");
mGraphHandlerThreads.clear();
if (incomingCall.getState() != CallState.DISCONNECTED &&
@@ -760,16 +765,17 @@
} else {
Log.i(this, "onCallFilteringCompleted: Call rejected! " +
"Exceeds maximum number of ringing calls.");
- rejectCallAndLog(incomingCall, result);
+ incomingCall.setMissedReason(AUTO_MISSED_MAXIMUM_RINGING);
+ autoMissCallAndLog(incomingCall, result);
}
} else if (hasMaximumManagedDialingCalls(incomingCall)) {
if (shouldSilenceInsteadOfReject(incomingCall)) {
incomingCall.silence();
} else {
-
Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
"dialing calls.");
- rejectCallAndLog(incomingCall, result);
+ incomingCall.setMissedReason(AUTO_MISSED_MAXIMUM_DIALING);
+ autoMissCallAndLog(incomingCall, result);
}
} else if (result.shouldScreenViaAudio) {
Log.i(this, "onCallFilteringCompleted: starting background audio processing");
@@ -1335,12 +1341,18 @@
if (isConference) {
notifyCreateConferenceFailed(phoneAccountHandle, call);
} else {
+ if (hasMaximumManagedRingingCalls(call)) {
+ call.setMissedReason(AUTO_MISSED_MAXIMUM_RINGING);
+ mCallLogManager.logCall(call, Calls.MISSED_TYPE,
+ true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
+ }
notifyCreateConnectionFailed(phoneAccountHandle, call);
}
} else if (isInEmergencyCall()) {
// The incoming call is implicitly being rejected so the user does not get any incoming
// call UI during an emergency call. In this case, log the call as missed instead of
// rejected since the user did not explicitly reject.
+ call.setMissedReason(AUTO_MISSED_EMERGENCY_CALL);
mCallLogManager.logCall(call, Calls.MISSED_TYPE,
true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
if (isConference) {
@@ -1586,6 +1598,17 @@
// call transitioning into the CONNECTING state.
if (isReusedCall) {
return CompletableFuture.completedFuture(finalCall);
+ } else {
+ Call reusableCall = reuseOutgoingCall(handle);
+ if (reusableCall != null) {
+ Log.i(CallsManager.this,
+ "reusable call %s came in later; disconnect it.",
+ reusableCall.getId());
+ mPendingCallsToDisconnect.remove(reusableCall);
+ reusableCall.disconnect();
+ markCallAsDisconnected(reusableCall,
+ new DisconnectCause(DisconnectCause.CANCELED));
+ }
}
// If we can not supportany more active calls, our options are to move a call
@@ -3437,7 +3460,8 @@
* Reject an incoming call and manually add it to the Call Log.
* @param incomingCall Incoming call that has been rejected
*/
- private void rejectCallAndLog(Call incomingCall, CallFilteringResult result) {
+ private void autoMissCallAndLog(Call incomingCall, CallFilteringResult result) {
+ incomingCall.getAnalytics().setMissedReason(incomingCall.getMissedReason());
if (incomingCall.getConnectionService() != null) {
// Only reject the call if it has not already been destroyed. If a call ends while
// incoming call filtering is taking place, it is possible that the call has already
@@ -3575,6 +3599,10 @@
(newState == CallState.DISCONNECTED)) {
maybeSendPostCallScreenIntent(call);
}
+ if (((newState == CallState.ABORTED) || (newState == CallState.DISCONNECTED))
+ && (call.getDisconnectCause().getCode() != DisconnectCause.MISSED)) {
+ call.setMissedReason(MISSED_REASON_NOT_MISSED);
+ }
maybeShowErrorDialogOnDisconnect(call);
Trace.beginSection("onCallStateChanged");
@@ -4731,6 +4759,7 @@
* @param call The {@link Call} which could not be added.
*/
private void notifyCreateConnectionFailed(PhoneAccountHandle phoneAccountHandle, Call call) {
+ call.getAnalytics().setMissedReason(call.getMissedReason());
if (phoneAccountHandle == null) {
return;
}
@@ -5340,4 +5369,9 @@
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityAsUser(intent, mCurrentUserHandle);
}
+
+ @VisibleForTesting
+ public void addToPendingCallsToDisconnect(Call call) {
+ mPendingCallsToDisconnect.add(call);
+ }
}
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 2221cb9..2e67b08 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -403,6 +403,7 @@
// When testing emergency calls, we want the calls to go through to the test connection
// service, not the telephony ConnectionService.
if (mCall.isTestEmergencyCall()) {
+ Log.i(this, "Processing test emergency call -- special rules");
allAccounts = mPhoneAccountRegistrar.filterRestrictedPhoneAccounts(allAccounts);
}
@@ -411,7 +412,7 @@
preferredPAH);
// Next, add all SIM phone accounts which can place emergency calls.
sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
- // and pick the fist one that can place emergency calls.
+ // and pick the first one that can place emergency calls.
for (PhoneAccount phoneAccount : allAccounts) {
if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
&& phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9a1c876..954aa44 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -1576,9 +1577,17 @@
p -> packageManager.checkPermission(
Manifest.permission.CONTROL_INCALL_EXPERIENCE,
p) == PackageManager.PERMISSION_GRANTED);
+
+ boolean hasAppOpsPermittedManageOngoingCalls = false;
+ if (isAppOpsPermittedManageOngoingCalls(serviceInfo.applicationInfo.uid,
+ serviceInfo.packageName)) {
+ hasAppOpsPermittedManageOngoingCalls = true;
+ }
+
boolean isCarModeUIService = serviceInfo.metaData != null &&
serviceInfo.metaData.getBoolean(
TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false);
+
if (isCarModeUIService && hasControlInCallPermission) {
return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
}
@@ -1593,7 +1602,8 @@
// Also allow any in-call service that has the control-experience permission (to ensure
// that it is a system app) and doesn't claim to show any UI.
- if (!isUIService && !isCarModeUIService && hasControlInCallPermission) {
+ if (!isUIService && !isCarModeUIService && (hasControlInCallPermission ||
+ hasAppOpsPermittedManageOngoingCalls)) {
return IN_CALL_SERVICE_TYPE_NON_UI;
}
@@ -2010,6 +2020,12 @@
return mCallsManager.getAudioState().isMuted();
}
+ private boolean isAppOpsPermittedManageOngoingCalls(int uid, String callingPackage) {
+ return PermissionChecker.checkPermissionForPreflight(mContext,
+ Manifest.permission.MANAGE_ONGOING_CALLS, PermissionChecker.PID_UNKNOWN, uid,
+ callingPackage) == PermissionChecker.PERMISSION_GRANTED;
+ }
+
private void sendCrashedInCallServiceNotification(String packageName) {
PackageManager packageManager = mContext.getPackageManager();
CharSequence appName;
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index e9ddfa0..9a4f7df 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -1266,11 +1266,11 @@
// Comparator which places PhoneAccounts with a specified sort order first, followed by
// those with no sort order.
Comparator<PhoneAccount> bySortOrder = (p1, p2) -> {
- String sort1 = p1.getExtras() == null ? null :
- p1.getExtras().getString(PhoneAccount.EXTRA_SORT_ORDER, null);
- String sort2 = p2.getExtras() == null ? null :
- p2.getExtras().getString(PhoneAccount.EXTRA_SORT_ORDER, null);
- return nullSafeStringComparator.compare(sort1, sort2);
+ int sort1 = p1.getExtras() == null ? Integer.MAX_VALUE:
+ p1.getExtras().getInt(PhoneAccount.EXTRA_SORT_ORDER, Integer.MAX_VALUE);
+ int sort2 = p2.getExtras() == null ? Integer.MAX_VALUE:
+ p2.getExtras().getInt(PhoneAccount.EXTRA_SORT_ORDER, Integer.MAX_VALUE);
+ return Integer.compare(sort1, sort2);
};
// Comparator which sorts PhoneAccounts by label.
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index f2531ed..f02f7e8 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -17,8 +17,16 @@
package com.android.server.telecom;
import android.telecom.Log;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyRegistryManager;
+import android.telephony.emergency.EmergencyNumber;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
/**
* Send a {@link TelephonyManager#ACTION_PHONE_STATE_CHANGED} broadcast when the call state
@@ -52,6 +60,10 @@
return;
}
updateStates(call);
+
+ if (call.isEmergencyCall() && !call.isIncoming()) {
+ sendOutgoingEmergencyCallEvent(call);
+ }
}
@Override
@@ -113,4 +125,31 @@
Log.i(this, "Broadcasted state change: %s", mCurrentState);
}
}
+
+ private void sendOutgoingEmergencyCallEvent(Call call) {
+ TelephonyManager tm = mCallsManager.getContext().getSystemService(TelephonyManager.class);
+ String strippedNumber =
+ PhoneNumberUtils.stripSeparators(call.getHandle().getSchemeSpecificPart());
+ Optional<EmergencyNumber> emergencyNumber = tm.getEmergencyNumberList().values().stream()
+ .flatMap(List::stream)
+ .filter(numberObj -> Objects.equals(numberObj.getNumber(), strippedNumber))
+ .findFirst();
+
+ int subscriptionId = tm.getSubscriptionId(call.getTargetPhoneAccount());
+ SubscriptionManager subscriptionManager =
+ mCallsManager.getContext().getSystemService(SubscriptionManager.class);
+ int simSlotIndex = SubscriptionManager.DEFAULT_PHONE_INDEX;
+ if (subscriptionManager != null) {
+ SubscriptionInfo subInfo =
+ subscriptionManager.getActiveSubscriptionInfo(subscriptionId);
+ if (subInfo != null) {
+ simSlotIndex = subInfo.getSimSlotIndex();
+ }
+ }
+
+ if (emergencyNumber.isPresent()) {
+ mRegistry.notifyOutgoingEmergencyCall(
+ simSlotIndex, subscriptionId, emergencyNumber.get());
+ }
+ }
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 1ee77a3..3481558 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -34,6 +34,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -831,6 +832,22 @@
}
/**
+ * @see android.telecom.TelecomManager#hasCompanionInCallServiceAccess
+ */
+ @Override
+ public boolean hasCompanionInCallServiceAccess(String callingPackage) {
+ try {
+ Log.startSession("TSI.hCICSA");
+ return PermissionChecker.checkPermissionForPreflight(mContext,
+ Manifest.permission.MANAGE_ONGOING_CALLS,
+ PermissionChecker.PID_UNKNOWN, Binder.getCallingUid(),
+ callingPackage) == PermissionChecker.PERMISSION_GRANTED;
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
* @see android.telecom.TelecomManager#isInManagedCall
*/
@Override
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index e37fe6b..4e32c9f 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -559,7 +559,8 @@
UserHandle userHandle) {
Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle);
- return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
/**
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index ec5f7ba..a64f39d 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.tests;
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -127,7 +129,8 @@
Analytics.dump(ip);
String dumpResult = sr.toString();
String[] expectedFields = {"startTime", "endTime", "direction", "isAdditionalCall",
- "isInterrupted", "callTechnologies", "callTerminationReason", "connectionService"};
+ "isInterrupted", "callTechnologies", "callTerminationReason", "connectionService",
+ "missedReason"};
for (String field : expectedFields) {
assertTrue(dumpResult.contains(field));
}
@@ -200,6 +203,8 @@
assertTrue(callAnalytics2.startTime > 0);
assertEquals(0, callAnalytics1.endTime);
assertEquals(0, callAnalytics2.endTime);
+ assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics1.missedReason);
+ assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics2.missedReason);
assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics1.callDirection);
assertEquals(Analytics.OUTGOING_DIRECTION, callAnalytics2.callDirection);
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 0cc04d6..3fd5e60 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -42,8 +42,11 @@
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.Process;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -56,6 +59,7 @@
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
import android.widget.Toast;
import com.android.server.telecom.AsyncRingtonePlayer;
@@ -116,6 +120,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -1381,6 +1387,80 @@
}
/**
+ * This test verifies a race condition seen with the new outgoing call broadcast.
+ * The scenario occurs when an incoming call is handled by an app which receives the
+ * NewOutgoingCallBroadcast. That app cancels the call by modifying the new outgoing call
+ * broadcast. Meanwhile, it places that same call again, expecting that Telecom will reuse the
+ * same same. HOWEVER, if the system delays passing of the new outgoing call broadcast back to
+ * Telecom, the app will have placed a new outgoing call BEFORE telecom is aware that the call
+ * was cancelled.
+ * The consequence of this is that in CallsManager#startOutgoingCall, when we first get the
+ * call to reuse, it will come back empty. Meanwhile, by the time we get into the various
+ * completable futures, the call WILL be in the list of calls which can be reused. Since the
+ * reusable call was not found earlier on, we end up aborting the new outgoing call.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testReuseCallConcurrency() throws Exception {
+ // Ensure contact info lookup succeeds
+ doAnswer(invocation -> {
+ Uri handle = invocation.getArgument(0);
+ CallerInfo info = new CallerInfo();
+ CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+ callerInfoFuture.complete(new Pair<>(handle, info));
+ return callerInfoFuture;
+ }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+ // Ensure we have candidate phone account handle info.
+ when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
+ SIM_1_HANDLE);
+ when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+ any(), anyInt(), anyInt())).thenReturn(
+ new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
+
+ // Let's add an existing call which is in connecting state; this emulates the case where
+ // we have an outgoing call which we have not yet disconnected as a result of the new
+ // outgoing call broadcast cancelling the call.
+ Call outgoingCall = addSpyCall(CallState.CONNECTING);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ // Get the handler for the main looper, which is the same one the CallsManager will use.
+ // We'll post a little something to block up the handler for now. This prevents
+ // startOutgoingCall from process it's completablefutures.
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(() -> {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ });
+
+ // Now while the main handler is blocked up we'll start another outgoing call.
+ CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+ outgoingCall.getHandle(), outgoingCall.getTargetPhoneAccount(), new Bundle(),
+ UserHandle.CURRENT, new Intent(), "com.test.stuff");
+
+ // And we'll add the initial outgoing call to the list of pending disconnects; this
+ // emulates a scenario where the pending disconnect call came in AFTER this call began.
+ mCallsManager.addToPendingCallsToDisconnect(outgoingCall);
+
+ // And we'll unblock the handler; this will let all the startOutgoingCall futures to happen.
+ latch.countDown();
+
+ // Wait for the future to become the present.
+ callFuture.join();
+
+ // We should have gotten a call out of this; if we did not then it means the call was
+ // aborted.
+ assertNotNull(callFuture.get());
+
+ // And the original call should be disconnected now.
+ assertEquals(CallState.DISCONNECTED, outgoingCall.getState());
+ }
+
+ /**
* Ensures that if we have two calls hosted by the same connection manager, but with
* different target phone accounts, we can swap between them.
* @throws Exception
@@ -1460,7 +1540,6 @@
// Mocks some methods to not call the real method.
doNothing().when(callSpy).unhold();
doNothing().when(callSpy).hold();
- doNothing().when(callSpy).disconnect();
doNothing().when(callSpy).answer(Matchers.anyInt());
doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index af062d7..a4302b6 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -42,6 +42,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
@@ -485,6 +486,7 @@
private final RoleManager mRoleManager = mock(RoleManager.class);
private final TelephonyRegistryManager mTelephonyRegistryManager =
mock(TelephonyRegistryManager.class);
+ private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class);
private TelecomManager mTelecomManager = mock(TelecomManager.class);
@@ -539,6 +541,14 @@
matches(Manifest.permission.CALL_COMPANION_APP), anyString()))
.thenReturn(PackageManager.PERMISSION_DENIED);
+ try {
+ when(mPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
+ mPermissionInfo);
+ } catch (PackageManager.NameNotFoundException ex) {
+ }
+
+ when(mPermissionInfo.isAppOp()).thenReturn(true);
+
// Used in CreateConnectionProcessor to rank emergency numbers by viability.
// For the test, make them all equal to INVALID so that the preferred PhoneAccount will be
// chosen.
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 693859b..6a6b9f3 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -52,6 +52,7 @@
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
@@ -122,6 +123,7 @@
@Mock ClockProxy mClockProxy;
@Mock Analytics.CallInfoImpl mCallInfo;
@Mock NotificationManager mNotificationManager;
+ @Mock PermissionInfo mMockPermissionInfo;
private static final int CURRENT_USER_ID = 900973;
private static final String DEF_PKG = "defpkg";
@@ -142,6 +144,9 @@
private static final String NONUI_PKG = "nonui_pkg";
private static final String NONUI_CLASS = "nonui_cls";
private static final int NONUI_UID = 6;
+ private static final String APPOP_NONUI_PKG = "appop_nonui_pkg";
+ private static final String APPOP_NONUI_CLASS = "appop_nonui_cls";
+ private static final int APPOP_NONUI_UID = 7;
private static final PhoneAccountHandle PA_HANDLE =
new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id");
@@ -173,6 +178,8 @@
when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
.thenReturn(mNotificationManager);
+ when(mMockPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
+ mMockPermissionInfo);
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
mEmergencyCallHelper, mCarModeTracker, mClockProxy);
@@ -198,6 +205,8 @@
return new String[] { CAR2_PKG };
case NONUI_UID:
return new String[] { NONUI_PKG };
+ case APPOP_NONUI_UID:
+ return new String[] { APPOP_NONUI_PKG };
}
return null;
}).when(mMockPackageManager).getPackagesForUid(anyInt());
@@ -213,6 +222,9 @@
when(mMockPackageManager.checkPermission(
matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
matches(NONUI_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mMockPackageManager.checkPermission(
+ matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
+ matches(APPOP_NONUI_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
}
@@ -822,6 +834,49 @@
verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
}
+ /**
+ * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
+ * supports third party app
+ */
+ @MediumTest
+ @Test
+ public void testBindToService_ThirdPartyApp() throws Exception {
+ setupMocks(false /* isExternalCall */);
+ setupMockPackageManager(false /* default */, false /* nonui */, true /* appop_nonui */,
+ true /* system */, false /* external calls */, false /* self mgd in default */,
+ false /* self mgd in car*/);
+
+ // Enable Third Party Companion App
+ when(mMockPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
+ mMockPermissionInfo);
+ when(mMockPermissionInfo.isAppOp()).thenReturn(true);
+ when(mMockAppOpsManager.unsafeCheckOpRawNoThrow(matches(
+ AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS), eq(APPOP_NONUI_UID),
+ matches(APPOP_NONUI_PKG))).thenReturn(AppOpsManager.MODE_ALLOWED);
+
+ // Now bind; we should bind to the system dialer and app op non ui app.
+ mInCallController.bindToServices(mMockCall);
+
+ // Bind InCallServices
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(2)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+
+ // Verify bind
+ assertEquals(2, bindIntentCaptor.getAllValues().size());
+
+ // Should have first bound to the system dialer.
+ verifyBinding(bindIntentCaptor, 0, SYS_PKG, SYS_CLASS);
+
+ // Should have next bound to the third party app op non ui app.
+ verifyBinding(bindIntentCaptor, 1, APPOP_NONUI_PKG, APPOP_NONUI_CLASS);
+ }
+
+
@MediumTest
@Test
public void testSanitizeContactName() throws Exception {
@@ -934,8 +989,8 @@
nullable(ContentResolver.class))).thenReturn(500L);
when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
- setupMockPackageManager(true /* default */, true /* nonui */, true /* system */,
- false /* external calls */,
+ setupMockPackageManager(true /* default */, true /* nonui */, false /* appop_nonui */ ,
+ true /* system */, false /* external calls */,
false /* self mgd in default*/, false /* self mgd in car*/);
mInCallController.bindToServices(mMockCall);
@@ -1195,9 +1250,21 @@
}};
}
+ private ResolveInfo getAppOpNonUiResolveinfo() {
+ return new ResolveInfo() {{
+ serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = APPOP_NONUI_PKG;
+ serviceInfo.name = APPOP_NONUI_CLASS;
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ serviceInfo.applicationInfo.uid = APPOP_NONUI_UID;
+ serviceInfo.enabled = true;
+ serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
+ }};
+ }
+
private void setupMockPackageManager(final boolean useDefaultDialer,
final boolean useSystemDialer, final boolean includeExternalCalls) {
- setupMockPackageManager(useDefaultDialer, false, useSystemDialer, includeExternalCalls,
+ setupMockPackageManager(useDefaultDialer, false, false, useSystemDialer, includeExternalCalls,
false /* self mgd */, false /* self mgd */);
}
@@ -1205,13 +1272,13 @@
final boolean useSystemDialer, final boolean includeExternalCalls,
final boolean includeSelfManagedCallsInDefaultDialer,
final boolean includeSelfManagedCallsInCarModeDialer) {
- setupMockPackageManager(useDefaultDialer, false /* nonui */, useSystemDialer,
- includeExternalCalls, includeSelfManagedCallsInDefaultDialer,
+ setupMockPackageManager(useDefaultDialer, false /* nonui */, false /* appop_nonui */,
+ useSystemDialer, includeExternalCalls, includeSelfManagedCallsInDefaultDialer,
includeSelfManagedCallsInCarModeDialer);
}
private void setupMockPackageManager(final boolean useDefaultDialer,
- final boolean useNonUiInCalls,
+ final boolean useNonUiInCalls, final boolean useAppOpNonUiInCalls,
final boolean useSystemDialer, final boolean includeExternalCalls,
final boolean includeSelfManagedCallsInDefaultDialer,
final boolean includeSelfManagedCallsInCarModeDialer) {
@@ -1254,6 +1321,10 @@
if (useNonUiInCalls) {
resolveInfo.add(getNonUiResolveinfo());
}
+ // InCallController uses a blank package name when querying for App Op non-ui incalls
+ if (useAppOpNonUiInCalls) {
+ resolveInfo.add(getAppOpNonUiResolveinfo());
+ }
}
return resolveInfo;
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
index 9dd90f5..db44dcd 100644
--- a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
@@ -400,9 +400,9 @@
TelecomBroadcastReceiver.class);
assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
- callBackIntent, PendingIntent.FLAG_NO_CREATE));
+ callBackIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
- smsIntent, PendingIntent.FLAG_NO_CREATE));
+ smsIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
}
@SmallTest
@@ -437,9 +437,9 @@
TelecomBroadcastReceiver.class);
assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
- callBackIntent, PendingIntent.FLAG_NO_CREATE));
+ callBackIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
assertNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
- smsIntent, PendingIntent.FLAG_NO_CREATE));
+ smsIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
}
@SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
new file mode 100644
index 0000000..d2c832a
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2019 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 android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.CallLog;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
+
+import com.android.server.telecom.Analytics;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallIntentProcessor;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.Map;
+
+public class MissedInformationTest extends TelecomSystemTest {
+ private static final int TEST_TIMEOUT_MILLIS = 1000;
+ private static final String TEST_NUMBER = "650-555-1212";
+ private static final String TEST_NUMBER_1 = "7";
+ private static final String PACKAGE_NAME = "com.android.server.telecom.tests";
+ @Mock ContentResolver mContentResolver;
+ @Mock IContentProvider mContentProvider;
+ @Mock Call mEmergencyCall;
+ @Mock Analytics.CallInfo mCallInfo;
+ private CallsManager mCallsManager;
+ private CallIntentProcessor.AdapterImpl mAdapter;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mCallsManager = mTelecomSystem.getCallsManager();
+ mAdapter = new CallIntentProcessor.AdapterImpl(mCallsManager.getDefaultDialerCache());
+ when(mContentResolver.getPackageName()).thenReturn(PACKAGE_NAME);
+ when(mContentResolver.acquireProvider(any(String.class))).thenReturn(mContentProvider);
+ when(mContentProvider.call(any(String.class), any(String.class),
+ any(String.class), any(Bundle.class))).thenReturn(new Bundle());
+ doReturn(mContentResolver).when(mSpyContext).getContentResolver();
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testNotMissedCall() throws Exception {
+ IdPair testCall = startAndMakeActiveIncomingCall(
+ TEST_NUMBER,
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ mConnectionServiceFixtureA.
+ sendSetDisconnected(testCall.mConnectionId, DisconnectCause.LOCAL);
+ ContentValues values = verifyInsertionWithCapture();
+
+ Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+ Analytics.CallInfoImpl callAnalytics = analyticsMap.get(testCall.mCallId);
+ assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics.missedReason);
+ assertEquals(MISSED_REASON_NOT_MISSED, (int) values.getAsInteger(CallLog.Calls.MISSED_REASON));
+ }
+
+ @Test
+ public void testEmergencyCallPlacing() throws Exception {
+ Analytics.dumpToParcelableAnalytics();
+ setUpEmergencyCall();
+ mCallsManager.addCall(mEmergencyCall);
+ assertTrue(mCallsManager.isInEmergencyCall());
+
+ Intent intent = new Intent();
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ mPhoneAccountA0.getAccountHandle());
+ mAdapter.processIncomingCallIntent(mCallsManager, intent);
+
+ ContentValues values = verifyInsertionWithCapture();
+
+ Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+ for (Analytics.CallInfoImpl ci : analyticsMap.values()) {
+ assertEquals(AUTO_MISSED_EMERGENCY_CALL, ci.missedReason);
+ }
+ assertEquals(AUTO_MISSED_EMERGENCY_CALL,
+ (int) values.getAsInteger(CallLog.Calls.MISSED_REASON));
+ }
+
+ @Test
+ public void testMaximumDialingCalls() throws Exception {
+ Analytics.dumpToParcelableAnalytics();
+ IdPair testDialingCall = startAndMakeDialingOutgoingCall(
+ TEST_NUMBER,
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ Intent intent = new Intent();
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ mPhoneAccountA0.getAccountHandle());
+ mAdapter.processIncomingCallIntent(mCallsManager, intent);
+
+ ContentValues values = verifyInsertionWithCapture();
+
+ Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+ for (String callId : analyticsMap.keySet()) {
+ if (callId.equals(testDialingCall.mCallId)) {
+ continue;
+ }
+ assertEquals(AUTO_MISSED_MAXIMUM_DIALING, analyticsMap.get(callId).missedReason);
+ }
+ assertEquals(AUTO_MISSED_MAXIMUM_DIALING,
+ (int) values.getAsInteger(CallLog.Calls.MISSED_REASON));
+ }
+
+ @Test
+ public void testMaximumRingingCalls() throws Exception {
+ Analytics.dumpToParcelableAnalytics();
+ IdPair testRingingCall = startAndMakeRingingIncomingCall(
+ TEST_NUMBER,
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ Intent intent = new Intent();
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ mPhoneAccountA0.getAccountHandle());
+ mAdapter.processIncomingCallIntent(mCallsManager, intent);
+
+ ContentValues values = verifyInsertionWithCapture();
+
+ Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+ for (String callId : analyticsMap.keySet()) {
+ if (callId.equals(testRingingCall.mCallId)) {
+ continue;
+ }
+ assertEquals(AUTO_MISSED_MAXIMUM_RINGING, analyticsMap.get(callId).missedReason);
+ }
+ assertEquals(AUTO_MISSED_MAXIMUM_RINGING,
+ (int) values.getAsInteger(CallLog.Calls.MISSED_REASON));
+ }
+
+ private ContentValues verifyInsertionWithCapture() {
+ ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
+ verify(mContentResolver, timeout(TEST_TIMEOUT_MILLIS))
+ .insert(any(Uri.class), captor.capture());
+ return captor.getValue();
+ }
+
+ private void setUpEmergencyCall() {
+ when(mEmergencyCall.isEmergencyCall()).thenReturn(true);
+ when(mEmergencyCall.getIntentExtras()).thenReturn(new Bundle());
+ when(mEmergencyCall.getAnalytics()).thenReturn(mCallInfo);
+ when(mEmergencyCall.getState()).thenReturn(CallState.ACTIVE);
+ when(mEmergencyCall.getContext()).thenReturn(mSpyContext);
+ when(mEmergencyCall.getHandle()).thenReturn(Uri.parse("tel:" + TEST_NUMBER));
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index 00ef87c..a56036a 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -725,16 +725,20 @@
mComponentContextFixture.addConnectionService(componentC,
Mockito.mock(IConnectionService.class));
+ Bundle account1Extras = new Bundle();
+ account1Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 1);
PhoneAccount account1 = new PhoneAccount.Builder(
makeQuickAccountHandle(componentA, "c"), "c")
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
- .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "A"))
+ .setExtras(account1Extras)
.build();
+ Bundle account2Extras = new Bundle();
+ account2Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 2);
PhoneAccount account2 = new PhoneAccount.Builder(
makeQuickAccountHandle(componentB, "b"), "b")
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
- .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "B"))
+ .setExtras(account2Extras)
.build();
PhoneAccount account3 = new PhoneAccount.Builder(
@@ -814,18 +818,23 @@
Mockito.mock(IConnectionService.class));
mComponentContextFixture.addConnectionService(componentZ,
Mockito.mock(IConnectionService.class));
+
+ Bundle account1Extras = new Bundle();
+ account1Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 2);
PhoneAccount account1 = new PhoneAccount.Builder(makeQuickAccountHandle(
makeQuickConnectionServiceComponentName(), "y"), "y")
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
- .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "2"))
+ .setExtras(account1Extras)
.build();
+ Bundle account2Extras = new Bundle();
+ account2Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 1);
PhoneAccount account2 = new PhoneAccount.Builder(makeQuickAccountHandle(
makeQuickConnectionServiceComponentName(), "z"), "z")
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
- .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "1"))
+ .setExtras(account2Extras)
.build();
PhoneAccount account3 = new PhoneAccount.Builder(makeQuickAccountHandle(
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 226e7ef..aeb68c5 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -1122,6 +1122,54 @@
return ids;
}
+ protected IdPair startAndMakeDialingOutgoingCall(
+ String number,
+ PhoneAccountHandle phoneAccountHandle,
+ ConnectionServiceFixture connectionServiceFixture) throws Exception {
+ IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
+ Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+
+ connectionServiceFixture.sendSetDialing(ids.mConnectionId);
+ if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+ assertEquals(Call.STATE_DIALING,
+ mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_DIALING,
+ mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+ }
+
+ return ids;
+ }
+
+ protected IdPair startAndMakeRingingIncomingCall(
+ String number,
+ PhoneAccountHandle phoneAccountHandle,
+ ConnectionServiceFixture connectionServiceFixture) throws Exception {
+ IdPair ids = startIncomingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
+
+ if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+ assertEquals(Call.STATE_RINGING,
+ mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_RINGING,
+ mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+ mInCallServiceFixtureX.mInCallAdapter
+ .answerCall(ids.mCallId, VideoProfile.STATE_AUDIO_ONLY);
+
+ waitForHandlerAction(mTelecomSystem.getCallsManager()
+ .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
+
+ if (!VideoProfile.isVideo(VideoProfile.STATE_AUDIO_ONLY)) {
+ verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
+ .answer(eq(ids.mConnectionId), any());
+ } else {
+ verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
+ .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_AUDIO_ONLY),
+ any());
+ }
+ }
+ return ids;
+ }
+
protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
int elapsed = 0;
while (elapsed < TEST_TIMEOUT) {