Adds the initial SmartSpace shared element transition!

To see this in action, enable the remote animation (adb shell setprop persist.wm.enable_remote_keyguard_animation 1) and enhanced SmartSpace (adb shell device_config put launcher ENABLE_SMARTSPACE_ENHANCED true). Also, set your lock mode to swipe or some sort of non-bypass biometrics, so you can swipe to unlock.
KIs:
- It looks pretty janky on a fast swipe - this is the same root cause as b/186847064 so will have the same fix
- Launcher animates in with window-level animations (Launcher team is looking into helping)
- Screen off animation is not yet implemented (this is for unlock only)

Bug: 187025480
Test: unlock with every possible permutation of lock settings
Change-Id: I8c186fe57132ebc9a0bc5e3d8785e753e72c3bf2
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index f98a959..b2ae2a0 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -40,7 +40,8 @@
     name: "SystemUISharedLib",
     srcs: [
         "src/**/*.java",
-        "src/**/I*.aidl",
+        "src/**/*.kt",
+        "src/**/*.aidl",
         ":wm_shell-aidls",
     ],
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 2cf3ad2..c468e41 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -36,6 +36,9 @@
  * Various shared constants between Launcher and SysUI as part of quickstep
  */
 public class QuickStepContract {
+    // Fully qualified name of the Launcher activity.
+    public static final String LAUNCHER_ACTIVITY_CLASS_NAME =
+            "com.google.android.apps.nexuslauncher.NexusLauncherActivity";
 
     public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
     public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
@@ -52,6 +55,8 @@
     // See IStartingWindow.aidl
     public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
             "extra_shell_starting_window";
+    // See ISmartspaceTransitionController.aidl
+    public static final String KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER = "smartspace_transition";
 
     public static final String NAV_BAR_MODE_2BUTTON_OVERLAY =
             WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl
new file mode 100644
index 0000000..511df4c
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system.smartspace;
+
+import com.android.systemui.shared.system.smartspace.SmartspaceState;
+
+// Methods for getting and setting the state of a SmartSpace. This is used to allow a remote process
+// (such as System UI) to sync with and control a SmartSpace view hosted in another process (such as
+// Launcher).
+interface ISmartspaceCallback {
+
+    // Return information about the state of the SmartSpace, including location on-screen and
+    // currently selected page.
+    SmartspaceState getSmartspaceState();
+
+    // Set the currently selected page of this SmartSpace.
+    oneway void setSelectedPage(int selectedPage);
+
+    oneway void setVisibility(int visibility);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
new file mode 100644
index 0000000..2b3e961
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system.smartspace;
+
+import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
+
+// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
+interface ISmartspaceTransitionController {
+    oneway void setSmartspace(ISmartspaceCallback callback);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.aidl
new file mode 100644
index 0000000..2d01d6a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system.smartspace;
+
+import com.android.systemui.shared.system.smartspace.SmartspaceState;
+
+parcelable SmartspaceState;
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
new file mode 100644
index 0000000..2d51c4d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system.smartspace
+
+import android.graphics.Rect
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Represents the state of a SmartSpace, including its location on screen and the index of the
+ * currently selected page. This object contains all of the information needed to synchronize two
+ * SmartSpace instances so that we can perform shared-element transitions between them.
+ */
+class SmartspaceState() : Parcelable {
+    var boundsOnScreen: Rect = Rect()
+    var selectedPage = 0
+
+    constructor(parcel: Parcel) : this() {
+        this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader)
+        this.selectedPage = parcel.readInt()
+    }
+
+    override fun writeToParcel(dest: Parcel?, flags: Int) {
+        dest?.writeParcelable(boundsOnScreen, 0)
+        dest?.writeInt(selectedPage)
+    }
+
+    override fun describeContents(): Int {
+        return 0
+    }
+
+    override fun toString(): String {
+        return "boundsOnScreen: $boundsOnScreen, selectedPage: $selectedPage"
+    }
+
+    companion object CREATOR : Parcelable.Creator<SmartspaceState> {
+        override fun createFromParcel(parcel: Parcel): SmartspaceState {
+            return SmartspaceState(parcel)
+        }
+
+        override fun newArray(size: Int): Array<SmartspaceState?> {
+            return arrayOfNulls(size)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 932860f..bc23481 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -32,8 +32,10 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -44,7 +46,9 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.ViewController;
 
+import java.util.HashSet;
 import java.util.Locale;
+import java.util.Set;
 import java.util.TimeZone;
 
 import javax.inject.Inject;
@@ -92,6 +96,9 @@
     // If set, will replace keyguard_status_area
     private View mSmartspaceView;
 
+    private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    private SmartspaceTransitionController mSmartspaceTransitionController;
+
     @Inject
     public KeyguardClockSwitchController(
             KeyguardClockSwitch keyguardClockSwitch,
@@ -104,7 +111,9 @@
             BatteryController batteryController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardBypassController bypassController,
-            LockscreenSmartspaceController smartspaceController) {
+            LockscreenSmartspaceController smartspaceController,
+            KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            SmartspaceTransitionController smartspaceTransitionController) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
         mColorExtractor = colorExtractor;
@@ -116,6 +125,9 @@
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mBypassController = bypassController;
         mSmartspaceController = smartspaceController;
+
+        mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+        mSmartspaceTransitionController = smartspaceTransitionController;
     }
 
     /**
@@ -187,6 +199,7 @@
             nic.setLayoutParams(lp);
 
             mView.setSmartspaceView(mSmartspaceView);
+            mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
         }
     }
 
@@ -282,12 +295,44 @@
         if (mSmartspaceView != null) {
             PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X,
                     x, props, animate);
+
+            // If we're unlocking with the SmartSpace shared element transition, let the controller
+            // know that it should re-position our SmartSpace.
+            if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) {
+                mKeyguardUnlockAnimationController.updateLockscreenSmartSpacePosition();
+            } else {
+                // Otherwise, reset Y translation in case it's still offset from a previous shared
+                // element transition.
+                ((View) mSmartspaceView).setTranslationY(0f);
+            }
         }
 
         mKeyguardSliceViewController.updatePosition(x, props, animate);
         mNotificationIconAreaController.updatePosition(x, props, animate);
     }
 
+    /** Sets an alpha value on every child view except for the smartspace. */
+    public void setChildrenAlphaExcludingSmartspace(float alpha) {
+        final Set<View> excludedViews = new HashSet<>();
+
+        if (mSmartspaceView != null) {
+            excludedViews.add(mSmartspaceView);
+        }
+
+        setChildrenAlphaExcluding(alpha, excludedViews);
+    }
+
+    /** Sets an alpha value on every child view except for the views in the provided set. */
+    public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) {
+        for (int i = 0; i < mView.getChildCount(); i++) {
+            final View child = mView.getChildAt(i);
+
+            if (!excludedViews.contains(child)) {
+                child.setAlpha(alpha);
+            }
+        }
+    }
+
     void updateTimeZone(TimeZone timeZone) {
         mView.onTimeZoneChanged(timeZone);
         if (mClockViewController != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 3ab2cca..250f598 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.GridLayout;
 import android.widget.TextView;
 
@@ -39,6 +40,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Set;
 
 /**
  * View consisting of:
@@ -55,6 +57,7 @@
     private final LockPatternUtils mLockPatternUtils;
     private final IActivityManager mIActivityManager;
 
+    private ViewGroup mStatusViewContainer;
     private TextView mLogoutView;
     private KeyguardClockSwitch mClockView;
     private TextView mOwnerInfo;
@@ -66,6 +69,7 @@
 
     private float mDarkAmount = 0;
     private int mTextColor;
+    private float mChildrenAlphaExcludingSmartSpace = 1f;
 
     /**
      * Bottom margin that defines the margin between bottom of smart space and top of notification
@@ -132,6 +136,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mStatusViewContainer = findViewById(R.id.status_view_container);
         mLogoutView = findViewById(R.id.logout);
         if (mLogoutView != null) {
             mLogoutView.setOnClickListener(this::onLogoutClicked);
@@ -251,6 +256,27 @@
         mClockView.setTextColor(blendedTextColor);
     }
 
+    public void setChildrenAlphaExcludingClockView(float alpha) {
+        setChildrenAlphaExcluding(alpha, Set.of(mClockView));
+    }
+
+    /** Sets an alpha value on every view except for the views in the provided set. */
+    public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) {
+        mChildrenAlphaExcludingSmartSpace = alpha;
+
+        for (int i = 0; i < mStatusViewContainer.getChildCount(); i++) {
+            final View child = mStatusViewContainer.getChildAt(i);
+
+            if (!excludedViews.contains(child)) {
+                child.setAlpha(alpha);
+            }
+        }
+    }
+
+    public float getChildrenAlphaExcludingSmartSpace() {
+        return mChildrenAlphaExcludingSmartSpace;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusView:");
         pw.println("  mOwnerInfo: " + (mOwnerInfo == null
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 388c085..7b6514a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,8 @@
 import android.os.UserHandle;
 import android.util.Slog;
 
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -49,6 +51,9 @@
     private final ConfigurationController mConfigurationController;
     private final DozeParameters mDozeParameters;
     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+    private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    private final KeyguardStateController mKeyguardStateController;
+    private SmartspaceTransitionController mSmartspaceTransitionController;
     private final Rect mClipBounds = new Rect();
 
     @Inject
@@ -59,15 +64,33 @@
             KeyguardStateController keyguardStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ConfigurationController configurationController,
-            DozeParameters dozeParameters) {
+            DozeParameters dozeParameters,
+            KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            SmartspaceTransitionController smartspaceTransitionController) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mConfigurationController = configurationController;
         mDozeParameters = dozeParameters;
+        mKeyguardStateController = keyguardStateController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                 dozeParameters);
+        mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+        mSmartspaceTransitionController = smartspaceTransitionController;
+
+        mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+            @Override
+            public void onKeyguardShowingChanged() {
+                // If we explicitly re-show the keyguard, make sure that all the child views are
+                // visible. They might have been animating out as part of the SmartSpace shared
+                // element transition.
+                if (keyguardStateController.isShowing()) {
+                    mView.setChildrenAlphaExcludingClockView(1f);
+                    mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(1f);
+                }
+            }
+        });
     }
 
     @Override
@@ -137,7 +160,24 @@
      */
     public void setAlpha(float alpha) {
         if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
-            mView.setAlpha(alpha);
+            // If we're capable of performing the SmartSpace shared element transition, and we are
+            // going to (we're swiping to dismiss vs. bringing up the PIN screen), then fade out
+            // everything except for the SmartSpace.
+            if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) {
+                mView.setChildrenAlphaExcludingClockView(alpha);
+                mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(alpha);
+            } else if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+                // Otherwise, we can just set the alpha for the entire container.
+                mView.setAlpha(alpha);
+
+                // If we previously unlocked with the shared element transition, some child views
+                // might still have alpha = 0f. Set them back to 1f since we're just using the
+                // parent container's alpha.
+                if (mView.getChildrenAlphaExcludingSmartSpace() < 1f) {
+                    mView.setChildrenAlphaExcludingClockView(1f);
+                    mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(1f);
+                }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index cc167b9..e6fe060 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -133,6 +133,7 @@
                     .setTaskViewFactory(Optional.ofNullable(null))
                     .setTransitions(Transitions.createEmptyForTesting())
                     .setStartingSurface(Optional.ofNullable(null));
+
         }
         mSysUIComponent = builder.build();
         if (mInitializeComponents) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1396099..f422e9e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -45,6 +45,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.settings.dagger.SettingsModule;
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -173,6 +174,12 @@
         return SystemUIFactory.getInstance();
     }
 
