Merge "Refine CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE" into tm-dev
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 66767e2..da429af 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -210,6 +210,8 @@
* on how frequently it can be scheduled. Only available (and automatically applied) to
* system alarms.
*
+ * <p>Note that alarms set with a {@link WorkSource} <b>do not</b> get this flag.
+ *
* @hide
*/
@UnsupportedAppUsage
diff --git a/core/api/current.txt b/core/api/current.txt
index 09258c2..c80c4cb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4276,7 +4276,6 @@
method public void setLocusContext(@Nullable android.content.LocusId, @Nullable android.os.Bundle);
method public final void setMediaController(android.media.session.MediaController);
method public void setPictureInPictureParams(@NonNull android.app.PictureInPictureParams);
- method public void setPreferDockBigOverlays(boolean);
method @Deprecated public final void setProgress(int);
method @Deprecated public final void setProgressBarIndeterminate(boolean);
method @Deprecated public final void setProgressBarIndeterminateVisibility(boolean);
@@ -4286,6 +4285,7 @@
method public final void setResult(int);
method public final void setResult(int, android.content.Intent);
method @Deprecated public final void setSecondaryProgress(int);
+ method public void setShouldDockBigOverlays(boolean);
method public void setShowWhenLocked(boolean);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
method public void setTitle(CharSequence);
@@ -4296,6 +4296,7 @@
method public void setVisible(boolean);
method public final void setVolumeControlStream(int);
method public void setVrModeEnabled(boolean, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public boolean shouldDockBigOverlays();
method public boolean shouldShowRequestPermissionRationale(@NonNull String);
method public boolean shouldUpRecreateTask(android.content.Intent);
method public boolean showAssist(android.os.Bundle);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5aec193..2eb0f19 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -422,9 +422,9 @@
method @NonNull public android.content.res.Configuration getConfiguration();
method public int getParentTaskId();
method @Nullable public android.app.PictureInPictureParams getPictureInPictureParams();
- method public boolean getPreferDockBigOverlays();
method @NonNull public android.window.WindowContainerToken getToken();
method public boolean hasParentTask();
+ method public boolean shouldDockBigOverlays();
}
public class TimePickerDialog extends android.app.AlertDialog implements android.content.DialogInterface.OnClickListener android.widget.TimePicker.OnTimeChangedListener {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2f7d07b..ec9bb26 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -984,6 +984,8 @@
private boolean mIsInMultiWindowMode;
private boolean mIsInPictureInPictureMode;
+ private boolean mShouldDockBigOverlays;
+
private UiTranslationController mUiTranslationController;
private SplashScreen mSplashScreen;
@@ -2977,13 +2979,28 @@
* <p> If specified, the system will try to respect the preference, but it may be
* overridden by a user preference.
*
- * @param preferDockBigOverlays indicates that the activity prefers big overlays to be
- * docked next to it instead of overlaying its content
+ * @param shouldDockBigOverlays indicates that big overlays should be docked next to the
+ * activity instead of overlay its content
*
* @see PictureInPictureParams.Builder#setExpandedAspectRatio
+ * @see #shouldDockBigOverlays
*/
- public void setPreferDockBigOverlays(boolean preferDockBigOverlays) {
- ActivityClient.getInstance().setPreferDockBigOverlays(mToken, preferDockBigOverlays);
+ public void setShouldDockBigOverlays(boolean shouldDockBigOverlays) {
+ ActivityClient.getInstance().setShouldDockBigOverlays(mToken, shouldDockBigOverlays);
+ mShouldDockBigOverlays = shouldDockBigOverlays;
+ }
+
+ /**
+ * Returns whether big overlays should be docked next to the activity as set by
+ * {@link #setShouldDockBigOverlays}.
+ *
+ * @return {@code true} if big overlays should be docked next to the activity instead
+ * of overlay its content
+ *
+ * @see #setShouldDockBigOverlays
+ */
+ public boolean shouldDockBigOverlays() {
+ return mShouldDockBigOverlays;
}
void dispatchMovedToDisplay(int displayId, Configuration config) {
@@ -8248,6 +8265,7 @@
.getWindowingMode();
mIsInMultiWindowMode = inMultiWindowMode(windowingMode);
mIsInPictureInPictureMode = windowingMode == WINDOWING_MODE_PINNED;
+ mShouldDockBigOverlays = getResources().getBoolean(R.bool.config_dockBigOverlayWindows);
restoreHasCurrentPermissionRequest(icicle);
if (persistentState != null) {
onCreate(icicle, persistentState);
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index cf8480c..7b7b1ef 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -324,9 +324,9 @@
}
}
- void setPreferDockBigOverlays(IBinder token, boolean preferDockBigOverlays) {
+ void setShouldDockBigOverlays(IBinder token, boolean shouldDockBigOverlays) {
try {
- getActivityClientController().setPreferDockBigOverlays(token, preferDockBigOverlays);
+ getActivityClientController().setShouldDockBigOverlays(token, shouldDockBigOverlays);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index caf1c41b7..1307161 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -88,7 +88,7 @@
boolean enterPictureInPictureMode(in IBinder token, in PictureInPictureParams params);
void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
- oneway void setPreferDockBigOverlays(in IBinder token, in boolean preferDockBigOverlays);
+ oneway void setShouldDockBigOverlays(in IBinder token, in boolean shouldDockBigOverlays);
void toggleFreeformWindowingMode(in IBinder token);
oneway void startLockTaskModeByToken(in IBinder token);
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 5c7c73c..1a38fcf 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -188,7 +188,7 @@
/**
* @hide
*/
- public boolean preferDockBigOverlays;
+ public boolean shouldDockBigOverlays;
/**
* The task id of the host Task of the launch-into-pip Activity, i.e., it points to the Task
@@ -392,8 +392,8 @@
/** @hide */
@TestApi
- public boolean getPreferDockBigOverlays() {
- return preferDockBigOverlays;
+ public boolean shouldDockBigOverlays() {
+ return shouldDockBigOverlays;
}
/** @hide */
@@ -465,7 +465,7 @@
&& displayAreaFeatureId == that.displayAreaFeatureId
&& Objects.equals(positionInParent, that.positionInParent)
&& Objects.equals(pictureInPictureParams, that.pictureInPictureParams)
- && Objects.equals(preferDockBigOverlays, that.preferDockBigOverlays)
+ && Objects.equals(shouldDockBigOverlays, that.shouldDockBigOverlays)
&& Objects.equals(displayCutoutInsets, that.displayCutoutInsets)
&& getWindowingMode() == that.getWindowingMode()
&& Objects.equals(taskDescription, that.taskDescription)
@@ -522,7 +522,7 @@
token = WindowContainerToken.CREATOR.createFromParcel(source);
topActivityType = source.readInt();
pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR);
- preferDockBigOverlays = source.readBoolean();
+ shouldDockBigOverlays = source.readBoolean();
launchIntoPipHostTaskId = source.readInt();
displayCutoutInsets = source.readTypedObject(Rect.CREATOR);
topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
@@ -569,7 +569,7 @@
token.writeToParcel(dest, flags);
dest.writeInt(topActivityType);
dest.writeTypedObject(pictureInPictureParams, flags);
- dest.writeBoolean(preferDockBigOverlays);
+ dest.writeBoolean(shouldDockBigOverlays);
dest.writeInt(launchIntoPipHostTaskId);
dest.writeTypedObject(displayCutoutInsets, flags);
dest.writeTypedObject(topActivityInfo, flags);
@@ -610,7 +610,7 @@
+ " token=" + token
+ " topActivityType=" + topActivityType
+ " pictureInPictureParams=" + pictureInPictureParams
- + " preferDockBigOverlays=" + preferDockBigOverlays
+ + " shouldDockBigOverlays=" + shouldDockBigOverlays
+ " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId
+ " displayCutoutSafeInsets=" + displayCutoutInsets
+ " topActivityInfo=" + topActivityInfo
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 71c1b7c..a4841f6 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -23,6 +23,7 @@
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.toInternalType;
import static android.view.InsetsState.toPublicType;
+import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.WindowInsets.Type.all;
import static android.view.WindowInsets.Type.ime;
@@ -682,9 +683,15 @@
@VisibleForTesting
public boolean onStateChanged(InsetsState state) {
- boolean stateChanged = !mState.equals(state, true /* excludingCaptionInsets */,
- false /* excludeInvisibleIme */)
- || !captionInsetsUnchanged();
+ boolean stateChanged = false;
+ if (!CAPTION_ON_SHELL) {
+ stateChanged = !mState.equals(state, true /* excludingCaptionInsets */,
+ false /* excludeInvisibleIme */)
+ || captionInsetsUnchanged();
+ } else {
+ stateChanged = !mState.equals(state, false /* excludingCaptionInsets */,
+ false /* excludeInvisibleIme */);
+ }
if (!stateChanged && mLastDispatchedState.equals(state)) {
return false;
}
@@ -758,16 +765,20 @@
}
private boolean captionInsetsUnchanged() {
+ if (CAPTION_ON_SHELL) {
+ return false;
+ }
if (mState.peekSource(ITYPE_CAPTION_BAR) == null
&& mCaptionInsetsHeight == 0) {
- return true;
+ return false;
}
if (mState.peekSource(ITYPE_CAPTION_BAR) != null
&& mCaptionInsetsHeight
== mState.peekSource(ITYPE_CAPTION_BAR).getFrame().height()) {
- return true;
+ return false;
}
- return false;
+
+ return true;
}
private void startResizingAnimationIfNeeded(InsetsState fromState) {
@@ -1582,11 +1593,15 @@
@Override
public void setCaptionInsetsHeight(int height) {
+ // This method is to be removed once the caption is moved to the shell.
+ if (CAPTION_ON_SHELL) {
+ return;
+ }
if (mCaptionInsetsHeight != height) {
mCaptionInsetsHeight = height;
if (mCaptionInsetsHeight != 0) {
- mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top,
- mFrame.right, mFrame.top + mCaptionInsetsHeight));
+ mState.getSource(ITYPE_CAPTION_BAR).setFrame(mFrame.left, mFrame.top,
+ mFrame.right, mFrame.top + mCaptionInsetsHeight);
} else {
mState.removeSource(ITYPE_CAPTION_BAR);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bde761e..18e65c9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -277,6 +277,12 @@
private static final boolean ENABLE_INPUT_LATENCY_TRACKING = true;
/**
+ * Whether the caption is drawn by the shell.
+ * @hide
+ */
+ public static final boolean CAPTION_ON_SHELL = false;
+
+ /**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
@@ -2561,6 +2567,9 @@
}
private boolean updateCaptionInsets() {
+ if (CAPTION_ON_SHELL) {
+ return false;
+ }
if (!(mView instanceof DecorView)) return false;
final int captionInsetsHeight = ((DecorView) mView).getCaptionInsetsHeight();
final Rect captionFrame = new Rect();
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 40e4085..89ac722 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -30,6 +30,7 @@
import static android.view.View.MeasureSpec.getMode;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.Window.DECOR_CAPTION_SHADE_DARK;
import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -2120,7 +2121,9 @@
* corresponding insets change to the InsetsController.
*/
public void notifyCaptionHeightChanged() {
- getWindowInsetsController().setCaptionInsetsHeight(getCaptionInsetsHeight());
+ if (!CAPTION_ON_SHELL) {
+ getWindowInsetsController().setCaptionInsetsHeight(getCaptionInsetsHeight());
+ }
}
void setWindow(PhoneWindow phoneWindow) {
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 227a8657..c504f0c 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -30,6 +30,7 @@
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.LAST_TYPE;
+import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
@@ -758,6 +759,11 @@
@Test
public void testCaptionInsetsStateAssemble() {
+ if (CAPTION_ON_SHELL) {
+ // For this case, the test is covered by WindowContainerInsetsSourceProviderTest, This
+ // test can be removed after the caption is moved to shell completely.
+ return;
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.onFrameChanged(new Rect(0, 0, 100, 300));
final InsetsState state = new InsetsState(mController.getState(), true);
@@ -769,6 +775,7 @@
assertEquals(captionFrame, currentState.peekSource(ITYPE_CAPTION_BAR).getFrame());
assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/,
true /* excludeInvisibleIme */));
+ // Test update to remove the caption bar
mController.setCaptionInsetsHeight(0);
mController.onStateChanged(state);
// The caption bar source should not be there at all, because we don't add empty
@@ -779,6 +786,11 @@
@Test
public void testNotifyCaptionInsetsOnlyChange() {
+ if (CAPTION_ON_SHELL) {
+ // For this case, the test is covered by WindowContainerInsetsSourceProviderTest, This
+ // test can be removed after the caption is moved to shell completely.
+ return;
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
final InsetsState state = new InsetsState(mController.getState(), true);
reset(mTestHost);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 17db45d..e4efae2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -23,7 +23,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.TypedArray;
-import android.os.Build;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -33,8 +32,6 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
-import com.android.internal.util.Preconditions;
-
/**
* Helper class for managing settings preferences that can be disabled
* by device admins via user restrictions.
@@ -42,8 +39,8 @@
public class RestrictedPreferenceHelper {
private final Context mContext;
private final Preference mPreference;
- final String packageName;
- final int uid;
+ String packageName;
+ int uid;
private boolean mDisabledByAdmin;
private EnforcedAdmin mEnforcedAdmin;
@@ -219,6 +216,11 @@
return mDisabledByAppOps;
}
+ public void updatePackageDetails(String packageName, int uid) {
+ this.packageName = packageName;
+ this.uid = uid;
+ }
+
private void updateDisabledState() {
if (!(mPreference instanceof RestrictedTopLevelPreference)) {
mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index c607d74..091e322 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -18,8 +18,11 @@
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Process;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.TypedValue;
@@ -28,6 +31,7 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
@@ -39,6 +43,7 @@
*/
public class RestrictedSwitchPreference extends SwitchPreference {
RestrictedPreferenceHelper mHelper;
+ AppOpsManager mAppOpsManager;
boolean mUseAdditionalSummary = false;
CharSequence mRestrictedSwitchSummary;
private int mIconSize;
@@ -90,6 +95,11 @@
this(context, null);
}
+ @VisibleForTesting
+ public void setAppOps(AppOpsManager appOps) {
+ mAppOpsManager = appOps;
+ }
+
public void setIconSize(int iconSize) {
mIconSize = iconSize;
}
@@ -97,6 +107,12 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ final View switchView = holder.findViewById(android.R.id.switch_widget);
+ if (switchView != null) {
+ final View rootView = switchView.getRootView();
+ rootView.setFilterTouchesWhenObscured(true);
+ }
+
mHelper.onBindViewHolder(holder);
CharSequence switchSummary;
@@ -164,11 +180,18 @@
@Override
public void setEnabled(boolean enabled) {
+ boolean changed = false;
if (enabled && isDisabledByAdmin()) {
mHelper.setDisabledByAdmin(null);
- return;
+ changed = true;
}
- super.setEnabled(enabled);
+ if (enabled && isDisabledByAppOps()) {
+ mHelper.setDisabledByAppOps(false);
+ changed = true;
+ }
+ if (!changed) {
+ super.setEnabled(enabled);
+ }
}
public void setDisabledByAdmin(EnforcedAdmin admin) {
@@ -180,4 +203,38 @@
public boolean isDisabledByAdmin() {
return mHelper.isDisabledByAdmin();
}
+
+ private void setDisabledByAppOps(boolean disabled) {
+ if (mHelper.setDisabledByAppOps(disabled)) {
+ notifyChanged();
+ }
+ }
+
+ public boolean isDisabledByAppOps() {
+ return mHelper.isDisabledByAppOps();
+ }
+
+ public int getUid() {
+ return mHelper != null ? mHelper.uid : Process.INVALID_UID;
+ }
+
+ public String getPackageName() {
+ return mHelper != null ? mHelper.packageName : null;
+ }
+
+ public void updateState(@NonNull String packageName, int uid, boolean isEnabled) {
+ mHelper.updatePackageDetails(packageName, uid);
+ if (mAppOpsManager == null) {
+ mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
+ }
+ final int mode = mAppOpsManager.noteOpNoThrow(
+ AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+ uid, packageName);
+ final boolean appOpsAllowed = mode == AppOpsManager.MODE_ALLOWED;
+ if (appOpsAllowed || isEnabled) {
+ setEnabled(true);
+ } else {
+ setDisabledByAppOps(true);
+ }
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
index 1d08711..d988111 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
@@ -89,6 +89,10 @@
@After
public void tearDown() {
+ String[] entries = mImagesDir.list();
+ for (String entry : entries) {
+ new File(mImagesDir, entry).delete();
+ }
mImagesDir.delete();
}
@@ -233,7 +237,8 @@
public void internalCropUsedIfNoSystemCropperFound() throws IOException {
when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false);
- new File(mImagesDir, "file.txt").createNewFile();
+ File file = new File(mImagesDir, "file.txt");
+ saveBitmapToFile(file);
Intent intent = new Intent();
intent.setData(Uri.parse(
@@ -241,10 +246,6 @@
mController.onActivityResult(
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
- Intent startIntent = verifyStartSystemActivityForResult(
- "com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
- assertThat(startIntent.getData()).isNotEqualTo(mTakePhotoUri);
-
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
InputStream imageStream = mContext.getContentResolver().openInputStream(mCropPhotoUri);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 31f466f..087817f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -113,6 +113,22 @@
}
}
+ /** Sets a translationY value on every child view except for the media view. */
+ public void setChildrenTranslationYExcludingMediaView(float translationY) {
+ setChildrenTranslationYExcluding(translationY, Set.of(mMediaHostContainer));
+ }
+
+ /** Sets a translationY value on every view except for the views in the provided set. */
+ private void setChildrenTranslationYExcluding(float translationY, Set<View> excludedViews) {
+ for (int i = 0; i < mStatusViewContainer.getChildCount(); i++) {
+ final View child = mStatusViewContainer.getChildAt(i);
+
+ if (!excludedViews.contains(child)) {
+ child.setTranslationY(translationY);
+ }
+ }
+ }
+
public float getChildrenAlphaExcludingSmartSpace() {
return mChildrenAlphaExcludingSmartSpace;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index af3da9f..14c9cb2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -137,6 +137,13 @@
}
/**
+ * Sets a translationY on the views on the keyguard, except on the media view.
+ */
+ public void setTranslationYExcludingMedia(float translationY) {
+ mView.setChildrenTranslationYExcludingMediaView(translationY);
+ }
+
+ /**
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 18b6699..7a4dee2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -292,6 +292,18 @@
}
/**
+ * Returns the amount of translationY of the media container, during the current guided
+ * transformation, if running. If there is no guided transformation running, it will return 0.
+ */
+ fun getGuidedTransformationTranslationY(): Int {
+ if (!isCurrentlyInGuidedTransformation()) {
+ return -1
+ }
+ val startHost = getHost(previousLocation) ?: return 0
+ return targetBounds.top - startHost.currentBounds.top
+ }
+
+ /**
* Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs,
* we wouldn't want to transition in that case.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 1ab0345..ab4d0dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -406,6 +406,7 @@
mediaHierarchyManager.setTransitionToFullShadeAmount(field)
transitionToShadeAmountCommon(field)
+ transitionToShadeAmountKeyguard(field)
}
}
}
@@ -420,11 +421,6 @@
val scrimProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
scrimController.setTransitionToFullShadeProgress(scrimProgress)
- // Fade out all content only visible on the lockscreen
- val npvcProgress =
- MathUtils.saturate(dragDownAmount / npvcKeyguardContentAlphaTransitionDistance)
- notificationPanelController.setKeyguardOnlyContentAlpha(1.0f - npvcProgress)
-
if (depthControllerTransitionDistance > 0) {
val depthProgress =
MathUtils.saturate(dragDownAmount / depthControllerTransitionDistance)
@@ -438,6 +434,22 @@
centralSurfaces.setTransitionToFullShadeProgress(statusBarProgress)
}
+ private fun transitionToShadeAmountKeyguard(dragDownAmount: Float) {
+ // Fade out all content only visible on the lockscreen
+ val keyguardAlphaProgress =
+ MathUtils.saturate(dragDownAmount / npvcKeyguardContentAlphaTransitionDistance)
+ val keyguardAlpha = 1f - keyguardAlphaProgress
+ val keyguardTranslationY = if (useSplitShade) {
+ // On split-shade, the translationY of the keyguard should stay in sync with the
+ // translation of media.
+ mediaHierarchyManager.getGuidedTransformationTranslationY()
+ } else {
+ 0
+ }
+ notificationPanelController
+ .setKeyguardTransitionProgress(keyguardAlpha, keyguardTranslationY)
+ }
+
private fun setDragDownAmountAnimated(
target: Float,
delay: Long = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 0a2ea4c..cbdf5c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -616,6 +616,11 @@
*/
private float mKeyguardOnlyContentAlpha = 1.0f;
+ /**
+ * The translationY of the views which only show on the keyguard but in shade / shade locked.
+ */
+ private int mKeyguardOnlyTransitionTranslationY = 0;
+
private float mUdfpsMaxYBurnInOffset;
/**
@@ -1575,6 +1580,8 @@
private void updateClock() {
float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
mKeyguardStatusViewController.setAlpha(alpha);
+ mKeyguardStatusViewController
+ .setTranslationYExcludingMedia(mKeyguardOnlyTransitionTranslationY);
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setAlpha(alpha);
}
@@ -2732,11 +2739,12 @@
}
/**
- * Set the alpha of the keyguard elements which only show on the lockscreen, but not in
- * shade locked / shade. This is used when dragging down to the full shade.
+ * Set the alpha and translationY of the keyguard elements which only show on the lockscreen,
+ * but not in shade locked / shade. This is used when dragging down to the full shade.
*/
- public void setKeyguardOnlyContentAlpha(float keyguardAlpha) {
+ public void setKeyguardTransitionProgress(float keyguardAlpha, int keyguardTranslationY) {
mKeyguardOnlyContentAlpha = Interpolators.ALPHA_IN.getInterpolation(keyguardAlpha);
+ mKeyguardOnlyTransitionTranslationY = keyguardTranslationY;
if (mBarState == KEYGUARD) {
// If the animator is running, it's already fading out the content and this is a reset
mBottomAreaShadeAlpha = mKeyguardOnlyContentAlpha;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 2fc9122..1753157 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -112,4 +112,13 @@
mKeyguardUpdateMonitorCallbackCaptor.getValue().onUserSwitchComplete(0);
verify(mKeyguardClockSwitchController).refreshFormat();
}
+
+ @Test
+ public void setTranslationYExcludingMedia_forwardsCallToView() {
+ float translationY = 123f;
+
+ mController.setTranslationYExcludingMedia(translationY);
+
+ verify(mKeyguardStatusView).setChildrenTranslationYExcludingMediaView(translationY);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
new file mode 100644
index 0000000..ce44f4d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -0,0 +1,55 @@
+package com.android.keyguard
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.children
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class KeyguardStatusViewTest : SysuiTestCase() {
+
+ private lateinit var keyguardStatusView: KeyguardStatusView
+ private val mediaView: View
+ get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
+ private val statusViewContainer: ViewGroup
+ get() = keyguardStatusView.findViewById(R.id.status_view_container)
+ private val childrenExcludingMedia
+ get() = statusViewContainer.children.filter { it != mediaView }
+
+ @Before
+ fun setUp() {
+ keyguardStatusView = LayoutInflater.from(context)
+ .inflate(R.layout.keyguard_status_view, /* root= */ null) as KeyguardStatusView
+ }
+
+ @Test
+ fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
+ val translationY = 1234f
+
+ keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
+
+ assertThat(mediaView.translationY).isEqualTo(0)
+ }
+
+ @Test
+ fun setChildrenTranslationYExcludingMediaView_childrenAreTranslated() {
+ val translationY = 1234f
+
+ keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
+
+ childrenExcludingMedia.forEach {
+ assertThat(it.translationY).isEqualTo(translationY)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index b359ae5..8e201b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -117,9 +117,9 @@
dreamOverlayStateController)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
- setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
- setupHost(qsHost, MediaHierarchyManager.LOCATION_QS)
- setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS)
+ setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP)
+ setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP)
+ setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
`when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
`when`(mediaCarouselController.mediaCarouselScrollHandler)
.thenReturn(mediaCarouselScrollHandler)
@@ -130,9 +130,9 @@
clearInvocations(mediaCarouselController)
}
- private fun setupHost(host: MediaHost, location: Int) {
+ private fun setupHost(host: MediaHost, location: Int, top: Int) {
`when`(host.location).thenReturn(location)
- `when`(host.currentBounds).thenReturn(Rect())
+ `when`(host.currentBounds).thenReturn(Rect(0, top, 0, top))
`when`(host.hostView).thenReturn(uniqueObjectHostView)
`when`(host.visible).thenReturn(true)
mediaHiearchyManager.register(host)
@@ -257,6 +257,20 @@
verify(mediaCarouselController).closeGuts()
}
+ @Test
+ fun getGuidedTransformationTranslationY_notInGuidedTransformation_returnsNegativeNumber() {
+ assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY()).isLessThan(0)
+ }
+
+ @Test
+ fun getGuidedTransformationTranslationY_inGuidedTransformation_returnsCurrentTranslation() {
+ enterGuidedTransformation()
+
+ val expectedTranslation = LOCKSCREEN_TOP - QS_TOP
+ assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY())
+ .isEqualTo(expectedTranslation)
+ }
+
private fun enableSplitShade() {
context.getOrCreateTestableResources().addOverride(
R.bool.config_use_split_notification_shade, true
@@ -284,4 +298,16 @@
private fun expandQS() {
mediaHiearchyManager.qsExpansion = 1.0f
}
+
+ private fun enterGuidedTransformation() {
+ mediaHiearchyManager.qsExpansion = 1.0f
+ goToLockscreen()
+ mediaHiearchyManager.setTransitionToFullShadeAmount(123f)
+ }
+
+ companion object {
+ private const val QQS_TOP = 123
+ private const val QS_TOP = 456
+ private const val LOCKSCREEN_TOP = 789
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 067caa9..64a0a23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -44,6 +44,7 @@
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.ArgumentMatchers.eq
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -260,6 +261,49 @@
}
@Test
+ fun setDragAmount_setsKeyguardTransitionProgress() {
+ transitionController.dragDownAmount = 10f
+
+ verify(notificationPanelController).setKeyguardTransitionProgress(anyFloat(), anyInt())
+ }
+
+ @Test
+ fun setDragAmount_setsKeyguardAlphaBasedOnDistance() {
+ val alphaDistance = context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance)
+ transitionController.dragDownAmount = 10f
+
+ val expectedAlpha = 1 - 10f / alphaDistance
+ verify(notificationPanelController)
+ .setKeyguardTransitionProgress(eq(expectedAlpha), anyInt())
+ }
+
+ @Test
+ fun setDragAmount_notInSplitShade_setsKeyguardTranslationToZero() {
+ val mediaTranslationY = 123
+ disableSplitShade()
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+ .thenReturn(mediaTranslationY)
+
+ transitionController.dragDownAmount = 10f
+
+ verify(notificationPanelController).setKeyguardTransitionProgress(anyFloat(), eq(0))
+ }
+
+ @Test
+ fun setDragAmount_inSplitShade_setsKeyguardTranslationBasedOnMediaTranslation() {
+ val mediaTranslationY = 123
+ enableSplitShade()
+ whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+ .thenReturn(mediaTranslationY)
+
+ transitionController.dragDownAmount = 10f
+
+ verify(notificationPanelController)
+ .setKeyguardTransitionProgress(anyFloat(), eq(mediaTranslationY))
+ }
+
+ @Test
fun setDragDownAmount_setsValueOnMediaHierarchyManager() {
transitionController.dragDownAmount = 10f
@@ -276,8 +320,16 @@
}
private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
context.getOrCreateTestableResources().addOverride(
- R.bool.config_use_split_notification_shade, true
+ R.bool.config_use_split_notification_shade, enabled
)
configurationController.notifyConfigurationChanged()
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index a4a200d..543e44c 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -757,12 +757,12 @@
}
@Override
- public void setPreferDockBigOverlays(IBinder token, boolean preferDockBigOverlays) {
+ public void setShouldDockBigOverlays(IBinder token, boolean shouldDockBigOverlays) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- r.setPreferDockBigOverlays(preferDockBigOverlays);
+ r.setShouldDockBigOverlays(shouldDockBigOverlays);
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2153f5f..677babe 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -534,7 +534,7 @@
// activity can enter picture in picture while pausing (only when switching to another task)
PictureInPictureParams pictureInPictureArgs = new PictureInPictureParams.Builder().build();
// The PiP params used when deferring the entering of picture-in-picture.
- boolean preferDockBigOverlays;
+ boolean shouldDockBigOverlays;
int launchCount; // count of launches since last state
long lastLaunchTime; // time of last launch of this activity
ComponentName requestedVrComponent; // the requested component for handling VR mode.
@@ -2042,7 +2042,7 @@
mLetterboxUiController = new LetterboxUiController(mWmService, this);
mCameraCompatControlEnabled = mWmService.mContext.getResources()
.getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
- preferDockBigOverlays = mWmService.mContext.getResources()
+ shouldDockBigOverlays = mWmService.mContext.getResources()
.getBoolean(R.bool.config_dockBigOverlayWindows);
if (_createTime > 0) {
@@ -9575,9 +9575,9 @@
getTask().getRootTask().onPictureInPictureParamsChanged();
}
- void setPreferDockBigOverlays(boolean preferDockBigOverlays) {
- this.preferDockBigOverlays = preferDockBigOverlays;
- getTask().getRootTask().onPreferDockBigOverlaysChanged();
+ void setShouldDockBigOverlays(boolean shouldDockBigOverlays) {
+ this.shouldDockBigOverlays = shouldDockBigOverlays;
+ getTask().getRootTask().onShouldDockBigOverlaysChanged();
}
@Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8edb313..cd7ebe3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3403,7 +3403,7 @@
info.positionInParent = getRelativePosition();
info.pictureInPictureParams = getPictureInPictureParams(top);
- info.preferDockBigOverlays = getPreferDockBigOverlays();
+ info.shouldDockBigOverlays = shouldDockBigOverlays();
if (info.pictureInPictureParams != null
&& info.pictureInPictureParams.isLaunchIntoPip()
&& top.getTopMostActivity().getLastParentBeforePip() != null) {
@@ -3456,9 +3456,9 @@
? null : new PictureInPictureParams(topMostActivity.pictureInPictureArgs);
}
- private boolean getPreferDockBigOverlays() {
+ private boolean shouldDockBigOverlays() {
final ActivityRecord topMostActivity = getTopMostActivity();
- return topMostActivity != null && topMostActivity.preferDockBigOverlays;
+ return topMostActivity != null && topMostActivity.shouldDockBigOverlays;
}
Rect getDisplayCutoutInsets() {
@@ -4354,7 +4354,7 @@
}
}
- void onPreferDockBigOverlaysChanged() {
+ void onShouldDockBigOverlaysChanged() {
dispatchTaskInfoChangedIfNeeded(true /* force */);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index a597fc6..5a95210 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -23,6 +23,25 @@
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 static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
import android.annotation.NonNull;
@@ -92,13 +111,25 @@
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
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();
// TODO: This may need to be a Handler(looper)
private final ScheduledExecutorService mScheduledExecutorService =
@@ -107,17 +138,20 @@
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;
final ComponentName mDetectionComponentName;
final int mUser;
final Context mContext;
+
volatile HotwordDetectionServiceIdentity mIdentity;
private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
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. */
@@ -133,7 +167,6 @@
private @NonNull ServiceConnection mRemoteHotwordDetectionService;
private IBinder mAudioFlinger;
private boolean mDebugHotwordLogging = false;
- private final int mDetectorType;
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
@@ -169,6 +202,8 @@
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);
}
@@ -201,6 +236,8 @@
// 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);
}
}
@@ -220,26 +257,32 @@
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) {
+ // TODO: Add a new atom for RemoteException case, the error doesn't very
+ // correct here
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);
@@ -254,8 +297,12 @@
}
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);
@@ -265,6 +312,23 @@
});
}
+ 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() {
synchronized (mLock) {
return mRemoteHotwordDetectionService.isBound();
@@ -481,12 +545,31 @@
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)
@@ -504,8 +587,17 @@
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;
@@ -520,11 +612,20 @@
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);
+ });
}
}
@@ -625,10 +726,16 @@
}
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);
}
}
@@ -793,6 +900,7 @@
private boolean mRespectServiceConnectionStatusChanged = true;
private boolean mIsBound = false;
+ private boolean mIsLoggedFirstConnect = false;
ServiceConnection(@NonNull Context context,
@NonNull Intent intent, int bindingFlags, int userId,
@@ -816,6 +924,12 @@
return;
}
mIsBound = connected;
+ if (connected && !mIsLoggedFirstConnect) {
+ mIsLoggedFirstConnect = true;
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
+ mVoiceInteractionServiceUid);
+ }
}
}
@@ -846,13 +960,25 @@
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
index de0b960..940aed3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
@@ -16,6 +16,24 @@
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;
/**
@@ -23,6 +41,13 @@
*/
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
}
@@ -31,39 +56,99 @@
* Logs information related to create hotword detector.
*/
public static void writeDetectorCreateEvent(int detectorType, boolean isCreated, int uid) {
- FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED, detectorType,
- isCreated, 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,
- detectorType, result);
+ 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,
- detectorType, reason);
+ 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,
- detectorType, result);
+ 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,
- 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;
+ }
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index fb4d73c..0519873 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -462,24 +462,33 @@
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);
}
@@ -488,17 +497,23 @@
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,
@@ -509,6 +524,15 @@
}
}
+ 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");