RESTRICT AUTOMERGE Restart recognition when failing to deliver event
If the client does not know that recognition is not running due to a
recognition event not having been delivered, we should attempt to
restart it.
Fixes: 193579626
Test: Manual verification of the scenario described in the bug.
Change-Id: I4fc3b3e8defed59a900fd156273e9e695a322b0c
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
index c1f8240..9999aff 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -133,13 +133,8 @@
* Throws a {@link SecurityException} iff the originator has permission to receive data.
*/
void enforcePermissionsForDataDelivery(@NonNull Identity identity, @NonNull String reason) {
- // TODO(b/186164881): remove
- // START TEMP HACK
- enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO);
- int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
- mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(hotwordOp, identity.uid,
- identity.packageName, identity.attributionTag, reason);
- // END TEMP HACK
+ enforcePermissionForDataDelivery(mContext, identity, RECORD_AUDIO,
+ reason);
enforcePermissionForDataDelivery(mContext, identity, CAPTURE_AUDIO_HOTWORD,
reason);
}
@@ -167,8 +162,8 @@
/**
* Throws a {@link SecurityException} if originator permanently doesn't have the given
- * permission, or a {@link ServiceSpecificException} with a {@link
- * Status#TEMPORARY_PERMISSION_DENIED} if caller originator doesn't have the given permission.
+ * permission.
+ * Soft (temporary) denials are considered OK for preflight purposes.
*
* @param context A {@link Context}, used for permission checks.
* @param identity The identity to check.
@@ -180,15 +175,12 @@
permission);
switch (status) {
case PermissionChecker.PERMISSION_GRANTED:
+ case PermissionChecker.PERMISSION_SOFT_DENIED:
return;
case PermissionChecker.PERMISSION_HARD_DENIED:
throw new SecurityException(
String.format("Failed to obtain permission %s for identity %s", permission,
ObjectPrinter.print(identity, true, 16)));
- case PermissionChecker.PERMISSION_SOFT_DENIED:
- throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
- String.format("Failed to obtain permission %s for identity %s", permission,
- ObjectPrinter.print(identity, true, 16)));
default:
throw new RuntimeException("Unexpected perimission check result.");
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 95a30c7..458eae9 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -318,6 +318,8 @@
*/
private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
+ private RecognitionConfig mConfig;
+
/**
* Check that the given parameter is known to be supported for this model.
*
@@ -369,6 +371,14 @@
void setActivityState(Activity activity) {
mActivityState.set(activity.ordinal());
}
+
+ void setRecognitionConfig(@NonNull RecognitionConfig config) {
+ mConfig = config;
+ }
+
+ RecognitionConfig getRecognitionConfig() {
+ return mConfig;
+ }
}
/**
@@ -502,6 +512,7 @@
// Normally, we would set the state after the operation succeeds. However, since
// the activity state may be reset outside of the lock, we set it here first,
// and reset it in case of exception.
+ modelState.setRecognitionConfig(config);
modelState.setActivityState(ModelState.Activity.ACTIVE);
mDelegate.startRecognition(modelHandle, config);
} catch (Exception e) {
@@ -542,6 +553,27 @@
}
}
+ private void restartIfIntercepted(int modelHandle) {
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mState == ModuleStatus.DETACHED) {
+ return;
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null
+ || modelState.getActivityState() != ModelState.Activity.INTERCEPTED) {
+ return;
+ }
+ try {
+ mDelegate.startRecognition(modelHandle, modelState.getRecognitionConfig());
+ modelState.setActivityState(ModelState.Activity.ACTIVE);
+ Log.i(TAG, "Restarted intercepted model " + modelHandle);
+ } catch (Exception e) {
+ Log.i(TAG, "Failed to restart intercepted model " + modelHandle, e);
+ }
+ }
+ }
+
@Override
public void forceRecognitionEvent(int modelHandle) {
// Input validation (always valid).
@@ -753,6 +785,10 @@
Log.e(TAG, "Client callback exception.", e);
if (event.status != RecognitionStatus.FORCED) {
modelState.setActivityState(ModelState.Activity.INTERCEPTED);
+ // If we failed to deliver an actual event to the client, they would never
+ // know to restart it whenever circumstances change. Thus, we restart it
+ // here. We do this from a separate thread to avoid any race conditions.
+ new Thread(() -> restartIfIntercepted(modelHandle)).start();
}
}
}
@@ -780,6 +816,10 @@
Log.e(TAG, "Client callback exception.", e);
if (event.common.status != RecognitionStatus.FORCED) {
modelState.setActivityState(ModelState.Activity.INTERCEPTED);
+ // If we failed to deliver an actual event to the client, they would never
+ // know to restart it whenever circumstances change. Thus, we restart it
+ // here. We do this from a separate thread to avoid any race conditions.
+ new Thread(() -> restartIfIntercepted(modelHandle)).start();
}
}
}