Add hotword detection metrics.

Bug: 207717787
Test: build & boot pass.
Test: manual. Both trust and no-trust still work.
Test: manual. In nornal case, log with expected values, see bug for
details. Log expected value for some error cases (not all are tested)
by local changes.

Change-Id: Iaa778616eca4cfad83a94297d9cd6116bb9577e7
This commit is contained in:
Joanne Chung
2022-03-07 19:20:23 +08:00
parent 54d7eb4497
commit e4b76fc200
3 changed files with 259 additions and 24 deletions

View File

@@ -23,6 +23,25 @@ import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPH
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN; import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS; 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 static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight; import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
import android.annotation.NonNull; import android.annotation.NonNull;
@@ -92,13 +111,25 @@ final class HotwordDetectionConnection {
static final boolean DEBUG = false; static final boolean DEBUG = false;
// TODO: These constants need to be refined. // 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 long MAX_UPDATE_TIMEOUT_MILLIS = 6000;
private static final Duration MAX_UPDATE_TIMEOUT_DURATION = private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS); Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
private static final int MAX_ISOLATED_PROCESS_NUMBER = 10; private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
// 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(); private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
// TODO: This may need to be a Handler(looper) // TODO: This may need to be a Handler(looper)
private final ScheduledExecutorService mScheduledExecutorService = private final ScheduledExecutorService mScheduledExecutorService =
@@ -107,17 +138,20 @@ final class HotwordDetectionConnection {
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied; private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
private final @NonNull ServiceConnectionFactory mServiceConnectionFactory; private final @NonNull ServiceConnectionFactory mServiceConnectionFactory;
private final IHotwordRecognitionStatusCallback mCallback; private final IHotwordRecognitionStatusCallback mCallback;
private final int mDetectorType;
final Object mLock; final Object mLock;
final int mVoiceInteractionServiceUid; final int mVoiceInteractionServiceUid;
final ComponentName mDetectionComponentName; final ComponentName mDetectionComponentName;
final int mUser; final int mUser;
final Context mContext; final Context mContext;
volatile HotwordDetectionServiceIdentity mIdentity; volatile HotwordDetectionServiceIdentity mIdentity;
private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback; private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
private Instant mLastRestartInstant; private Instant mLastRestartInstant;
private ScheduledFuture<?> mCancellationTaskFuture; private ScheduledFuture<?> mCancellationTaskFuture;
private ScheduledFuture<?> mCancellationKeyPhraseDetectionFuture;
private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null; private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null;
/** Identity used for attributing app ops when delivering data to the Interactor. */ /** Identity used for attributing app ops when delivering data to the Interactor. */
@@ -133,7 +167,6 @@ final class HotwordDetectionConnection {
private @NonNull ServiceConnection mRemoteHotwordDetectionService; private @NonNull ServiceConnection mRemoteHotwordDetectionService;
private IBinder mAudioFlinger; private IBinder mAudioFlinger;
private boolean mDebugHotwordLogging = false; private boolean mDebugHotwordLogging = false;
private final int mDetectorType;
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid, HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity, ComponentName serviceName, int userId, Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
@@ -169,6 +202,8 @@ final class HotwordDetectionConnection {
Slog.v(TAG, "Time to restart the process, TTL has passed"); Slog.v(TAG, "Time to restart the process, TTL has passed");
synchronized (mLock) { synchronized (mLock) {
restartProcessLocked(); restartProcessLocked();
HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
} }
}, 30, 30, TimeUnit.MINUTES); }, 30, 30, TimeUnit.MINUTES);
} }
@@ -201,6 +236,8 @@ final class HotwordDetectionConnection {
// We restart the process instead of simply sending over the new binder, to avoid race // We restart the process instead of simply sending over the new binder, to avoid race
// conditions with audio reading in the service. // conditions with audio reading in the service.
restartProcessLocked(); restartProcessLocked();
HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED);
} }
} }
@@ -220,26 +257,32 @@ final class HotwordDetectionConnection {
future.complete(null); future.complete(null);
if (mUpdateStateAfterStartFinished.getAndSet(true)) { if (mUpdateStateAfterStartFinished.getAndSet(true)) {
Slog.w(TAG, "call callback after timeout"); Slog.w(TAG, "call callback after timeout");
HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT,
mVoiceInteractionServiceUid);
return; return;
} }
int status = bundle != null ? bundle.getInt( Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle);
KEY_INITIALIZATION_STATUS, int status = statusResultPair.first;
INITIALIZATION_STATUS_UNKNOWN) int initResultMetricsResult = statusResultPair.second;
: INITIALIZATION_STATUS_UNKNOWN;
// Add the protection to avoid unexpected status
if (status > HotwordDetectionService.getMaxCustomInitializationStatus()
&& status != INITIALIZATION_STATUS_UNKNOWN) {
status = INITIALIZATION_STATUS_UNKNOWN;
}
try { try {
mCallback.onStatusReported(status); mCallback.onStatusReported(status);
HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
initResultMetricsResult);
} catch (RemoteException e) { } catch (RemoteException e) {
// TODO: Add a new atom for RemoteException case, the error doesn't very
// correct here
Slog.w(TAG, "Failed to report initialization status: " + e); Slog.w(TAG, "Failed to report initialization status: " + e);
HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
METRICS_INIT_CALLBACK_STATE_ERROR);
} }
} }
}; };
try { try {
service.updateState(options, sharedMemory, statusCallback); service.updateState(options, sharedMemory, statusCallback);
HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
mVoiceInteractionServiceUid);
} catch (RemoteException e) { } catch (RemoteException e) {
// TODO: (b/181842909) Report an error to voice interactor // TODO: (b/181842909) Report an error to voice interactor
Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e); Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
@@ -254,8 +297,12 @@ final class HotwordDetectionConnection {
} }
try { try {
mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN); mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
METRICS_INIT_UNKNOWN_TIMEOUT);
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status UNKNOWN", e); Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
METRICS_INIT_CALLBACK_STATE_ERROR);
} }
} else if (err != null) { } else if (err != null) {
Slog.w(TAG, "Failed to update state: " + err); Slog.w(TAG, "Failed to update state: " + err);
@@ -265,6 +312,23 @@ 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()
&& status != INITIALIZATION_STATUS_UNKNOWN) {
return new Pair<>(INITIALIZATION_STATUS_UNKNOWN,
METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE);
}
// TODO: should guard against negative here
int metricsResult = status == INITIALIZATION_STATUS_UNKNOWN
? METRICS_INIT_CALLBACK_STATE_ERROR
: METRICS_INIT_CALLBACK_STATE_SUCCESS;
return new Pair<>(status, metricsResult);
}
private boolean isBound() { private boolean isBound() {
synchronized (mLock) { synchronized (mLock) {
return mRemoteHotwordDetectionService.isBound(); return mRemoteHotwordDetectionService.isBound();
@@ -481,12 +545,31 @@ final class HotwordDetectionConnection {
Slog.d(TAG, "onDetected"); Slog.d(TAG, "onDetected");
} }
synchronized (mLock) { 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) { if (!mValidatingDspTrigger) {
Slog.i(TAG, "Ignoring #onDetected due to a process restart"); Slog.i(TAG, "Ignoring #onDetected due to a process restart");
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION);
return; return;
} }
mValidatingDspTrigger = false; mValidatingDspTrigger = false;
try {
enforcePermissionsForDataDelivery(); enforcePermissionsForDataDelivery();
} catch (SecurityException e) {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION);
throw e;
}
externalCallback.onKeyphraseDetected(recognitionEvent, result); externalCallback.onKeyphraseDetected(recognitionEvent, result);
if (result != null) { if (result != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result) Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -504,8 +587,17 @@ final class HotwordDetectionConnection {
Slog.d(TAG, "onRejected"); Slog.d(TAG, "onRejected");
} }
synchronized (mLock) { synchronized (mLock) {
if (mCancellationKeyPhraseDetectionFuture != null) {
mCancellationKeyPhraseDetectionFuture.cancel(true);
}
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
if (!mValidatingDspTrigger) { if (!mValidatingDspTrigger) {
Slog.i(TAG, "Ignoring #onRejected due to a process restart"); Slog.i(TAG, "Ignoring #onRejected due to a process restart");
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_EXCEPTION);
return; return;
} }
mValidatingDspTrigger = false; mValidatingDspTrigger = false;
@@ -520,11 +612,20 @@ final class HotwordDetectionConnection {
synchronized (mLock) { synchronized (mLock) {
mValidatingDspTrigger = true; mValidatingDspTrigger = true;
mRemoteHotwordDetectionService.run( mRemoteHotwordDetectionService.run(
service -> service.detectFromDspSource( 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,
recognitionEvent.getCaptureFormat(), recognitionEvent.getCaptureFormat(),
VALIDATION_TIMEOUT_MILLIS, VALIDATION_TIMEOUT_MILLIS,
internalCallback)); internalCallback);
});
} }
} }
@@ -625,10 +726,16 @@ final class HotwordDetectionConnection {
} }
final boolean useHotwordDetectionService = mHotwordDetectionConnection != null; final boolean useHotwordDetectionService = mHotwordDetectionConnection != null;
if (useHotwordDetectionService) { if (useHotwordDetectionService) {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
mRecognitionEvent = recognitionEvent; mRecognitionEvent = recognitionEvent;
mHotwordDetectionConnection.detectFromDspSource( mHotwordDetectionConnection.detectFromDspSource(
recognitionEvent, mExternalCallback); recognitionEvent, mExternalCallback);
} else { } else {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
mExternalCallback.onKeyphraseDetected(recognitionEvent, null); mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
} }
} }
@@ -793,6 +900,7 @@ final class HotwordDetectionConnection {
private boolean mRespectServiceConnectionStatusChanged = true; private boolean mRespectServiceConnectionStatusChanged = true;
private boolean mIsBound = false; private boolean mIsBound = false;
private boolean mIsLoggedFirstConnect = false;
ServiceConnection(@NonNull Context context, ServiceConnection(@NonNull Context context,
@NonNull Intent intent, int bindingFlags, int userId, @NonNull Intent intent, int bindingFlags, int userId,
@@ -816,6 +924,12 @@ final class HotwordDetectionConnection {
return; return;
} }
mIsBound = connected; mIsBound = connected;
if (connected && !mIsLoggedFirstConnect) {
mIsLoggedFirstConnect = true;
HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
mVoiceInteractionServiceUid);
}
} }
} }
@@ -846,13 +960,25 @@ final class HotwordDetectionConnection {
protected boolean bindService( protected boolean bindService(
@NonNull android.content.ServiceConnection serviceConnection) { @NonNull android.content.ServiceConnection serviceConnection) {
try { try {
return mContext.bindIsolatedService( HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
mVoiceInteractionServiceUid);
boolean bindResult = mContext.bindIsolatedService(
mIntent, mIntent,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags,
"hotword_detector_" + mInstanceNumber, "hotword_detector_" + mInstanceNumber,
mExecutor, mExecutor,
serviceConnection); serviceConnection);
if (!bindResult) {
HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
mVoiceInteractionServiceUid);
}
return bindResult;
} catch (IllegalArgumentException e) { } 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); Slog.wtf(TAG, "Can't bind to the hotword detection service!", e);
return false; return false;
} }

