diff options
author | Joanne Chung <joannechung@google.com> | 2022-03-18 10:02:36 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2022-03-18 10:02:36 +0000 |
commit | 0f608efde92f423c080272384c80f94d681e6018 (patch) | |
tree | e84ee8abed9ca26b40c0a65f901885c3a19af9ab | |
parent | 44ac2e0c01a5e179bea491e00718262f38549169 (diff) | |
parent | 28faf49c0b893fb54b9d5166b4c4b5e193c44a8c (diff) |
Merge changes from topic "presubmit-am-08bbcb587d4e47b6afcc900c23899344" into sc-v2-dev
* changes:
Add hotword detection metrics.
Add HotwordMetricsLogger for statistics logging.
9 files changed, 390 insertions, 31 deletions
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java index dbe108974684..192260791a8b 100644 --- a/core/java/android/service/voice/AbstractHotwordDetector.java +++ b/core/java/android/service/voice/AbstractHotwordDetector.java @@ -44,14 +44,17 @@ abstract class AbstractHotwordDetector implements HotwordDetector { private final IVoiceInteractionManagerService mManagerService; private final Handler mHandler; private final HotwordDetector.Callback mCallback; + private final int mDetectorType; AbstractHotwordDetector( IVoiceInteractionManagerService managerService, - HotwordDetector.Callback callback) { + HotwordDetector.Callback callback, + int detectorType) { mManagerService = managerService; // TODO: this needs to be supplied from above mHandler = new Handler(Looper.getMainLooper()); mCallback = callback; + mDetectorType = detectorType; } /** @@ -104,19 +107,20 @@ abstract class AbstractHotwordDetector implements HotwordDetector { Slog.d(TAG, "updateState()"); } synchronized (mLock) { - updateStateLocked(options, sharedMemory, null /* callback */); + updateStateLocked(options, sharedMemory, null /* callback */, mDetectorType); } } protected void updateStateLocked(@Nullable PersistableBundle options, - @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) { + @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback, + int detectorType) { if (DEBUG) { Slog.d(TAG, "updateStateLocked()"); } Identity identity = new Identity(); identity.packageName = ActivityThread.currentOpPackageName(); try { - mManagerService.updateState(identity, options, sharedMemory, callback); + mManagerService.updateState(identity, options, sharedMemory, callback, detectorType); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index face870ca1b4..c9daf52b5685 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -578,7 +578,9 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { IVoiceInteractionManagerService modelManagementService, int targetSdkVersion, boolean supportHotwordDetectionService, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { - super(modelManagementService, callback); + super(modelManagementService, callback, + supportHotwordDetectionService ? DETECTOR_TYPE_TRUSTED_HOTWORD_DSP + : DETECTOR_TYPE_NORMAL); mHandler = new MyHandler(); mText = text; @@ -590,7 +592,8 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { mTargetSdkVersion = targetSdkVersion; mSupportHotwordDetectionService = supportHotwordDetectionService; if (mSupportHotwordDetectionService) { - updateStateLocked(options, sharedMemory, mInternalCallback); + updateStateLocked(options, sharedMemory, mInternalCallback, + DETECTOR_TYPE_TRUSTED_HOTWORD_DSP); } try { Identity identity = new Identity(); diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java index e2478195bdde..969ec22beb97 100644 --- a/core/java/android/service/voice/HotwordDetector.java +++ b/core/java/android/service/voice/HotwordDetector.java @@ -37,6 +37,27 @@ import android.os.SharedMemory; public interface HotwordDetector { /** + * Indicates that it is a non-trusted hotword detector. + * + * @hide + */ + int DETECTOR_TYPE_NORMAL = 0; + + /** + * Indicates that it is a DSP trusted hotword detector. + * + * @hide + */ + int DETECTOR_TYPE_TRUSTED_HOTWORD_DSP = 1; + + /** + * Indicates that it is a software trusted hotword detector. + * + * @hide + */ + int DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE = 2; + + /** * Starts hotword recognition. * <p> * On calling this, the system streams audio from the device microphone to this application's @@ -98,6 +119,22 @@ public interface HotwordDetector { void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory); /** + * @hide + */ + static String detectorTypeToString(int detectorType) { + switch (detectorType) { + case DETECTOR_TYPE_NORMAL: + return "normal"; + case DETECTOR_TYPE_TRUSTED_HOTWORD_DSP: + return "trusted_hotword_dsp"; + case DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: + return "trusted_hotword_software"; + default: + return Integer.toString(detectorType); + } + } + + /** * The callback to notify of detection events. */ interface Callback { diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java index f7a3415259fd..512a654adbab 100644 --- a/core/java/android/service/voice/SoftwareHotwordDetector.java +++ b/core/java/android/service/voice/SoftwareHotwordDetector.java @@ -60,14 +60,15 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector { PersistableBundle options, SharedMemory sharedMemory, HotwordDetector.Callback callback) { - super(managerService, callback); + super(managerService, callback, DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE); mManagerService = managerService; mAudioFormat = audioFormat; mCallback = callback; mHandler = new Handler(Looper.getMainLooper()); updateStateLocked(options, sharedMemory, - new InitializationStateListener(mHandler, mCallback)); + new InitializationStateListener(mHandler, mCallback), + DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE); } @RequiresPermission(RECORD_AUDIO) diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 998526209c72..61c9128e499e 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -242,12 +242,14 @@ interface IVoiceInteractionManagerService { * {@link HotwordDetectionService}. Use this to provide the hotword models data or other * such data to the trusted process. * @param callback Use this to report {@link HotwordDetectionService} status. + * @param detectorType Indicate which detector is used. */ void updateState( in Identity originatorIdentity, in PersistableBundle options, in SharedMemory sharedMemory, - in IHotwordRecognitionStatusCallback callback); + in IHotwordRecognitionStatusCallback callback, + int detectorType); /** * Requests to shutdown hotword detection service. diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 36bb375be3c8..823cc07d9f7b 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -20,9 +20,30 @@ import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.RECORD_AUDIO; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE; +import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS; import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN; import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -89,12 +110,24 @@ final class HotwordDetectionConnection { static final boolean DEBUG = false; // TODO: These constants need to be refined. - private static final long VALIDATION_TIMEOUT_MILLIS = 3000; + private static final long VALIDATION_TIMEOUT_MILLIS = 4000; private static final long MAX_UPDATE_TIMEOUT_MILLIS = 6000; private static final Duration MAX_UPDATE_TIMEOUT_DURATION = Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS); private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour + // Hotword metrics + private static final int METRICS_INIT_UNKNOWN_TIMEOUT = + HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT; + private static final int METRICS_INIT_UNKNOWN_NO_VALUE = + HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE; + private static final int METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE = + HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE; + private static final int METRICS_INIT_CALLBACK_STATE_ERROR = + HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR; + private static final int METRICS_INIT_CALLBACK_STATE_SUCCESS = + HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS; + private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool(); // TODO: This may need to be a Handler(looper) private final ScheduledExecutorService mScheduledExecutorService = @@ -103,6 +136,7 @@ final class HotwordDetectionConnection { private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied; private final @NonNull ServiceConnectionFactory mServiceConnectionFactory; private final IHotwordRecognitionStatusCallback mCallback; + private final int mDetectorType; final Object mLock; final int mVoiceInteractionServiceUid; @@ -114,6 +148,7 @@ final class HotwordDetectionConnection { private Instant mLastRestartInstant; private ScheduledFuture<?> mCancellationTaskFuture; + private ScheduledFuture<?> mCancellationKeyPhraseDetectionFuture; private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null; /** Identity used for attributing app ops when delivering data to the Interactor. */ @@ -134,7 +169,7 @@ final class HotwordDetectionConnection { Identity voiceInteractorIdentity, ComponentName serviceName, int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, - @NonNull IHotwordRecognitionStatusCallback callback) { + @NonNull IHotwordRecognitionStatusCallback callback, int detectorType) { if (callback == null) { Slog.w(TAG, "Callback is null while creating connection"); throw new IllegalArgumentException("Callback is null while creating connection"); @@ -146,6 +181,7 @@ final class HotwordDetectionConnection { mDetectionComponentName = serviceName; mUser = userId; mCallback = callback; + mDetectorType = detectorType; final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE); intent.setComponent(mDetectionComponentName); initAudioFlingerLocked(); @@ -163,6 +199,8 @@ final class HotwordDetectionConnection { Slog.v(TAG, "Time to restart the process, TTL has passed"); synchronized (mLock) { restartProcessLocked(); + HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType, + HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE); } }, 30, 30, TimeUnit.MINUTES); } @@ -195,6 +233,8 @@ final class HotwordDetectionConnection { // We restart the process instead of simply sending over the new binder, to avoid race // conditions with audio reading in the service. restartProcessLocked(); + HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType, + HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED); } } @@ -214,26 +254,30 @@ final class HotwordDetectionConnection { future.complete(null); if (mUpdateStateAfterStartFinished.getAndSet(true)) { Slog.w(TAG, "call callback after timeout"); + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT, + mVoiceInteractionServiceUid); return; } - int status = bundle != null ? bundle.getInt( - KEY_INITIALIZATION_STATUS, - INITIALIZATION_STATUS_UNKNOWN) - : INITIALIZATION_STATUS_UNKNOWN; - // Add the protection to avoid unexpected status - if (status > HotwordDetectionService.getMaxCustomInitializationStatus() - && status != INITIALIZATION_STATUS_UNKNOWN) { - status = INITIALIZATION_STATUS_UNKNOWN; - } + Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle); + int status = statusResultPair.first; + int initResultMetricsResult = statusResultPair.second; try { mCallback.onStatusReported(status); + HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType, + initResultMetricsResult); } catch (RemoteException e) { Slog.w(TAG, "Failed to report initialization status: " + e); + HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType, + METRICS_INIT_CALLBACK_STATE_ERROR); } } }; try { service.updateState(options, sharedMemory, statusCallback); + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE, + mVoiceInteractionServiceUid); } catch (RemoteException e) { // TODO: (b/181842909) Report an error to voice interactor Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e); @@ -248,8 +292,12 @@ final class HotwordDetectionConnection { } try { mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN); + HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType, + METRICS_INIT_UNKNOWN_TIMEOUT); } catch (RemoteException e) { Slog.w(TAG, "Failed to report initialization status UNKNOWN", e); + HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType, + METRICS_INIT_CALLBACK_STATE_ERROR); } } else if (err != null) { Slog.w(TAG, "Failed to update state: " + err); @@ -259,6 +307,24 @@ final class HotwordDetectionConnection { }); } + private static Pair<Integer, Integer> getInitStatusAndMetricsResult(Bundle bundle) { + if (bundle == null) { + return new Pair<>(INITIALIZATION_STATUS_UNKNOWN, METRICS_INIT_UNKNOWN_NO_VALUE); + } + int status = bundle.getInt(KEY_INITIALIZATION_STATUS, INITIALIZATION_STATUS_UNKNOWN); + if (status > HotwordDetectionService.getMaxCustomInitializationStatus()) { + return new Pair<>(INITIALIZATION_STATUS_UNKNOWN, + status == INITIALIZATION_STATUS_UNKNOWN + ? METRICS_INIT_UNKNOWN_NO_VALUE + :METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE); + } + // TODO: should guard against negative here + int metricsResult = status == INITIALIZATION_STATUS_SUCCESS + ? METRICS_INIT_CALLBACK_STATE_SUCCESS + : METRICS_INIT_CALLBACK_STATE_ERROR; + return new Pair<>(status, metricsResult); + } + private boolean isBound() { synchronized (mLock) { return mRemoteHotwordDetectionService.isBound(); @@ -473,12 +539,31 @@ final class HotwordDetectionConnection { Slog.d(TAG, "onDetected"); } synchronized (mLock) { + // TODO: If the dsp trigger comes in after the timeout, we will log both events. + // Because we don't enforce the timeout yet. We should add some synchronizations + // within the runnable to prevent the race condition to log both events. + if (mCancellationKeyPhraseDetectionFuture != null) { + mCancellationKeyPhraseDetectionFuture.cancel(true); + } + HotwordMetricsLogger.writeKeyphraseTriggerEvent( + mDetectorType, + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED); if (!mValidatingDspTrigger) { Slog.i(TAG, "Ignoring #onDetected due to a process restart"); + HotwordMetricsLogger.writeKeyphraseTriggerEvent( + mDetectorType, + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION); return; } mValidatingDspTrigger = false; - enforcePermissionsForDataDelivery(); + try { + enforcePermissionsForDataDelivery(); + } catch (SecurityException e) { + HotwordMetricsLogger.writeKeyphraseTriggerEvent( + mDetectorType, + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION); + throw e; + } externalCallback.onKeyphraseDetected(recognitionEvent, result); if (result != null) { Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result) @@ -496,8 +581,17 @@ final class HotwordDetectionConnection { Slog.d(TAG, "onRejected"); } synchronized (mLock) { + if (mCancellationKeyPhraseDetectionFuture != null) { + mCancellationKeyPhraseDetectionFuture.cancel(true); + } + HotwordMetricsLogger.writeKeyphraseTriggerEvent( + mDetectorType, + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED); if (!mValidatingDspTrigger) { Slog.i(TAG, "Ignoring #onRejected due to a process restart"); + HotwordMetricsLogger.writeKeyphraseTriggerEvent( + mDetectorType, + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION); return; } mValidatingDspTrigger = false; @@ -512,11 +606,20 @@ final class HotwordDetectionConnection { synchronized (mLock) { mValidatingDspTrigger = true; mRemoteHotwordDetectionService.run( - service -> service.detectFromDspSource( - recognitionEvent, - recognitionEvent.getCaptureFormat(), - VALIDATION_TIMEOUT_MILLIS, - internalCallback)); + service -> { + // TODO: avoid allocate every time + mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule( + () -> HotwordMetricsLogger + .writeKeyphraseTriggerEvent(mDetectorType, + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT), + VALIDATION_TIMEOUT_MILLIS, + TimeUnit.MILLISECONDS); + service.detectFromDspSource( + recognitionEvent, + recognitionEvent.getCaptureFormat(), + VALIDATION_TIMEOUT_MILLIS, + internalCallback); + }); } } @@ -617,10 +720,16 @@ final class HotwordDetectionConnection { } final boolean useHotwordDetectionService = mHotwordDetectionConnection != null; if (useHotwordDetectionService) { + HotwordMetricsLogger.writeKeyphraseTriggerEvent( + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP, + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER); mRecognitionEvent = recognitionEvent; mHotwordDetectionConnection.detectFromDspSource( recognitionEvent, mExternalCallback); } else { + HotwordMetricsLogger.writeKeyphraseTriggerEvent( + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR, + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER); mExternalCallback.onKeyphraseDetected(recognitionEvent, null); } } @@ -783,6 +892,7 @@ final class HotwordDetectionConnection { private boolean mRespectServiceConnectionStatusChanged = true; private boolean mIsBound = false; + private boolean mIsLoggedFirstConnect = false; ServiceConnection(@NonNull Context context, @NonNull Intent intent, int bindingFlags, int userId, @@ -806,6 +916,12 @@ final class HotwordDetectionConnection { return; } mIsBound = connected; + if (connected && !mIsLoggedFirstConnect) { + mIsLoggedFirstConnect = true; + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED, + mVoiceInteractionServiceUid); + } } } @@ -836,13 +952,25 @@ final class HotwordDetectionConnection { protected boolean bindService( @NonNull android.content.ServiceConnection serviceConnection) { try { - return mContext.bindIsolatedService( + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE, + mVoiceInteractionServiceUid); + boolean bindResult = mContext.bindIsolatedService( mIntent, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags, "hotword_detector_" + mInstanceNumber, mExecutor, serviceConnection); + if (!bindResult) { + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL, + mVoiceInteractionServiceUid); + } + return bindResult; } catch (IllegalArgumentException e) { + HotwordMetricsLogger.writeDetectorEvent(mDetectorType, + HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL, + mVoiceInteractionServiceUid); Slog.wtf(TAG, "Can't bind to the hotword detection service!", e); return false; } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java new file mode 100644 index 000000000000..940aed34b7fb --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.voiceinteraction; + +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__NORMAL_DETECTOR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__NORMAL_DETECTOR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__NORMAL_DETECTOR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; + +import android.service.voice.HotwordDetector; + +import com.android.internal.util.FrameworkStatsLog; + +/** + * A utility class for logging hotword statistics event. + */ +public final class HotwordMetricsLogger { + + private static final int METRICS_INIT_DETECTOR_SOFTWARE = + HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; + private static final int METRICS_INIT_DETECTOR_DSP = + HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; + private static final int METRICS_INIT_NORMAL_DETECTOR = + HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR; + + private HotwordMetricsLogger() { + // Class only contains static utility functions, and should not be instantiated + } + + /** + * Logs information related to create hotword detector. + */ + public static void writeDetectorCreateEvent(int detectorType, boolean isCreated, int uid) { + int metricsDetectorType = getCreateMetricsDetectorType(detectorType); + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED, + metricsDetectorType, isCreated, uid); + } + + /** + * Logs information related to hotword detection service init result. + */ + public static void writeServiceInitResultEvent(int detectorType, int result) { + int metricsDetectorType = getInitMetricsDetectorType(detectorType); + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED, + metricsDetectorType, result); + } + + /** + * Logs information related to hotword detection service restarting. + */ + public static void writeServiceRestartEvent(int detectorType, int reason) { + int metricsDetectorType = getRestartMetricsDetectorType(detectorType); + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED, + metricsDetectorType, reason); + } + + /** + * Logs information related to keyphrase trigger. + */ + public static void writeKeyphraseTriggerEvent(int detectorType, int result) { + int metricsDetectorType = getKeyphraseMetricsDetectorType(detectorType); + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED, + metricsDetectorType, result); + } + + /** + * Logs information related to hotword detector events. + */ + public static void writeDetectorEvent(int detectorType, int event, int uid) { + int metricsDetectorType = getDetectorMetricsDetectorType(detectorType); + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS, + metricsDetectorType, event, uid); + } + + private static int getCreateMetricsDetectorType(int detectorType) { + switch (detectorType) { + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: + return HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP: + return HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; + default: + return HOTWORD_DETECTOR_CREATE_REQUESTED__DETECTOR_TYPE__NORMAL_DETECTOR; + } + } + + private static int getRestartMetricsDetectorType(int detectorType) { + switch (detectorType) { + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: + return HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP: + return HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; + default: + return HOTWORD_DETECTION_SERVICE_RESTARTED__DETECTOR_TYPE__NORMAL_DETECTOR; + } + } + + private static int getInitMetricsDetectorType(int detectorType) { + switch (detectorType) { + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: + return METRICS_INIT_DETECTOR_SOFTWARE; + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP: + return METRICS_INIT_DETECTOR_DSP; + default: + return METRICS_INIT_NORMAL_DETECTOR; + } + } + + private static int getKeyphraseMetricsDetectorType(int detectorType) { + switch (detectorType) { + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: + return HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP: + return HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; + default: + return HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR; + } + } + + private static int getDetectorMetricsDetectorType(int detectorType) { + switch (detectorType) { + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: + return HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP: + return HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; + default: + return HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__NORMAL_DETECTOR; + } + } +} diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 8445ed4884e2..1285a84ea752 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1168,7 +1168,8 @@ public class VoiceInteractionManagerService extends SystemService { @NonNull Identity voiceInteractorIdentity, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, - IHotwordRecognitionStatusCallback callback) { + IHotwordRecognitionStatusCallback callback, + int detectorType) { enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION); synchronized (this) { enforceIsCurrentVoiceInteractionService(); @@ -1184,7 +1185,7 @@ public class VoiceInteractionManagerService extends SystemService { final long caller = Binder.clearCallingIdentity(); try { mImpl.updateStateLocked( - voiceInteractorIdentity, options, sharedMemory, callback); + voiceInteractorIdentity, options, sharedMemory, callback, detectorType); } finally { Binder.restoreCallingIdentity(caller); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 52c5b6bbc239..20cf16aea18e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -53,6 +53,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SharedMemory; import android.os.UserHandle; +import android.service.voice.HotwordDetector; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -102,6 +103,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne VoiceInteractionSessionConnection mActiveSession; int mDisabledShowContext; + int mDetectorType; final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -456,26 +458,36 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne @NonNull Identity voiceInteractorIdentity, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, - IHotwordRecognitionStatusCallback callback) { + IHotwordRecognitionStatusCallback callback, + int detectorType) { Slog.v(TAG, "updateStateLocked"); + int voiceInteractionServiceUid = mInfo.getServiceInfo().applicationInfo.uid; if (mHotwordDetectionComponentName == null) { Slog.w(TAG, "Hotword detection service name not found"); + logDetectorCreateEventIfNeeded(callback, detectorType, false, + voiceInteractionServiceUid); throw new IllegalStateException("Hotword detection service name not found"); } ServiceInfo hotwordDetectionServiceInfo = getServiceInfoLocked( mHotwordDetectionComponentName, mUser); if (hotwordDetectionServiceInfo == null) { Slog.w(TAG, "Hotword detection service info not found"); + logDetectorCreateEventIfNeeded(callback, detectorType, false, + voiceInteractionServiceUid); throw new IllegalStateException("Hotword detection service info not found"); } if (!isIsolatedProcessLocked(hotwordDetectionServiceInfo)) { Slog.w(TAG, "Hotword detection service not in isolated process"); + logDetectorCreateEventIfNeeded(callback, detectorType, false, + voiceInteractionServiceUid); throw new IllegalStateException("Hotword detection service not in isolated process"); } if (!Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE.equals( hotwordDetectionServiceInfo.permission)) { Slog.w(TAG, "Hotword detection service does not require permission " + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE); + logDetectorCreateEventIfNeeded(callback, detectorType, false, + voiceInteractionServiceUid); throw new SecurityException("Hotword detection service does not require permission " + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE); } @@ -484,25 +496,40 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Voice interaction service should not hold permission " + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE); + logDetectorCreateEventIfNeeded(callback, detectorType, false, + voiceInteractionServiceUid); throw new SecurityException("Voice interaction service should not hold permission " + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE); } if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) { Slog.w(TAG, "Can't set sharedMemory to be read-only"); + logDetectorCreateEventIfNeeded(callback, detectorType, false, + voiceInteractionServiceUid); throw new IllegalStateException("Can't set sharedMemory to be read-only"); } + mDetectorType = detectorType; + logDetectorCreateEventIfNeeded(callback, detectorType, true, + voiceInteractionServiceUid); if (mHotwordDetectionConnection == null) { mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext, mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity, mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false, - options, sharedMemory, callback); + options, sharedMemory, callback, detectorType); } else { mHotwordDetectionConnection.updateStateLocked(options, sharedMemory); } } + private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback, + int detectorType, boolean isCreated, int voiceInteractionServiceUid) { + if (callback != null) { + HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, true, + voiceInteractionServiceUid); + } + } + public void shutdownHotwordDetectionServiceLocked() { if (DEBUG) { Slog.d(TAG, "shutdownHotwordDetectionServiceLocked"); @@ -667,6 +694,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne pw.println(Integer.toHexString(mDisabledShowContext)); } pw.print(" mBound="); pw.print(mBound); pw.print(" mService="); pw.println(mService); + pw.print(" mDetectorType="); + pw.println(HotwordDetector.detectorTypeToString(mDetectorType)); if (mHotwordDetectionConnection != null) { pw.println(" Hotword detection connection:"); mHotwordDetectionConnection.dump(" ", pw); |