[DO NOT MERGE] Controls Lockscreen affordance

Place a button on the lockscreen similar to the wallet
affordance. Only enable the button if:

1. User has enabled the setting
2. Device control services are enabled
3. The user has favorited at least 1 control

Fixes: 194435266
Test: manual
Change-Id: Idc755656e2d3b7df77bf95d06f076b63349ece9a
(cherry picked from commit 9172eef0cd7130404fdc265087210729d076439b)
diff --git a/packages/SystemUI/res/drawable/wallet_lockscreen_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/wallet_lockscreen_bg.xml
rename to packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 9ce83a7..3a4985f 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -82,18 +82,32 @@
 
     <ImageView
         android:id="@+id/wallet_button"
-        android:layout_height="@dimen/keyguard_affordance_wallet_height"
-        android:layout_width="@dimen/keyguard_affordance_wallet_width"
+        android:layout_height="@dimen/keyguard_affordance_fixed_height"
+        android:layout_width="@dimen/keyguard_affordance_fixed_width"
         android:layout_gravity="bottom|end"
         android:scaleType="center"
         android:tint="?android:attr/textColorPrimary"
         android:src="@drawable/ic_wallet_lockscreen"
-        android:background="@drawable/wallet_lockscreen_bg"
+        android:background="@drawable/keyguard_bottom_affordance_bg"
         android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
         android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
         android:contentDescription="@string/accessibility_wallet_button"
         android:visibility="gone" />
 
+    <ImageView
+        android:id="@+id/controls_button"
+        android:layout_height="@dimen/keyguard_affordance_fixed_height"
+        android:layout_width="@dimen/keyguard_affordance_fixed_width"
+        android:layout_gravity="bottom|start"
+        android:scaleType="center"
+        android:tint="?android:attr/textColorPrimary"
+        android:src="@drawable/ic_device_light"
+        android:background="@drawable/keyguard_bottom_affordance_bg"
+        android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+        android:contentDescription="@string/quick_controls_title"
+        android:visibility="gone" />
+
     <FrameLayout
         android:id="@+id/overlay_container"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 06e4483..7ee6a9b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -916,8 +916,8 @@
     <dimen name="keyguard_affordance_height">48dp</dimen>
     <dimen name="keyguard_affordance_width">48dp</dimen>
 
-    <dimen name="keyguard_affordance_wallet_height">48dp</dimen>
-    <dimen name="keyguard_affordance_wallet_width">48dp</dimen>
+    <dimen name="keyguard_affordance_fixed_height">48dp</dimen>
+    <dimen name="keyguard_affordance_fixed_width">48dp</dimen>
 
     <dimen name="keyguard_affordance_horizontal_offset">32dp</dimen>
     <dimen name="keyguard_affordance_vertical_offset">32dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 70ed817..51eabf6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -764,6 +764,8 @@
         <item name="android:windowBackground">@android:color/black</item>
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:statusBarColor">@android:color/black</item>
+        <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
+        <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
         <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
     </style>
 
@@ -898,5 +900,7 @@
     <style name="Wallet.Theme" parent="@android:style/Theme.DeviceDefault">
       <item name="android:colorBackground">@android:color/system_neutral1_900</item>
       <item name="android:itemBackground">@android:color/system_neutral1_800</item>
+      <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen.  -->
+      <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
     </style>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 91d503b..0a4e59c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -19,6 +19,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
@@ -40,6 +41,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
@@ -81,6 +83,11 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.camera.CameraIntents;
+import com.android.systemui.controls.ControlsServiceInfo;
+import com.android.systemui.controls.dagger.ControlsComponent;
+import com.android.systemui.controls.management.ControlsListingController;
+import com.android.systemui.controls.ui.ControlsActivity;
+import com.android.systemui.controls.ui.ControlsUiController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.IntentButtonProvider;
@@ -98,6 +105,8 @@
 import com.android.systemui.wallet.controller.QuickAccessWalletController;
 import com.android.systemui.wallet.ui.WalletActivity;
 