+    @SysUISingleton
+    @Provides
+    static SmartspaceTransitionController provideSmartspaceTransitionController() {
+        return new SmartspaceTransitionController();
+    }
+
     // TODO: This should provided by the WM component
     /** Provides Optional of BubbleManager */
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 411c328..665376a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -23,11 +23,14 @@
 import android.graphics.Matrix
 import android.view.RemoteAnimationTarget
 import android.view.SyncRtSurfaceTransactionApplier
+import android.view.View
 import androidx.core.math.MathUtils
 import com.android.internal.R
+import com.android.keyguard.KeyguardClockSwitchController
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Lazy
 import javax.inject.Inject
@@ -85,7 +88,8 @@
     context: Context,
     private val keyguardStateController: KeyguardStateController,
     private val keyguardViewMediator: Lazy<KeyguardViewMediator>,
-    private val keyguardViewController: KeyguardViewController
+    private val keyguardViewController: KeyguardViewController,
+    private val smartspaceTransitionController: SmartspaceTransitionController
 ) : KeyguardStateController.Callback {
 
     /**
@@ -131,6 +135,21 @@
     /** Rounded corner radius to apply to the surface behind the keyguard. */
     private var roundedCornerRadius = 0f
 
+    /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */
+    public var lockscreenSmartSpace: View? = null
+
+    /**
+     * Whether we are currently in the process of unlocking the keyguard, and we are performing the
+     * shared element SmartSpace transition.
+     */
+    private var unlockingWithSmartSpaceTransition: Boolean = false
+
+    /**
+     * Whether we tried to start the SmartSpace shared element transition for this unlock swipe.
+     * It's possible we're unable to do so (if the Launcher SmartSpace is not available).
+     */
+    private var attemptedSmartSpaceTransitionForThisSwipe = false
+
     init {
         surfaceBehindAlphaAnimator.duration = 150
         surfaceBehindAlphaAnimator.interpolator = Interpolators.ALPHA_IN
@@ -214,6 +233,24 @@
     }
 
     /**
+     * Whether we are currently in the process of unlocking the keyguard, and we are performing the
+     * shared element SmartSpace transition.
+     */
+    fun isUnlockingWithSmartSpaceTransition(): Boolean {
+        return unlockingWithSmartSpaceTransition
+    }
+
+    /**
+     * Update the lockscreen SmartSpace to be positioned according to the current dismiss amount. As
+     * the dismiss amount increases, we will increase our SmartSpace's progress to the destination
+     * bounds (the location of the Launcher SmartSpace).
+     */
+    fun updateLockscreenSmartSpacePosition() {
+        smartspaceTransitionController.setProgressToDestinationBounds(
+                keyguardStateController.dismissAmount / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)
+    }
+
+    /**
      * Scales in and translates up the surface behind the keyguard. This is used during unlock
      * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is
      * cancelled).
@@ -284,36 +321,85 @@
             return
         }
 
+        if (keyguardViewController.isShowing) {
+            updateKeyguardViewMediatorIfThresholdsReached()
+
+            // If the surface is visible or it's about to be, start updating its appearance to
+            // reflect the new dismiss amount.
+            if (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+                    keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
+                updateSurfaceBehindAppearAmount()
+            }
+        }
+
+        // The end of the SmartSpace transition can occur after the keyguard is hidden (when we tell
+        // Launcher's SmartSpace to become visible again), so update it even if the keyguard view is
+        // no longer showing.
+        updateSmartSpaceTransition()
+    }
+
+    /**
+     * Lets the KeyguardViewMediator know if the dismiss amount has crossed a threshold of interest,
+     * such as reaching the point in the dismiss swipe where we need to make the surface behind the
+     * keyguard visible.
+     */
+    private fun updateKeyguardViewMediatorIfThresholdsReached() {
         val dismissAmount = keyguardStateController.dismissAmount
 
         // Hide the keyguard if we're fully dismissed, or if we're swiping to dismiss and have
         // crossed the threshold to finish the dismissal.
         val reachedHideKeyguardThreshold = (dismissAmount >= 1f ||
                 (keyguardStateController.isDismissingFromSwipe &&
-                // Don't hide if we're flinging during a swipe, since we need to finish
-                // animating it out. This will be called again after the fling ends.
-                !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
-                dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD))
+                        // Don't hide if we're flinging during a swipe, since we need to finish
+                        // animating it out. This will be called again after the fling ends.
+                        !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
+                        dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD))
 
         if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
                 !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
