Merge "Fix issue where expectedListenerUpdate is not stable on test runs."
diff --git a/Android.bp b/Android.bp
index 88cffb8..1b422aa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -54,7 +54,8 @@
"androidx.legacy_legacy-support-core-utils",
"androidx.core_core",
"androidx.fragment_fragment",
- "androidx.test.ext.junit"
+ "androidx.test.ext.junit",
+ "platform-compat-test-rules",
],
srcs: [
"tests/src/**/*.java",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index dbde12c..d376b6a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -61,6 +61,7 @@
<uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS"/>
<uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"/>
<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"/>
<permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
@@ -262,7 +263,7 @@
<activity android:name=".RespondViaSmsSettings"
android:label="@string/respond_via_sms_setting_title"
android:configChanges="orientation|screenSize|keyboardHidden"
- android:theme="@style/Theme.Telecom.DialerSettings"
+ android:theme="@style/CallSettingsWithoutDividerTheme"
android:process=":ui"
android:exported="true">
<intent-filter>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 2e14650..acab8ef 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -15,6 +15,38 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsTelephony2TestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsTelephony3TestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsSimRestrictedApisTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsTelephonyProviderTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
}
],
"presubmit-large": [
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
deleted file mode 100644
index da31304..0000000
--- a/res/values-night/styles.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<resources>
-
- <style name="Theme.Telecom.DialerSettings" parent="@android:style/Theme.Material.Light">
- <item name="android:forceDarkAllowed">true</item>
- <item name="android:tint">@color/blocked_numbers_secondary_text_color</item>
- <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
- <item name="android:navigationBarColor">@color/background_color</item>
- <item name="android:windowBackground">@color/background_color</item>
- <item name="android:colorPrimaryDark">@color/background_color</item>
- <item name="android:actionOverflowButtonStyle">@style/TelecomDialerSettingsActionOverflowButtonStyle</item>
- <item name="android:windowContentOverlay">@null</item>
- </style>
-
- <style name="Theme.Telecom.BlockedNumbers" parent="@android:style/Theme.Material.Light">
- <item name="android:forceDarkAllowed">true</item>
- <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
- <item name="android:navigationBarColor">@color/background_color</item>
- <item name="android:windowBackground">@color/background_color</item>
- <item name="android:colorPrimaryDark">@color/background_color</item>
- <item name="android:windowContentOverlay">@null</item>
- <item name="android:colorAccent">@color/theme_color</item>
- <item name="android:listDivider">@null</item>
- </style>
-
-</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 5ba0a3f..53e1bcb 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -28,33 +28,24 @@
<style name="Theme.Telecom.DialerSettings" parent="@android:style/Theme.DeviceDefault.Light">
<item name="android:forceDarkAllowed">true</item>
- <item name="android:tint">@color/blocked_numbers_secondary_text_color</item>
<item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
<item name="android:actionOverflowButtonStyle">@style/TelecomDialerSettingsActionOverflowButtonStyle</item>
- <item name="android:colorPrimaryDark">@color/background_color</item>
<item name="android:windowLightStatusBar">true</item>
- <item name="android:navigationBarColor">@color/background_color</item>
- <item name="android:navigationBarDividerColor">@color/blocked_numbers_divider_color</item>
<item name="android:windowLightNavigationBar">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<style name="Theme.Telecom.BlockedNumbers" parent="@android:style/Theme.DeviceDefault.Light">
<item name="android:forceDarkAllowed">true</item>
- <item name="android:tint">@color/blocked_numbers_secondary_text_color</item>
<item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
- <item name="android:colorPrimaryDark">@color/background_color</item>
<item name="android:windowLightStatusBar">true</item>
- <item name="android:navigationBarColor">@color/background_color</item>
- <item name="android:navigationBarDividerColor">@color/blocked_numbers_divider_color</item>
<item name="android:windowLightNavigationBar">true</item>
<item name="android:windowContentOverlay">@null</item>
- <item name="android:colorAccent">@color/theme_color</item>
<item name="android:listDivider">@null</item>
</style>
<style name="TelecomDialerSettingsActionBarStyle" parent="android:Widget.DeviceDefault.ActionBar">
- <item name="android:background">@color/background_color</item>
+ <item name="android:forceDarkAllowed">true</item>
<item name="android:titleTextStyle">@style/BlockedNumbersTextHead1</item>
<item name="android:elevation">@dimen/dialer_settings_actionbar_elevation</item>
<!-- Empty icon -->
@@ -63,33 +54,36 @@
<style name="TelecomDialerSettingsActionOverflowButtonStyle"
parent="@android:style/Widget.DeviceDefault.Light.ActionButton.Overflow">
+ <item name="android:forceDarkAllowed">true</item>
<item name="android:src">@drawable/ic_more_vert_24dp</item>
</style>
<style name="BlockedNumbersButton" parent="BlockedNumbersTextPrimary2">
- <item name="android:textColor">@color/theme_color</item>
</style>
<style name="BlockedNumbersTextHead1"
parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
- <item name="android:textColor">@color/blocked_numbers_primary_text_color</item>
+ <item name="android:forceDarkAllowed">true</item>
<item name="android:textSize">@dimen/blocked_numbers_head1_font_size</item>
<item name="android:fontFamily">sans-serif-regular</item>
</style>
- <style name="BlockedNumbersTextPrimary2">
- <item name="android:textColor">@color/blocked_numbers_primary_text_color</item>
+ <style name="BlockedNumbersTextPrimary2" parent="Theme.Telecom.BlockedNumbers">
<item name="android:textSize">@dimen/blocked_numbers_primary2_font_size</item>
<item name="android:fontFamily">sans-serif-regular</item>
<item name="android:lineSpacingExtra">@dimen/blocked_numbers_line_spacing</item>
<item name="android:capitalize">sentences</item>
</style>
- <style name="BlockedNumbersTextSecondary">
- <item name="android:textColor">@color/blocked_numbers_secondary_text_color</item>
+ <style name="BlockedNumbersTextSecondary" parent="Theme.Telecom.BlockedNumbers">
<item name="android:textSize">@dimen/blocked_numbers_secondary_font_size</item>
<item name="android:fontFamily">sans-serif-regular</item>
<item name="android:lineSpacingExtra">@dimen/blocked_numbers_secondary_line_spacing</item>
<item name="android:capitalize">sentences</item>
</style>
+
+ <style name="CallSettingsWithoutDividerTheme" parent="Theme.Telecom.DialerSettings">
+ <item name="android:listDivider">@null</item>
+ </style>
+
</resources>
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index d7786e1..7f51f1b 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -172,13 +172,13 @@
// Use haptic-only ringtone or do not play anything.
if (!isRingerAudible || Uri.EMPTY.equals(incomingCall.getRingtone())) {
if (isVibrationEnabled) {
- mRingtone = factory.getHapticOnlyRingtone();
+ setRingtone(factory.getHapticOnlyRingtone());
if (mRingtone == null) {
completeHapticFuture(false /* ringtoneHasHaptics */);
return;
}
} else {
- mRingtone = null;
+ setRingtone(null);
completeHapticFuture(false /* ringtoneHasHaptics */);
return;
}
@@ -188,7 +188,7 @@
Log.i(this, "handlePlay: Play ringtone.");
if (mRingtone == null) {
- mRingtone = factory.getRingtone(incomingCall, volumeShaperConfig);
+ setRingtone(factory.getRingtone(incomingCall, volumeShaperConfig));
if (mRingtone == null) {
Uri ringtoneUri = incomingCall.getRingtone();
String ringtoneUriString = (ringtoneUri == null) ? "null" :
@@ -209,7 +209,7 @@
isVibrationEnabled);
SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
if (hasHaptics && (volumeShaperConfig == null
- || systemSettingsUtil.enableAudioCoupledVibrationForRampingRinger())) {
+ || systemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled())) {
AudioAttributes attributes = mRingtone.getAudioAttributes();
Log.d(this, "handlePlay: %s haptic channel",
(isVibrationEnabled ? "unmuting" : "muting"));
@@ -240,11 +240,7 @@
ThreadUtil.checkNotOnMainThread();
Log.i(this, "Stop ringtone.");
- if (mRingtone != null) {
- Log.d(this, "Ringtone.stop() invoked.");
- mRingtone.stop();
- mRingtone = null;
- }
+ setRingtone(null);
synchronized(this) {
if (mHandler.hasMessages(EVENT_PLAY)) {
@@ -262,6 +258,17 @@
return mRingtone != null;
}
+ private void setRingtone(@Nullable Ringtone ringtone) {
+ // Make sure that any previously created instance of Ringtone is stopped so the MediaPlayer
+ // can be released, before replacing mRingtone with a new instance. This is always created
+ // as a looping Ringtone, so if not stopped it will keep playing on the background.
+ if (mRingtone != null) {
+ Log.d(this, "Ringtone.stop() invoked.");
+ mRingtone.stop();
+ }
+ mRingtone = ringtone;
+ }
+
private void completeHapticFuture(boolean ringtoneHasHaptics) {
if (mHapticsFuture != null) {
mHapticsFuture.complete(ringtoneHasHaptics);
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 08dfb1e..60016fd 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -898,7 +898,7 @@
@Override
public String toString() {
return String.format(Locale.US, "[Call id=%s, state=%s, tpac=%s, cmgr=%s, handle=%s, "
- + "vidst=%s, childs(%d), has_parent(%b), cap=%s, prop=%s]",
+ + "vidst=%s, childs(%d), has_parent(%b), cap=%s, prop=%s], voip=%b",
mId,
CallState.toString(getParcelableCallState()),
getTargetPhoneAccount(),
@@ -908,7 +908,8 @@
getChildCalls().size(),
getParentCall() != null,
Connection.capabilitiesToStringShort(getConnectionCapabilities()),
- Connection.propertiesToStringShort(getConnectionProperties()));
+ Connection.propertiesToStringShort(getConnectionProperties()),
+ mIsVoipAudioMode);
}
@Override
@@ -1262,7 +1263,7 @@
}
}
- boolean isRingbackRequested() {
+ public boolean isRingbackRequested() {
return mRingbackRequested;
}
@@ -3653,6 +3654,9 @@
}
public void setIsVoipAudioMode(boolean audioModeIsVoip) {
+ if (mIsVoipAudioMode != audioModeIsVoip) {
+ Log.addEvent(this, LogUtils.Events.SET_VOIP_MODE, audioModeIsVoip ? "Y" : "N");
+ }
mIsVoipAudioMode = audioModeIsVoip;
for (Listener l : mListeners) {
l.onIsVoipAudioModeChanged(this);
@@ -4138,8 +4142,8 @@
return mStartRingTime;
}
- public void setStartRingTime(long startRingTime) {
- mStartRingTime = startRingTime;
+ public void setStartRingTime() {
+ mStartRingTime = mClockProxy.elapsedRealtime();
}
public CharSequence getCallScreeningAppName() {
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 6a7261e..1863cde 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -107,6 +107,10 @@
// No audio management for calls in a conference, or external calls.
return;
}
+ if (oldState == newState) {
+ // State did not change, so no need to do anything.
+ return;
+ }
Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
CallState.toString(oldState), CallState.toString(newState));
@@ -464,8 +468,13 @@
@VisibleForTesting
public boolean startRinging() {
synchronized (mCallsManager.getLock()) {
- return mRinger.startRinging(mForegroundCall,
+ Call localForegroundCall = mForegroundCall;
+ boolean result = mRinger.startRinging(localForegroundCall,
mCallAudioRouteStateMachine.isHfpDeviceAvailable());
+ if (result) {
+ localForegroundCall.setStartRingTime();
+ }
+ return result;
}
}
@@ -537,14 +546,14 @@
pw.println("Foreground call:");
pw.println(mForegroundCall);
- pw.println("CallAudioModeStateMachine pending messages:");
+ pw.println("CallAudioModeStateMachine:");
pw.increaseIndent();
- mCallAudioModeStateMachine.dumpPendingMessages(pw);
+ mCallAudioModeStateMachine.dump(pw);
pw.decreaseIndent();
- pw.println("CallAudioRouteStateMachine pending messages:");
+ pw.println("CallAudioRouteStateMachine:");
pw.increaseIndent();
- mCallAudioRouteStateMachine.dumpPendingMessages(pw);
+ mCallAudioRouteStateMachine.dump(pw);
pw.decreaseIndent();
pw.println("BluetoothDeviceManager:");
@@ -557,6 +566,7 @@
@VisibleForTesting
public void setIsTonePlaying(boolean isTonePlaying) {
+ Log.i(this, "setIsTonePlaying; isTonePlaying=%b", isTonePlaying);
mIsTonePlaying = isTonePlaying;
mCallAudioModeStateMachine.sendMessageWithArgs(
isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
@@ -728,10 +738,31 @@
.setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
.setIsTonePlaying(mIsTonePlaying)
.setForegroundCallIsVoip(
- mForegroundCall != null && mForegroundCall.getIsVoipAudioMode())
+ mForegroundCall != null && isCallVoip(mForegroundCall))
.setSession(Log.createSubsession()).build();
}
+ /**
+ * Determines if a {@link Call} is a VOIP call for audio purposes.
+ * For top level calls, we get this from {@link Call#getIsVoipAudioMode()}. A {@link Call}
+ * representing a {@link android.telecom.Conference}, however, has no means of specifying that
+ * it is a VOIP conference, so we will get that attribute from one of the children.
+ * @param call The call.
+ * @return {@code true} if the call is a VOIP call, {@code false} if is a SIM call.
+ */
+ @VisibleForTesting
+ public boolean isCallVoip(Call call) {
+ if (call.isConference() && call.getChildCalls() != null
+ && call.getChildCalls().size() > 0 ) {
+ // If this is a conference with children, we can get the VOIP audio mode attribute from
+ // one of the children. The Conference doesn't have a VOIP audio mode property, so we
+ // need to infer from the first child.
+ Call firstChild = call.getChildCalls().get(0);
+ return firstChild.getIsVoipAudioMode();
+ }
+ return call.getIsVoipAudioMode();
+ }
+
private HashSet<Call> getBinForCall(Call call) {
if (call.getState() == CallState.ANSWERED) {
// If the call has the speed-up-mt-audio capability, treat answered state as active
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 2aa9d5d..a1c5f4b 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -22,6 +22,7 @@
import android.telecom.Log;
import android.telecom.Logging.Runnable;
import android.telecom.Logging.Session;
+import android.util.LocalLog;
import android.util.SparseArray;
import com.android.internal.util.IState;
@@ -30,6 +31,11 @@
import com.android.internal.util.StateMachine;
public class CallAudioModeStateMachine extends StateMachine {
+ /**
+ * Captures the most recent CallAudioModeStateMachine state transitions and the corresponding
+ * changes to the {@link AudioManager#setMode}.
+ */
+ private LocalLog mLocalLog = new LocalLog(20);
public static class Factory {
public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
AudioManager am) {
@@ -227,9 +233,12 @@
private class UnfocusedState extends BaseState {
@Override
public void enter() {
+ Log.i(LOG_TAG, "Audio focus entering UNFOCUSED state");
+ mLocalLog.log("Enter UNFOCUSED");
if (mIsInitialized) {
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ mLocalLog.log("Mode MODE_NORMAL");
mMostRecentMode = AudioManager.MODE_NORMAL;
// Don't release focus here -- wait until we get a signal that any other audio
// operations triggered by this are done before releasing focus.
@@ -290,9 +299,12 @@
private class AudioProcessingFocusState extends BaseState {
@Override
public void enter() {
+ Log.i(LOG_TAG, "Audio focus entering AUDIO_PROCESSING state");
+ mLocalLog.log("Enter AUDIO_PROCESSING");
if (mIsInitialized) {
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
mAudioManager.setMode(NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING);
+ mLocalLog.log("Mode MODE_CALL_SCREENING");
mMostRecentMode = NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING;
}
}
@@ -371,6 +383,7 @@
// trips up the audio system.
if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+ mLocalLog.log("Mode MODE_RINGTONE");
}
mCallAudioManager.setCallAudioRouteFocusState(
CallAudioRouteStateMachine.RINGING_FOCUS);
@@ -383,6 +396,7 @@
@Override
public void enter() {
Log.i(LOG_TAG, "Audio focus entering RINGING state");
+ mLocalLog.log("Enter RINGING");
tryStartRinging();
mCallAudioManager.stopCallWaiting();
}
@@ -456,9 +470,11 @@
@Override
public void enter() {
Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
+ mLocalLog.log("Enter SIM_CALL");
mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ mLocalLog.log("Mode MODE_IN_CALL");
mMostRecentMode = AudioManager.MODE_IN_CALL;
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
}
@@ -537,9 +553,11 @@
@Override
public void enter() {
Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
+ mLocalLog.log("Enter VOIP_CALL");
mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+ mLocalLog.log("Mode MODE_IN_COMMUNICATION");
mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
}
@@ -614,9 +632,11 @@
@Override
public void enter() {
Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
+ mLocalLog.log("Enter TONE/HOLDING");
mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mAudioManager.setMode(mMostRecentMode);
+ mLocalLog.log("Mode " + mMostRecentMode);
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
}
@@ -763,6 +783,13 @@
getHandler().getLooper().dump(pw::println, "");
}
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("History:");
+ mLocalLog.dump(pw);
+ pw.println("Pending Msg:");
+ dumpPendingMessages(pw);
+ }
+
@Override
protected void onPostHandleMessage(Message msg) {
Log.endSession();
diff --git a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
index 2b6ba64..d96f953 100644
--- a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
@@ -45,6 +45,16 @@
return mBluetoothRouteManager.isBluetoothAudioConnectedOrPending();
}
+ public boolean isHearingAidDeviceOn() {
+ return mBluetoothRouteManager.isCachedHearingAidDevice(
+ mBluetoothRouteManager.getBluetoothAudioConnectedDevice());
+ }
+
+ public boolean isLeAudioDeviceOn() {
+ return mBluetoothRouteManager.isCachedLeAudioDevice(
+ mBluetoothRouteManager.getBluetoothAudioConnectedDevice());
+ }
+
@Override
public void onBluetoothDeviceListChanged() {
mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index f6f710e..2ed7d95 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -411,6 +411,8 @@
Log.w(this, "Ignoring switch to headset command. Not available.");
}
return HANDLED;
+ case CONNECT_DOCK:
+ // fall through; we want to switch to speaker mode when docked and in a call.
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
setSpeakerphoneOn(true);
@@ -486,6 +488,8 @@
Log.w(this, "Ignoring switch to headset command. Not available.");
}
return HANDLED;
+ case CONNECT_DOCK:
+ // fall through; we want to go to the quiescent speaker route when out of a call
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
transitionTo(mQuiescentSpeakerRoute);
@@ -537,10 +541,6 @@
case BT_AUDIO_DISCONNECTED:
// This may be sent as a confirmation by the BT stack after switch off BT.
return HANDLED;
- case CONNECT_DOCK:
- setSpeakerphoneOn(true);
- sendInternalMessage(SWITCH_SPEAKER);
- return HANDLED;
case DISCONNECT_DOCK:
// Nothing to do here
return HANDLED;
@@ -1273,6 +1273,8 @@
case SPEAKER_ON:
// Nothing to do
return HANDLED;
+ case DISCONNECT_DOCK:
+ // Fall-through; same as if speaker goes off, we want to switch baseline.
case SPEAKER_OFF:
sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
return HANDLED;
@@ -1619,6 +1621,15 @@
quitNow();
}
+ public void dump(IndentingPrintWriter pw) {
+ pw.print("Current state: ");
+ pw.println(getCurrentState().getName());
+ pw.println("Pending messages:");
+ pw.increaseIndent();
+ dumpPendingMessages(pw);
+ pw.decreaseIndent();
+ }
+
public void dumpPendingMessages(IndentingPrintWriter pw) {
getHandler().getLooper().dump(pw::println, "");
}
@@ -1629,8 +1640,26 @@
private void setSpeakerphoneOn(boolean on) {
Log.i(this, "turning speaker phone %s", on);
- mAudioManager.setSpeakerphoneOn(on);
- mStatusBarNotifier.notifySpeakerphone(on);
+ AudioDeviceInfo speakerDevice = null;
+ for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
+ if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ speakerDevice = info;
+ break;
+ }
+ }
+ boolean speakerOn = false;
+ if (speakerDevice != null && on) {
+ boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
+ if (result) {
+ speakerOn = true;
+ }
+ } else {
+ AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
+ if (curDevice != null && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ mAudioManager.clearCommunicationDevice();
+ }
+ }
+ mStatusBarNotifier.notifySpeakerphone(speakerOn);
}
private void setBluetoothOn(String address) {
@@ -1678,8 +1707,8 @@
// may run as a separate user from the foreground user. If we
// used AudioManager directly, we would change mute for the system's
// user and not the current foreground, which we want to avoid.
- audio.setMicrophoneMute(
- mute, mContext.getOpPackageName(), getCurrentUserId());
+ audio.setMicrophoneMute(mute, mContext.getOpPackageName(),
+ getCurrentUserId(), mContext.getAttributionTag());
} catch (RemoteException e) {
Log.e(this, e, "Remote exception while toggling mute.");
}
diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java
index 9435250..167bd1b 100644
--- a/src/com/android/server/telecom/CallScreeningServiceHelper.java
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -145,9 +145,9 @@
if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) {
Log.i(this, "bindAndGetCallIdentification - bind failed");
- Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName);
mFuture.complete(null);
}
+ Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName);
// Set up a timeout so that we're not waiting forever for the caller ID information.
Handler handler = new Handler();
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 6757ead..4d829bb 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -17,6 +17,10 @@
package com.android.server.telecom;
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static android.provider.CallLog.Calls.SHORT_RING_THRESHOLD;
+import static android.provider.CallLog.Calls.USER_MISSED_NEVER_RANG;
+import static android.provider.CallLog.Calls.USER_MISSED_NO_ANSWER;
+import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
import static android.telecom.TelecomManager.ACTION_POST_CALL;
import static android.telecom.TelecomManager.DURATION_LONG;
import static android.telecom.TelecomManager.DURATION_MEDIUM;
@@ -136,6 +140,7 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -339,10 +344,8 @@
private RespondViaSmsManager mRespondViaSmsManager;
private final Ringer mRinger;
private final InCallWakeLockController mInCallWakeLockController;
- // For this set initial table size to 16 because we add 13 listeners in
- // the CallsManager constructor.
- private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
+ private final CopyOnWriteArrayList<CallsManagerListener> mListeners =
+ new CopyOnWriteArrayList<>();
private final HeadsetMediaButton mHeadsetMediaButton;
private final WiredHeadsetManager mWiredHeadsetManager;
private final SystemStateHelper mSystemStateHelper;
@@ -575,7 +578,6 @@
mListeners.add(mInCallWakeLockController);
mListeners.add(statusBarNotifier);
mListeners.add(mCallLogManager);
- mListeners.add(mPhoneStateBroadcaster);
mListeners.add(mInCallController);
mListeners.add(mCallDiagnosticServiceController);
mListeners.add(mCallAudioManager);
@@ -586,6 +588,9 @@
mListeners.add(mProximitySensorManager);
mListeners.add(audioProcessingNotification);
+ // this needs to be after the mCallAudioManager
+ mListeners.add(mPhoneStateBroadcaster);
+
// There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
final UserManager userManager = UserManager.get(mContext);
// Don't load missed call if it is run in split user model.
@@ -2081,7 +2086,7 @@
int videoState, boolean shouldCancelCall,
String uiAction) {
Log.i(this, "onCallRedirectionComplete for Call %s with handle %s" +
- " and phoneAccountHandle %s", call, handle, phoneAccountHandle);
+ " and phoneAccountHandle %s", call, Log.pii(handle), phoneAccountHandle);
boolean endEarly = false;
String disconnectReason = "";
@@ -2193,7 +2198,7 @@
* @param callId The ID of the call to show the redirection dialog for.
*/
private void showRedirectionDialog(@NonNull String callId, @NonNull CharSequence appName) {
- AlertDialog confirmDialog = FrameworksUtils.makeAlertDialogBuilder(mContext).create();
+ AlertDialog confirmDialog = (new AlertDialog.Builder(mContext)).create();
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
View dialogView = layoutInflater.inflate(R.layout.call_redirection_confirm_dialog, null);
@@ -3142,6 +3147,22 @@
// be marked as missed.
call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
}
+ if (call.getState() == CallState.NEW
+ && disconnectCause.getCode() == DisconnectCause.MISSED) {
+ Log.i(this, "markCallAsDisconnected: missed call never rang ", call.getId());
+ call.setMissedReason(USER_MISSED_NEVER_RANG);
+ }
+ if (call.getState() == CallState.RINGING
+ || call.getState() == CallState.SIMULATED_RINGING) {
+ if (call.getStartRingTime() > 0
+ && (mClockProxy.elapsedRealtime() - call.getStartRingTime())
+ < SHORT_RING_THRESHOLD) {
+ Log.i(this, "markCallAsDisconnected; callid=%s, short ring.", call.getId());
+ call.setUserMissed(USER_MISSED_SHORT_RING);
+ } else if (call.getStartRingTime() > 0) {
+ call.setUserMissed(USER_MISSED_NO_ANSWER);
+ }
+ }
// If a call diagnostic service is in use, we will log the original telephony-provided
// disconnect cause, inform the CDS of the disconnection, and then chain the update of the
@@ -3300,7 +3321,7 @@
*
* @return {@code True} if there are any non-external calls, {@code false} otherwise.
*/
- boolean hasAnyCalls() {
+ public boolean hasAnyCalls() {
if (mCalls.isEmpty()) {
return false;
}
@@ -3987,6 +4008,19 @@
return false;
}
+ /**
+ * Determines if there are any ongoing self managed calls for the given package/user.
+ * @param packageName The package name to check.
+ * @param userHandle The userhandle to check.
+ * @return {@code true} if the app has ongoing calls, or {@code false} otherwise.
+ */
+ public boolean isInSelfManagedCall(String packageName, UserHandle userHandle) {
+ return mCalls.stream().anyMatch(
+ c -> c.isSelfManaged()
+ && c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
+ && c.getTargetPhoneAccount().getUserHandle().equals(userHandle));
+ }
+
@VisibleForTesting
public int getNumCallsWithState(final boolean isSelfManaged, Call excludeCall,
PhoneAccountHandle phoneAccountHandle, int... states) {
@@ -5595,4 +5629,9 @@
mCalls.forEach(c -> Log.addEvent(c, LogUtils.Events.USER_LOG_MARK, message));
Log.addEvent(null /* global */, LogUtils.Events.USER_LOG_MARK, message);
}
+
+ @VisibleForTesting
+ public Ringer getRinger() {
+ return mRinger;
+ }
}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 3a4d143..5bb1dbe 100755
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -62,6 +62,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.Objects;
/**
* Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
@@ -967,6 +968,31 @@
if (alreadyAddedConnection != null
&& mCallIdMapper.getCall(callId) == null) {
+ if (!Objects.equals(connection.getHandle(),
+ alreadyAddedConnection.getHandle())) {
+ alreadyAddedConnection.setHandle(connection.getHandle());
+ }
+ if (connection.getHandlePresentation() !=
+ alreadyAddedConnection.getHandlePresentation()) {
+ alreadyAddedConnection.setHandle(connection.getHandle(),
+ connection.getHandlePresentation());
+ }
+ if (!Objects.equals(connection.getCallerDisplayName(),
+ alreadyAddedConnection.getCallerDisplayName())) {
+ alreadyAddedConnection.setCallerDisplayName(connection
+ .getCallerDisplayName(),
+ connection.getCallerDisplayNamePresentation());
+ }
+ if (connection.getConnectionCapabilities() !=
+ alreadyAddedConnection.getConnectionCapabilities()) {
+ alreadyAddedConnection.setConnectionCapabilities(connection
+ .getConnectionCapabilities());
+ }
+ if (connection.getConnectionProperties() !=
+ alreadyAddedConnection.getConnectionProperties()) {
+ alreadyAddedConnection.setConnectionCapabilities(connection
+ .getConnectionProperties());
+ }
mCallIdMapper.addCall(alreadyAddedConnection, callId);
alreadyAddedConnection
.replaceConnectionService(ConnectionServiceWrapper.this);
diff --git a/src/com/android/server/telecom/DockManager.java b/src/com/android/server/telecom/DockManager.java
index 46b2efc..dda5711 100644
--- a/src/com/android/server/telecom/DockManager.java
+++ b/src/com/android/server/telecom/DockManager.java
@@ -29,7 +29,12 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-/** Listens for and caches car dock state. */
+/**
+ * Listens for and caches car dock state.
+ * Testing; you can enable/disable dock with adb commands:
+ * adb shell dumpsys DockObserver set state 3
+ * adb shell dumpsys DockObserver set state 0
+ */
@VisibleForTesting
public class DockManager {
@VisibleForTesting
diff --git a/src/com/android/server/telecom/FrameworksUtils.java b/src/com/android/server/telecom/FrameworksUtils.java
deleted file mode 100644
index 08a19f5..0000000
--- a/src/com/android/server/telecom/FrameworksUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.telecom;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.res.Configuration;
-
-/**
- * This class provides utility functions over framework APIs
- */
-public class FrameworksUtils {
- /**
- * Create a new instance of {@link AlertDialog.Builder}.
- * @param context reference to a Context
- * @return an instance of AlertDialog.Builder
- */
- public static AlertDialog.Builder makeAlertDialogBuilder(Context context) {
- boolean isDarkTheme = (context.getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
- return new AlertDialog.Builder(context, isDarkTheme
- ? android.R.style.Theme_DeviceDefault_Dialog_Alert : 0);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index ad95c34..b1471c2 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -46,6 +46,47 @@
private static final int MSG_MEDIA_SESSION_INITIALIZE = 0;
private static final int MSG_MEDIA_SESSION_SET_ACTIVE = 1;
+ /**
+ * Wrapper class that abstracts an instance of {@link MediaSession} to the
+ * {@link MediaSessionAdapter} interface this class uses. This is done because
+ * {@link MediaSession} is a final class and cannot be mocked for testing purposes.
+ */
+ public class MediaSessionWrapper implements MediaSessionAdapter {
+ private final MediaSession mMediaSession;
+
+ public MediaSessionWrapper(MediaSession mediaSession) {
+ mMediaSession = mediaSession;
+ }
+
+ /**
+ * Sets the underlying {@link MediaSession} active status.
+ * @param active
+ */
+ @Override
+ public void setActive(boolean active) {
+ mMediaSession.setActive(active);
+ }
+
+ /**
+ * Gets the underlying {@link MediaSession} active status.
+ * @return {@code true} if active, {@code false} otherwise.
+ */
+ @Override
+ public boolean isActive() {
+ return mMediaSession.isActive();
+ }
+ }
+
+ /**
+ * Interface which defines the basic functionality of a {@link MediaSession} which is important
+ * for the {@link HeadsetMediaButton} to operator; this is for testing purposes so we can mock
+ * out that functionality.
+ */
+ public interface MediaSessionAdapter {
+ void setActive(boolean active);
+ boolean isActive();
+ }
+
private final MediaSession.Callback mSessionCallback = new MediaSession.Callback() {
@Override
public boolean onMediaButtonEvent(Intent intent) {
@@ -81,7 +122,7 @@
session.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY
| MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
session.setPlaybackToLocal(AUDIO_ATTRIBUTES);
- mSession = session;
+ mSession = new MediaSessionWrapper(session);
break;
}
case MSG_MEDIA_SESSION_SET_ACTIVE: {
@@ -102,9 +143,37 @@
private final Context mContext;
private final CallsManager mCallsManager;
private final TelecomSystem.SyncRoot mLock;
- private MediaSession mSession;
+ private MediaSessionAdapter mSession;
private KeyEvent mLastHookEvent;
+ /**
+ * Constructor used for testing purposes to initialize a {@link HeadsetMediaButton} with a
+ * specified {@link MediaSessionAdapter}. Will not trigger MSG_MEDIA_SESSION_INITIALIZE and
+ * cause an actual {@link MediaSession} instance to be created.
+ * @param context the context
+ * @param callsManager the mock calls manager
+ * @param lock the lock
+ * @param adapter the adapter
+ */
+ @VisibleForTesting
+ public HeadsetMediaButton(
+ Context context,
+ CallsManager callsManager,
+ TelecomSystem.SyncRoot lock,
+ MediaSessionAdapter adapter) {
+ mContext = context;
+ mCallsManager = callsManager;
+ mLock = lock;
+ mSession = adapter;
+ }
+
+ /**
+ * Production code constructor; this version triggers MSG_MEDIA_SESSION_INITIALIZE which will
+ * create an actual instance of {@link MediaSession}.
+ * @param context the context
+ * @param callsManager the calls manager
+ * @param lock the telecom lock
+ */
public HeadsetMediaButton(
Context context,
CallsManager callsManager,
@@ -155,6 +224,13 @@
if (call.isExternalCall()) {
return;
}
+ handleCallAddition();
+ }
+
+ /**
+ * Triggers session activation due to call addition.
+ */
+ private void handleCallAddition() {
mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
}
@@ -164,6 +240,13 @@
if (call.isExternalCall()) {
return;
}
+ handleCallRemoval();
+ }
+
+ /**
+ * Triggers session deactivation due to call removal.
+ */
+ private void handleCallRemoval() {
if (!mCallsManager.hasAnyCalls()) {
mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
}
@@ -172,10 +255,20 @@
/** ${inheritDoc} */
@Override
public void onExternalCallChanged(Call call, boolean isExternalCall) {
+ // Note: We don't use the onCallAdded/onCallRemoved methods here since they do checks to see
+ // if the call is external or not and would skip the session activation/deactivation.
if (isExternalCall) {
- onCallRemoved(call);
+ handleCallRemoval();
} else {
- onCallAdded(call);
+ handleCallAddition();
}
}
+
+ @VisibleForTesting
+ /**
+ * @return the handler this class instance uses for operation; used for unit testing.
+ */
+ public Handler getHandler() {
+ return mMediaSessionHandler;
+ }
}
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 766cb12..ec87555 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -21,9 +21,13 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.app.compat.CompatChanges;
import android.app.Notification;
import android.app.NotificationManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -37,6 +41,7 @@
import android.content.pm.ServiceInfo;
import android.hardware.SensorPrivacyManager;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -86,7 +91,6 @@
AppOpsManager.OnOpActiveChangedListener {
public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName();
public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
-
public class InCallServiceConnection {
/**
* Indicates that a call to {@link #connect(Call)} has succeeded and resulted in a
@@ -616,24 +620,17 @@
getInCallServiceComponent(packageName,
IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabed */);
- if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)) {
+ if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)
+ && carModeConnectionInfo != null) {
Log.i(this, "changeCarModeApp: " + currentConnectionInfo + " => "
+ carModeConnectionInfo);
if (mIsConnected) {
mCurrentConnection.disconnect();
}
- if (carModeConnectionInfo != null) {
- // Valid car mode app.
- mCarModeConnection = mCurrentConnection =
- new InCallServiceBindingConnection(carModeConnectionInfo);
- mIsCarMode = true;
- } else {
- // The app is not enabled. Using the default dialer connection instead
- mCarModeConnection = null;
- mIsCarMode = false;
- mCurrentConnection = mDialerConnection;
- }
+ mCarModeConnection = mCurrentConnection =
+ new InCallServiceBindingConnection(carModeConnectionInfo);
+ mIsCarMode = true;
int result = mCurrentConnection.connect(null);
mIsConnected = result == CONNECTION_SUCCEEDED;
@@ -1625,25 +1622,31 @@
mNonUIInCallServiceConnections.connect(call);
}
- private InCallServiceInfo getDefaultDialerComponent() {
- String packageName = mDefaultDialerCache.getDefaultDialerApplication(
+ private @Nullable InCallServiceInfo getDefaultDialerComponent() {
+ String defaultPhoneAppName = mDefaultDialerCache.getDefaultDialerApplication(
mCallsManager.getCurrentUserHandle().getIdentifier());
- String systemPackageName = mDefaultDialerCache.getSystemDialerApplication();
- Log.d(this, "Default Dialer package: " + packageName);
+ String systemPhoneAppName = mDefaultDialerCache.getSystemDialerApplication();
- InCallServiceInfo defaultDialerComponent =
- (systemPackageName != null && systemPackageName.equals(packageName))
- ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI,
- true /* ignoreDisabled */)
- : getInCallServiceComponent(packageName,
+ Log.d(this, "getDefaultDialerComponent: defaultPhoneAppName=[%s]", defaultPhoneAppName);
+ Log.d(this, "getDefaultDialerComponent: systemPhoneAppName=[%s]", systemPhoneAppName);
+
+ // Get the defaultPhoneApp InCallService component...
+ InCallServiceInfo defaultPhoneAppComponent =
+ (systemPhoneAppName != null && systemPhoneAppName.equals(defaultPhoneAppName)) ?
+ /* The defaultPhoneApp is also the systemPhoneApp. Get systemPhoneApp info*/
+ getInCallServiceComponent(defaultPhoneAppName,
+ IN_CALL_SERVICE_TYPE_SYSTEM_UI, true /* ignoreDisabled */)
+ /* The defaultPhoneApp is NOT the systemPhoneApp. Get defaultPhoneApp info*/
+ : getInCallServiceComponent(defaultPhoneAppName,
IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI, true /* ignoreDisabled */);
- /* TODO: in Android 12 re-enable this an InCallService is required by the dialer role.
- if (packageName != null && defaultDialerComponent == null) {
- // The in call service of default phone app is disabled, send notification.
- sendCrashedInCallServiceNotification(packageName);
- }
- */
- return defaultDialerComponent;
+
+ Log.d(this, "getDefaultDialerComponent: defaultPhoneAppComponent=[%s]",
+ defaultPhoneAppComponent);
+
+ // defaultPhoneAppComponent is null in the case when the defaultPhoneApp does not implement
+ // the InCallService && is the package is different from the systemPhoneApp
+
+ return defaultPhoneAppComponent;
}
private InCallServiceInfo getCurrentCarModeComponent() {
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 524d119..4665ec2 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -33,6 +33,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Play a call-related tone (ringback, busy signal, etc.) either through ToneGenerator, or using a
@@ -181,12 +182,15 @@
private static final int STATE_ON = 1;
private static final int STATE_STOPPED = 2;
+ // Invalid audio stream
+ private static final int STREAM_INVALID = -1;
+
/**
* Keeps count of the number of actively playing tones so that we can notify CallAudioManager
* when we need focus and when it can be release. This should only be manipulated from the main
* thread.
*/
- private static int sTonesPlaying = 0;
+ private static AtomicInteger sTonesPlaying = new AtomicInteger(0);
private final CallAudioManager mCallAudioManager;
private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
@@ -213,6 +217,12 @@
private final AudioManagerAdapter mAudioManagerAdapter;
/**
+ * Latch used for awaiting on playback, which may be interrupted if the tone is stopped from
+ * outside the playback.
+ */
+ private final CountDownLatch mPlaybackLatch = new CountDownLatch(1);
+
+ /**
* Initializes the tone player. Private; use the {@link Factory} to create tone players.
*
* @param toneId ID of the tone to play, see TONE_* constants.
@@ -355,8 +365,14 @@
if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) {
stream = AudioManager.STREAM_BLUETOOTH_SCO;
}
-
if (toneType != ToneGenerator.TONE_UNKNOWN) {
+ if (stream == AudioManager.STREAM_BLUETOOTH_SCO) {
+ // Override audio stream for BT le device and hearing aid device
+ if (mCallAudioRoutePeripheralAdapter.isLeAudioDeviceOn()
+ || mCallAudioRoutePeripheralAdapter.isHearingAidDeviceOn()) {
+ stream = AudioManager.STREAM_VOICE_CALL;
+ }
+ }
playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis);
} else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) {
playMediaTone(stream, mediaResourceId);
@@ -388,26 +404,21 @@
}
Log.i(this, "playToneGeneratorTone: toneType=%d", toneType);
- // TODO: Certain CDMA tones need to check the ringer-volume state before
- // playing. See CallNotifier.InCallTonePlayer.
- // TODO: Some tones play through the end of a call so we need to inform
- // CallAudioManager that we want focus the same way that Ringer does.
-
- synchronized (this) {
- if (mState != STATE_STOPPED) {
- mState = STATE_ON;
- toneGenerator.startTone(toneType);
- try {
- Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
- toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
- wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
- } catch (InterruptedException e) {
- Log.w(this, "wait interrupted", e);
- }
+ mState = STATE_ON;
+ toneGenerator.startTone(toneType);
+ try {
+ Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
+ toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
+ if (mPlaybackLatch.await(toneLengthMillis + TIMEOUT_BUFFER_MILLIS,
+ TimeUnit.MILLISECONDS)) {
+ Log.i(this, "playToneGeneratorTone: tone playback stopped.");
}
+ } catch (InterruptedException e) {
+ Log.w(this, "playToneGeneratorTone: wait interrupted", e);
}
- mState = STATE_OFF;
+ // Redundant; don't want anyone re-using at this point.
+ mState = STATE_STOPPED;
} finally {
if (toneGenerator != null) {
toneGenerator.release();
@@ -421,42 +432,40 @@
* @param toneResourceId The resource ID of the tone to play.
*/
private void playMediaTone(int stream, int toneResourceId) {
- synchronized (this) {
- if (mState != STATE_STOPPED) {
- mState = STATE_ON;
+ mState = STATE_ON;
+ Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId);
+ AudioAttributes attributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+ .setLegacyStreamType(stream)
+ .build();
+ mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes);
+ mToneMediaPlayer.setLooping(false);
+ int durationMillis = mToneMediaPlayer.getDuration();
+ mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ Log.i(InCallTonePlayer.this, "playMediaTone: toneResourceId=%d completed.",
+ toneResourceId);
+ mPlaybackLatch.countDown();
}
- Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId);
- AudioAttributes attributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
- .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
- .setLegacyStreamType(stream)
- .build();
- mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes);
- mToneMediaPlayer.setLooping(false);
- int durationMillis = mToneMediaPlayer.getDuration();
- final CountDownLatch toneLatch = new CountDownLatch(1);
- mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- Log.i(this, "playMediaTone: toneResourceId=%d completed.", toneResourceId);
- synchronized (InCallTonePlayer.this) {
- mState = STATE_OFF;
- }
- mToneMediaPlayer.release();
- mToneMediaPlayer = null;
- toneLatch.countDown();
- }
- });
- mToneMediaPlayer.start();
- try {
- // Wait for the tone to stop playing; timeout at 2x the length of the file just to
- // be on the safe side.
- toneLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS);
- } catch (InterruptedException ie) {
- Log.e(this, ie, "playMediaTone: tone playback interrupted.");
- }
- }
+ });
+ try {
+ mToneMediaPlayer.start();
+ // Wait for the tone to stop playing; timeout at 2x the length of the file just to
+ // be on the safe side. Playback can also be stopped via stopTone().
+ if (mPlaybackLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS)) {
+ Log.i(this, "playMediaTone: tone playback stopped.");
+ }
+ } catch (InterruptedException ie) {
+ Log.e(this, ie, "playMediaTone: tone playback interrupted.");
+ } finally {
+ // Redundant; don't want anyone re-using at this point.
+ mState = STATE_STOPPED;
+ mToneMediaPlayer.release();
+ mToneMediaPlayer = null;
+ }
}
@VisibleForTesting
@@ -467,8 +476,12 @@
return false;
}
- sTonesPlaying++;
- if (sTonesPlaying == 1) {
+ // Tone already done; don't allow re-used
+ if (mState == STATE_STOPPED) {
+ return false;
+ }
+
+ if (sTonesPlaying.incrementAndGet() == 1) {
mCallAudioManager.setIsTonePlaying(true);
}
@@ -493,29 +506,38 @@
*/
@VisibleForTesting
public void stopTone() {
- synchronized (this) {
- if (mState == STATE_ON) {
- Log.d(this, "Stopping the tone %d.", mToneId);
- notify();
- }
- mState = STATE_STOPPED;
- }
+ Log.i(this, "stopTone: Stopping the tone %d.", mToneId);
+ // Notify the playback to end early.
+ mPlaybackLatch.countDown();
+
+ mState = STATE_STOPPED;
}
@VisibleForTesting
public void cleanup() {
- sTonesPlaying = 0;
+ sTonesPlaying.set(0);
}
private void cleanUpTonePlayer() {
+ Log.d(this, "cleanUpTonePlayer(): posting cleanup");
// Release focus on the main thread.
mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) {
@Override
public void loggedRun() {
- if (sTonesPlaying == 0) {
- Log.wtf(this, "Over-releasing focus for tone player.");
- } else if (--sTonesPlaying == 0 && mCallAudioManager != null) {
- mCallAudioManager.setIsTonePlaying(false);
+ int newToneCount = sTonesPlaying.updateAndGet( t -> Math.min(0, t--));
+
+ if (newToneCount == 0) {
+ Log.i(InCallTonePlayer.this,
+ "cleanUpTonePlayer(): tonesPlaying=%d, tone completed", newToneCount);
+ if (mCallAudioManager != null) {
+ mCallAudioManager.setIsTonePlaying(false);
+ } else {
+ Log.w(InCallTonePlayer.this,
+ "cleanUpTonePlayer(): mCallAudioManager is null!");
+ }
+ } else {
+ Log.i(InCallTonePlayer.this,
+ "cleanUpTonePlayer(): tonesPlaying=%d; still playing", newToneCount);
}
}
}.prepare());
diff --git a/src/com/android/server/telecom/InCallWakeLockController.java b/src/com/android/server/telecom/InCallWakeLockController.java
index b37883d..bc555e2 100644
--- a/src/com/android/server/telecom/InCallWakeLockController.java
+++ b/src/com/android/server/telecom/InCallWakeLockController.java
@@ -60,10 +60,18 @@
handleWakeLock();
}
+ @Override
+ public void onExternalCallChanged(Call call, boolean isExternalCall) {
+ // In case a call changes its external call state during ringing, we need to trigger
+ // the wake lock update correspondingly. External call is handled by another device
+ // and should not hold a wake lock on the local device.
+ handleWakeLock();
+ }
+
private void handleWakeLock() {
// We grab a full lock as long as there exists a ringing call.
Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
- if (ringingCall != null) {
+ if (ringingCall != null && !ringingCall.isExternalCall()) {
mTelecomWakeLock.acquire();
Log.i(this, "Acquiring full wake lock");
} else {
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index f53f239..12e780f 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -21,6 +21,8 @@
import android.telecom.Logging.EventManager;
import android.telecom.Logging.EventManager.TimedEventPair;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.HashMap;
import java.util.Map;
@@ -34,6 +36,9 @@
private static final String LOGUTILS_TAG = "LogUtils";
public static final boolean SYSTRACE_DEBUG = false; /* STOP SHIP if true */
+ private static boolean sInitializedLoggerListeners = false; // used to gate re-init of listeners
+ private static int sInitializedCounter = 0; /* For testing purposes only */
+ private static final Object sLock = new Object(); // Coarse lock for all of LogUtils
public static class EventTimer {
private long mLastElapsedMillis;
@@ -203,6 +208,7 @@
public static final String CALL_DIAGNOSTIC_SERVICE_TIMEOUT =
"CALL_DIAGNOSTIC_SERVICE_TIMEOUT";
public static final String VERSTAT_CHANGED = "VERSTAT_CHANGED";
+ public static final String SET_VOIP_MODE = "SET_VOIP_MODE";
public static class Timings {
public static final String ACCEPT_TIMING = "accept";
@@ -250,12 +256,13 @@
EventManager.Loggable recordEntry = eventRecord.getRecordEntry();
if (recordEntry instanceof Call) {
Call callRecordEntry = (Call) recordEntry;
- android.telecom.Log.i(LOGUTILS_TAG, "EventRecord added as Call: " + callRecordEntry);
Analytics.CallInfo callInfo = callRecordEntry.getAnalytics();
if(callInfo != null) {
callInfo.setCallEvents(eventRecord);
} else {
- android.telecom.Log.w(LOGUTILS_TAG, "Could not get Analytics CallInfo.");
+ if(!android.telecom.Log.isUnitTestingEnabled()) {
+ android.telecom.Log.w(LOGUTILS_TAG, "Could not get Analytics CallInfo.");
+ }
}
} else {
android.telecom.Log.w(LOGUTILS_TAG, "Non-Call EventRecord Added.");
@@ -263,13 +270,41 @@
}
public static void initLogging(Context context) {
- android.telecom.Log.setTag(TAG);
- android.telecom.Log.setSessionContext(context);
- for (EventManager.TimedEventPair p : Events.Timings.sTimedEvents) {
- android.telecom.Log.addRequestResponsePair(p);
+ android.telecom.Log.d(LOGUTILS_TAG, "initLogging: attempting to acquire LogUtils sLock");
+ synchronized (sLock) {
+ android.telecom.Log.d(LOGUTILS_TAG, "initLogging: grabbed LogUtils sLock");
+ if (!sInitializedLoggerListeners) {
+ android.telecom.Log.d(LOGUTILS_TAG,
+ "initLogging: called for first time. Registering the EventListener &"
+ + " SessionListener.");
+
+ android.telecom.Log.setTag(TAG);
+ android.telecom.Log.setSessionContext(context);
+ for (EventManager.TimedEventPair p : Events.Timings.sTimedEvents) {
+ android.telecom.Log.addRequestResponsePair(p);
+ }
+ android.telecom.Log.registerEventListener(LogUtils::eventRecordAdded);
+ // Store analytics about recently completed Sessions.
+ android.telecom.Log.registerSessionListener(Analytics::addSessionTiming);
+
+ // Ensure LogUtils#initLogging(Context) is called once throughout the entire
+ // lifecycle of not only TelecomSystem, but the Testing Framework.
+ sInitializedLoggerListeners = true;
+ sInitializedCounter++; /* For testing purposes only */
+ } else {
+ android.telecom.Log.d(LOGUTILS_TAG, "initLogging: called again. Doing nothing.");
+ }
}
- android.telecom.Log.registerEventListener(LogUtils::eventRecordAdded);
- // Store analytics about recently completed Sessions.
- android.telecom.Log.registerSessionListener(Analytics::addSessionTiming);
+ }
+
+ /**
+ * Needed in order to test if the registerEventListener & registerSessionListener are ever
+ * re-initialized in the entire process of the Testing Framework or TelecomSystem.
+ *
+ * @return the number of times initLogging(Context) listeners have been initialized
+ */
+ @VisibleForTesting
+ public static int getInitializedCounter() {
+ return sInitializedCounter;
}
}
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index d90337e..c566e5a 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -19,9 +19,11 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -45,6 +47,7 @@
import android.telecom.PhoneAccountHandle;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -132,6 +135,26 @@
PhoneAccount phoneAccount) {}
}
+ /**
+ * Receiver for detecting when a managed profile has been removed so that PhoneAccountRegistrar
+ * can clean up orphan {@link PhoneAccount}s
+ */
+ private final BroadcastReceiver mManagedProfileReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.startSession("PARbR.oR");
+ try {
+ synchronized (mLock) {
+ if (intent.getAction().equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)) {
+ cleanupOrphanedPhoneAccounts();
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+ };
+
public static final String FILE_NAME = "phone-account-registrar-state.xml";
@VisibleForTesting
public static final int EXPECTED_STATE_VERSION = 9;
@@ -144,9 +167,11 @@
private final AtomicFile mAtomicFile;
private final Context mContext;
private final UserManager mUserManager;
+ private final TelephonyManager mTelephonyManager;
private final SubscriptionManager mSubscriptionManager;
private final DefaultDialerCache mDefaultDialerCache;
private final AppLabelProxy mAppLabelProxy;
+ private final TelecomSystem.SyncRoot mLock;
private State mState;
private UserHandle mCurrentUserHandle;
private String mTestPhoneAccountPackageNameFilter;
@@ -155,24 +180,31 @@
new PhoneAccountRegistrarWriteLock() {};
@VisibleForTesting
- public PhoneAccountRegistrar(Context context, DefaultDialerCache defaultDialerCache,
- AppLabelProxy appLabelProxy) {
- this(context, FILE_NAME, defaultDialerCache, appLabelProxy);
+ public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock,
+ DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
+ this(context, lock, FILE_NAME, defaultDialerCache, appLabelProxy);
}
@VisibleForTesting
- public PhoneAccountRegistrar(Context context, String fileName,
+ public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock, String fileName,
DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
mState = new State();
mContext = context;
+ mLock = lock;
mUserManager = UserManager.get(context);
mDefaultDialerCache = defaultDialerCache;
mSubscriptionManager = SubscriptionManager.from(mContext);
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAppLabelProxy = appLabelProxy;
mCurrentUserHandle = Process.myUserHandle();
+
+ // register context based receiver to clean up orphan phone accounts
+ IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+ mContext.registerReceiver(mManagedProfileReceiver, intentFilter);
+
read();
}
@@ -188,9 +220,7 @@
PhoneAccount account = getPhoneAccountUnchecked(accountHandle);
if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
- TelephonyManager tm =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- return tm.getSubscriptionId(accountHandle);
+ return mTelephonyManager.getSubscriptionId(accountHandle);
}
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
@@ -336,11 +366,11 @@
isSimAccount = true;
}
- Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s", accountHandle);
mState.defaultOutgoingAccountHandles
.put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle,
account.getGroupId()));
}
+ Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s", accountHandle);
// Potentially update the default voice subid in SubscriptionManager.
if (!Objects.equals(currentDefaultPhoneAccount, accountHandle)) {
@@ -779,6 +809,25 @@
}
/**
+ * Retrieves a list of all {@link PhoneAccount#CAPABILITY_SELF_MANAGED} phone accounts
+ * registered by a specified package.
+ *
+ * @param packageName The name of the package that registered the phone accounts.
+ * @return The self-managed phone account handles for the given package.
+ */
+ public List<PhoneAccountHandle> getSelfManagedPhoneAccountsForPackage(String packageName,
+ UserHandle userHandle) {
+ List<PhoneAccountHandle> phoneAccountsHandles = new ArrayList<>();
+ for (PhoneAccountHandle pah : getPhoneAccountsForPackage(packageName,
+ userHandle)) {
+ if (isSelfManagedPhoneAccount(pah)) {
+ phoneAccountsHandles.add(pah);
+ }
+ }
+ return phoneAccountsHandles;
+ }
+
+ /**
* Determines if a {@link PhoneAccountHandle} is for a self-managed {@link ConnectionService}.
* @param handle The handle.
* @return {@code true} if for a self-managed {@link ConnectionService}, {@code false}
@@ -1104,11 +1153,10 @@
"Notifying telephony of voice service override change for %d SIMs, hasService = %b",
simHandlesToNotify.size(),
hasService);
- TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
for (PhoneAccountHandle simHandle : simHandlesToNotify) {
// This may be null if there are no active SIMs but the device is still camped for
// emergency calls and registered a SIM_SUBSCRIPTION for that purpose.
- TelephonyManager simTm = tm.createForPhoneAccountHandle(simHandle);
+ TelephonyManager simTm = mTelephonyManager.createForPhoneAccountHandle(simHandle);
if (simTm == null) continue;
simTm.setVoiceServiceStateOverride(hasService);
}
@@ -1366,7 +1414,7 @@
public final UserHandle userHandle;
- public final PhoneAccountHandle phoneAccountHandle;
+ public PhoneAccountHandle phoneAccountHandle;
public final String groupId;
@@ -1485,8 +1533,7 @@
try {
sortPhoneAccounts();
ByteArrayOutputStream os = new ByteArrayOutputStream();
- XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(os, "utf-8");
+ XmlSerializer serializer = Xml.resolveSerializer(os);
writeToXml(mState, serializer, mContext);
serializer.flush();
new AsyncXmlWriter().execute(os);
@@ -1505,12 +1552,11 @@
boolean versionChanged = false;
- XmlPullParser parser;
try {
- parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(is), null);
+ XmlPullParser parser = Xml.resolvePullParser(is);
parser.nextTag();
mState = readFromXml(parser, mContext);
+ migratePhoneAccountHandle(mState);
versionChanged = mState.versionNumber < EXPECTED_STATE_VERSION;
} catch (IOException | XmlPullParserException e) {
@@ -1555,6 +1601,50 @@
return s != null ? s : new State();
}
+ /**
+ * Try to migrate the ID of default phone account handle from IccId to SubId.
+ */
+ @VisibleForTesting
+ public void migratePhoneAccountHandle(State state) {
+ if (mSubscriptionManager == null) {
+ return;
+ }
+ // Use getAllSubscirptionInfoList() to get the mapping between iccId and subId
+ // from the subscription database
+ List<SubscriptionInfo> subscriptionInfos = mSubscriptionManager
+ .getAllSubscriptionInfoList();
+ Map<UserHandle, DefaultPhoneAccountHandle> defaultPhoneAccountHandles
+ = state.defaultOutgoingAccountHandles;
+ for (Map.Entry<UserHandle, DefaultPhoneAccountHandle> entry
+ : defaultPhoneAccountHandles.entrySet()) {
+ DefaultPhoneAccountHandle defaultPhoneAccountHandle = entry.getValue();
+
+ // Migrate Telephony PhoneAccountHandle only
+ String telephonyComponentName =
+ "com.android.phone/com.android.services.telephony.TelephonyConnectionService";
+ if (!defaultPhoneAccountHandle.phoneAccountHandle.getComponentName()
+ .flattenToString().equals(telephonyComponentName)) {
+ continue;
+ }
+ // Migrate from IccId to SubId
+ for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
+ String phoneAccountHandleId = defaultPhoneAccountHandle.phoneAccountHandle.getId();
+ // Some phone account handle would store phone account handle id with the IccId
+ // string plus "F", and the getIccId() returns IccId string itself without "F",
+ // so here need to use "startsWith" to match.
+ if (phoneAccountHandleId != null && phoneAccountHandleId.startsWith(
+ subscriptionInfo.getIccId())) {
+ Log.i(this, "Found subscription ID to migrate: "
+ + subscriptionInfo.getSubscriptionId());
+ defaultPhoneAccountHandle.phoneAccountHandle = new PhoneAccountHandle(
+ defaultPhoneAccountHandle.phoneAccountHandle.getComponentName(),
+ Integer.toString(subscriptionInfo.getSubscriptionId()));
+ break;
+ }
+ }
+ }
+ }
+
////////////////////////////////////////////////////////////////////////////////////////////////
//
// XML serialization
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 6c9b1cc..b29b500 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -16,11 +16,14 @@
package com.android.server.telecom;
+import static android.provider.CallLog.Calls.USER_MISSED_DND_MODE;
+import static android.provider.CallLog.Calls.USER_MISSED_LOW_RING_VOLUME;
+import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Person;
import android.content.Context;
-import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.VolumeShaper;
@@ -28,6 +31,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.telecom.Log;
@@ -38,6 +42,7 @@
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -59,6 +64,9 @@
@VisibleForTesting
public VibrationEffect mDefaultVibrationEffect;
+ // Used for test to notify the completion of RingerAttributes
+ private CountDownLatch mAttributesLatch;
+
private static final long[] PULSE_PRIMING_PATTERN = {0,12,250,12,500}; // priming + interval
private static final int[] PULSE_PRIMING_AMPLITUDE = {0,255,0,255,0}; // priming + interval
@@ -119,12 +127,9 @@
private static final float EPSILON = 1e-6f;
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
- .build();
+ private static final VibrationAttributes VIBRATION_ATTRIBUTES =
+ new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_RINGTONE).build();
- private static VibrationEffect mRampingRingerVibrationEffect;
private static VolumeShaper.Configuration mVolumeShaperConfig;
/**
@@ -167,6 +172,12 @@
private Handler mHandler = null;
+ /**
+ * Use lock different from the Telecom sync because ringing process is asynchronous outside that
+ * lock
+ */
+ private final Object mLock;
+
/** Initializes the Ringer. */
@VisibleForTesting
public Ringer(
@@ -179,6 +190,7 @@
VibrationEffectProxy vibrationEffectProxy,
InCallController inCallController) {
+ mLock = new Object();
mSystemSettingsUtil = systemSettingsUtil;
mPlayerFactory = playerFactory;
mContext = context;
@@ -189,7 +201,6 @@
mRingtoneFactory = ringtoneFactory;
mInCallController = inCallController;
mVibrationEffectProxy = vibrationEffectProxy;
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
@@ -232,6 +243,7 @@
RingerAttributes attributes = null;
try {
+ mAttributesLatch = new CountDownLatch(1);
attributes = ringerAttributesFuture.get(
RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
@@ -263,7 +275,9 @@
VibrationEffect effect;
CompletableFuture<Boolean> hapticsFuture = null;
// Determine if the settings and DND mode indicate that the vibrator can be used right now.
- boolean isVibratorEnabled = isVibratorEnabled(mContext, foregroundCall);
+ boolean isVibratorEnabled = isVibratorEnabled(mContext);
+ boolean shouldApplyRampingRinger =
+ isVibratorEnabled && mSystemSettingsUtil.isRampingRingerEnabled(mContext);
if (attributes.isRingerAudible()) {
mRingingCall = foregroundCall;
Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
@@ -271,9 +285,9 @@
// call (for the purposes of direct-to-voicemail), the information about custom
// ringtones should be available by the time this code executes. We can safely
// request the custom ringtone from the call and expect it to be current.
- if (mSystemSettingsUtil.applyRampingRinger(mContext)) {
+ if (shouldApplyRampingRinger) {
Log.i(this, "start ramping ringer.");
- if (mSystemSettingsUtil.enableAudioCoupledVibrationForRampingRinger()) {
+ if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
} else {
effect = mDefaultVibrationEffect;
@@ -319,8 +333,8 @@
isUsingAudioCoupledHaptics, mIsHapticPlaybackSupportedByDevice);
maybeStartVibration(foregroundCall, shouldRingForContact, effect,
isVibratorEnabled, isRingerAudible);
- } else if (mSystemSettingsUtil.applyRampingRinger(mContext)
- && !mSystemSettingsUtil.enableAudioCoupledVibrationForRampingRinger()) {
+ } else if (shouldApplyRampingRinger
+ && !mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
Log.i(this, "startRinging: apply ramping ringer vibration");
maybeStartVibration(foregroundCall, shouldRingForContact, effect,
isVibratorEnabled, isRingerAudible);
@@ -346,20 +360,29 @@
private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact,
VibrationEffect effect, boolean isVibrationEnabled, boolean isRingerAudible) {
- if (isVibrationEnabled
- && !mIsVibrating && shouldRingForContact) {
- if (mSystemSettingsUtil.applyRampingRinger(mContext)
- && isRingerAudible) {
- Log.i(this, "start vibration for ramping ringer.");
+ synchronized (mLock) {
+ mAudioManager = mContext.getSystemService(AudioManager.class);
+ if (isVibrationEnabled && !mIsVibrating && shouldRingForContact) {
+ Log.addEvent(foregroundCall, LogUtils.Events.START_VIBRATOR,
+ "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
+ mVibrator.hasVibrator(),
+ mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+ mAudioManager.getRingerModeInternal(), mIsVibrating);
+ if (mSystemSettingsUtil.isRampingRingerEnabled(mContext) && isRingerAudible) {
+ Log.i(this, "start vibration for ramping ringer.");
+ } else {
+ Log.i(this, "start normal vibration.");
+ }
mIsVibrating = true;
mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
} else {
- Log.i(this, "start normal vibration.");
- mIsVibrating = true;
- mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
+ foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
+ "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
+ mVibrator.hasVibrator(),
+ mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+ mAudioManager.getRingerModeInternal(), mIsVibrating);
}
- } else if (mIsVibrating) {
- Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "already vibrating");
}
}
@@ -419,24 +442,26 @@
}
public void stopRinging() {
- if (mRingingCall != null) {
- Log.addEvent(mRingingCall, LogUtils.Events.STOP_RINGER);
- mRingingCall = null;
- }
+ synchronized (mLock) {
+ if (mRingingCall != null) {
+ Log.addEvent(mRingingCall, LogUtils.Events.STOP_RINGER);
+ mRingingCall = null;
+ }
- mRingtonePlayer.stop();
+ mRingtonePlayer.stop();
- // If we haven't started vibrating because we were waiting for the haptics info, cancel
- // it and don't vibrate at all.
- if (mVibrateFuture != null) {
- mVibrateFuture.cancel(true);
- }
+ // If we haven't started vibrating because we were waiting for the haptics info, cancel
+ // it and don't vibrate at all.
+ if (mVibrateFuture != null) {
+ mVibrateFuture.cancel(true);
+ }
- if (mIsVibrating) {
- Log.addEvent(mVibratingCall, LogUtils.Events.STOP_VIBRATOR);
- mVibrator.cancel();
- mIsVibrating = false;
- mVibratingCall = null;
+ if (mIsVibrating) {
+ Log.addEvent(mVibratingCall, LogUtils.Events.STOP_VIBRATOR);
+ mVibrator.cancel();
+ mIsVibrating = false;
+ mVibratingCall = null;
+ }
}
}
@@ -472,48 +497,21 @@
private boolean hasExternalRinger(Call foregroundCall) {
Bundle intentExtras = foregroundCall.getIntentExtras();
if (intentExtras != null) {
- return intentExtras.getBoolean(TelecomManager.EXTRA_CALL_EXTERNAL_RINGER, false);
+ return intentExtras.getBoolean(TelecomManager.EXTRA_CALL_HAS_IN_BAND_RINGTONE, false);
} else {
return false;
}
}
- private boolean isVibratorEnabled(Context context, Call call) {
+ private boolean isVibratorEnabled(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- int ringerMode = audioManager.getRingerModeInternal();
- boolean shouldVibrate;
- if (getVibrateWhenRinging(context)) {
- shouldVibrate = ringerMode != AudioManager.RINGER_MODE_SILENT;
- } else {
- shouldVibrate = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
- }
-
- // Technically this should be in the calling method, but it seemed a little odd to pass
- // around a whole bunch of state just for logging purposes.
- if (shouldVibrate) {
- Log.addEvent(call, LogUtils.Events.START_VIBRATOR,
- "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
- mVibrator.hasVibrator(), mSystemSettingsUtil.canVibrateWhenRinging(context),
- ringerMode, mIsVibrating);
- } else {
- Log.addEvent(call, LogUtils.Events.SKIP_VIBRATION,
- "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
- mVibrator.hasVibrator(), mSystemSettingsUtil.canVibrateWhenRinging(context),
- ringerMode, mIsVibrating);
- }
-
- return shouldVibrate;
- }
-
- private boolean getVibrateWhenRinging(Context context) {
- if (!mVibrator.hasVibrator()) {
- return false;
- }
- return mSystemSettingsUtil.canVibrateWhenRinging(context)
- || mSystemSettingsUtil.applyRampingRinger(context);
+ return mVibrator.hasVibrator()
+ && mSystemSettingsUtil.isRingVibrationEnabled(context)
+ && audioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_SILENT;
}
private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) {
+ mAudioManager = mContext.getSystemService(AudioManager.class);
RingerAttributes.Builder builder = new RingerAttributes.Builder();
LogUtils.EventTimer timer = new EventTimer();
@@ -565,6 +563,15 @@
boolean shouldAcquireAudioFocus =
isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
+ // Set missed reason according to attributes
+ if (!isVolumeOverZero) {
+ call.setUserMissed(USER_MISSED_LOW_RING_VOLUME);
+ }
+ if (!shouldRingForContact) {
+ call.setUserMissed(USER_MISSED_DND_MODE);
+ }
+
+ mAttributesLatch.countDown();
return builder.setEndEarly(endEarly)
.setLetDialerHandleRinging(letDialerHandleRinging)
.setAcquireAudioFocus(shouldAcquireAudioFocus)
@@ -583,4 +590,13 @@
}
return mHandler;
}
+
+ @VisibleForTesting
+ public boolean waitForAttributesCompletion() throws InterruptedException {
+ if (mAttributesLatch != null) {
+ return mAttributesLatch.await(RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
+ } else {
+ return false;
+ }
+ }
}
diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java
index 7baae05..cdd14df 100644
--- a/src/com/android/server/telecom/SystemSettingsUtil.java
+++ b/src/com/android/server/telecom/SystemSettingsUtil.java
@@ -18,9 +18,10 @@
import android.content.Context;
import android.media.AudioManager;
+import android.os.VibrationAttributes;
+import android.os.Vibrator;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.telecom.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -39,9 +40,16 @@
0) == 1;
}
- public boolean canVibrateWhenRinging(Context context) {
+ public boolean isRingVibrationEnabled(Context context) {
+ // VIBRATE_WHEN_RINGING setting was deprecated, only RING_VIBRATION_INTENSITY controls the
+ // ringtone vibrations on/off state now. Ramping ringer should only be applied when ring
+ // vibration intensity is ON, otherwise the ringtone sound should not be delayed as there
+ // will be no ring vibration.
return Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, 0, context.getUserId()) != 0;
+ Settings.System.RING_VIBRATION_INTENSITY,
+ context.getSystemService(Vibrator.class).getDefaultVibrationIntensity(
+ VibrationAttributes.USAGE_RINGTONE),
+ context.getUserId()) != Vibrator.VIBRATION_INTENSITY_OFF;
}
public boolean isEnhancedCallBlockingEnabled(Context context) {
@@ -55,12 +63,11 @@
context.getUserId());
}
- public boolean applyRampingRinger(Context context) {
- return Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.APPLY_RAMPING_RINGER, 0) == 1;
+ public boolean isRampingRingerEnabled(Context context) {
+ return context.getSystemService(AudioManager.class).isRampingRingerEnabled();
}
- public boolean enableAudioCoupledVibrationForRampingRinger() {
+ public boolean isAudioCoupledVibrationForRampingRingerEnabled() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
RAMPING_RINGER_AUDIO_COUPLED_VIBRATION_ENABLED, false);
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 2045f13..06a190b 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -26,6 +26,7 @@
import static android.Manifest.permission.READ_SMS;
import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.Manifest.permission.MANAGE_OWN_CALLS;
import android.Manifest;
import android.app.ActivityManager;
@@ -249,6 +250,42 @@
}
@Override
+ public List<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts(String callingPackage,
+ String callingFeatureId) {
+ try {
+ Log.startSession("TSI.gOSMPA", Log.getPackageAbbreviation(callingPackage));
+ try {
+ enforceCallingPackage(callingPackage, "getOwnSelfManagedPhoneAccounts");
+ }
+ catch(SecurityException se){
+ EventLog.writeEvent(0x534e4554, "231986341", Binder.getCallingUid(),
+ "getOwnSelfManagedPhoneAccounts: invalid calling package");
+ throw se;
+ }
+ if (!canReadMangeOwnCalls("Requires MANAGE_OWN_CALLS permission.")) {
+ throw new SecurityException("Requires MANAGE_OWN_CALLS permission.");
+ }
+ synchronized (mLock) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mPhoneAccountRegistrar.getSelfManagedPhoneAccountsForPackage(
+ callingPackage,
+ callingUserHandle);
+ } catch (Exception e) {
+ Log.e(this, e,
+ "getSelfManagedPhoneAccountsForPackage");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme,
String callingPackage) {
try {
@@ -501,12 +538,6 @@
try {
Log.startSession("TSI.rPA");
synchronized (mLock) {
- if (!((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
- .isVoiceCapable()) {
- Log.w(this,
- "registerPhoneAccount not allowed on non-voice capable device.");
- return;
- }
try {
enforcePhoneAccountModificationForPackage(
account.getAccountHandle().getComponentName().getPackageName());
@@ -1723,10 +1754,14 @@
* @see android.telecom.TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)
*/
@Override
- public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
+ public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle,
+ String callingPackage) {
+ Log.startSession("TSI.iICP");
try {
- Log.startSession("TSI.iICP");
+ enforceCallingPackage(callingPackage, "isIncomingCallPermitted");
+ enforcePhoneAccountHandleMatchesCaller(phoneAccountHandle, callingPackage);
enforcePermission(android.Manifest.permission.MANAGE_OWN_CALLS);
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
@@ -1744,10 +1779,14 @@
* @see android.telecom.TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)
*/
@Override
- public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
+ public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle,
+ String callingPackage) {
+ Log.startSession("TSI.iOCP");
try {
- Log.startSession("TSI.iOCP");
+ enforceCallingPackage(callingPackage, "isOutgoingCallPermitted");
+ enforcePhoneAccountHandleMatchesCaller(phoneAccountHandle, callingPackage);
enforcePermission(android.Manifest.permission.MANAGE_OWN_CALLS);
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
@@ -2074,6 +2113,39 @@
Log.endSession();
}
}
+
+ /**
+ * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
+ * calls for a given {@code packageName} and {@code userHandle}.
+ *
+ * @param packageName the package name of the app to check calls for.
+ * @param userHandle the user handle on which to check for calls.
+ * @param callingPackage The caller's package name.
+ * @return {@code true} if there are ongoing calls, {@code false} otherwise.
+ */
+ @Override
+ public boolean isInSelfManagedCall(String packageName, UserHandle userHandle,
+ String callingPackage) {
+ try {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system can call this API");
+ }
+ mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+ "READ_PRIVILEGED_PHONE_STATE required.");
+
+ Log.startSession("TSI.iISMC", Log.getPackageAbbreviation(callingPackage));
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mCallsManager.isInSelfManagedCall(packageName, userHandle);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
};
/**
@@ -2286,7 +2358,7 @@
if (result != PackageManager.PERMISSION_GRANTED) {
// Other callers are only allowed to modify PhoneAccounts if the relevant system
// feature is enabled ...
- enforceConnectionServiceFeature();
+ enforceTelecomFeature();
// ... and the PhoneAccounts they refer to are for their own package.
enforceCallingPackage(packageName, "enforcePhoneAccountModificationForPackage");
}
@@ -2322,8 +2394,13 @@
}
}
- private void enforceConnectionServiceFeature() {
- enforceFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
+ private void enforceTelecomFeature() {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) {
+ throw new UnsupportedOperationException(
+ "System does not support feature " + PackageManager.FEATURE_TELECOM);
+ }
}
private void enforceRegisterSimSubscriptionPermission() {
@@ -2383,6 +2460,13 @@
}
}
+ private void enforcePhoneAccountHandleMatchesCaller(PhoneAccountHandle phoneAccountHandle,
+ String callingPackage) {
+ if (!callingPackage.equals(phoneAccountHandle.getComponentName().getPackageName())) {
+ throw new SecurityException("Caller does not own the PhoneAccountHandle");
+ }
+ }
+
private void enforceCrossUserPermission(int callingUid) {
if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
mContext.enforceCallingOrSelfPermission(
@@ -2391,14 +2475,6 @@
}
}
- private void enforceFeature(String feature) {
- PackageManager pm = mContext.getPackageManager();
- if (!pm.hasSystemFeature(feature)) {
- throw new UnsupportedOperationException(
- "System does not support feature " + feature);
- }
- }
-
// to be used for TestApi methods that can only be called with SHELL UID.
private void enforceShellOnly(int callingUid, String message) {
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
@@ -2431,6 +2507,15 @@
}
}
+ private boolean canReadMangeOwnCalls(String message) {
+ try {
+ mContext.enforceCallingOrSelfPermission(MANAGE_OWN_CALLS, message);
+ return true;
+ } catch (SecurityException e) {
+ return false;
+ }
+ }
+
private boolean canReadPhoneNumbers(String callingPackage, String callingFeatureId,
String message) {
boolean targetSdkPreR = false;
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 67e3be4..237f039 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -220,7 +220,7 @@
Log.startSession("TS.init");
// Wrap this in a try block to ensure session cleanup occurs in the case of error.
try {
- mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache,
+ mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, mLock, defaultDialerCache,
packageName -> AppLabelProxy.Util.getAppLabel(
mContext.getPackageManager(), packageName));
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 337d383..bd2c6f1 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -650,6 +650,14 @@
mHfpActiveDeviceCache != null;
}
+ public boolean isCachedLeAudioDevice(BluetoothDevice device) {
+ return mLeAudioActiveDeviceCache != null && mLeAudioActiveDeviceCache.equals(device);
+ }
+
+ public boolean isCachedHearingAidDevice(BluetoothDevice device) {
+ return mHearingAidActiveDeviceCache != null && mHearingAidActiveDeviceCache.equals(device);
+ }
+
public Collection<BluetoothDevice> getConnectedDevices() {
return mDeviceManager.getUniqueConnectedDevices();
}
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 0c30a16..51b1bc6 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -319,7 +319,6 @@
mCallsManager.getCurrentUserHandle(), mPackageName, connection)) {
Log.i(this, "Call screening service binding failed.");
resultFuture.complete(mPriorStageResult);
- mConnection = null;
} else {
mConnection = connection;
}
diff --git a/src/com/android/server/telecom/components/ErrorDialogActivity.java b/src/com/android/server/telecom/components/ErrorDialogActivity.java
index 3618b77..fd55a35 100644
--- a/src/com/android/server/telecom/components/ErrorDialogActivity.java
+++ b/src/com/android/server/telecom/components/ErrorDialogActivity.java
@@ -16,7 +16,6 @@
package com.android.server.telecom.components;
-import com.android.server.telecom.FrameworksUtils;
import com.android.server.telecom.R;
import android.app.Activity;
@@ -85,7 +84,7 @@
}
};
- final AlertDialog errorDialog = FrameworksUtils.makeAlertDialogBuilder(this)
+ final AlertDialog errorDialog = new AlertDialog.Builder(this)
.setMessage(msg).setPositiveButton(android.R.string.ok, clickListener)
.setOnCancelListener(cancelListener).create();
@@ -98,7 +97,7 @@
}
private void showMissingVoicemailErrorDialog() {
- FrameworksUtils.makeAlertDialogBuilder(this)
+ final AlertDialog errorDialog = new AlertDialog.Builder(this)
.setTitle(R.string.no_vm_number)
.setMessage(R.string.no_vm_number_msg)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index ca8fef7..1d85884 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -65,7 +65,7 @@
Intent intent = getIntent();
verifyCallAction(intent);
final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
- final UserHandle userHandle = new UserHandle(userManager.getUserHandle());
+ final UserHandle userHandle = new UserHandle(userManager.getProcessUserId());
// Once control flow has passed to this activity, it is no longer guaranteed that we can
// accurately determine whether the calling package has the CALL_PHONE runtime permission.
// At this point in time we trust that the ActivityManager has already performed this
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index ae76708..cad7b4c 100755
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -80,11 +80,6 @@
*/
public void processIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency, boolean isLocalInvocation) {
- // Ensure call intents are not processed on devices that are not capable of calling.
- if (!isVoiceCapable()) {
- return;
- }
-
String action = intent.getAction();
if (Intent.ACTION_CALL.equals(action) ||
@@ -160,16 +155,6 @@
}
/**
- * Returns whether the device is voice-capable (e.g. a phone vs a tablet).
- *
- * @return {@code True} if the device is voice-capable.
- */
- private boolean isVoiceCapable() {
- return ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
- .isVoiceCapable();
- }
-
- /**
* Potentially trampolines the intent to Telecom via TelecomServiceImpl.
* If the caller is local to the Telecom service, we send the intent to Telecom without
* sending it through TelecomServiceImpl.
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index 036fb91..bc54e11 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -54,7 +54,6 @@
import android.widget.TextView;
import android.widget.Toast;
-import com.android.server.telecom.FrameworksUtils;
import com.android.server.telecom.R;
@@ -215,7 +214,7 @@
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(this, BlockedNumberContract.BlockedNumbers.CONTENT_URI,
PROJECTION, SELECTION, null,
- BlockedNumberContract.BlockedNumbers.COLUMN_ID + " DESC");
+ BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + " ASC");
}
@Override
@@ -246,7 +245,7 @@
final EditText editText = (EditText) dialogView.findViewById(R.id.add_blocked_number);
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
editText.addTextChangedListener(this);
- AlertDialog dialog = FrameworksUtils.makeAlertDialogBuilder(this)
+ AlertDialog dialog = new AlertDialog.Builder(this)
.setView(dialogView)
.setPositiveButton(R.string.block_button, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
@@ -261,23 +260,23 @@
})
.create();
dialog.setOnShowListener(new AlertDialog.OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- mBlockButton = ((AlertDialog) dialog)
- .getButton(AlertDialog.BUTTON_POSITIVE);
- mBlockButtonNegative = ((AlertDialog) dialog)
- .getButton(AlertDialog.BUTTON_NEGATIVE);
- mBlockButton.setAllCaps(false);
- mBlockButtonNegative.setAllCaps(false);
- mBlockButton.setEnabled(false);
- // show keyboard
- InputMethodManager inputMethodManager =
- (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- inputMethodManager.showSoftInput(editText,
- InputMethodManager.SHOW_IMPLICIT);
+ @Override
+ public void onShow(DialogInterface dialog) {
+ mBlockButton = ((AlertDialog) dialog)
+ .getButton(AlertDialog.BUTTON_POSITIVE);
+ mBlockButtonNegative = ((AlertDialog) dialog)
+ .getButton(AlertDialog.BUTTON_NEGATIVE);
+ mBlockButton.setAllCaps(false);
+ mBlockButtonNegative.setAllCaps(false);
+ mBlockButton.setEnabled(false);
+ // show keyboard
+ InputMethodManager inputMethodManager =
+ (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.showSoftInput(editText,
+ InputMethodManager.SHOW_IMPLICIT);
- }
- });
+ }
+ });
dialog.show();
}
@@ -337,4 +336,4 @@
}
mAddButton.setEnabled(true);
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java b/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
index 333e451..df68f6e 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
@@ -29,7 +29,6 @@
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
-import com.android.server.telecom.FrameworksUtils;
import com.android.server.telecom.R;
public class BlockedNumbersAdapter extends SimpleCursorAdapter {
@@ -73,7 +72,7 @@
Spannable messageSpannable = new SpannableString(message);
PhoneNumberUtils.addTtsSpan(messageSpannable, startingPosition,
startingPosition + formattedNumber.length());
- AlertDialog dialog = FrameworksUtils.makeAlertDialogBuilder(context)
+ AlertDialog dialog = new AlertDialog.Builder(context)
.setMessage(messageSpannable)
.setPositiveButton(R.string.unblock_button,
new DialogInterface.OnClickListener() {
diff --git a/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java b/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java
index 232546b..5f42b37 100644
--- a/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java
+++ b/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java
@@ -22,7 +22,6 @@
import android.os.Bundle;
import android.provider.BlockedNumberContract;
-import com.android.server.telecom.FrameworksUtils;
import com.android.server.telecom.R;
/**
@@ -51,7 +50,7 @@
return;
}
- AlertDialog.Builder builder = FrameworksUtils.makeAlertDialogBuilder(this);
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
mDialog = builder
.setTitle(R.string.phone_strings_emergency_call_made_dialog_title_txt)
.setMessage(R.string
diff --git a/src/com/android/server/telecom/ui/CallRedirectionTimeoutDialogActivity.java b/src/com/android/server/telecom/ui/CallRedirectionTimeoutDialogActivity.java
index b5c850e..5aa80c6 100644
--- a/src/com/android/server/telecom/ui/CallRedirectionTimeoutDialogActivity.java
+++ b/src/com/android/server/telecom/ui/CallRedirectionTimeoutDialogActivity.java
@@ -16,7 +16,6 @@
package com.android.server.telecom.ui;
-import com.android.server.telecom.FrameworksUtils;
import com.android.server.telecom.R;
import android.app.Activity;
@@ -46,7 +45,7 @@
Log.i(this, "showDialog: timeout redirection with %s", redirectionAppName);
CharSequence message = getString(
R.string.alert_redirect_outgoing_call_timeout, redirectionAppName);
- final AlertDialog errorDialog = FrameworksUtils.makeAlertDialogBuilder(this)
+ final AlertDialog errorDialog = new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
diff --git a/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java b/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java
index e9f99b6..4735e3c 100644
--- a/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java
+++ b/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java
@@ -16,7 +16,6 @@
package com.android.server.telecom.ui;
-import com.android.server.telecom.FrameworksUtils;
import com.android.server.telecom.R;
import com.android.server.telecom.TelecomBroadcastIntentProcessor;
import com.android.server.telecom.components.TelecomBroadcastReceiver;
@@ -49,7 +48,7 @@
private void showDialog(final String callId, CharSequence ongoingAppName) {
Log.i(this, "showDialog: confirming callId=%s, ongoing=%s", callId, ongoingAppName);
CharSequence message = getString(R.string.alert_outgoing_call, ongoingAppName);
- final AlertDialog errorDialog = FrameworksUtils.makeAlertDialogBuilder(this)
+ final AlertDialog errorDialog = new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index 48295c4..6b97f97 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -17,15 +17,45 @@
package com.android.server.telecom.ui;
import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.app.admin.DevicePolicyResources.Strings.Telecomm.NOTIFICATION_MISSED_WORK_CALL_TITLE;
import android.annotation.NonNull;
import android.app.BroadcastOptions;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.app.admin.DevicePolicyManager;
+import android.content.AsyncQueryHandler;
import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.CallLog.Calls;
+import android.telecom.CallerInfo;
+import android.telecom.Log;
import android.telecom.Logging.Runnable;
+import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManagerListenerBase;
@@ -40,38 +70,6 @@
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.components.TelecomBroadcastReceiver;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.TaskStackBuilder;
-import android.content.AsyncQueryHandler;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Binder;
-import android.os.UserHandle;
-import android.provider.CallLog.Calls;
-import android.telecom.Log;
-import android.telecom.PhoneAccount;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.text.BidiFormatter;
-import android.text.TextDirectionHeuristics;
-import android.text.TextUtils;
-
-import android.telecom.CallerInfo;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import java.lang.Override;
-import java.lang.String;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -270,7 +268,9 @@
createClearMissedCallsPendingIntent(userHandle))
.putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, missedCallCount)
.putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
- callInfo == null ? null : callInfo.getPhoneNumber());
+ callInfo == null ? null : callInfo.getPhoneNumber())
+ .putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ callInfo == null ? null : callInfo.getPhoneAccountHandle());
if (missedCallCount == 1 && callInfo != null) {
final Uri handleUri = callInfo.getHandle();
@@ -328,7 +328,7 @@
return;
}
- final int titleResId;
+ final String titleText;
final String expandedText; // The text in the notification's line 1 and 2.
// Display the first line of the notification:
@@ -339,12 +339,14 @@
CallerInfo ci = callInfo.getCallerInfo();
if (ci != null && ci.userType == CallerInfo.USER_TYPE_WORK) {
- titleResId = R.string.notification_missedWorkCallTitle;
+ titleText = mContext.getSystemService(DevicePolicyManager.class).getResources()
+ .getString(NOTIFICATION_MISSED_WORK_CALL_TITLE, () ->
+ mContext.getString(R.string.notification_missedWorkCallTitle));
} else {
- titleResId = R.string.notification_missedCallTitle;
+ titleText = mContext.getString(R.string.notification_missedCallTitle);
}
} else {
- titleResId = R.string.notification_missedCallsTitle;
+ titleText = mContext.getString(R.string.notification_missedCallsTitle);
expandedText =
mContext.getString(R.string.notification_missedCallsMsg, missedCallCounts);
}
@@ -362,7 +364,7 @@
.setContentTitle(mContext.getText(R.string.userCallActivityLabel))
// Notification details shows that there are missed call(s), but does not reveal
// the missed caller information.
- .setContentText(mContext.getText(titleResId))
+ .setContentText(titleText)
.setContentIntent(createCallLogPendingIntent(userHandle))
.setAutoCancel(true)
.setDeleteIntent(createClearMissedCallsPendingIntent(userHandle));
@@ -373,7 +375,7 @@
.setColor(mContext.getResources().getColor(R.color.theme_color))
.setWhen(callInfo.getCreationTimeMillis())
.setShowWhen(true)
- .setContentTitle(mContext.getText(titleResId))
+ .setContentTitle(titleText)
.setContentText(expandedText)
.setContentIntent(createCallLogPendingIntent(userHandle))
.setAutoCancel(true)
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallList.java b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
index 322c94c..32bc372 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
@@ -124,7 +124,7 @@
}
public Call getCall(int position) {
- return mCalls.get(position);
+ return (position < mCalls.size()) ? mCalls.get(position) : null;
}
public void addCall(Call call) {
diff --git a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
index 010d6ee..f17af2c 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
@@ -4,6 +4,7 @@
import android.app.Activity;
import android.app.UiModeManager;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
@@ -133,9 +134,11 @@
}
private void setDefault() {
- final Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
- intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, getPackageName());
- startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER);
+ RoleManager roleManager = getSystemService(RoleManager.class);
+ if(roleManager!= null) {
+ startActivityForResult(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER),
+ REQUEST_CODE_SET_DEFAULT_DIALER);
+ }
}
private void placeCall() {
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index 2a304a4..5da7f31 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -18,6 +18,7 @@
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
+import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -209,7 +210,8 @@
assertEquals(0, callAnalytics2.endTime);
long missedReason1 = callAnalytics1.missedReason;
assertTrue(missedReason1 == MISSED_REASON_NOT_MISSED
- || missedReason1 == USER_MISSED_CALL_FILTERS_TIMEOUT);
+ || ((missedReason1 & USER_MISSED_CALL_FILTERS_TIMEOUT) > 0)
+ || ((missedReason1 & USER_MISSED_NO_VIBRATE) > 0));
assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics2.missedReason);
assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics1.callDirection);
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 058eb12..049a501 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -37,6 +37,7 @@
import android.content.Context;
import android.content.IContentProvider;
import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Binder;
@@ -627,22 +628,27 @@
mInCallServiceFixtureX.mInCallAdapter.mute(true);
verify(mAudioService, timeout(TEST_TIMEOUT))
- .setMicrophoneMute(eq(true), any(String.class), any(Integer.class));
+ .setMicrophoneMute(eq(true), any(String.class), any(Integer.class),
+ nullable(String.class));
mInCallServiceFixtureX.mInCallAdapter.mute(false);
verify(mAudioService, timeout(TEST_TIMEOUT))
- .setMicrophoneMute(eq(false), any(String.class), any(Integer.class));
+ .setMicrophoneMute(eq(false), any(String.class), any(Integer.class),
+ nullable(String.class));
mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
.getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
- verify(audioManager, timeout(TEST_TIMEOUT))
- .setSpeakerphoneOn(true);
+ ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor =
+ ArgumentCaptor.forClass(AudioDeviceInfo.class);
+ verify(audioManager, timeout(TEST_TIMEOUT)).setCommunicationDevice(
+ infoArgumentCaptor.capture());
+ assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, infoArgumentCaptor.getValue().getType());
mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE, null);
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
.getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
// setSpeakerPhoneOn(false) gets called once during the call initiation phase
verify(audioManager, timeout(TEST_TIMEOUT).atLeast(1))
- .setSpeakerphoneOn(false);
+ .clearCommunicationDevice();
mConnectionServiceFixtureA.
sendSetDisconnected(outgoing.mConnectionId, DisconnectCause.REMOTE);
@@ -1042,7 +1048,9 @@
@Test
public void testIsOutgoingCallPermitted() throws Exception {
assertTrue(mTelecomSystem.getTelecomServiceImpl().getBinder()
- .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle()));
+ .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle(),
+ mPhoneAccountSelfManaged.getAccountHandle().getComponentName()
+ .getPackageName()));
}
/**
@@ -1059,7 +1067,9 @@
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertTrue(mTelecomSystem.getTelecomServiceImpl().getBinder()
- .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle()));
+ .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle(),
+ mPhoneAccountSelfManaged.getAccountHandle().getComponentName()
+ .getPackageName()));
}
/**
@@ -1077,7 +1087,9 @@
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertTrue(mTelecomSystem.getTelecomServiceImpl().getBinder()
- .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle()));
+ .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle(),
+ mPhoneAccountSelfManaged.getAccountHandle().getComponentName()
+ .getPackageName()));
}
/**
@@ -1194,7 +1206,7 @@
ArgumentCaptor<Boolean> muteValueCaptor = ArgumentCaptor.forClass(Boolean.class);
verify(mAudioService, times(2)).setMicrophoneMute(muteValueCaptor.capture(),
- any(String.class), any(Integer.class));
+ any(String.class), any(Integer.class), nullable(String.class));
List<Boolean> muteValues = muteValueCaptor.getAllValues();
// Check mute status was changed twice with true and false.
assertTrue(muteValues.get(0));
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 26aca69..81f476e 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -428,6 +428,37 @@
@SmallTest
@Test
+ public void testConnectDisconnectAudioLeAudio() {
+ receiverUnderTest.setIsInCall(true);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+ BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+ leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
+ when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+
+ AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+ when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+ List<AudioDeviceInfo> devices = new ArrayList<>();
+ devices.add(mockAudioDeviceInfo);
+
+ when(mockAudioManager.getAvailableCommunicationDevices())
+ .thenReturn(devices);
+ when(mockAudioManager.setCommunicationDevice(mockAudioDeviceInfo))
+ .thenReturn(true);
+
+ mBluetoothDeviceManager.connectAudio(device5.getAddress());
+ verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+ verify(mBluetoothHeadset, never()).connectAudio();
+ verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+
+ mBluetoothDeviceManager.disconnectAudio();
+ verify(mockAudioManager).clearCommunicationDevice();
+ }
+
+ @SmallTest
+ @Test
public void testConnectEarbudLeAudio() {
receiverUnderTest.setIsInCall(true);
receiverUnderTest.onReceive(mContext,
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index 7af29aa..4adafc8 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -45,6 +45,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.stream.Collectors;
@@ -294,6 +295,60 @@
verifyProperCleanup();
}
+ @MediumTest
+ @Test
+ public void testRingbackStartStop() {
+ Call call = mock(Call.class);
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+ when(call.getState()).thenReturn(CallState.CONNECTING);
+ when(call.isRingbackRequested()).thenReturn(true);
+
+ mCallAudioManager.onCallAdded(call);
+ assertEquals(call, mCallAudioManager.getForegroundCall());
+ verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ CallAudioModeStateMachine.MessageArgs expectedArgs =
+ new Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+
+ when(call.getState()).thenReturn(CallState.DIALING);
+ mCallAudioManager.onCallStateChanged(call, CallState.CONNECTING, CallState.DIALING);
+ verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+
+ // Ensure we started ringback.
+ verify(mRingbackPlayer).startRingbackForCall(any(Call.class));
+
+ // Report state change from dialing to dialing, which happens when a call is locally
+ // disconnected.
+ mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.DIALING);
+ // Should not have stopped ringback.
+ verify(mRingbackPlayer, never()).stopRingbackForCall(any(Call.class));
+ // Should still only have initial ringback start
+ verify(mRingbackPlayer, times(1)).startRingbackForCall(any(Call.class));
+
+ // Report state to disconnected
+ when(call.getState()).thenReturn(CallState.DISCONNECTED);
+ mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.DISCONNECTED);
+ // Now we should have stopped ringback.
+ verify(mRingbackPlayer).stopRingbackForCall(any(Call.class));
+ // Should still only have initial ringback start recorded from before (don't restart it).
+ verify(mRingbackPlayer, times(1)).startRingbackForCall(any(Call.class));
+ }
+
@SmallTest
@Test
public void testNewCallGoesToAudioProcessing() {
@@ -607,6 +662,21 @@
verifyProperCleanup();
}
+ @SmallTest
+ @Test
+ public void testGetVoipMode() {
+ Call child = mock(Call.class);
+ when(child.getIsVoipAudioMode()).thenReturn(true);
+
+ Call conference = mock(Call.class);
+ when(conference.isConference()).thenReturn(true);
+ when(conference.getIsVoipAudioMode()).thenReturn(false);
+ when(conference.getChildCalls()).thenReturn(Arrays.asList(child));
+
+ assertTrue(mCallAudioManager.isCallVoip(conference));
+ assertTrue(mCallAudioManager.isCallVoip(child));
+ }
+
private Call createSimulatedRingingCall() {
Call call = mock(Call.class);
when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 91ec7f3..6092293 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.IAudioService;
import android.os.HandlerThread;
@@ -505,6 +506,37 @@
@SmallTest
@Test
+ public void testDockWhenInQuiescentState() {
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper());
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+ when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+ CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+ stateMachine.initialize(initState);
+
+ // Raise a dock connect event.
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_DOCK);
+ waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ assertTrue(!stateMachine.isInActiveState());
+ verify(mockAudioManager, never()).setSpeakerphoneOn(eq(true));
+
+ // Raise a dock disconnect event.
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.DISCONNECT_DOCK);
+ waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+ assertTrue(!stateMachine.isInActiveState());
+ verify(mockAudioManager, never()).setSpeakerphoneOn(eq(false));
+ }
+
+ @SmallTest
+ @Test
public void testFocusChangeFromQuiescentSpeaker() {
CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
mContext,
@@ -531,7 +563,10 @@
// Make sure that we've successfully switched to the active speaker route and that we've
// called setSpeakerOn
assertTrue(stateMachine.isInActiveState());
- verify(mockAudioManager).setSpeakerphoneOn(true);
+ ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+ AudioDeviceInfo.class);
+ verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+ assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, infoArgumentCaptor.getValue().getType());
}
@SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 879ed0f..3eacc3a 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.doAnswer;
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.verify;
@@ -30,6 +31,7 @@
import android.bluetooth.BluetoothDevice;
import android.content.Context;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.IAudioService;
import android.os.Handler;
@@ -325,11 +327,18 @@
switch (mParams.speakerInteraction) {
case NONE:
- verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
+ verify(mockAudioManager, never()).setCommunicationDevice(
+ any(AudioDeviceInfo.class));
break;
- case ON: // fall through
+ case ON:
+ ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+ AudioDeviceInfo.class);
+ verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+ assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ infoArgumentCaptor.getValue().getType());
+ break;
case OFF:
- verify(mockAudioManager).setSpeakerphoneOn(mParams.speakerInteraction == ON);
+ verify(mockAudioManager).clearCommunicationDevice();
break;
case OPTIONAL:
// optional, don't test
@@ -823,6 +832,30 @@
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
));
+ params.add(new RoutingTestParameters(
+ "Connect dock from earpiece", // name
+ CallAudioState.ROUTE_EARPIECE, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, // availableRoutes
+ ON, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.CONNECT_DOCK, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, // expectedAvailRoutes
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+ ));
+
+ params.add(new RoutingTestParameters(
+ "Disconnect dock from speaker", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, // availableRoutes
+ OFF, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_DOCK, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, // expectedAvailRoutes
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+ ));
+
return params;
}
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index da72933..6fd8334 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -1625,6 +1625,57 @@
verify(callSpy, never()).setDisconnectCause(any(DisconnectCause.class));
}
+ @Test
+ public void testIsInSelfManagedCallOnlyManaged() {
+ Call managedCall = createCall(SIM_1_HANDLE, CallState.ACTIVE);
+ managedCall.setIsSelfManaged(false);
+ mCallsManager.addCall(managedCall);
+
+ // Certainly nothing from the self managed handle.
+ assertFalse(mCallsManager.isInSelfManagedCall(
+ SELF_MANAGED_HANDLE.getComponentName().getPackageName(),
+ SELF_MANAGED_HANDLE.getUserHandle()));
+ // And nothing in a random other package.
+ assertFalse(mCallsManager.isInSelfManagedCall(
+ "com.foo",
+ SELF_MANAGED_HANDLE.getUserHandle()));
+ // And this method is only checking self managed not managed.
+ assertFalse(mCallsManager.isInSelfManagedCall(
+ SIM_1_HANDLE.getComponentName().getPackageName(),
+ SELF_MANAGED_HANDLE.getUserHandle()));
+ }
+
+ @Test
+ public void testIsInSelfManagedCallOnlySelfManaged() {
+ Call selfManagedCall = createCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+ selfManagedCall.setIsSelfManaged(true);
+ mCallsManager.addCall(selfManagedCall);
+
+ assertTrue(mCallsManager.isInSelfManagedCall(
+ SELF_MANAGED_HANDLE.getComponentName().getPackageName(),
+ SELF_MANAGED_HANDLE.getUserHandle()));
+ assertFalse(mCallsManager.isInSelfManagedCall(
+ "com.foo",
+ SELF_MANAGED_HANDLE.getUserHandle()));
+ assertFalse(mCallsManager.isInSelfManagedCall(
+ SIM_1_HANDLE.getComponentName().getPackageName(),
+ SELF_MANAGED_HANDLE.getUserHandle()));
+
+ Call managedCall = createCall(SIM_1_HANDLE, CallState.ACTIVE);
+ managedCall.setIsSelfManaged(false);
+ mCallsManager.addCall(managedCall);
+
+ // Still not including managed
+ assertFalse(mCallsManager.isInSelfManagedCall(
+ SIM_1_HANDLE.getComponentName().getPackageName(),
+ SELF_MANAGED_HANDLE.getUserHandle()));
+
+ // Also shouldn't be something in another user's version of the same package.
+ assertFalse(mCallsManager.isInSelfManagedCall(
+ SELF_MANAGED_HANDLE.getComponentName().getPackageName(),
+ new UserHandle(90210)));
+ }
+
private Call addSpyCall() {
return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
}
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 47a6177..639ac22 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -51,7 +51,6 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
-import android.location.Country;
import android.location.CountryDetector;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
@@ -59,17 +58,15 @@
import android.os.Handler;
import android.os.IInterface;
import android.os.PersistableBundle;
-import android.os.PowerWhitelistManager;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.Vibrator;
import android.os.VibratorManager;
import android.permission.PermissionCheckerManager;
-import android.telecom.CallAudioState;
import android.telecom.ConnectionService;
import android.telecom.Log;
import android.telecom.InCallService;
-import android.telecom.PhoneAccount;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
@@ -224,6 +221,8 @@
return mTelephonyRegistryManager;
case Context.UI_MODE_SERVICE:
return mUiModeManager;
+ case Context.VIBRATOR_SERVICE:
+ return mVibrator;
case Context.VIBRATOR_MANAGER_SERVICE:
return mVibratorManager;
case Context.PERMISSION_CHECKER_SERVICE:
@@ -253,6 +252,8 @@
return Context.TELEPHONY_REGISTRY_SERVICE;
} else if (svcClass == UiModeManager.class) {
return Context.UI_MODE_SERVICE;
+ } else if (svcClass == Vibrator.class) {
+ return Context.VIBRATOR_SERVICE;
} else if (svcClass == VibratorManager.class) {
return Context.VIBRATOR_MANAGER_SERVICE;
} else if (svcClass == PermissionCheckerManager.class) {
@@ -436,6 +437,7 @@
private int mAudioStreamValue = 1;
private int mMode = AudioManager.MODE_NORMAL;
private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
+ private AudioDeviceInfo mCommunicationDevice;
public FakeAudioManager(Context context) {
super(context);
@@ -492,7 +494,18 @@
}
@Override
+ public void clearCommunicationDevice() {
+ mCommunicationDevice = null;
+ }
+
+ @Override
+ public AudioDeviceInfo getCommunicationDevice() {
+ return mCommunicationDevice;
+ }
+
+ @Override
public boolean setCommunicationDevice(AudioDeviceInfo device) {
+ mCommunicationDevice = device;
return true;
}
}
@@ -540,7 +553,7 @@
private final NotificationManager mNotificationManager = mock(NotificationManager.class);
private final UserManager mUserManager = mock(UserManager.class);
private final StatusBarManager mStatusBarManager = mock(StatusBarManager.class);
- private final SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
+ private SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
private final CarrierConfigManager mCarrierConfigManager = mock(CarrierConfigManager.class);
private final CountryDetector mCountryDetector = mock(CountryDetector.class);
private final Map<String, IContentProvider> mIContentProviderByUri = new HashMap<>();
@@ -549,6 +562,7 @@
private final RoleManager mRoleManager = mock(RoleManager.class);
private final TelephonyRegistryManager mTelephonyRegistryManager =
mock(TelephonyRegistryManager.class);
+ private final Vibrator mVibrator = mock(Vibrator.class);
private final VibratorManager mVibratorManager = mock(VibratorManager.class);
private final UiModeManager mUiModeManager = mock(UiModeManager.class);
private final PermissionCheckerManager mPermissionCheckerManager =
@@ -620,7 +634,10 @@
}
when(mPermissionInfo.isAppOp()).thenReturn(true);
+ when(mVibrator.getDefaultVibrationIntensity(anyInt()))
+ .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
when(mVibratorManager.getVibratorIds()).thenReturn(new int[0]);
+ when(mVibratorManager.getDefaultVibrator()).thenReturn(mVibrator);
// 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
@@ -652,6 +669,7 @@
// Make sure we do not hide PII during testing.
Log.setTag("TelecomTEST");
Log.setIsExtendedLoggingEnabled(true);
+ Log.setUnitTestingEnabled(true);
Log.VERBOSE = true;
}
@@ -735,6 +753,10 @@
mTelecomManager = telecomManager;
}
+ public void setSubscriptionManager(SubscriptionManager subscriptionManager) {
+ mSubscriptionManager = subscriptionManager;
+ }
+
public TelephonyManager getTelephonyManager() {
return mTelephonyManager;
}
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
index 1a649dc..cb376af 100644
--- a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
@@ -21,6 +21,7 @@
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
+import android.os.UserHandle;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -50,7 +51,10 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
@@ -611,6 +615,71 @@
verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
}
+ /**
+ * Tests to verify that the
+ * {@link CreateConnectionProcessor#sortSimPhoneAccountsForEmergency(List, PhoneAccount)} can
+ * successfully sort without running into sort issues related to the hashcodes of the
+ * PhoneAccounts.
+ */
+ @Test
+ public void testSortIntegrity() {
+ // Note: 5L was chosen as a random seed on purpose since in combination with a count of
+ // 500 accounts it would result in a crash in the sort algorithm.
+ ArrayList<PhoneAccount> accounts = generateRandomPhoneAccounts(5L, 500);
+ try {
+ mTestCreateConnectionProcessor.sortSimPhoneAccountsForEmergency(accounts,
+ null);
+ } catch (Exception e) {
+ fail("Failed to sort phone accounts");
+ }
+ }
+
+ /**
+ * Generates random phone accounts.
+ * @param seed random seed to use for random UUIDs; passed in for determinism.
+ * @param count How many phone accounts to use.
+ * @return Random phone accounts.
+ */
+ private ArrayList<PhoneAccount> generateRandomPhoneAccounts(long seed, int count) {
+ Random random = new Random(seed);
+ ArrayList<PhoneAccount> accounts = new ArrayList<>();
+ for (int ix = 0 ; ix < count; ix++) {
+ ArrayList<String> supportedSchemes = new ArrayList<>();
+ supportedSchemes.add("tel");
+ supportedSchemes.add("sip");
+ supportedSchemes.add("custom");
+
+ PhoneAccountHandle handle = new PhoneAccountHandle(
+ ComponentName.unflattenFromString(
+ "com.android.server.telecom.testapps/"
+ + "com.android.server.telecom.testapps"
+ + ".SelfManagedConnectionService"),
+ getRandomUuid(random).toString(), new UserHandle(0));
+ PhoneAccount acct = new PhoneAccount.Builder(handle, "TelecommTests")
+ .setAddress(Uri.fromParts("tel", "555-1212", null))
+ .setCapabilities(3080)
+ .setHighlightColor(0)
+ .setShortDescription("test_" + ix)
+ .setSupportedUriSchemes(supportedSchemes)
+ .setIsEnabled(true)
+ .setSupportedAudioRoutes(15)
+ .build();
+ accounts.add(acct);
+ }
+ return accounts;
+ }
+
+ /**
+ * Returns a random UUID based on the passed in Random generator.
+ * @param random Random generator.
+ * @return The UUID.
+ */
+ private UUID getRandomUuid(Random random) {
+ byte[] array = new byte[16];
+ random.nextBytes(array);
+ return UUID.nameUUIDFromBytes(array);
+ }
+
private PhoneAccount makeEmergencyTestPhoneAccount(String id, int capabilities) {
final PhoneAccount emergencyPhoneAccount = makeQuickAccount(id, capabilities |
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
diff --git a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
new file mode 100644
index 0000000..6d15e60
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.HeadsetMediaButton;
+import com.android.server.telecom.TelecomSystem;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public class HeadsetMediaButtonTest extends TelecomTestCase {
+ private static final int TEST_TIMEOUT_MILLIS = 1000;
+
+ private HeadsetMediaButton mHeadsetMediaButton;
+
+ @Mock private CallsManager mMockCallsManager;
+ @Mock private HeadsetMediaButton.MediaSessionAdapter mMediaSessionAdapter;
+ private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mHeadsetMediaButton = new HeadsetMediaButton(mContext, mMockCallsManager, mLock,
+ mMediaSessionAdapter);
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ mHeadsetMediaButton = null;
+ super.tearDown();
+ }
+
+ /**
+ * Nominal case; just add a call and remove it.
+ */
+ @Test
+ public void testAddCall() {
+ Call regularCall = getRegularCall();
+
+ when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+ mHeadsetMediaButton.onCallAdded(regularCall);
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter).setActive(eq(true));
+ // ... and thus we see how the original code isn't amenable to tests.
+ when(mMediaSessionAdapter.isActive()).thenReturn(true);
+
+ when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
+ mHeadsetMediaButton.onCallRemoved(regularCall);
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter).setActive(eq(false));
+ }
+
+ /**
+ * Test a case where a regular call becomes an external call, and back again.
+ */
+ @Test
+ public void testRegularCallThatBecomesExternal() {
+ Call regularCall = getRegularCall();
+
+ // Start with a regular old call.
+ when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+ mHeadsetMediaButton.onCallAdded(regularCall);
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter).setActive(eq(true));
+ when(mMediaSessionAdapter.isActive()).thenReturn(true);
+
+ // Change so it is external.
+ when(regularCall.isExternalCall()).thenReturn(true);
+ when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
+ mHeadsetMediaButton.onExternalCallChanged(regularCall, true);
+ // Expect to set session inactive.
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter).setActive(eq(false));
+
+ // For good measure lets make it non-external again.
+ when(regularCall.isExternalCall()).thenReturn(false);
+ when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+ mHeadsetMediaButton.onExternalCallChanged(regularCall, false);
+ // Expect to set session active.
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter).setActive(eq(true));
+ }
+
+ /**
+ * @return a mock call instance of a regular non-external call.
+ */
+ private Call getRegularCall() {
+ Call regularCall = Mockito.mock(Call.class);
+ when(regularCall.isExternalCall()).thenReturn(false);
+ return regularCall;
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index f661878..ddacf43 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -64,6 +64,8 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -102,7 +104,9 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
@@ -120,6 +124,8 @@
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import libcore.junit.util.compat.CoreCompatChangeRule;
+
@RunWith(JUnit4.class)
public class InCallControllerTests extends TelecomTestCase {
@Mock CallsManager mMockCallsManager;
@@ -139,6 +145,9 @@
@Mock NotificationManager mNotificationManager;
@Mock PermissionInfo mMockPermissionInfo;
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
private static final int CURRENT_USER_ID = 900973;
private static final String DEF_PKG = "defpkg";
private static final String DEF_CLASS = "defcls";
@@ -909,7 +918,7 @@
/**
* Ensures that the {@link InCallController} will bind to an {@link InCallService} which
- * supports third party app
+ * supports third party app.
*/
@MediumTest
@Test
@@ -924,6 +933,11 @@
true /* system */, false /* external calls */, false /* self mgd in default */,
false /* self mgd in car*/);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
+ // set up mock call for ICSC#sendCrashedInCallServiceNotification(String)
+ when(mMockContext.getApplicationInfo()).thenReturn(applicationInfo);
+
// Enable Third Party Companion App
ExtendedMockito.doReturn(PermissionChecker.PERMISSION_GRANTED).when(() ->
PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
@@ -950,6 +964,7 @@
// Should have next bound to the third party app op non ui app.
verifyBinding(bindIntentCaptor, 1, APPOP_NONUI_PKG, APPOP_NONUI_CLASS);
+
} finally {
mockitoSession.finishMocking();
}
@@ -967,6 +982,13 @@
true /* system */, false /* external calls */, false /* self mgd in default */,
false /* self mgd in car*/, true /* self managed in nonui */);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
+ when(mMockContext.getApplicationInfo()).thenReturn(applicationInfo);
+ // Package doesn't have metadata of TelecomManager.METADATA_IN_CALL_SERVICE_UI should
+ // not be the default dialer. This is to mock the default dialer is null in this case.
+ when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(null);
+
// we should bind to only the non ui app.
mInCallController.bindToServices(mMockCall);
@@ -984,6 +1006,10 @@
// Should have bound to the third party non ui app.
verifyBinding(bindIntentCaptor, 0, NONUI_PKG, NONUI_CLASS);
+
+ // Verify notification is not sent by NotificationManager
+ verify(mNotificationManager, times(0)).notify(eq(InCallController.NOTIFICATION_TAG),
+ eq(InCallController.IN_CALL_SERVICE_NOTIFICATION_ID), any());
}
@MediumTest
diff --git a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
index cb0497a..eadda0d 100644
--- a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -19,23 +19,32 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.ToneGenerator;
import android.test.suitebuilder.annotation.SmallTest;
-import androidx.test.filters.FlakyTest;
-
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioRoutePeripheralAdapter;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.DockManager;
import com.android.server.telecom.InCallTonePlayer;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import org.junit.After;
import org.junit.Before;
@@ -47,19 +56,21 @@
@RunWith(JUnit4.class)
public class InCallTonePlayerTest extends TelecomTestCase {
+ private static final long TEST_TIMEOUT = 5000L;
private InCallTonePlayer.Factory mFactory;
-
- @Mock
private CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
- @Mock
- private TelecomSystem.SyncRoot mLock;
-
- @Mock
- private ToneGenerator mToneGenerator;
-
- @Mock
- private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
+ @Mock private BluetoothRouteManager mBluetoothRouteManager;
+ @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+ @Mock private Timeouts.Adapter mTimeoutsAdapter;
+ @Mock private BluetoothDeviceManager mBluetoothDeviceManager;
+ @Mock private TelecomSystem.SyncRoot mLock;
+ @Mock private ToneGenerator mToneGenerator;
+ @Mock private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
+ @Mock private WiredHeadsetManager mWiredHeadsetManager;
+ @Mock private DockManager mDockManager;
+ @Mock private BluetoothDevice mDevice;
+ @Mock private BluetoothAdapter mBluetoothAdapter;
private InCallTonePlayer.MediaPlayerAdapter mMediaPlayerAdapter =
new InCallTonePlayer.MediaPlayerAdapter() {
@@ -87,7 +98,7 @@
@Override
public int getDuration() {
- return 0;
+ return 1000;
}
};
@@ -109,7 +120,11 @@
when(mToneGeneratorFactory.get(anyInt(), anyInt())).thenReturn(mToneGenerator);
when(mMediaPlayerFactory.get(anyInt(), any())).thenReturn(mMediaPlayerAdapter);
+ doNothing().when(mCallAudioManager).setIsTonePlaying(anyBoolean());
+ mCallAudioRoutePeripheralAdapter = new CallAudioRoutePeripheralAdapter(
+ mCallAudioRouteStateMachine, mBluetoothRouteManager, mWiredHeadsetManager,
+ mDockManager);
mFactory = new InCallTonePlayer.Factory(mCallAudioRoutePeripheralAdapter, mLock,
mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter);
mFactory.setCallAudioManager(mCallAudioManager);
@@ -134,7 +149,43 @@
verify(mMediaPlayerFactory, never()).get(anyInt(), any());
}
- @FlakyTest
+ @SmallTest
+ @Test
+ public void testInterruptMediaTone() {
+ when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+ assertTrue(mInCallTonePlayer.startTone());
+ // Verify we did play a tone.
+ verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
+ verify(mCallAudioManager).setIsTonePlaying(eq(true));
+
+ mInCallTonePlayer.stopTone();
+ // Timeouts due to threads!
+ verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+
+ // Correctness check: ensure we can't start the tone again.
+ assertFalse(mInCallTonePlayer.startTone());
+ }
+
+ @SmallTest
+ @Test
+ public void testInterruptToneGenerator() {
+ mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+ when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+ assertTrue(mInCallTonePlayer.startTone());
+ verify(mToneGenerator, timeout(TEST_TIMEOUT)).startTone(anyInt());
+ verify(mCallAudioManager).setIsTonePlaying(eq(true));
+
+ mInCallTonePlayer.stopTone();
+ // Timeouts due to threads!
+ verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+ // Ideally it would be nice to verify this, however release is a native method so appears to
+ // cause flakiness when testing on Cuttlefish.
+ // verify(mToneGenerator, timeout(TEST_TIMEOUT)).release();
+
+ // Correctness check: ensure we can't start the tone again.
+ assertFalse(mInCallTonePlayer.startTone());
+ }
+
@SmallTest
@Test
public void testEndCallToneWhenNotSilenced() {
@@ -142,7 +193,79 @@
assertTrue(mInCallTonePlayer.startTone());
// Verify we did play a tone.
- verify(mMediaPlayerFactory, timeout(5000)).get(anyInt(), any());
+ verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
+ verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(true));
+ }
+
+ @SmallTest
+ @Test
+ public void testRingbackToneAudioStreamHeadset() {
+ when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+ mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
+ when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
+ when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+
+ when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
+ when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
+
+ mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+ assertTrue(mInCallTonePlayer.startTone());
+ verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+ .get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
+ verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ }
+
+ @SmallTest
+ @Test
+ public void testCallWaitingToneAudioStreamHeadset() {
+ when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+ mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
+ when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
+ when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+
+ when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
+ when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
+
+ mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+ assertTrue(mInCallTonePlayer.startTone());
+ verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+ .get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
+ verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ }
+
+ @SmallTest
+ @Test
+ public void testRingbackToneAudioStreamHearingAid() {
+ when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+ mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
+ when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
+ when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+
+ when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
+ when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
+
+ mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+ assertTrue(mInCallTonePlayer.startTone());
+ verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+ .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
+ verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ }
+
+ @SmallTest
+ @Test
+ public void testCallWaitingToneAudioStreamHearingAid() {
+ when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+ mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
+ when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
+ when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+
+ when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
+ when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
+
+ mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+ assertTrue(mInCallTonePlayer.startTone());
+ verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+ .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
verify(mCallAudioManager).setIsTonePlaying(eq(true));
}
}
diff --git a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
index fd7505b..f935908 100644
--- a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
@@ -17,6 +17,7 @@
package com.android.server.telecom.tests;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.never;
@@ -128,4 +129,34 @@
verify(mWakeLockAdapter, never()).acquire();
}
+
+ @SmallTest
+ @Test
+ public void testExternalCallStateChangeDuringRinging() throws Exception {
+ // A call is ringing on the local device directly.
+ when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(mCall);
+ when(mCall.isExternalCall()).thenReturn(false);
+
+ mInCallWakeLockController.onCallAdded(mCall);
+
+ verify(mWakeLockAdapter).acquire();
+
+ // The call then becomes an external call during ringing. The wake lock should be
+ // released.
+ reset(mWakeLockAdapter);
+ when(mWakeLockAdapter.isHeld()).thenReturn(true);
+ when(mCall.isExternalCall()).thenReturn(true);
+
+ mInCallWakeLockController.onExternalCallChanged(mCall, /* isExternalCall= */ true);
+
+ verify(mWakeLockAdapter).release(0);
+
+ // The call then is pulled to the device, wake up the device and acquire the wake lock.
+ reset(mWakeLockAdapter);
+ when(mCall.isExternalCall()).thenReturn(false);
+
+ mInCallWakeLockController.onExternalCallChanged(mCall, /* isExternalCall= */ true);
+
+ verify(mWakeLockAdapter).acquire();
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/LogUtilsTest.java b/tests/src/com/android/server/telecom/tests/LogUtilsTest.java
new file mode 100644
index 0000000..637dfbc
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/LogUtilsTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.LogUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LogUtilsTest extends TelecomTestCase {
+
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Tests LogUtils#initLogging(Context) listeners cannot be initialized more than once by calling
+ * the init function multiple times. If the listeners are ever re-initialized, log spewing
+ * will occur.
+ *
+ * Note, LogUtils will already be initialized at the start of the testing framework,
+ * so you cannot assume it is 0 at the start of this testing class.
+ */
+ @SmallTest
+ @Test
+ public void testLogUtilsIsNotReInitialized() {
+
+ // assert the listeners of LogUtils are never re-initialized
+ assertTrue(LogUtils.getInitializedCounter() <= 1);
+ // call initLogging an arbitrary amount of times...
+ LogUtils.initLogging(mContext);
+ LogUtils.initLogging(mContext);
+ LogUtils.initLogging(mContext);
+ // assert the listeners of LogUtils are never re-initialized
+ assertTrue(LogUtils.getInitializedCounter() <= 1);
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
index e6cd986..f2f0cd8 100644
--- a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -22,6 +22,11 @@
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
import static android.provider.CallLog.Calls.USER_MISSED_CALL_SCREENING_SERVICE_SILENCED;
+import static android.provider.CallLog.Calls.USER_MISSED_DND_MODE;
+import static android.provider.CallLog.Calls.USER_MISSED_LOW_RING_VOLUME;
+import static android.provider.CallLog.Calls.USER_MISSED_NEVER_RANG;
+import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
+import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -29,17 +34,21 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.media.AudioManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -47,9 +56,11 @@
import android.provider.CallLog;
import android.telecom.DisconnectCause;
import android.telecom.TelecomManager;
+import android.util.Log;
import com.android.server.telecom.Analytics;
import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallIntentProcessor;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;
@@ -63,9 +74,13 @@
import org.mockito.Mock;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
public class MissedInformationTest extends TelecomSystemTest {
- private static final int TEST_TIMEOUT_MILLIS = 1000;
+ private static final int TEST_TIMEOUT_MILLIS = 2000;
+ private static final long SHORT_RING_TIME = 2000;
+ private static final long LONG_RING_TIME = 6000;
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";
@@ -77,9 +92,13 @@
@Mock Call mEmergencyCall;
@Mock Analytics.CallInfo mCallInfo;
@Mock Call mIncomingCall;
+ @Mock AudioManager mAudioManager;
+ @Mock NotificationManager mNotificationManager;
+
private CallsManager mCallsManager;
private CallIntentProcessor.AdapterImpl mAdapter;
private PackageManager mPackageManager;
+ private CountDownLatch mCountDownLatch;
@Override
@Before
@@ -87,6 +106,8 @@
super.setUp();
mCallsManager = mTelecomSystem.getCallsManager();
mAdapter = new CallIntentProcessor.AdapterImpl(mCallsManager.getDefaultDialerCache());
+ mNotificationManager = spy((NotificationManager) mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE));
when(mContentResolver.getPackageName()).thenReturn(PACKAGE_NAME);
when(mContentResolver.acquireProvider(any(String.class))).thenReturn(mContentProvider);
when(mContentProvider.call(any(String.class), any(String.class),
@@ -95,6 +116,7 @@
doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
mPackageManager = mContext.getPackageManager();
when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
+ mCountDownLatch = new CountDownLatch(1);
}
@Override
@@ -202,6 +224,7 @@
.setShouldAllowCall(true)
.build();
mCallsManager.onCallFilteringComplete(mIncomingCall, result, true);
+ assertTrue(mCountDownLatch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
mCallsManager.markCallAsDisconnected(mIncomingCall,
new DisconnectCause(DisconnectCause.MISSED));
ContentValues values = verifyInsertionWithCapture();
@@ -222,6 +245,7 @@
.setCallScreeningComponentName(CALL_SCREENING_COMPONENT_NAME)
.build();
mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+ assertTrue(mCountDownLatch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
assertTrue(mIncomingCall.isIncoming());
mCallsManager.markCallAsDisconnected(mIncomingCall,
new DisconnectCause(DisconnectCause.MISSED));
@@ -237,6 +261,132 @@
assertTrue((missedReason & USER_MISSED_CALL_SCREENING_SERVICE_SILENCED) > 0);
}
+ @Test
+ public void testShortRing() throws Exception {
+ setUpIncomingCall();
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .build();
+ mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+ assertTrue(mCountDownLatch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+ when(mClockProxy.elapsedRealtime()).thenReturn(1L + SHORT_RING_TIME);
+ mCallsManager.markCallAsDisconnected(mIncomingCall,
+ new DisconnectCause(DisconnectCause.MISSED));
+ ContentValues values = verifyInsertionWithCapture();
+
+ long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+ assertTrue((missedReason & USER_MISSED_SHORT_RING) > 0);
+ missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+ assertTrue((missedReason & USER_MISSED_SHORT_RING) > 0);
+ }
+
+ @Test
+ public void testLongRing() throws Exception {
+ setUpIncomingCall();
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .build();
+ mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+ assertTrue(mCountDownLatch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+ when(mClockProxy.elapsedRealtime()).thenReturn(1L + LONG_RING_TIME);
+ mCallsManager.markCallAsDisconnected(mIncomingCall,
+ new DisconnectCause(DisconnectCause.MISSED));
+ ContentValues values = verifyInsertionWithCapture();
+
+ long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+ assertEquals(0, missedReason & USER_MISSED_SHORT_RING);
+ missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+ assertEquals(0, missedReason & USER_MISSED_SHORT_RING);
+ }
+
+ @Test
+ public void testLowRingVolume() throws Exception {
+ CallAudioManager callAudioManager = mCallsManager.getCallAudioManager();
+ when(mSpyContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
+ when(mAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+ setUpIncomingCall();
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .build();
+ mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+
+ // Wait for ringer attributes build completed
+ verify(mAudioManager, timeout(TEST_TIMEOUT_MILLIS)).getStreamVolume(anyInt());
+ mCallsManager.getRinger().waitForAttributesCompletion();
+
+ mCallsManager.markCallAsDisconnected(mIncomingCall,
+ new DisconnectCause(DisconnectCause.MISSED));
+ ContentValues values = verifyInsertionWithCapture();
+
+ long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+ assertTrue((missedReason & USER_MISSED_LOW_RING_VOLUME) > 0);
+ missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+ assertTrue((missedReason & USER_MISSED_LOW_RING_VOLUME) > 0);
+ }
+
+ @Test
+ public void testNoVibrate() throws Exception {
+ when(mSpyContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
+ when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
+ setUpIncomingCall();
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .build();
+ mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+
+ // Wait for ringer attributes build completed
+ verify(mAudioManager, timeout(TEST_TIMEOUT_MILLIS)).getStreamVolume(anyInt());
+ mCallsManager.getRinger().waitForAttributesCompletion();
+
+ mCallsManager.markCallAsDisconnected(mIncomingCall,
+ new DisconnectCause(DisconnectCause.MISSED));
+ ContentValues values = verifyInsertionWithCapture();
+
+ long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+ assertTrue((missedReason & USER_MISSED_NO_VIBRATE) > 0);
+ missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+ assertTrue((missedReason & USER_MISSED_NO_VIBRATE) > 0);
+ }
+
+ @Test
+ public void testDndMode() throws Exception {
+ setUpIncomingCall();
+ doReturn(mNotificationManager).when(mSpyContext)
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ doReturn(false).when(mNotificationManager).matchesCallFilter(any(Bundle.class));
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .build();
+ mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+
+ // Wait for ringer attributes build completed
+ verify(mNotificationManager, timeout(TEST_TIMEOUT_MILLIS))
+ .matchesCallFilter(any(Bundle.class));
+ mCallsManager.getRinger().waitForAttributesCompletion();
+
+ mCallsManager.markCallAsDisconnected(mIncomingCall,
+ new DisconnectCause(DisconnectCause.MISSED));
+ ContentValues values = verifyInsertionWithCapture();
+
+ long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+ assertTrue((missedReason & USER_MISSED_DND_MODE) > 0);
+ missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+ assertTrue((missedReason & USER_MISSED_DND_MODE) > 0);
+ }
+
+ @Test
+ public void testNeverRang() throws Exception {
+ setUpIncomingCall();
+ mCallsManager.markCallAsDisconnected(mIncomingCall,
+ new DisconnectCause(DisconnectCause.MISSED));
+ ContentValues values = verifyInsertionWithCapture();
+
+ long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+ assertEquals(USER_MISSED_NEVER_RANG, missedReason);
+ missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+ assertEquals(USER_MISSED_NEVER_RANG, missedReason);
+ }
+
private ContentValues verifyInsertionWithCapture() {
ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
verify(mContentResolver, timeout(TEST_TIMEOUT_MILLIS))
@@ -260,8 +410,11 @@
null, null, mPhoneAccountA0.getAccountHandle(),
Call.CALL_DIRECTION_INCOMING, false, false,
mClockProxy, null));
+ doReturn(1L).when(mIncomingCall).getStartRingTime();
+ doAnswer((x) -> {
+ mCountDownLatch.countDown();
+ return 1L;
+ }).when(mClockProxy).elapsedRealtime();
mIncomingCall.initAnalytics();
- when(mIncomingCall.getIntentExtras()).thenReturn(new Bundle());
- when(mIncomingCall.getViaNumber()).thenReturn(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 e7cb75d..ffa08e2 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -51,6 +51,8 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Xml;
@@ -63,6 +65,7 @@
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneAccountRegistrar.DefaultPhoneAccountHandle;
+import com.android.server.telecom.TelecomSystem;
import org.junit.After;
import org.junit.Before;
@@ -82,6 +85,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -95,7 +99,9 @@
private final String PACKAGE_1 = "PACKAGE_1";
private final String PACKAGE_2 = "PACKAGE_2";
private final String COMPONENT_NAME = "com.android.server.telecom.tests.MockConnectionService";
+ private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
private PhoneAccountRegistrar mRegistrar;
+ @Mock private SubscriptionManager mSubscriptionManager;
@Mock private TelecomManager mTelecomManager;
@Mock private DefaultDialerCache mDefaultDialerCache;
@Mock private AppLabelProxy mAppLabelProxy;
@@ -106,6 +112,7 @@
super.setUp();
MockitoAnnotations.initMocks(this);
mComponentContextFixture.setTelecomManager(mTelecomManager);
+ mComponentContextFixture.setSubscriptionManager(mSubscriptionManager);
new File(
mComponentContextFixture.getTestDouble().getApplicationContext().getFilesDir(),
FILE_NAME)
@@ -116,7 +123,7 @@
.thenReturn(TEST_LABEL);
mRegistrar = new PhoneAccountRegistrar(
mComponentContextFixture.getTestDouble().getApplicationContext(),
- FILE_NAME, mDefaultDialerCache, mAppLabelProxy);
+ mLock, FILE_NAME, mDefaultDialerCache, mAppLabelProxy);
}
@Override
@@ -1224,6 +1231,29 @@
clearInvocations(mComponentContextFixture.getTelephonyManager());
}
+ /**
+ * Test PhoneAccountHandle Migration Logic.
+ */
+ @Test
+ public void testPhoneAccountMigration() throws Exception {
+ PhoneAccountRegistrar.State testState = makeQuickStateWithTelephonyPhoneAccountHandle();
+ final int mTestPhoneAccountHandleSubIdInt = 123;
+ // Mock SubscriptionManager
+ SubscriptionInfo subscriptionInfo = new SubscriptionInfo(
+ mTestPhoneAccountHandleSubIdInt, "id0", 1, "a", "b", 1, 1, "test",
+ 1, null, null, null, null, false, null, null);
+ List<SubscriptionInfo> subscriptionInfoList = new ArrayList<>();
+ subscriptionInfoList.add(subscriptionInfo);
+ when(mSubscriptionManager.getAllSubscriptionInfoList()).thenReturn(subscriptionInfoList);
+ mRegistrar.migratePhoneAccountHandle(testState);
+ Collection<DefaultPhoneAccountHandle> defaultPhoneAccountHandles
+ = testState.defaultOutgoingAccountHandles.values();
+ DefaultPhoneAccountHandle defaultPhoneAccountHandle
+ = defaultPhoneAccountHandles.iterator().next();
+ assertEquals(Integer.toString(mTestPhoneAccountHandleSubIdInt),
+ defaultPhoneAccountHandle.phoneAccountHandle.getId());
+ }
+
private static ComponentName makeQuickConnectionServiceComponentName() {
return new ComponentName(
"com.android.server.telecom.tests",
@@ -1445,6 +1475,25 @@
}
}
+ private PhoneAccountRegistrar.State makeQuickStateWithTelephonyPhoneAccountHandle() {
+ PhoneAccountRegistrar.State s = new PhoneAccountRegistrar.State();
+ s.accounts.add(makeQuickAccount("id0", 0));
+ s.accounts.add(makeQuickAccount("id1", 1));
+ s.accounts.add(makeQuickAccount("id2", 2));
+ PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(new ComponentName(
+ "com.android.phone",
+ "com.android.services.telephony.TelephonyConnectionService"), "id0");
+ UserHandle userHandle = phoneAccountHandle.getUserHandle();
+ when(UserManager.get(mContext).getSerialNumberForUser(userHandle))
+ .thenReturn(0L);
+ when(UserManager.get(mContext).getUserForSerialNumber(0L))
+ .thenReturn(userHandle);
+ s.defaultOutgoingAccountHandles
+ .put(userHandle, new DefaultPhoneAccountHandle(userHandle, phoneAccountHandle,
+ "testGroup"));
+ return s;
+ }
+
private PhoneAccountRegistrar.State makeQuickState() {
PhoneAccountRegistrar.State s = new PhoneAccountRegistrar.State();
s.accounts.add(makeQuickAccount("id0", 0));
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 00456e2..59a5f02 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -24,8 +24,8 @@
import android.media.VolumeShaper;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Looper;
import android.os.Parcel;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.telecom.TelecomManager;
@@ -47,8 +47,6 @@
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.Spy;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -177,7 +175,7 @@
verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
verify(mockVibrator, never())
- .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@@ -185,7 +183,7 @@
public void testNoActionWithExternalRinger() {
mFuture.complete(false); // not using audio coupled haptics
Bundle externalRingerExtra = new Bundle();
- externalRingerExtra.putBoolean(TelecomManager.EXTRA_CALL_EXTERNAL_RINGER, true);
+ externalRingerExtra.putBoolean(TelecomManager.EXTRA_CALL_HAS_IN_BAND_RINGTONE, true);
when(mockCall1.getIntentExtras()).thenReturn(externalRingerExtra);
when(mockCall2.getIntentExtras()).thenReturn(externalRingerExtra);
// Start call waiting to make sure that it doesn't stop when we start ringing
@@ -195,7 +193,7 @@
verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
verify(mockVibrator, never())
- .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@@ -228,7 +226,7 @@
verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
verify(mockVibrator, never())
- .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@@ -244,7 +242,7 @@
verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
verify(mockVibrator, never())
- .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@@ -263,7 +261,7 @@
verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
verify(mockVibrator, never())
- .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@@ -280,7 +278,7 @@
verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
- any(AudioAttributes.class));
+ any(VibrationAttributes.class));
}
@SmallTest
@@ -299,7 +297,7 @@
eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
// Play default vibration when future completes with no audio coupled haptics
verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
- any(AudioAttributes.class));
+ any(VibrationAttributes.class));
}
@SmallTest
@@ -320,7 +318,7 @@
eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
// Play default vibration when future completes with no audio coupled haptics
verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
- any(AudioAttributes.class));
+ any(VibrationAttributes.class));
}
@SmallTest
@@ -341,7 +339,7 @@
eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
// Skip vibration for audio coupled haptics
verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
- any(AudioAttributes.class));
+ any(VibrationAttributes.class));
}
@SmallTest
@@ -357,7 +355,7 @@
verify(mockRingtonePlayer).play(nullable(RingtoneFactory.class), nullable(Call.class),
nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
verify(mockVibrator, never()).vibrate(nullable(VibrationEffect.class),
- nullable(AudioAttributes.class));
+ nullable(VibrationAttributes.class));
// Simulate something stopping the ringer
mRingerUnderTest.stopRinging();
verify(mockRingtonePlayer).stop();
@@ -366,7 +364,7 @@
mFuture.complete(false);
// Then make sure that we don't actually start vibrating.
verify(mockVibrator, never()).vibrate(nullable(VibrationEffect.class),
- nullable(AudioAttributes.class));
+ nullable(VibrationAttributes.class));
}
@SmallTest
@@ -385,7 +383,7 @@
verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
verify(mockVibrator).vibrate(eq(spyVibrationEffectProxy.get(FAKE_RINGTONE_URI, mContext)),
- any(AudioAttributes.class));
+ any(VibrationAttributes.class));
}
@SmallTest
@@ -401,7 +399,7 @@
verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
eq(true) /* isRingerAudible */, eq(false) /* isVibrationEnabled */);
verify(mockVibrator, never())
- .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@@ -436,7 +434,7 @@
verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
verify(mockVibrator, never())
- .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@@ -454,7 +452,7 @@
verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
verify(mockVibrator, never())
- .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
private void ensureRingerIsAudible() {
@@ -466,15 +464,15 @@
private void enableVibrationWhenRinging() {
when(mockVibrator.hasVibrator()).thenReturn(true);
- when(mockSystemSettingsUtil.canVibrateWhenRinging(any(Context.class))).thenReturn(true);
+ when(mockSystemSettingsUtil.isRingVibrationEnabled(any(Context.class))).thenReturn(true);
}
private void enableVibrationOnlyWhenNotRinging() {
when(mockVibrator.hasVibrator()).thenReturn(true);
- when(mockSystemSettingsUtil.canVibrateWhenRinging(any(Context.class))).thenReturn(false);
+ when(mockSystemSettingsUtil.isRingVibrationEnabled(any(Context.class))).thenReturn(false);
}
private void enableRampingRinger() {
- when(mockSystemSettingsUtil.applyRampingRinger(any(Context.class))).thenReturn(true);
+ when(mockSystemSettingsUtil.isRampingRingerEnabled(any(Context.class))).thenReturn(true);
}
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 5c131c8..210f80e 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -199,10 +199,6 @@
super.setUp();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
- TelephonyManager mockTelephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- when(mockTelephonyManager.isVoiceCapable()).thenReturn(true);
-
doReturn(mContext).when(mContext).getApplicationContext();
doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
@@ -516,6 +512,25 @@
doReturn(PackageManager.PERMISSION_DENIED)
.when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
PackageManager pm = mContext.getPackageManager();
+ when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(true);
+
+ registerPhoneAccountTestHelper(phoneAccount, true);
+ }
+
+ @SmallTest
+ @Test
+ public void testRegisterPhoneAccountWithOldFeatureFlag() throws RemoteException {
+ // tests the case where the package does not have MODIFY_PHONE_STATE but is
+ // registering its own phone account as a third-party connection service
+ String packageNameToUse = "com.thirdparty.connectionservice";
+ PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+ packageNameToUse, "cs"), "asdf", Binder.getCallingUserHandle());
+ PhoneAccount phoneAccount = makePhoneAccount(phHandle).build();
+
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+ PackageManager pm = mContext.getPackageManager();
+ when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(false);
when(pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)).thenReturn(true);
registerPhoneAccountTestHelper(phoneAccount, true);
@@ -534,7 +549,7 @@
doReturn(PackageManager.PERMISSION_DENIED)
.when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
PackageManager pm = mContext.getPackageManager();
- when(pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)).thenReturn(false);
+ when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(false);
registerPhoneAccountTestHelper(phoneAccount, false);
}
@@ -623,7 +638,7 @@
doReturn(PackageManager.PERMISSION_DENIED)
.when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
PackageManager pm = mContext.getPackageManager();
- when(pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)).thenReturn(false);
+ when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(false);
try {
mTSIBinder.unregisterPhoneAccount(phHandle);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 51e1d35..d6ff196 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -573,9 +573,6 @@
com.android.server.telecom.R.string.incall_default_class,
mInCallServiceComponentNameX.getClassName());
- doReturn(true).when(mComponentContextFixture.getTelephonyManager())
- .isVoiceCapable();
-
mInCallServiceFixtureX = new InCallServiceFixture();
mInCallServiceFixtureY = new InCallServiceFixture();
@@ -606,8 +603,8 @@
doReturn(args[0]).when(fakeAudioManager).isMicrophoneMute();
return null;
}
- }).when(audioService)
- .setMicrophoneMute(any(Boolean.class), any(String.class), any(Integer.class));
+ }).when(audioService).setMicrophoneMute(any(Boolean.class), any(String.class),
+ any(Integer.class), nullable(String.class));
} catch (android.os.RemoteException e) {
// Do nothing, leave the faked microphone state as-is
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index 264e087..5353bc6 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -40,6 +40,7 @@
public void setUp() throws Exception {
Log.setTag(TESTING_TAG);
Log.setIsExtendedLoggingEnabled(true);
+ Log.setUnitTestingEnabled(true);
mMockitoHelper.setUp(InstrumentationRegistry.getContext(), getClass());
mComponentContextFixture = new ComponentContextFixture();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();