View File

@@ -16,6 +16,24 @@
package com.android.server.voiceinteraction; 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; import com.android.internal.util.FrameworkStatsLog;
/** /**
@@ -23,6 +41,13 @@ import com.android.internal.util.FrameworkStatsLog;
*/ */
public final class HotwordMetricsLogger { 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() { private HotwordMetricsLogger() {
// Class only contains static utility functions, and should not be instantiated // Class only contains static utility functions, and should not be instantiated
} }
@@ -31,39 +56,99 @@ public final class HotwordMetricsLogger {
* Logs information related to create hotword detector. * Logs information related to create hotword detector.
*/ */
public static void writeDetectorCreateEvent(int detectorType, boolean isCreated, int uid) { public static void writeDetectorCreateEvent(int detectorType, boolean isCreated, int uid) {
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED, detectorType, int metricsDetectorType = getCreateMetricsDetectorType(detectorType);
isCreated, uid); FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED,
metricsDetectorType, isCreated, uid);
} }
/** /**
* Logs information related to hotword detection service init result. * Logs information related to hotword detection service init result.
*/ */
public static void writeServiceInitResultEvent(int detectorType, int result) { public static void writeServiceInitResultEvent(int detectorType, int result) {
int metricsDetectorType = getInitMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED, FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED,
detectorType, result); metricsDetectorType, result);
} }
/** /**
* Logs information related to hotword detection service restarting. * Logs information related to hotword detection service restarting.
*/ */
public static void writeServiceRestartEvent(int detectorType, int reason) { public static void writeServiceRestartEvent(int detectorType, int reason) {
int metricsDetectorType = getRestartMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED, FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED,
detectorType, reason); metricsDetectorType, reason);
} }
/** /**
* Logs information related to keyphrase trigger. * Logs information related to keyphrase trigger.
*/ */
public static void writeKeyphraseTriggerEvent(int detectorType, int result) { public static void writeKeyphraseTriggerEvent(int detectorType, int result) {
int metricsDetectorType = getKeyphraseMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED, FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED,
detectorType, result); metricsDetectorType, result);
} }
/** /**
* Logs information related to hotword detector events. * Logs information related to hotword detector events.
*/ */
public static void writeDetectorEvent(int detectorType, int event, int uid) { public static void writeDetectorEvent(int detectorType, int event, int uid) {
int metricsDetectorType = getDetectorMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS, FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS,
detectorType, event, uid); 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;
}
} }
} }