-            // We passed the threshold, and we're not yet showing the surface behind the keyguard.
-            // Animate it in.
+            // We passed the threshold, and we're not yet showing the surface behind the
+            // keyguard. Animate it in.
             keyguardViewMediator.get().showSurfaceBehindKeyguard()
             fadeInSurfaceBehind()
         } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
                 keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
-            // We're no longer past the threshold but we are showing the surface. Animate it out.
+            // We're no longer past the threshold but we are showing the surface. Animate it
+            // out.
             keyguardViewMediator.get().hideSurfaceBehindKeyguard()
             fadeOutSurfaceBehind()
-        } else if (keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe &&
+        } else if (keyguardViewMediator.get()
+                        .isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe &&
                 reachedHideKeyguardThreshold) {
             keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished()
         }
+    }
 
-        if (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
-                keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
-            updateSurfaceBehindAppearAmount()
+    /**
+     * Updates flags related to the SmartSpace transition in response to a change in keyguard
+     * dismiss amount, and also updates the SmartSpaceTransitionController, which will let Launcher
+     * know if it needs to do something as a result.
+     */
+    private fun updateSmartSpaceTransition() {
+        val dismissAmount = keyguardStateController.dismissAmount
+
+        // If we've begun a swipe, and are capable of doing the SmartSpace transition, start it!
+        if (!attemptedSmartSpaceTransitionForThisSwipe &&
+                dismissAmount > 0f &&
+                dismissAmount < 1f &&
+                keyguardViewController.isShowing) {
+            attemptedSmartSpaceTransitionForThisSwipe = true
+
+            smartspaceTransitionController.prepareForUnlockTransition()
+            if (keyguardStateController.canPerformSmartSpaceTransition()) {
+                unlockingWithSmartSpaceTransition = true
+                smartspaceTransitionController.launcherSmartspace?.setVisibility(
+                        View.INVISIBLE)
+            }
+        } else if (attemptedSmartSpaceTransitionForThisSwipe &&
+                (dismissAmount == 0f || dismissAmount == 1f)) {
+            attemptedSmartSpaceTransitionForThisSwipe = false
+            unlockingWithSmartSpaceTransition = false
+            smartspaceTransitionController.launcherSmartspace?.setVisibility(View.VISIBLE)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 20c3e81..6a07a00 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -29,6 +29,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
@@ -88,6 +89,7 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -148,6 +150,7 @@
     private final CommandQueue mCommandQueue;
     private final ShellTransitions mShellTransitions;
     private final Optional<StartingSurface> mStartingSurface;
+    private final SmartspaceTransitionController mSmartspaceTransitionController;
 
     private Region mActiveNavBarRegion;
 
@@ -540,6 +543,9 @@
             mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
                     KEY_EXTRA_SHELL_STARTING_WINDOW,
                     startingwindow.createExternalInterface().asBinder()));
+            params.putBinder(
+                    KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER,
+                    mSmartspaceTransitionController.createExternalInterface().asBinder());
 
             try {
                 Log.d(TAG_OPS + " b/182478748", "OverviewProxyService.onInitialize: curUser="
@@ -601,7 +607,8 @@
             Optional<OneHanded> oneHandedOptional,
             BroadcastDispatcher broadcastDispatcher,
             ShellTransitions shellTransitions,
-            Optional<StartingSurface> startingSurface) {
+            Optional<StartingSurface> startingSurface,
+            SmartspaceTransitionController smartspaceTransitionController) {
         super(broadcastDispatcher);
         mContext = context;
         mPipOptional = pipOptional;
@@ -663,6 +670,7 @@
         updateEnabledState();
         startConnectionToCurrentUser();
         mStartingSurface = startingSurface;
+        mSmartspaceTransitionController = smartspaceTransitionController;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt
new file mode 100644
index 0000000..89b3df0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system.smartspace
+
+import android.graphics.Rect
+import android.view.View
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.shared.system.QuickStepContract
+import kotlin.math.min
+
+/**
+ * Controller that keeps track of SmartSpace instances in remote processes (such as Launcher),
+ * allowing System UI to query or update their state during shared-element transitions.
+ */
+class SmartspaceTransitionController {
+
+    /**
+     * Implementation of [ISmartspaceTransitionController] that we provide to Launcher, allowing it
+     * to provide us with a callback to query and update the state of its Smartspace.
+     */
+    private val ISmartspaceTransitionController = object : ISmartspaceTransitionController.Stub() {
+        override fun setSmartspace(callback: ISmartspaceCallback?) {
+            this@SmartspaceTransitionController.launcherSmartspace = callback
+            updateLauncherSmartSpaceState()
+        }
+    }
+
+    /**
+     * Callback provided by Launcher to allow us to query and update the state of its SmartSpace.
+     */
+    public var launcherSmartspace: ISmartspaceCallback? = null
+
+    public var lockscreenSmartspace: View? = null
+
+    /**
+     * Cached state of the Launcher SmartSpace. Retrieving the state is an IPC, so we should avoid
+     * unnecessary
+     */
+    public var mLauncherSmartspaceState: SmartspaceState? = null
+
+    /**
+     * The bounds of our SmartSpace when the shared element transition began. We'll interpolate
+     * between this and [smartspaceDestinationBounds] as the dismiss amount changes.
+     */
+    private val smartspaceOriginBounds = Rect()
+
+    /** The bounds of the Launcher's SmartSpace, which is where we are animating our SmartSpace. */
+
+    private val smartspaceDestinationBounds = Rect()
+
+    fun createExternalInterface(): ISmartspaceTransitionController {
+        return ISmartspaceTransitionController
+    }
+
+    /**
+     * Updates [mLauncherSmartspaceState] and returns it. This will trigger a binder call, so use the
+     * cached [mLauncherSmartspaceState] if possible.
+     */
+    fun updateLauncherSmartSpaceState(): SmartspaceState? {
+        return launcherSmartspace?.smartspaceState.also {
+            mLauncherSmartspaceState = it
+        }
+    }
+
+    fun prepareForUnlockTransition() {
+        updateLauncherSmartSpaceState().also { state ->
+            if (state?.boundsOnScreen != null && lockscreenSmartspace != null) {
+                lockscreenSmartspace!!.getBoundsOnScreen(smartspaceOriginBounds)
+                with(smartspaceDestinationBounds) {
+                    set(state.boundsOnScreen)
+                    offset(-lockscreenSmartspace!!.paddingLeft,
+                            -lockscreenSmartspace!!.paddingTop)
+                }
+            }
+        }
+    }
+
+    fun setProgressToDestinationBounds(progress: Float) {
+        if (!isSmartspaceTransitionPossible()) {
+            return
+        }
+
+        val progressClamped = min(1f, progress)
+
+        // Calculate the distance (relative to the origin) that we need to be for the current
+        // progress value.
+        val progressX =
+                (smartspaceDestinationBounds.left - smartspaceOriginBounds.left) * progressClamped
+        val progressY =
+                (smartspaceDestinationBounds.top - smartspaceOriginBounds.top) * progressClamped
+
+        val lockscreenSmartspaceCurrentBounds = Rect().also {
+            lockscreenSmartspace!!.getBoundsOnScreen(it)
+        }
+
+        // Figure out how far that is from our present location on the screen. This approach
+        // compensates for the fact that our parent container is also translating to animate out.
+        val dx = smartspaceOriginBounds.left + progressX -
+                lockscreenSmartspaceCurrentBounds.left
+        var dy = smartspaceOriginBounds.top + progressY -
+                lockscreenSmartspaceCurrentBounds.top
+
+        with(lockscreenSmartspace!!) {
+            translationX = translationX + dx
+            translationY = translationY + dy
+        }
+    }
+
+    /**
+     * Whether we're capable of performing the Smartspace shared element transition when we unlock.
+     * This is true if:
+     *
+     * - The Launcher registered a Smartspace with us, it's reporting non-empty bounds on screen.
+     * - Launcher is behind the keyguard, and the Smartspace is visible on the currently selected
+     *   page.
+     */
+    public fun isSmartspaceTransitionPossible(): Boolean {
+        val smartSpaceNullOrBoundsEmpty = mLauncherSmartspaceState?.boundsOnScreen?.isEmpty ?: true
+        return isLauncherUnderneath() && !smartSpaceNullOrBoundsEmpty
+    }
+
+    companion object {
+        fun isLauncherUnderneath(): Boolean {
+            return ActivityManagerWrapper.getInstance()
+                    .runningTask?.topActivity?.className?.equals(
+                            QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ec012bf..6f1ab0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -456,6 +456,7 @@
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final BrightnessSlider.Factory mBrightnessSliderFactory;
     private final FeatureFlags mFeatureFlags;
+    private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
 
     private final List<ExpansionChangedListener> mExpansionChangedListeners;
 
@@ -877,6 +878,8 @@
         mAnimationScheduler = animationScheduler;
         mStatusBarLocationPublisher = locationPublisher;
         mFeatureFlags = featureFlags;
+        mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         lockscreenShadeTransitionController.setStatusbar(this);
 
@@ -1365,7 +1368,8 @@
         // are already animating the keyguard dismiss (since we will need to either finish or cancel
         // the animation).
         if (trackingTouch
-                || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) {
+                || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()
+                || mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) {
             mKeyguardStateController.notifyKeyguardDismissAmountChanged(
                     1f - expansion, trackingTouch);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index e7201f8..af7bf95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -50,6 +50,13 @@
     boolean canDismissLockScreen();
 
     /**
+     * Whether we can currently perform the shared element SmartSpace transition. This is true if
+     * we're on the lockscreen, it can be dismissed with a swipe, and the Launcher is underneath the
+     * keyguard and displaying a SmartSpace that it has registered with System UI.
+     */
+    boolean canPerformSmartSpaceTransition();
+
+    /**
      * If the device has PIN/pattern/password or a lock screen at all.
      */
     boolean isMethodSecure();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index e69c1f2..0945a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -32,6 +32,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -53,6 +54,7 @@
     private final LockPatternUtils mLockPatternUtils;
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
             new UpdateMonitorCallback();
+    private final SmartspaceTransitionController mSmartspaceTransitionController;
 
     private boolean mCanDismissLockScreen;
     private boolean mShowing;
@@ -96,10 +98,12 @@
      */
     @Inject
     public KeyguardStateControllerImpl(Context context,
-            KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils,
+            SmartspaceTransitionController smartspaceTransitionController) {
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+        mSmartspaceTransitionController = smartspaceTransitionController;
 
         update(true /* updateAlways */);
         if (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB) {
@@ -158,6 +162,11 @@
         mShowing = showing;
         mOccluded = occluded;
         notifyKeyguardChanged();
+
+        // Update the dismiss amount to the full 0f/1f if we explicitly show or hide the keyguard.
+        // Otherwise, the dismiss amount could be left at a random value if we show/hide during a
+        // dismiss gesture, canceling the gesture.
+        notifyKeyguardDismissAmountChanged(showing ? 0f : 1f, false);
     }
 
     private void notifyKeyguardChanged() {
@@ -228,6 +237,12 @@
     }
 
     @Override
+    public boolean canPerformSmartSpaceTransition() {
+        return canDismissLockScreen()
+                && mSmartspaceTransitionController.isSmartspaceTransitionPossible();
+    }
+
+    @Override
     public boolean isFaceAuthEnabled() {
         return mFaceAuthEnabled;
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 98467d4..1111e70 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -40,8 +40,10 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -86,6 +88,9 @@
 
     @Mock
     Resources mResources;
+    KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    @Mock
+    SmartspaceTransitionController mSmartSpaceTransitionController;
     @Mock
     private ClockPlugin mClockPlugin;
     @Mock
@@ -135,7 +140,10 @@
                 mBatteryController,
                 mKeyguardUpdateMonitor,
                 mBypassController,
-                mSmartspaceController);
+                mSmartspaceController,
+                mKeyguardUnlockAnimationController,
+                mSmartSpaceTransitionController
+        );
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 49f1655..f9b6d44 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -22,6 +22,8 @@
 import android.testing.AndroidTestingRunner;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -50,6 +52,10 @@
     ConfigurationController mConfigurationController;
     @Mock
     DozeParameters mDozeParameters;
+    @Mock
+    KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    @Mock
+    SmartspaceTransitionController mSmartSpaceTransitionController;
 
     private KeyguardStatusViewController mController;
 
@@ -64,7 +70,9 @@
                 mKeyguardStateController,
                 mKeyguardUpdateMonitor,
                 mConfigurationController,
-                mDozeParameters);
+                mDozeParameters,
+                mKeyguardUnlockAnimationController,
+                mSmartSpaceTransitionController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 53a2efc..4b87ec8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -32,6 +32,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,12 +50,14 @@
     @Mock
     private LockPatternUtils mLockPatternUtils;
     private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private SmartspaceTransitionController mSmartSpaceTransitionController;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mKeyguardStateController = new KeyguardStateControllerImpl(mContext,
-                mKeyguardUpdateMonitor, mLockPatternUtils);
+                mKeyguardUpdateMonitor, mLockPatternUtils, mSmartSpaceTransitionController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
index 5fb779a..1aebf1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
@@ -127,4 +127,9 @@
     public void notifyPanelFlingEnd() {
 
     }
+
+    @Override
+    public boolean canPerformSmartSpaceTransition() {
+        return false;
+    }
 }