+import java.util.List;
+
 /**
  * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
  * text.
@@ -133,9 +142,12 @@
     private KeyguardAffordanceView mLeftAffordanceView;
 
     private ImageView mWalletButton;
+    private ImageView mControlsButton;
     private boolean mHasCard = false;
     private WalletCardRetriever mCardRetriever = new WalletCardRetriever();
     private QuickAccessWalletController mQuickAccessWalletController;
+    private ControlsComponent mControlsComponent;
+    private boolean mControlServicesAvailable = false;
 
     private ViewGroup mIndicationArea;
     private TextView mIndicationText;
@@ -188,6 +200,19 @@
     private ActivityIntentHelper mActivityIntentHelper;
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
+    private ControlsListingController.ControlsListingCallback mListingCallback =
+            new ControlsListingController.ControlsListingCallback() {
+                public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
+                    boolean available = !serviceInfos.isEmpty();
+
+                    if (available != mControlServicesAvailable) {
+                        mControlServicesAvailable = available;
+                        updateControlsVisibility();
+                        updateAffordanceColors();
+                    }
+                }
+            };
+
     public KeyguardBottomAreaView(Context context) {
         this(context, null);
     }
@@ -253,6 +278,7 @@
         mRightAffordanceView = findViewById(R.id.camera_button);
         mLeftAffordanceView = findViewById(R.id.left_button);
         mWalletButton = findViewById(R.id.wallet_button);
+        mControlsButton = findViewById(R.id.controls_button);
         mIndicationArea = findViewById(R.id.keyguard_indication_area);
         mIndicationText = findViewById(R.id.keyguard_indication_text);
         mIndicationTextBottom = findViewById(R.id.keyguard_indication_text_bottom);
@@ -276,6 +302,7 @@
         mIndicationPadding = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_area_padding);
         updateWalletVisibility();
+        updateControlsVisibility();
     }
 
     /**
@@ -328,6 +355,11 @@
             mQuickAccessWalletController.unregisterWalletChangeObservers(
                     WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
         }
+
+        if (mControlsComponent != null) {
+            mControlsComponent.getControlsListingController().ifPresent(
+                    c -> c.removeCallback(mListingCallback));
+        }
     }
 
     private void initAccessibility() {
@@ -369,13 +401,20 @@
         updateLeftAffordanceIcon();
 
         lp = mWalletButton.getLayoutParams();
-        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_wallet_width);
-        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_wallet_width);
+        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
+        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
         mWalletButton.setLayoutParams(lp);
 
+        lp = mControlsButton.getLayoutParams();
+        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
+        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
+        mControlsButton.setLayoutParams(lp);
+
         mIndicationPadding = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_area_padding);
+
         updateWalletVisibility();
+        updateAffordanceColors();
     }
 
     private void updateRightAffordanceIcon() {
@@ -454,22 +493,38 @@
                 || !mQuickAccessWalletController.isWalletEnabled()
                 || !mHasCard) {
             mWalletButton.setVisibility(GONE);
-            mIndicationArea.setPadding(0, 0, 0, 0);
-        } else {
-            Drawable tileIcon = mQuickAccessWalletController.getWalletClient().getTileIcon();
-            if (tileIcon != null) {
-                mWalletButton.setImageDrawable(tileIcon);
+
+            if (mControlsButton.getVisibility() == GONE) {
+                mIndicationArea.setPadding(0, 0, 0, 0);
             }
-            mWalletButton.getDrawable().setTint(
-                    Utils.getColorAttr(
-                            mContext,
-                            com.android.internal.R.attr.textColorPrimary).getDefaultColor());
+        } else {
             mWalletButton.setVisibility(VISIBLE);
             mWalletButton.setOnClickListener(this::onWalletClick);
             mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
         }
     }
 
+    private void updateControlsVisibility() {
+        if (mControlsComponent == null) return;
+
+        boolean hasFavorites = mControlsComponent.getControlsController()
+                .map(c -> c.getFavorites().size() > 0)
+                .orElse(false);
+        if (mDozing
+                || !hasFavorites
+                || !mControlServicesAvailable
+                || mControlsComponent.getVisibility() != AVAILABLE) {
+            mControlsButton.setVisibility(GONE);
+            if (mWalletButton.getVisibility() == GONE) {
+                mIndicationArea.setPadding(0, 0, 0, 0);
+            }
+        } else {
+            mControlsButton.setVisibility(VISIBLE);
+            mControlsButton.setOnClickListener(this::onControlsClick);
+            mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
+        }
+    }
+
     public boolean isLeftVoiceAssist() {
         return mLeftIsVoiceAssist;
     }
@@ -751,6 +806,9 @@
         if (mWalletButton.getVisibility() == View.VISIBLE) {
             startFinishDozeAnimationElement(mWalletButton, delay);
         }
+        if (mControlsButton.getVisibility() == View.VISIBLE) {
+            startFinishDozeAnimationElement(mControlsButton, delay);
+        }
         if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
             startFinishDozeAnimationElement(mLeftAffordanceView, delay);
             delay += DOZE_ANIMATION_STAGGER_DELAY;
@@ -824,6 +882,7 @@
         updateCameraVisibility();
         updateLeftAffordanceIcon();
         updateWalletVisibility();
+        updateControlsVisibility();
 
         if (dozing) {
             mOverlayContainer.setVisibility(INVISIBLE);
@@ -857,6 +916,7 @@
         mRightAffordanceView.setAlpha(alpha);
         mIndicationArea.setAlpha(alpha);
         mWalletButton.setAlpha(alpha);
+        mControlsButton.setAlpha(alpha);
     }
 
     private class DefaultLeftButton implements IntentButton {
@@ -950,6 +1010,32 @@
         mQuickAccessWalletController.queryWalletCards(mCardRetriever);
 
         updateWalletVisibility();
+        updateAffordanceColors();
+    }
+
+    private void updateAffordanceColors() {
+        int iconColor = Utils.getColorAttrDefaultColor(
+                mContext,
+                com.android.internal.R.attr.textColorPrimary);
+        mWalletButton.getDrawable().setTint(iconColor);
+        mControlsButton.getDrawable().setTint(iconColor);
+
+        ColorStateList bgColor = Utils.getColorAttr(
+                mContext,
+                com.android.internal.R.attr.colorSurface);
+        mWalletButton.setBackgroundTintList(bgColor);
+        mControlsButton.setBackgroundTintList(bgColor);
+    }
+
+    /**
+      * Initialize controls via the ControlsComponent
+      */
+    public void initControls(ControlsComponent controlsComponent) {
+        mControlsComponent = controlsComponent;
+        mControlsComponent.getControlsListingController().ifPresent(
+                c -> c.addCallback(mListingCallback));
+
+        updateAffordanceColors();
     }
 
     private void onWalletClick(View v) {
@@ -974,19 +1060,41 @@
         }
     }
 