View File

@@ -462,24 +462,33 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
IHotwordRecognitionStatusCallback callback, IHotwordRecognitionStatusCallback callback,
int detectorType) { int detectorType) {
Slog.v(TAG, "updateStateLocked"); Slog.v(TAG, "updateStateLocked");
int voiceInteractionServiceUid = mInfo.getServiceInfo().applicationInfo.uid;
if (mHotwordDetectionComponentName == null) { if (mHotwordDetectionComponentName == null) {
Slog.w(TAG, "Hotword detection service name not found"); Slog.w(TAG, "Hotword detection service name not found");
logDetectorCreateEventIfNeeded(callback, detectorType, false,
voiceInteractionServiceUid);
throw new IllegalStateException("Hotword detection service name not found"); throw new IllegalStateException("Hotword detection service name not found");
} }
ServiceInfo hotwordDetectionServiceInfo = getServiceInfoLocked( ServiceInfo hotwordDetectionServiceInfo = getServiceInfoLocked(
mHotwordDetectionComponentName, mUser); mHotwordDetectionComponentName, mUser);
if (hotwordDetectionServiceInfo == null) { if (hotwordDetectionServiceInfo == null) {
Slog.w(TAG, "Hotword detection service info not found"); Slog.w(TAG, "Hotword detection service info not found");
logDetectorCreateEventIfNeeded(callback, detectorType, false,
voiceInteractionServiceUid);
throw new IllegalStateException("Hotword detection service info not found"); throw new IllegalStateException("Hotword detection service info not found");
} }
if (!isIsolatedProcessLocked(hotwordDetectionServiceInfo)) { if (!isIsolatedProcessLocked(hotwordDetectionServiceInfo)) {
Slog.w(TAG, "Hotword detection service not in isolated process"); 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"); throw new IllegalStateException("Hotword detection service not in isolated process");
} }
if (!Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE.equals( if (!Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE.equals(
hotwordDetectionServiceInfo.permission)) { hotwordDetectionServiceInfo.permission)) {
Slog.w(TAG, "Hotword detection service does not require permission " Slog.w(TAG, "Hotword detection service does not require permission "
+ Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE); + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
logDetectorCreateEventIfNeeded(callback, detectorType, false,
voiceInteractionServiceUid);
throw new SecurityException("Hotword detection service does not require permission " throw new SecurityException("Hotword detection service does not require permission "
+ Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE); + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
} }
@@ -488,17 +497,23 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED) { mInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Voice interaction service should not hold permission " Slog.w(TAG, "Voice interaction service should not hold permission "
+ Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE); + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
logDetectorCreateEventIfNeeded(callback, detectorType, false,
voiceInteractionServiceUid);
throw new SecurityException("Voice interaction service should not hold permission " throw new SecurityException("Voice interaction service should not hold permission "
+ Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE); + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
} }
if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) { if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
Slog.w(TAG, "Can't set sharedMemory to be read-only"); 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"); throw new IllegalStateException("Can't set sharedMemory to be read-only");
} }
mDetectorType = detectorType; mDetectorType = detectorType;
logDetectorCreateEventIfNeeded(callback, detectorType, true,
voiceInteractionServiceUid);
if (mHotwordDetectionConnection == null) { if (mHotwordDetectionConnection == null) {
mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext, mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity, mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
@@ -509,6 +524,15 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
} }
} }
private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
int detectorType, boolean isCreated, int voiceInteractionServiceUid) {
if (callback != null) {
HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, true,
voiceInteractionServiceUid);
}
}
public void shutdownHotwordDetectionServiceLocked() { public void shutdownHotwordDetectionServiceLocked() {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "shutdownHotwordDetectionServiceLocked"); Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");