From 27c97de8ae0e20278f444434bef82629cd1331e4 Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Tue, 22 Nov 2022 23:26:19 +0000 Subject: [PATCH 1/4] Allow setting buffer length for HotwordAudioStreamManager A HotwordDetectionService can specify a buffer length for each HotwordAudioStream in the HotwordDetectedResult by adding an int in the HotwordAudioStream's metadata PersistableBundle, with the key HotwordAudioStream.KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES. If the value is not specified or is an invalid value, the default buffer length is 2,560 bytes. Bug: 257509629 Test: Manually with test app, verified with logs Test: atest CtsVoiceInteractionTestCases Change-Id: I28c7f21bdf7c00b62c9cc6c29153d79ce9d85286 Merged-In: I28c7f21bdf7c00b62c9cc6c29153d79ce9d85286 --- .../service/voice/HotwordAudioStream.java | 15 ++++ .../HotwordAudioStreamManager.java | 77 ++++++++++++------- 2 files changed, 65 insertions(+), 27 deletions(-) diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java index 5442860df007f..1dceb7a08c133 100644 --- a/core/java/android/service/voice/HotwordAudioStream.java +++ b/core/java/android/service/voice/HotwordAudioStream.java @@ -36,6 +36,21 @@ import java.util.Objects; */ public final class HotwordAudioStream implements Parcelable { + /** + * Key for int value to be read from {@link #getMetadata()}. The value is read by the system and + * is the length (in bytes) of the byte buffers created to copy bytes in the + * {@link #getAudioStreamParcelFileDescriptor()} written by the {@link HotwordDetectionService}. + * The buffer length should be chosen such that no additional latency is introduced. Typically, + * this should be at least the size of byte chunks written by the + * {@link HotwordDetectionService}. + * + *

If no value specified in the metadata for the buffer length, or if the value is less than + * 1, or if it is greater than 65,536, or if it is not an int, the default value of 2,560 will + * be used.