+    private void onControlsClick(View v) {
+        if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return;
+        }
+
+        Intent intent = new Intent(mContext, ControlsActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
+
+        if (mControlsComponent.getVisibility() == AVAILABLE) {
+            mContext.startActivity(intent);
+        } else {
+            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */);
+        }
+    }
+
     private class WalletCardRetriever implements
             QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
 
         @Override
         public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) {
             mHasCard = !response.getWalletCards().isEmpty();
+            Drawable tileIcon = mQuickAccessWalletController.getWalletClient().getTileIcon();
+            if (tileIcon != null) {
+                mWalletButton.setImageDrawable(tileIcon);
+            }
             updateWalletVisibility();
+            updateAffordanceColors();
         }
 
         @Override
         public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) {
             mHasCard = false;
             updateWalletVisibility();
+            updateAffordanceColors();
         }
     }
 }
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 8afdb87..e4d283e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -104,6 +104,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
@@ -326,6 +327,7 @@
     private final ScrimController mScrimController;
     private final PrivacyDotViewController mPrivacyDotViewController;
     private final QuickAccessWalletController mQuickAccessWalletController;
+    private final ControlsComponent mControlsComponent;
     private final NotificationRemoteInputManager mRemoteInputManager;
 
     // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
@@ -730,7 +732,8 @@
             SecureSettings secureSettings,
             SplitShadeHeaderController splitShadeHeaderController,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
-            NotificationRemoteInputManager remoteInputManager) {
+            NotificationRemoteInputManager remoteInputManager,
+            ControlsComponent controlsComponent) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 statusBarKeyguardViewManager, latencyTracker, flingAnimationUtilsBuilder.get(),
@@ -740,6 +743,7 @@
         mKeyguardMediaController = keyguardMediaController;
         mPrivacyDotViewController = privacyDotViewController;
         mQuickAccessWalletController = quickAccessWalletController;
+        mControlsComponent = controlsComponent;
         mMetricsLogger = metricsLogger;
         mActivityManager = activityManager;
         mConfigurationController = configurationController;
@@ -1188,6 +1192,7 @@
         mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
         mKeyguardBottomArea.setFalsingManager(mFalsingManager);
         mKeyguardBottomArea.initWallet(mQuickAccessWalletController);
+        mKeyguardBottomArea.initControls(mControlsComponent);
     }
 
     private void updateMaxDisplayedNotifications(boolean recompute) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index d2f87ec..cbaca3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -88,6 +88,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
@@ -297,6 +298,8 @@
     private NotificationRemoteInputManager mNotificationRemoteInputManager;
     @Mock
     private RecordingController mRecordingController;
+    @Mock
+    private ControlsComponent mControlsComponent;
 
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -440,7 +443,8 @@
                 mSecureSettings,
                 mSplitShadeHeaderController,
                 mUnlockedScreenOffAnimationController,
-                mNotificationRemoteInputManager);
+                mNotificationRemoteInputManager,
+                mControlsComponent);
         mNotificationPanelViewController.initDependencies(
                 mStatusBar,
                 mNotificationShelfController);