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");