+ */ + public static final String KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES = + "android.service.voice.key.AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES"; + /** * The {@link AudioFormat} of the audio stream. */ diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java index d5eea1f3ff355..5ce388c97261c 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java @@ -17,6 +17,7 @@ package com.android.server.voiceinteraction; import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.service.voice.HotwordAudioStream.KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES; import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG; @@ -24,9 +25,9 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.media.permission.Identity; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.service.voice.HotwordAudioStream; import android.service.voice.HotwordDetectedResult; -import android.util.Pair; import android.util.Slog; import java.io.IOException; @@ -45,6 +46,10 @@ final class HotwordAudioStreamManager { private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService"; private static final String TASK_ID_PREFIX = "HotwordDetectedResult@"; private static final String THREAD_NAME_PREFIX = "Copy-"; + private static final int DEFAULT_COPY_BUFFER_LENGTH_BYTES = 2_560; + + // Corresponds to the OS pipe capacity in bytes + private static final int MAX_COPY_BUFFER_LENGTH_BYTES = 65_536; private final AppOpsManager mAppOpsManager; private final Identity mVoiceInteractorIdentity; @@ -76,8 +81,7 @@ final class HotwordAudioStreamManager { } List newAudioStreams = new ArrayList<>(audioStreams.size()); - List> sourcesAndSinks = new ArrayList<>( - audioStreams.size()); + List copyTaskInfos = new ArrayList<>(audioStreams.size()); for (HotwordAudioStream audioStream : audioStreams) { ParcelFileDescriptor[] clientPipe = ParcelFileDescriptor.createReliablePipe(); ParcelFileDescriptor clientAudioSource = clientPipe[0]; @@ -87,41 +91,64 @@ final class HotwordAudioStreamManager { clientAudioSource).build(); newAudioStreams.add(newAudioStream); + int copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES; + PersistableBundle metadata = audioStream.getMetadata(); + if (metadata.containsKey(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES)) { + copyBufferLength = metadata.getInt(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES, -1); + if (copyBufferLength < 1 || copyBufferLength > MAX_COPY_BUFFER_LENGTH_BYTES) { + Slog.w(TAG, "Attempted to set an invalid copy buffer length (" + + copyBufferLength + ") for: " + audioStream); + copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES; + } else if (DEBUG) { + Slog.i(TAG, "Copy buffer length set to " + copyBufferLength + " for: " + + audioStream); + } + } + ParcelFileDescriptor serviceAudioSource = audioStream.getAudioStreamParcelFileDescriptor(); - sourcesAndSinks.add(new Pair<>(serviceAudioSource, clientAudioSink)); + copyTaskInfos.add(new CopyTaskInfo(serviceAudioSource, clientAudioSink, + copyBufferLength)); } String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result); - mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, sourcesAndSinks)); + mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, copyTaskInfos)); return result.buildUpon().setAudioStreams(newAudioStreams).build(); } + private static class CopyTaskInfo { + private final ParcelFileDescriptor mSource; + private final ParcelFileDescriptor mSink; + private final int mCopyBufferLength; + + CopyTaskInfo(ParcelFileDescriptor source, ParcelFileDescriptor sink, int copyBufferLength) { + mSource = source; + mSink = sink; + mCopyBufferLength = copyBufferLength; + } + } + private class HotwordDetectedResultCopyTask implements Runnable { private final String mResultTaskId; - private final List> mSourcesAndSinks; + private final List mCopyTaskInfos; private final ExecutorService mExecutorService = Executors.newCachedThreadPool(); - HotwordDetectedResultCopyTask(String resultTaskId, - List> sourcesAndSinks) { + HotwordDetectedResultCopyTask(String resultTaskId, List copyTaskInfos) { mResultTaskId = resultTaskId; - mSourcesAndSinks = sourcesAndSinks; + mCopyTaskInfos = copyTaskInfos; } @Override public void run() { Thread.currentThread().setName(THREAD_NAME_PREFIX + mResultTaskId); - int size = mSourcesAndSinks.size(); + int size = mCopyTaskInfos.size(); List tasks = new ArrayList<>(size); for (int i = 0; i < size; i++) { - Pair sourceAndSink = - mSourcesAndSinks.get(i); - ParcelFileDescriptor serviceAudioSource = sourceAndSink.first; - ParcelFileDescriptor clientAudioSink = sourceAndSink.second; + CopyTaskInfo copyTaskInfo = mCopyTaskInfos.get(i); String streamTaskId = mResultTaskId + "@" + i; - tasks.add(new SingleAudioStreamCopyTask(streamTaskId, serviceAudioSource, - clientAudioSink)); + tasks.add(new SingleAudioStreamCopyTask(streamTaskId, copyTaskInfo.mSource, + copyTaskInfo.mSink, copyTaskInfo.mCopyBufferLength)); } if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, @@ -148,12 +175,9 @@ final class HotwordAudioStreamManager { private void bestEffortPropagateError(@NonNull String errorMessage) { try { - for (Pair sourceAndSink : - mSourcesAndSinks) { - ParcelFileDescriptor serviceAudioSource = sourceAndSink.first; - ParcelFileDescriptor clientAudioSink = sourceAndSink.second; - serviceAudioSource.closeWithError(errorMessage); - clientAudioSink.closeWithError(errorMessage); + for (CopyTaskInfo copyTaskInfo : mCopyTaskInfos) { + copyTaskInfo.mSource.closeWithError(errorMessage); + copyTaskInfo.mSink.closeWithError(errorMessage); } } catch (IOException e) { Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e); @@ -162,18 +186,17 @@ final class HotwordAudioStreamManager { } private static class SingleAudioStreamCopyTask implements Callable { - // TODO: Make this buffer size customizable from updateState() - private static final int COPY_BUFFER_LENGTH = 2_560; - private final String mStreamTaskId; private final ParcelFileDescriptor mAudioSource; private final ParcelFileDescriptor mAudioSink; + private final int mCopyBufferLength; SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource, - ParcelFileDescriptor audioSink) { + ParcelFileDescriptor audioSink, int copyBufferLength) { mStreamTaskId = streamTaskId; mAudioSource = audioSource; mAudioSink = audioSink; + mCopyBufferLength = copyBufferLength; } @Override @@ -189,7 +212,7 @@ final class HotwordAudioStreamManager { try { fis = new ParcelFileDescriptor.AutoCloseInputStream(mAudioSource); fos = new ParcelFileDescriptor.AutoCloseOutputStream(mAudioSink); - byte[] buffer = new byte[COPY_BUFFER_LENGTH]; + byte[] buffer = new byte[mCopyBufferLength]; while (true) { if (Thread.interrupted()) { Slog.e(TAG, From 53007b4d5b6abce47ab50c68afc40b34e23c6779 Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Wed, 23 Nov 2022 00:45:32 +0000 Subject: [PATCH 2/4] Remove dependency on Identity in HotwordAudioStreamManager android.media.permission.Identity is not needed for AppOpsManager calls and is an unnecessary coupling. Bug: 258323047 Test: None with this change; pure refactoring Change-Id: I1c70f95a73e36c2793e87116b426d55ce359b74b Merged-In: I1c70f95a73e36c2793e87116b426d55ce359b74b --- .../HotwordAudioStreamManager.java | 27 +++++++++++-------- .../HotwordDetectionConnection.java | 3 ++- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java index 5ce388c97261c..57fabcc169a61 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java @@ -23,7 +23,6 @@ import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEB import android.annotation.NonNull; import android.app.AppOpsManager; -import android.media.permission.Identity; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.service.voice.HotwordAudioStream; @@ -52,13 +51,18 @@ final class HotwordAudioStreamManager { private static final int MAX_COPY_BUFFER_LENGTH_BYTES = 65_536; private final AppOpsManager mAppOpsManager; - private final Identity mVoiceInteractorIdentity; + private final int mVoiceInteractorUid; + private final String mVoiceInteractorPackageName; + private final String mVoiceInteractorAttributionTag; private final ExecutorService mExecutorService = Executors.newCachedThreadPool(); HotwordAudioStreamManager(@NonNull AppOpsManager appOpsManager, - @NonNull Identity voiceInteractorIdentity) { + int voiceInteractorUid, @NonNull String voiceInteractorPackageName, + @NonNull String voiceInteractorAttributionTag) { mAppOpsManager = appOpsManager; - mVoiceInteractorIdentity = voiceInteractorIdentity; + mVoiceInteractorUid = voiceInteractorUid; + mVoiceInteractorPackageName = voiceInteractorPackageName; + mVoiceInteractorAttributionTag = voiceInteractorAttributionTag; } /** @@ -152,8 +156,8 @@ final class HotwordAudioStreamManager { } if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, - mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, - mVoiceInteractorIdentity.attributionTag, OP_MESSAGE) == MODE_ALLOWED) { + mVoiceInteractorUid, mVoiceInteractorPackageName, + mVoiceInteractorAttributionTag, OP_MESSAGE) == MODE_ALLOWED) { try { // TODO(b/244599891): Set timeout, close after inactivity mExecutorService.invokeAll(tasks); @@ -162,14 +166,15 @@ final class HotwordAudioStreamManager { bestEffortPropagateError(e.getMessage()); } finally { mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, - mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, - mVoiceInteractorIdentity.attributionTag); + mVoiceInteractorUid, mVoiceInteractorPackageName, + mVoiceInteractorAttributionTag); } } else { bestEffortPropagateError( - "Failed to obtain RECORD_AUDIO_HOTWORD permission for " - + SoundTriggerSessionPermissionsDecorator.toString( - mVoiceInteractorIdentity)); + "Failed to obtain RECORD_AUDIO_HOTWORD permission for voice interactor with" + + " uid=" + mVoiceInteractorUid + + " packageName=" + mVoiceInteractorPackageName + + " attributionTag=" + mVoiceInteractorAttributionTag); } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 6f7d80caa1471..8c5a2c854d712 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -233,7 +233,8 @@ final class HotwordDetectionConnection { mVoiceInteractorIdentity = voiceInteractorIdentity; mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mHotwordAudioStreamManager = new HotwordAudioStreamManager(mAppOpsManager, - mVoiceInteractorIdentity); + mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, + mVoiceInteractorIdentity.attributionTag); mDetectionComponentName = serviceName; mUser = userId; mCallback = callback; From 714691fc4658b78c41e9dc9839e4fba27e5a0a87 Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Wed, 23 Nov 2022 08:51:27 +0000 Subject: [PATCH 3/4] Rename HotwordAudioStreamManager and add Javadoc "Manager" is a little vague and "Copier" is clearer with the class's purpose. Bug: 258323047 Test: None with this change; simple refactoring Change-Id: I43a24b27983838e0ef9798eef2f45d1d4b1c9ac8 Merged-In: I43a24b27983838e0ef9798eef2f45d1d4b1c9ac8 --- ...anager.java => HotwordAudioStreamCopier.java} | 16 ++++++++++++---- .../HotwordDetectionConnection.java | 10 +++++----- 2 files changed, 17 insertions(+), 9 deletions(-) rename services/voiceinteraction/java/com/android/server/voiceinteraction/{HotwordAudioStreamManager.java => HotwordAudioStreamCopier.java} (95%) diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java similarity index 95% rename from services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java rename to services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java index 57fabcc169a61..76574542da4f8 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java @@ -39,9 +39,17 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -final class HotwordAudioStreamManager { +/** + * Copies the audio streams in {@link HotwordDetectedResult}s. This allows the system to manage the + * lifetime of the {@link ParcelFileDescriptor}s and ensures that the flow of data is in the right + * direction from the {@link android.service.voice.HotwordDetectionService} to the client (i.e., the + * voice interactor). + * + * @hide + */ +final class HotwordAudioStreamCopier { - private static final String TAG = "HotwordAudioStreamManager"; + private static final String TAG = "HotwordAudioStreamCopier"; private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService"; private static final String TASK_ID_PREFIX = "HotwordDetectedResult@"; private static final String THREAD_NAME_PREFIX = "Copy-"; @@ -56,7 +64,7 @@ final class HotwordAudioStreamManager { private final String mVoiceInteractorAttributionTag; private final ExecutorService mExecutorService = Executors.newCachedThreadPool(); - HotwordAudioStreamManager(@NonNull AppOpsManager appOpsManager, + HotwordAudioStreamCopier(@NonNull AppOpsManager appOpsManager, int voiceInteractorUid, @NonNull String voiceInteractorPackageName, @NonNull String voiceInteractorAttributionTag) { mAppOpsManager = appOpsManager; @@ -70,7 +78,7 @@ final class HotwordAudioStreamManager { *

* The returned {@link HotwordDetectedResult} is identical the one that was passed in, except * that the {@link ParcelFileDescriptor}s within {@link HotwordDetectedResult#getAudioStreams()} - * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamManager}. The + * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamCopier}. The * returned value should be passed on to the client (i.e., the voice interactor). *

* diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 8c5a2c854d712..3cc8093c63edf 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -170,7 +170,7 @@ final class HotwordDetectionConnection { private final ScheduledExecutorService mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); private final AppOpsManager mAppOpsManager; - private final HotwordAudioStreamManager mHotwordAudioStreamManager; + private final HotwordAudioStreamCopier mHotwordAudioStreamCopier; @Nullable private final ScheduledFuture mCancellationTaskFuture; private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false); private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied; @@ -232,7 +232,7 @@ final class HotwordDetectionConnection { mVoiceInteractionServiceUid = voiceInteractionServiceUid; mVoiceInteractorIdentity = voiceInteractorIdentity; mAppOpsManager = mContext.getSystemService(AppOpsManager.class); - mHotwordAudioStreamManager = new HotwordAudioStreamManager(mAppOpsManager, + mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, mVoiceInteractorIdentity.attributionTag); mDetectionComponentName = serviceName; @@ -491,7 +491,7 @@ final class HotwordDetectionConnection { saveProximityValueToBundle(result); HotwordDetectedResult newResult; try { - newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result); + newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result); } catch (IOException e) { // TODO: Write event mSoftwareCallback.onError(); @@ -683,7 +683,7 @@ final class HotwordDetectionConnection { saveProximityValueToBundle(result); HotwordDetectedResult newResult; try { - newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result); + newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result); } catch (IOException e) { // TODO: Write event externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR); @@ -1015,7 +1015,7 @@ final class HotwordDetectionConnection { HotwordDetectedResult newResult; try { newResult = - mHotwordAudioStreamManager.startCopyingAudioStreams( + mHotwordAudioStreamCopier.startCopyingAudioStreams( triggerResult); } catch (IOException e) { // TODO: Write event From 48c42fda8031a9209778768a60c1c4e4dd0b8ca8 Mon Sep 17 00:00:00 2001 From: Ivan Chiang Date: Tue, 29 Nov 2022 04:56:29 +0000 Subject: [PATCH 4/4] Hotword: Add metrics for Audio Egress Bug: 249269473 Test: statsd_testdrive 434 Change-Id: Ia84489dcef1cc96f8fc61a44ccac34d433658bc9 Merged-In: Ia84489dcef1cc96f8fc61a44ccac34d433658bc9 --- .../HotwordAudioStreamCopier.java | 43 +++++++++++++++++-- .../HotwordDetectionConnection.java | 2 +- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java index 76574542da4f8..b9d2ae6b0d392 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java @@ -19,6 +19,13 @@ package com.android.server.voiceinteraction; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.service.voice.HotwordAudioStream.KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_EMPTY_AUDIO_STREAM_LIST; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_END; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_ILLEGAL_COPY_BUFFER_SIZE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_INTERRUPTED_EXCEPTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_NO_PERMISSION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_START; import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG; import android.annotation.NonNull; @@ -59,15 +66,17 @@ final class HotwordAudioStreamCopier { private static final int MAX_COPY_BUFFER_LENGTH_BYTES = 65_536; private final AppOpsManager mAppOpsManager; + private final int mDetectorType; private final int mVoiceInteractorUid; private final String mVoiceInteractorPackageName; private final String mVoiceInteractorAttributionTag; private final ExecutorService mExecutorService = Executors.newCachedThreadPool(); - HotwordAudioStreamCopier(@NonNull AppOpsManager appOpsManager, + HotwordAudioStreamCopier(@NonNull AppOpsManager appOpsManager, int detectorType, int voiceInteractorUid, @NonNull String voiceInteractorPackageName, @NonNull String voiceInteractorAttributionTag) { mAppOpsManager = appOpsManager; + mDetectorType = detectorType; mVoiceInteractorUid = voiceInteractorUid; mVoiceInteractorPackageName = voiceInteractorPackageName; mVoiceInteractorAttributionTag = voiceInteractorAttributionTag; @@ -89,6 +98,9 @@ final class HotwordAudioStreamCopier { throws IOException { List audioStreams = result.getAudioStreams(); if (audioStreams.isEmpty()) { + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_EMPTY_AUDIO_STREAM_LIST, + mVoiceInteractorUid); return result; } @@ -108,6 +120,9 @@ final class HotwordAudioStreamCopier { if (metadata.containsKey(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES)) { copyBufferLength = metadata.getInt(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES, -1); if (copyBufferLength < 1 || copyBufferLength > MAX_COPY_BUFFER_LENGTH_BYTES) { + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_ILLEGAL_COPY_BUFFER_SIZE, + mVoiceInteractorUid); Slog.w(TAG, "Attempted to set an invalid copy buffer length (" + copyBufferLength + ") for: " + audioStream); copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES; @@ -160,16 +175,26 @@ final class HotwordAudioStreamCopier { CopyTaskInfo copyTaskInfo = mCopyTaskInfos.get(i); String streamTaskId = mResultTaskId + "@" + i; tasks.add(new SingleAudioStreamCopyTask(streamTaskId, copyTaskInfo.mSource, - copyTaskInfo.mSink, copyTaskInfo.mCopyBufferLength)); + copyTaskInfo.mSink, copyTaskInfo.mCopyBufferLength, mDetectorType, + mVoiceInteractorUid)); } if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, mVoiceInteractorUid, mVoiceInteractorPackageName, mVoiceInteractorAttributionTag, OP_MESSAGE) == MODE_ALLOWED) { try { + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_START, + mVoiceInteractorUid); // TODO(b/244599891): Set timeout, close after inactivity mExecutorService.invokeAll(tasks); + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_END, + mVoiceInteractorUid); } catch (InterruptedException e) { + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_INTERRUPTED_EXCEPTION, + mVoiceInteractorUid); Slog.e(TAG, mResultTaskId + ": Task was interrupted", e); bestEffortPropagateError(e.getMessage()); } finally { @@ -178,6 +203,9 @@ final class HotwordAudioStreamCopier { mVoiceInteractorAttributionTag); } } else { + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_NO_PERMISSION, + mVoiceInteractorUid); bestEffortPropagateError( "Failed to obtain RECORD_AUDIO_HOTWORD permission for voice interactor with" + " uid=" + mVoiceInteractorUid @@ -192,6 +220,9 @@ final class HotwordAudioStreamCopier { copyTaskInfo.mSource.closeWithError(errorMessage); copyTaskInfo.mSink.closeWithError(errorMessage); } + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM, + mVoiceInteractorUid); } catch (IOException e) { Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e); } @@ -203,13 +234,17 @@ final class HotwordAudioStreamCopier { private final ParcelFileDescriptor mAudioSource; private final ParcelFileDescriptor mAudioSink; private final int mCopyBufferLength; + private final int mDetectorType; + private final int mUid; SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource, - ParcelFileDescriptor audioSink, int copyBufferLength) { + ParcelFileDescriptor audioSink, int copyBufferLength, int detectorType, int uid) { mStreamTaskId = streamTaskId; mAudioSource = audioSource; mAudioSink = audioSink; mCopyBufferLength = copyBufferLength; + mDetectorType = detectorType; + mUid = uid; } @Override @@ -253,6 +288,8 @@ final class HotwordAudioStreamCopier { mAudioSource.closeWithError(e.getMessage()); mAudioSink.closeWithError(e.getMessage()); Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e); + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM, mUid); } finally { if (fis != null) { fis.close(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 3cc8093c63edf..7074b19cecd07 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -232,7 +232,7 @@ final class HotwordDetectionConnection { mVoiceInteractionServiceUid = voiceInteractionServiceUid; mVoiceInteractorIdentity = voiceInteractorIdentity; mAppOpsManager = mContext.getSystemService(AppOpsManager.class); - mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, + mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, detectorType, mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, mVoiceInteractorIdentity.attributionTag); mDetectionComponentName = serviceName;