Merge "Cherrypick from git_tm-dev: Fixing broken {docroot} links"
diff --git a/Android.bp b/Android.bp
index 2740ccc..d031284 100644
--- a/Android.bp
+++ b/Android.bp
@@ -404,7 +404,7 @@
"modules-utils-uieventlogger-interface",
"framework-permission-aidl-java",
"spatializer-aidl-java",
- "audiopolicy-types-aidl-java",
+ "audiopolicy-aidl-java",
"sounddose-aidl-java",
],
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5f4c19b..14d43f5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6635,6 +6635,7 @@
method public boolean isAudioServerRunning();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isBluetoothVariableLatencyEnabled();
method public boolean isHdmiSystemAudioSupported();
+ method @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public boolean isHotwordStreamSupported(boolean);
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
method @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public boolean isUltrasoundSupported();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
@@ -6763,12 +6764,16 @@
public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
method public static long getMaxSharedAudioHistoryMillis();
+ method public boolean isHotwordLookbackStream();
+ method public boolean isHotwordStream();
method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.MediaSyncEvent shareAudioHistory(@NonNull String, @IntRange(from=0) long);
}
public static class AudioRecord.Builder {
method public android.media.AudioRecord.Builder setAudioAttributes(@NonNull android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioRecord.Builder setMaxSharedAudioHistoryMillis(long) throws java.lang.IllegalArgumentException;
+ method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioRecord.Builder setRequestHotwordLookbackStream(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioRecord.Builder setRequestHotwordStream(boolean);
method public android.media.AudioRecord.Builder setSessionId(int) throws java.lang.IllegalArgumentException;
method @NonNull public android.media.AudioRecord.Builder setSharedAudioEvent(@NonNull android.media.MediaSyncEvent) throws java.lang.IllegalArgumentException;
}
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 30333d2..1910331 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -149,7 +149,8 @@
jint channelIndexMask, jint audioFormat,
jint buffSizeInBytes, jintArray jSession,
jobject jAttributionSource, jlong nativeRecordInJavaObj,
- jint sharedAudioHistoryMs) {
+ jint sharedAudioHistoryMs,
+ jint halFlags) {
//ALOGV(">> Entering android_media_AudioRecord_setup");
//ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d "
// "nativeRecordInJavaObj=0x%llX",
@@ -239,10 +240,7 @@
}
ALOGV("AudioRecord_setup for source=%d tags=%s flags=%08x", paa->source, paa->tags, paa->flags);
- audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE;
- if (paa->flags & AUDIO_FLAG_HW_HOTWORD) {
- flags = AUDIO_INPUT_FLAG_HW_HOTWORD;
- }
+ const auto flags = static_cast<audio_input_flags_t>(halFlags);
// create the callback information:
// this data will be passed with every AudioRecord callback
// we use a weak reference so the AudioRecord object can be garbage collected.
@@ -831,7 +829,7 @@
{"native_start", "(II)I", (void *)android_media_AudioRecord_start},
{"native_stop", "()V", (void *)android_media_AudioRecord_stop},
{"native_setup",
- "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JI)I",
+ "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JII)I",
(void *)android_media_AudioRecord_setup},
{"native_finalize", "()V", (void *)android_media_AudioRecord_finalize},
{"native_release", "()V", (void *)android_media_AudioRecord_release},
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 19610a93..a84c42af 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7528,6 +7528,27 @@
/**
* @hide
+ * Indicates whether the platform supports capturing content from the hotword recognition
+ * pipeline. To capture content of this type, create an AudioRecord with
+ * {@link AudioRecord.Builder.setRequestHotwordStream(boolean, boolean)}.
+ * @param lookbackAudio Query if the hotword stream additionally supports providing buffered
+ * audio prior to stream open.
+ * @return True if the platform supports capturing hotword content, and if lookbackAudio
+ * is true, if it additionally supports capturing buffered hotword content prior to stream
+ * open. False otherwise.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
+ public boolean isHotwordStreamSupported(boolean lookbackAudio) {
+ try {
+ return getService().isHotwordStreamSupported(lookbackAudio);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * @hide
* Introspection API to retrieve audio product strategies.
* When implementing {Car|Oem}AudioManager, use this method to retrieve the collection of
* audio product strategies, which is indexed by a weakly typed index in order to be extended
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 6e3829a..b6ab262 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -37,6 +37,7 @@
import android.content.AttributionSource.ScopedParcelState;
import android.content.Context;
import android.media.MediaRecorder.Source;
+import android.media.audio.common.AudioInputFlags;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicy;
@@ -276,6 +277,11 @@
*/
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
/**
+ * Audio HAL Input Flags as bitfield.
+ */
+ private int mHalInputFlags = 0;
+
+ /**
* AudioAttributes
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -360,7 +366,8 @@
public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int sessionId) throws IllegalArgumentException {
this(attributes, format, bufferSizeInBytes, sessionId,
- ActivityThread.currentApplication(), 0 /*maxSharedAudioHistoryMs*/);
+ ActivityThread.currentApplication(),
+ 0 /*maxSharedAudioHistoryMs*/, 0 /* halInputFlags */);
}
/**
@@ -382,14 +389,15 @@
* time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before
* construction.
* @param context An optional context on whose behalf the recoding is performed.
- *
+ * @param maxSharedAudioHistoryMs
+ * @param halInputFlags Bitfield indexed by {@link AudioInputFlags} which is passed to the HAL.
* @throws IllegalArgumentException
*/
private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int sessionId, @Nullable Context context,
- int maxSharedAudioHistoryMs) throws IllegalArgumentException {
+ int maxSharedAudioHistoryMs, int halInputFlags) throws IllegalArgumentException {
mRecordingState = RECORDSTATE_STOPPED;
-
+ mHalInputFlags = halInputFlags;
if (attributes == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes");
}
@@ -469,7 +477,7 @@
int initResult = native_setup(new WeakReference<AudioRecord>(this), mAudioAttributes,
sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
mNativeBufferSizeInBytes, session, attributionSourceState.getParcel(),
- 0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs);
+ 0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs, mHalInputFlags);
if (initResult != SUCCESS) {
loge("Error code " + initResult + " when initializing native AudioRecord object.");
return; // with mState == STATE_UNINITIALIZED
@@ -535,7 +543,8 @@
session,
attributionSourceState.getParcel(),
nativeRecordInJavaObj,
- 0);
+ 0 /*maxSharedAudioHistoryMs*/,
+ 0 /*halInputFlags*/);
}
if (initResult != SUCCESS) {
loge("Error code "+initResult+" when initializing native AudioRecord object.");
@@ -597,6 +606,8 @@
private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
private int mMaxSharedAudioHistoryMs = 0;
private int mCallRedirectionMode = AudioManager.CALL_REDIRECT_NONE;
+ private boolean mIsHotwordStream = false;
+ private boolean mIsHotwordLookback = false;
private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
private static final int PRIVACY_SENSITIVE_DISABLED = 0;
@@ -906,17 +917,73 @@
}
/**
+ * @hide
+ * Set to indicate that the requested AudioRecord object should produce the same type
+ * of audio content that the hotword recognition model consumes. SoundTrigger hotword
+ * recognition will not be disrupted. The source in the set AudioAttributes and the set
+ * audio source will be overridden if this API is used.
+ * <br> Use {@link AudioManager#isHotwordStreamSupported(boolean)} to query support.
+ * @param hotwordContent true if AudioRecord should produce content captured from the
+ * hotword pipeline. false if AudioRecord should produce content captured outside
+ * the hotword pipeline.
+ * @return the same Builder instance.
+ **/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
+ public @NonNull Builder setRequestHotwordStream(boolean hotwordContent) {
+ mIsHotwordStream = hotwordContent;
+ return this;
+ }
+
+ /**
+ * @hide
+ * Set to indicate that the requested AudioRecord object should produce the same type
+ * of audio content that the hotword recognition model consumes and that the stream will
+ * be able to provide buffered audio content from an unspecified duration prior to stream
+ * open. The source in the set AudioAttributes and the set audio source will be overridden
+ * if this API is used.
+ * <br> Use {@link AudioManager#isHotwordStreamSupported(boolean)} to query support.
+ * <br> If this is set, {@link AudioRecord.Builder#setRequestHotwordStream(boolean)}
+ * must not be set, or {@link AudioRecord.Builder#build()} will throw.
+ * @param hotwordLookbackContent true if AudioRecord should produce content captured from
+ * the hotword pipeline with capture content from prior to open. false if AudioRecord
+ * should not capture such content.
+ * to stream open is requested.
+ * @return the same Builder instance.
+ **/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
+ public @NonNull Builder setRequestHotwordLookbackStream(boolean hotwordLookbackContent) {
+ mIsHotwordLookback = hotwordLookbackContent;
+ return this;
+ }
+
+
+ /**
* @return a new {@link AudioRecord} instance successfully initialized with all
* the parameters set on this <code>Builder</code>.
* @throws UnsupportedOperationException if the parameters set on the <code>Builder</code>
- * were incompatible, or if they are not supported by the device,
- * or if the device was not available.
+ * were incompatible, if the parameters are not supported by the device, if the caller
+ * does not hold the appropriate permissions, or if the device was not available.
*/
@RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
public AudioRecord build() throws UnsupportedOperationException {
if (mAudioPlaybackCaptureConfiguration != null) {
return buildAudioPlaybackCaptureRecord();
}
+ int halInputFlags = 0;
+ if (mIsHotwordStream) {
+ if (mIsHotwordLookback) {
+ throw new UnsupportedOperationException(
+ "setRequestHotwordLookbackStream and " +
+ "setRequestHotwordStream used concurrently");
+ } else {
+ halInputFlags = (1 << AudioInputFlags.HOTWORD_TAP);
+ }
+ } else if (mIsHotwordLookback) {
+ halInputFlags = (1 << AudioInputFlags.HOTWORD_TAP) |
+ (1 << AudioInputFlags.HW_LOOKBACK);
+ }
if (mFormat == null) {
mFormat = new AudioFormat.Builder()
@@ -942,6 +1009,12 @@
.build();
}
+ if (mIsHotwordStream || mIsHotwordLookback) {
+ mAttributes = new AudioAttributes.Builder(mAttributes)
+ .setInternalCapturePreset(MediaRecorder.AudioSource.VOICE_RECOGNITION)
+ .build();
+ }
+
// If mPrivacySensitive is default, the privacy flag is already set
// according to audio source in audio attributes.
if (mPrivacySensitive != PRIVACY_SENSITIVE_DEFAULT) {
@@ -980,7 +1053,7 @@
}
final AudioRecord record = new AudioRecord(
mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext,
- mMaxSharedAudioHistoryMs);
+ mMaxSharedAudioHistoryMs, halInputFlags);
if (record.getState() == STATE_UNINITIALIZED) {
// release is not necessary
throw new UnsupportedOperationException("Cannot create AudioRecord");
@@ -1041,8 +1114,8 @@
case AudioFormat.CHANNEL_IN_DEFAULT: // AudioFormat.CHANNEL_CONFIGURATION_DEFAULT
case AudioFormat.CHANNEL_IN_MONO:
case AudioFormat.CHANNEL_CONFIGURATION_MONO:
- mask = AudioFormat.CHANNEL_IN_MONO;
- break;
+ mask = AudioFormat.CHANNEL_IN_MONO;
+ break;
case AudioFormat.CHANNEL_IN_STEREO:
case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
mask = AudioFormat.CHANNEL_IN_STEREO;
@@ -1384,6 +1457,35 @@
return (mAudioAttributes.getAllFlags() & AudioAttributes.FLAG_CAPTURE_PRIVATE) != 0;
}
+ /**
+ * @hide
+ * Returns whether the AudioRecord object produces the same type of audio content that
+ * the hotword recognition model consumes.
+ * <br> If {@link isHotwordLookbackStream(boolean)} is true, this will return false
+ * <br> See {@link Builder#setRequestHotwordStream(boolean)}
+ * @return true if AudioRecord produces hotword content, false otherwise
+ **/
+ @SystemApi
+ public boolean isHotwordStream() {
+ return ((mHalInputFlags & (1 << AudioInputFlags.HOTWORD_TAP)) != 0 &&
+ (mHalInputFlags & (1 << AudioInputFlags.HW_LOOKBACK)) == 0);
+ }
+
+ /**
+ * @hide
+ * Returns whether the AudioRecord object produces the same type of audio content that
+ * the hotword recognition model consumes, and includes capture content from prior to
+ * stream open.
+ * <br> See {@link Builder#setRequestHotwordLookbackStream(boolean)}
+ * @return true if AudioRecord produces hotword capture content from
+ * prior to stream open, false otherwise
+ **/
+ @SystemApi
+ public boolean isHotwordLookbackStream() {
+ return ((mHalInputFlags & (1 << AudioInputFlags.HW_LOOKBACK)) != 0);
+ }
+
+
//---------------------------------------------------------
// Transport control methods
//--------------------
@@ -2346,13 +2448,13 @@
Object /*AudioAttributes*/ attributes,
int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
int buffSizeInBytes, int[] sessionId, String opPackageName,
- long nativeRecordInJavaObj) {
+ long nativeRecordInJavaObj, int halInputFlags) {
AttributionSource attributionSource = AttributionSource.myAttributionSource()
.withPackageName(opPackageName);
try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
return native_setup(audiorecordThis, attributes, sampleRate, channelMask,
channelIndexMask, audioFormat, buffSizeInBytes, sessionId,
- attributionSourceState.getParcel(), nativeRecordInJavaObj, 0);
+ attributionSourceState.getParcel(), nativeRecordInJavaObj, 0, halInputFlags);
}
}
@@ -2360,7 +2462,7 @@
Object /*AudioAttributes*/ attributes,
int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
- long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
+ long nativeRecordInJavaObj, int maxSharedAudioHistoryMs, int halInputFlags);
// TODO remove: implementation calls directly into implementation of native_release()
private native void native_finalize();
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4b36237..0de367d 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -165,6 +165,9 @@
@EnforcePermission("ACCESS_ULTRASOUND")
boolean isUltrasoundSupported();
+ @EnforcePermission("CAPTURE_AUDIO_HOTWORD")
+ boolean isHotwordStreamSupported(boolean lookbackAudio);
+
void setMicrophoneMute(boolean on, String callingPackage, int userId, in String attributionTag);
oneway void setMicrophoneMuteFromSwitch(boolean on);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 57a00c9..b6b657e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -204,15 +204,6 @@
Trace.endSection();
}
- @Override
- public void onUserListItemClicked(@NonNull UserRecord record,
- @Nullable UserSwitchDialogController.DialogShower dialogShower) {
- if (dialogShower != null) {
- mDialogShower.dismiss();
- }
- super.onUserListItemClicked(record, dialogShower);
- }
-
public void linkToViewGroup(ViewGroup viewGroup) {
PseudoGridView.ViewGroupAdapterBridge.link(viewGroup, this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 68d30d3..2b4f51c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -60,7 +60,7 @@
* animation to and from the parent dialog.
*/
@JvmOverloads
- open fun onUserListItemClicked(
+ fun onUserListItemClicked(
record: UserRecord,
dialogShower: DialogShower? = null,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 45cb11e..c8f98c3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -455,6 +455,7 @@
}
UserActionModel.ADD_SUPERVISED_USER -> {
uiEventLogger.log(MultiUserActionsEvent.CREATE_RESTRICTED_USER_FROM_USER_SWITCHER)
+ dismissDialog()
activityStarter.startActivity(
Intent()
.setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index d451230..79721b3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -66,12 +66,6 @@
private fun startHandlingDialogShowRequests() {
applicationScope.get().launch {
interactor.get().dialogShowRequests.filterNotNull().collect { request ->
- currentDialog?.let {
- if (it.isShowing) {
- it.cancel()
- }
- }
-
val (dialog, dialogCuj) =
when (request) {
is ShowDialogRequestModel.ShowAddUserDialog ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 08a90b7..18e40f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -30,7 +30,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.qs.QSUserSwitcherEvent
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.source.UserRecord
import org.junit.Assert.assertEquals
@@ -42,7 +41,6 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -152,15 +150,6 @@
assertNull(adapter.users.find { it.isManageUsers })
}
- @Test
- fun clickDismissDialog() {
- val shower: UserSwitchDialogController.DialogShower =
- mock(UserSwitchDialogController.DialogShower::class.java)
- adapter.injectDialogShower(shower)
- adapter.onUserListItemClicked(createUserRecord(current = true, guest = false), shower)
- verify(shower).dismiss()
- }
-
private fun createUserRecord(current: Boolean, guest: Boolean) =
UserRecord(
UserInfo(0 /* id */, "name", 0 /* flags */),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 9534575..1a2ee18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -61,12 +62,12 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -74,11 +75,13 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class UserInteractorTest : SysuiTestCase() {
@@ -95,7 +98,7 @@
private lateinit var underTest: UserInteractor
- private lateinit var testCoroutineScope: TestCoroutineScope
+ private lateinit var testScope: TestScope
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var telephonyRepository: FakeTelephonyRepository
@@ -119,11 +122,12 @@
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
telephonyRepository = FakeTelephonyRepository()
- testCoroutineScope = TestCoroutineScope()
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
val refreshUsersScheduler =
RefreshUsersScheduler(
- applicationScope = testCoroutineScope,
- mainDispatcher = IMMEDIATE,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
repository = userRepository,
)
underTest =
@@ -136,21 +140,21 @@
repository = keyguardRepository,
),
manager = manager,
- applicationScope = testCoroutineScope,
+ applicationScope = testScope.backgroundScope,
telephonyInteractor =
TelephonyInteractor(
repository = telephonyRepository,
),
broadcastDispatcher = fakeBroadcastDispatcher,
- backgroundDispatcher = IMMEDIATE,
+ backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor =
GuestUserInteractor(
applicationContext = context,
- applicationScope = testCoroutineScope,
- mainDispatcher = IMMEDIATE,
- backgroundDispatcher = IMMEDIATE,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
manager = manager,
repository = userRepository,
deviceProvisionedController = deviceProvisionedController,
@@ -167,7 +171,7 @@
@Test
fun `onRecordSelected - user`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -184,7 +188,7 @@
@Test
fun `onRecordSelected - switch to guest user`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -200,7 +204,7 @@
@Test
fun `onRecordSelected - switch to restricted user`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var userInfos = createUserInfos(count = 2, includeGuest = false).toMutableList()
userInfos.add(
UserInfo(
@@ -225,7 +229,7 @@
@Test
fun `onRecordSelected - enter guest mode`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -234,6 +238,7 @@
whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+ runCurrent()
verify(uiEventLogger, times(1))
.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
@@ -244,7 +249,7 @@
@Test
fun `onRecordSelected - action`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -260,81 +265,72 @@
@Test
fun `users - switcher enabled`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 3, includeGuest = true)
+ val value = collectLastValue(underTest.users)
- job.cancel()
+ assertUsers(models = value(), count = 3, includeGuest = true)
}
@Test
fun `users - switches to second user`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.users)
userRepository.setSelectedUserInfo(userInfos[1])
- assertUsers(models = value, count = 2, selectedIndex = 1)
- job.cancel()
+ assertUsers(models = value(), count = 2, selectedIndex = 1)
}
@Test
fun `users - switcher not enabled`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 1)
-
- job.cancel()
+ val value = collectLastValue(underTest.users)
+ assertUsers(models = value(), count = 1)
}
@Test
fun selectedUser() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var value: UserModel? = null
- val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
- assertUser(value, id = 0, isSelected = true)
+ val value = collectLastValue(underTest.selectedUser)
+ assertUser(value(), id = 0, isSelected = true)
userRepository.setSelectedUserInfo(userInfos[1])
- assertUser(value, id = 1, isSelected = true)
-
- job.cancel()
+ assertUser(value(), id = 1, isSelected = true)
}
@Test
fun `actions - device unlocked`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value)
+ runCurrent()
+
+ assertThat(value())
.isEqualTo(
listOf(
UserActionModel.ENTER_GUEST_MODE,
@@ -343,13 +339,11 @@
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
-
- job.cancel()
}
@Test
fun `actions - device unlocked - full screen`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
@@ -357,10 +351,9 @@
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value)
+ assertThat(value())
.isEqualTo(
listOf(
UserActionModel.ADD_USER,
@@ -369,46 +362,38 @@
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
-
- job.cancel()
}
@Test
fun `actions - device unlocked user not primary - empty list`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
+ assertThat(value()).isEqualTo(emptyList<UserActionModel>())
}
@Test
fun `actions - device unlocked user is guest - empty list`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = true)
assertThat(userInfos[1].isGuest).isTrue()
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
+ assertThat(value()).isEqualTo(emptyList<UserActionModel>())
}
@Test
fun `actions - device locked add from lockscreen set - full list`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -419,10 +404,9 @@
)
)
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value)
+ assertThat(value())
.isEqualTo(
listOf(
UserActionModel.ENTER_GUEST_MODE,
@@ -431,13 +415,11 @@
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
-
- job.cancel()
}
@Test
fun `actions - device locked add from lockscreen set - full list - full screen`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
@@ -449,10 +431,9 @@
)
)
keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value)
+ assertThat(value())
.isEqualTo(
listOf(
UserActionModel.ADD_USER,
@@ -461,42 +442,35 @@
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
)
-
- job.cancel()
}
@Test
fun `actions - device locked - only manage user is shown`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
keyguardRepository.setKeyguardShowing(true)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
+ val value = collectLastValue(underTest.actions)
- assertThat(value).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
-
- job.cancel()
+ assertThat(value()).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
}
@Test
fun `executeAction - add user - dialog shown`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
keyguardRepository.setKeyguardShowing(false)
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
val dialogShower: UserSwitchDialogController.DialogShower = mock()
underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
-
verify(uiEventLogger, times(1))
.log(MultiUserActionsEvent.CREATE_USER_FROM_USER_SWITCHER)
- assertThat(dialogRequest)
+ assertThat(dialogRequest())
.isEqualTo(
ShowDialogRequestModel.ShowAddUserDialog(
userHandle = userInfos[0].userHandle,
@@ -507,14 +481,12 @@
)
underTest.onDialogShown()
- assertThat(dialogRequest).isNull()
-
- job.cancel()
+ assertThat(dialogRequest()).isNull()
}
@Test
- fun `executeAction - add supervised user - starts activity`() =
- runBlocking(IMMEDIATE) {
+ fun `executeAction - add supervised user - dismisses dialog and starts activity`() =
+ testScope.runTest {
underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
verify(uiEventLogger, times(1))
@@ -528,7 +500,7 @@
@Test
fun `executeAction - navigate to manage users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
val intentCaptor = kotlinArgumentCaptor<Intent>()
@@ -538,7 +510,7 @@
@Test
fun `executeAction - guest mode`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -546,25 +518,24 @@
val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
- val showDialogsJob =
- underTest.dialogShowRequests
- .onEach {
- dialogRequests.add(it)
- if (it != null) {
- underTest.onDialogShown()
- }
+ backgroundScope.launch {
+ underTest.dialogShowRequests.collect {
+ dialogRequests.add(it)
+ if (it != null) {
+ underTest.onDialogShown()
}
- .launchIn(this)
- val dismissDialogsJob =
- underTest.dialogDismissRequests
- .onEach {
- if (it != null) {
- underTest.onDialogDismissed()
- }
+ }
+ }
+ backgroundScope.launch {
+ underTest.dialogDismissRequests.collect {
+ if (it != null) {
+ underTest.onDialogDismissed()
}
- .launchIn(this)
+ }
+ }
underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+ runCurrent()
verify(uiEventLogger, times(1))
.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
@@ -573,85 +544,79 @@
ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
)
verify(activityManager).switchUser(guestUserInfo.id)
-
- showDialogsJob.cancel()
- dismissDialogsJob.cancel()
}
@Test
fun `selectUser - already selected guest re-selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = true)
val guestUserInfo = userInfos[1]
assertThat(guestUserInfo.isGuest).isTrue()
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(guestUserInfo)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
underTest.selectUser(
newlySelectedUserId = guestUserInfo.id,
dialogShower = dialogShower,
)
- assertThat(dialogRequest)
+ assertThat(dialogRequest())
.isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
verify(dialogShower, never()).dismiss()
- job.cancel()
}
@Test
fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = true)
val guestUserInfo = userInfos[1]
assertThat(guestUserInfo.isGuest).isTrue()
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(guestUserInfo)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
- assertThat(dialogRequest)
+ assertThat(dialogRequest())
.isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
verify(dialogShower, never()).dismiss()
- job.cancel()
}
@Test
fun `selectUser - not currently guest - switches users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
- assertThat(dialogRequest).isNull()
+ assertThat(dialogRequest()).isNull()
verify(activityManager).switchUser(userInfos[1].id)
verify(dialogShower).dismiss()
- job.cancel()
}
@Test
fun `Telephony call state changes - refreshes users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
+ runCurrent()
+
val refreshUsersCallCount = userRepository.refreshUsersCallCount
telephonyRepository.setCallState(1)
+ runCurrent()
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
}
@Test
fun `User switched broadcast`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -660,9 +625,11 @@
val callback2: UserInteractor.UserCallback = mock()
underTest.addCallback(callback1)
underTest.addCallback(callback2)
+ runCurrent()
val refreshUsersCallCount = userRepository.refreshUsersCallCount
userRepository.setSelectedUserInfo(userInfos[1])
+ runCurrent()
fakeBroadcastDispatcher.registeredReceivers.forEach {
it.onReceive(
context,
@@ -670,16 +637,17 @@
.putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
)
}
+ runCurrent()
- verify(callback1).onUserStateChanged()
- verify(callback2).onUserStateChanged()
+ verify(callback1, atLeastOnce()).onUserStateChanged()
+ verify(callback2, atLeastOnce()).onUserStateChanged()
assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
}
@Test
fun `User info changed broadcast`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -692,12 +660,14 @@
)
}
+ runCurrent()
+
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
}
@Test
fun `System user unlocked broadcast - refresh users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -710,13 +680,14 @@
.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
)
}
+ runCurrent()
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
}
@Test
fun `Non-system user unlocked broadcast - do not refresh users`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -734,14 +705,14 @@
@Test
fun userRecords() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
keyguardRepository.setKeyguardShowing(false)
- testCoroutineScope.advanceUntilIdle()
+ runCurrent()
assertRecords(
records = underTest.userRecords.value,
@@ -760,7 +731,7 @@
@Test
fun userRecordsFullScreen() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
@@ -768,7 +739,7 @@
userRepository.setSelectedUserInfo(userInfos[0])
keyguardRepository.setKeyguardShowing(false)
- testCoroutineScope.advanceUntilIdle()
+ runCurrent()
assertRecords(
records = underTest.userRecords.value,
@@ -787,7 +758,7 @@
@Test
fun selectedUserRecord() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
userRepository.setUserInfos(userInfos)
@@ -805,64 +776,54 @@
@Test
fun `users - secondary user - guest user can be switched to`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var res: List<UserModel>? = null
- val job = underTest.users.onEach { res = it }.launchIn(this)
- assertThat(res?.size == 3).isTrue()
- assertThat(res?.find { it.isGuest }).isNotNull()
- job.cancel()
+ val res = collectLastValue(underTest.users)
+ assertThat(res()?.size == 3).isTrue()
+ assertThat(res()?.find { it.isGuest }).isNotNull()
}
@Test
fun `users - secondary user - no guest action`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var res: List<UserActionModel>? = null
- val job = underTest.actions.onEach { res = it }.launchIn(this)
- assertThat(res?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
- job.cancel()
+ val res = collectLastValue(underTest.actions)
+ assertThat(res()?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
}
@Test
fun `users - secondary user - no guest user record`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = true)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var res: List<UserRecord>? = null
- val job = underTest.userRecords.onEach { res = it }.launchIn(this)
- assertThat(res?.find { it.isGuest }).isNull()
- job.cancel()
+ assertThat(underTest.userRecords.value.find { it.isGuest }).isNull()
}
@Test
fun `show user switcher - full screen disabled - shows dialog switcher`() =
- runBlocking(IMMEDIATE) {
- var dialogRequest: ShowDialogRequestModel? = null
+ testScope.runTest {
val expandable = mock<Expandable>()
underTest.showUserSwitcher(context, expandable)
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
// Dialog is shown.
- assertThat(dialogRequest)
+ assertThat(dialogRequest())
.isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
underTest.onDialogShown()
- assertThat(dialogRequest).isNull()
-
- job.cancel()
+ assertThat(dialogRequest()).isNull()
}
@Test
@@ -893,8 +854,8 @@
@Test
fun `users - secondary user - managed profile is not included`() =
- runBlocking(IMMEDIATE) {
- var userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+ testScope.runTest {
+ val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
userInfos.add(
UserInfo(
50,
@@ -907,23 +868,19 @@
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var res: List<UserModel>? = null
- val job = underTest.users.onEach { res = it }.launchIn(this)
- assertThat(res?.size == 3).isTrue()
- job.cancel()
+ val res = collectLastValue(underTest.users)
+ assertThat(res()?.size == 3).isTrue()
}
@Test
fun `current user is not primary and user switcher is disabled`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[1])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
- var selectedUser: UserModel? = null
- val job = underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
- assertThat(selectedUser).isNotNull()
- job.cancel()
+ val selectedUser = collectLastValue(underTest.selectedUser)
+ assertThat(selectedUser()).isNotNull()
}
private fun assertUsers(
@@ -1061,7 +1018,6 @@
}
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
private val GUEST_ICON: Drawable = mock()
private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5c00452..4d53b48 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -173,7 +173,6 @@
"android.hardware.power.stats-V1-java",
"android.hardware.power-V4-java",
"android.hidl.manager-V1.2-java",
- "capture_state_listener-aidl-java",
"icu4j_calendar_astronomer",
"netd-client",
"overlayable_policy_aidl-java",
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 0fec082..1d4d425 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -655,8 +655,7 @@
// TODO: migrate alarm-prioritization flag to BroadcastConstants
return (isForeground()
|| interactive
- || alarm)
- && receivers.size() == 1;
+ || alarm);
}
@NonNull String getHostingRecordTriggerType() {
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index d4ef638..658e38b 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -668,8 +668,8 @@
mIProximityUpdateCallback = new IProximityUpdateCallback.Stub() {
@Override
public void onProximityUpdate(double distance) {
+ mCallbackInternal.onProximityUpdate(distance);
synchronized (mLock) {
- mCallbackInternal.onProximityUpdate(distance);
freeIfInactiveLocked();
}
}
diff --git a/services/core/java/com/android/server/audio/AudioPolicyFacade.java b/services/core/java/com/android/server/audio/AudioPolicyFacade.java
new file mode 100644
index 0000000..02e80d6
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioPolicyFacade.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 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.audio;
+
+
+/**
+ * Facade to IAudioPolicyService which fulfills AudioService dependencies.
+ * See @link{IAudioPolicyService.aidl}
+ */
+public interface AudioPolicyFacade {
+
+ public boolean isHotwordStreamSupported(boolean lookbackAudio);
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 24c7d2c..0f7a725 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -146,6 +146,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PermissionEnforcer;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
@@ -249,6 +250,7 @@
private final AudioSystemAdapter mAudioSystem;
private final SystemServerAdapter mSystemServer;
private final SettingsAdapter mSettings;
+ private final AudioPolicyFacade mAudioPolicy;
/** Debug audio mode */
protected static final boolean DEBUG_MODE = false;
@@ -923,7 +925,13 @@
public Lifecycle(Context context) {
super(context);
- mService = new AudioService(context);
+ mService = new AudioService(context,
+ AudioSystemAdapter.getDefaultAdapter(),
+ SystemServerAdapter.getDefaultAdapter(context),
+ SettingsAdapter.getDefaultAdapter(),
+ new DefaultAudioPolicyFacade(),
+ null);
+
}
@Override
@@ -976,14 +984,6 @@
// Construction
///////////////////////////////////////////////////////////////////////////
- /** @hide */
- public AudioService(Context context) {
- this(context,
- AudioSystemAdapter.getDefaultAdapter(),
- SystemServerAdapter.getDefaultAdapter(context),
- SettingsAdapter.getDefaultAdapter(),
- null);
- }
/**
* @param context
@@ -994,9 +994,11 @@
* {@link AudioSystemThread} is created as the messaging thread instead.
*/
public AudioService(Context context, AudioSystemAdapter audioSystem,
- SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper) {
- this (context, audioSystem, systemServer, settings, looper,
- context.getSystemService(AppOpsManager.class));
+ SystemServerAdapter systemServer, SettingsAdapter settings,
+ AudioPolicyFacade audioPolicy, @Nullable Looper looper) {
+ this (context, audioSystem, systemServer, settings, audioPolicy, looper,
+ context.getSystemService(AppOpsManager.class),
+ PermissionEnforcer.fromContext(context));
}
/**
@@ -1009,8 +1011,10 @@
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public AudioService(Context context, AudioSystemAdapter audioSystem,
- SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
- AppOpsManager appOps) {
+ SystemServerAdapter systemServer, SettingsAdapter settings,
+ AudioPolicyFacade audioPolicy, @Nullable Looper looper, AppOpsManager appOps,
+ @NonNull PermissionEnforcer enforcer) {
+ super(enforcer);
sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
mContext = context;
mContentResolver = context.getContentResolver();
@@ -1019,7 +1023,7 @@
mAudioSystem = audioSystem;
mSystemServer = systemServer;
mSettings = settings;
-
+ mAudioPolicy = audioPolicy;
mPlatformType = AudioSystem.getPlatformType(context);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -3955,6 +3959,20 @@
return AudioSystem.isUltrasoundSupported();
}
+ /** @see AudioManager#isHotwordStreamSupported() */
+ @android.annotation.EnforcePermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
+ public boolean isHotwordStreamSupported(boolean lookbackAudio) {
+ super.isHotwordStreamSupported_enforcePermission();
+ try {
+ return mAudioPolicy.isHotwordStreamSupported(lookbackAudio);
+ } catch (IllegalStateException e) {
+ // Suppress connection failure to APM, since the method is purely informative
+ Log.e(TAG, "Suppressing exception calling into AudioPolicy", e);
+ return false;
+ }
+ }
+
+
private boolean canChangeAccessibilityVolume() {
synchronized (mAccessibilityServiceUidsLock) {
if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
new file mode 100644
index 0000000..37b8126
--- /dev/null
+++ b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 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.audio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.IAudioPolicyService;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.SafeCloseable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Default implementation of a facade to IAudioPolicyManager which fulfills AudioService
+ * dependencies. This forwards calls as-is to IAudioPolicyManager.
+ * Public methods throw IllegalStateException if AudioPolicy is not initialized/available
+ */
+public class DefaultAudioPolicyFacade implements AudioPolicyFacade, IBinder.DeathRecipient {
+
+ private static final String TAG = "DefaultAudioPolicyFacade";
+ private static final String AUDIO_POLICY_SERVICE_NAME = "media.audio_policy";
+
+ private final Object mServiceLock = new Object();
+ @GuardedBy("mServiceLock")
+ private IAudioPolicyService mAudioPolicy;
+
+ public DefaultAudioPolicyFacade() {
+ try {
+ getAudioPolicyOrInit();
+ } catch (IllegalStateException e) {
+ // Log and suppress this exception, we may be able to connect later
+ Log.e(TAG, "Failed to initialize APM connection", e);
+ }
+ }
+
+ @Override
+ public boolean isHotwordStreamSupported(boolean lookbackAudio) {
+ IAudioPolicyService ap = getAudioPolicyOrInit();
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ return ap.isHotwordStreamSupported(lookbackAudio);
+ } catch (RemoteException e) {
+ resetServiceConnection(ap.asBinder());
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ Log.wtf(TAG, "Unexpected binderDied without IBinder object");
+ }
+
+ @Override
+ public void binderDied(@NonNull IBinder who) {
+ resetServiceConnection(who);
+ }
+
+ private void resetServiceConnection(@Nullable IBinder deadAudioPolicy) {
+ synchronized (mServiceLock) {
+ if (mAudioPolicy != null && mAudioPolicy.asBinder().equals(deadAudioPolicy)) {
+ mAudioPolicy.asBinder().unlinkToDeath(this, 0);
+ mAudioPolicy = null;
+ }
+ }
+ }
+
+ private @Nullable IAudioPolicyService getAudioPolicy() {
+ synchronized (mServiceLock) {
+ return mAudioPolicy;
+ }
+ }
+
+ /*
+ * Does not block.
+ * @throws IllegalStateException for any failed connection
+ */
+ private @NonNull IAudioPolicyService getAudioPolicyOrInit() {
+ synchronized (mServiceLock) {
+ if (mAudioPolicy != null) {
+ return mAudioPolicy;
+ }
+ // Do not block while attempting to connect to APM. Defer to caller.
+ IAudioPolicyService ap = IAudioPolicyService.Stub.asInterface(
+ ServiceManager.checkService(AUDIO_POLICY_SERVICE_NAME));
+ if (ap == null) {
+ throw new IllegalStateException(TAG + ": Unable to connect to AudioPolicy");
+ }
+ try {
+ ap.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(
+ TAG + ": Unable to link deathListener to AudioPolicy", e);
+ }
+ mAudioPolicy = ap;
+ return mAudioPolicy;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/OWNERS b/services/core/java/com/android/server/security/OWNERS
index 5c2d5ba..5bcc98b6 100644
--- a/services/core/java/com/android/server/security/OWNERS
+++ b/services/core/java/com/android/server/security/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 36824
per-file *AttestationVerification* = file:/core/java/android/security/attestationverification/OWNERS
-per-file FileIntegrityService.java = victorhsieh@google.com
+per-file FileIntegrity*.java = victorhsieh@google.com
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index ad2e7e4..38093de 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -64,6 +64,8 @@
private IAudioDeviceVolumeDispatcher.Stub mMockDispatcher =
mock(IAudioDeviceVolumeDispatcher.Stub.class);
+ private AudioPolicyFacade mMockAudioPolicy = mock(AudioPolicyFacade.class);
+
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
@@ -74,7 +76,7 @@
mSystemServer = new NoOpSystemServerAdapter();
mSettingsAdapter = new NoOpSettingsAdapter();
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
- mSettingsAdapter, mTestLooper.getLooper()) {
+ mSettingsAdapter, mMockAudioPolicy, mTestLooper.getLooper()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 7f54b63..4e9ac7c 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -17,6 +17,7 @@
package com.android.server.audio;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -45,6 +46,7 @@
private SystemServerAdapter mSystemServer;
private SettingsAdapter mSettingsAdapter;
private TestLooper mTestLooper;
+ private AudioPolicyFacade mAudioPolicyMock = mock(AudioPolicyFacade.class);
private AudioService mAudioService;
@@ -59,7 +61,7 @@
mSystemServer = new NoOpSystemServerAdapter();
mSettingsAdapter = new NoOpSettingsAdapter();
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
- mSettingsAdapter, mTestLooper.getLooper()) {
+ mSettingsAdapter, mAudioPolicyMock, mTestLooper.getLooper()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index adcbe6b..88d57ac 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.audio;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
@@ -28,6 +29,7 @@
import android.content.Context;
import android.media.AudioSystem;
import android.os.Looper;
+import android.os.PermissionEnforcer;
import android.os.UserHandle;
import android.util.Log;
@@ -37,8 +39,11 @@
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.mockito.Mock;
import org.mockito.Spy;
@@ -49,11 +54,18 @@
private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100;
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
private Context mContext;
private AudioSystemAdapter mAudioSystem;
- @Spy private SystemServerAdapter mSpySystemServer;
private SettingsAdapter mSettingsAdapter;
+
+ @Spy private NoOpSystemServerAdapter mSpySystemServer;
@Mock private AppOpsManager mMockAppOpsManager;
+ @Mock private AudioPolicyFacade mMockAudioPolicy;
+ @Mock private PermissionEnforcer mMockPermissionEnforcer;
+
// the class being unit-tested here
private AudioService mAudioService;
@@ -67,13 +79,12 @@
}
mContext = InstrumentationRegistry.getTargetContext();
mAudioSystem = new NoOpAudioSystemAdapter();
- mSpySystemServer = spy(new NoOpSystemServerAdapter());
mSettingsAdapter = new NoOpSettingsAdapter();
- mMockAppOpsManager = mock(AppOpsManager.class);
when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString()))
.thenReturn(AppOpsManager.MODE_ALLOWED);
mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer,
- mSettingsAdapter, null, mMockAppOpsManager);
+ mSettingsAdapter, mMockAudioPolicy, null, mMockAppOpsManager,
+ mMockPermissionEnforcer);
}
/**
@@ -153,4 +164,15 @@
Assert.assertEquals(ringMaxVol, mAudioService.getStreamVolume(
AudioSystem.STREAM_NOTIFICATION));
}
+
+ @Test
+ public void testAudioPolicyException() throws Exception {
+ Log.i(TAG, "running testAudioPolicyException");
+ Assert.assertNotNull(mAudioService);
+ // Ensure that AudioPolicy inavailability doesn't bring down SystemServer
+ when(mMockAudioPolicy.isHotwordStreamSupported(anyBoolean())).thenThrow(
+ new IllegalStateException(), new IllegalStateException());
+ Assert.assertEquals(false, mAudioService.isHotwordStreamSupported(false));
+ Assert.assertEquals(false, mAudioService.isHotwordStreamSupported(true));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index d89c6d5..77a6286 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
import android.content.Context;
@@ -47,6 +48,7 @@
private SystemServerAdapter mSystemServer;
private SettingsAdapter mSettingsAdapter;
private TestLooper mTestLooper;
+ private AudioPolicyFacade mAudioPolicyMock = mock(AudioPolicyFacade.class);
private AudioService mAudioService;
@@ -67,7 +69,7 @@
mSystemServer = new NoOpSystemServerAdapter();
mSettingsAdapter = new NoOpSettingsAdapter();
mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
- mSettingsAdapter, mTestLooper.getLooper());
+ mSettingsAdapter, mAudioPolicyMock, mTestLooper.getLooper());
mTestLooper.dispatchAll();
}