Added Focus Outline To QS Tiles

Implemented new focus states for QS Tiles according to UI Specs:
go/qs-tiles-focus-state

Test: Manual, Tab to focus on qs tiles in shade -> ensure rectangular
outline consistent with UI specs in go/qs-tiles-focus-state
Flag: ACONFIG com.android.systemui.qs_tile_focus_state DISABLED
Fixes: 312899524

Change-Id: Ib090b7ab6f47931b1382f4bd8578c62c6f24c15b
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d05d40d..da06830 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -610,3 +610,10 @@
     description: "Refactors media code to follow the recommended architecture"
     bug: "326408371"
 }
+
+flag {
+    name: "qs_tile_focus_state"
+    namespace: "systemui"
+    description: "enables new focus outline for qs tiles when focused on with physical keyboard"
+    bug: "312899524"
+}
diff --git a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
new file mode 100644
index 0000000..cf7a730
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <!-- Since this layer list has just one layer, we can remove it and replace with the inner
+         layer drawable. However this should only be done when the flag
+         com.android.systemui.qs_tile_focus_state has completed all its stages and this drawable
+         fully replaces the previous one to ensure consistency with code sections searching for
+         specific ids in drawable hierarchy -->
+    <item
+        android:id="@id/background">
+        <layer-list>
+            <item
+                android:id="@+id/qs_tile_background_base"
+                android:drawable="@drawable/qs_tile_background_shape" />
+            <item android:id="@+id/qs_tile_background_overlay">
+                <selector>
+                    <item
+                        android:state_hovered="true"
+                        android:drawable="@drawable/qs_tile_background_shape" />
+                </selector>
+            </item>
+            <!-- In the layer below we have negative insets because we need the focus outline
+                 to draw outside the bounds, around the main background. We use 5dp because
+                 the outline stroke is 3dp and the required padding is 2dp.-->
+            <item
+                android:top="-5dp"
+                android:right="-5dp"
+                android:left="-5dp"
+                android:bottom="-5dp">
+                <selector>
+                    <item
+                        android:state_focused="true"
+                        android:drawable="@drawable/qs_tile_focused_background"/>
+                </selector>
+            </item>
+        </layer-list>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_tile_focused_background.xml b/packages/SystemUI/res/drawable/qs_tile_focused_background.xml
new file mode 100644
index 0000000..fd456df
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_tile_focused_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 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.
+  -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <corners android:radius="30dp"/>
+    <stroke android:width="3dp" android:color="?androidprv:attr/materialColorSecondaryFixed"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index e5af8e6..58858df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -46,7 +46,6 @@
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.res.R;
 import com.android.systemui.qs.QSEditEvent;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.TileLayout;
@@ -57,6 +56,7 @@
 import com.android.systemui.qs.dagger.QSThemedContext;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.res.R;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -319,6 +319,9 @@
         }
         FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
                 false);
+        if (com.android.systemui.Flags.qsTileFocusState()) {
+            frame.setClipChildren(false);
+        }
         View view = new CustomizeTileView(context);
         frame.addView(view);
         return new Holder(frame);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 9fefcbd..6cc682a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -47,6 +47,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.traceSection
 import com.android.settingslib.Utils
+import com.android.systemui.Flags
 import com.android.systemui.FontSizeUtils
 import com.android.systemui.animation.LaunchableView
 import com.android.systemui.animation.LaunchableViewDelegate
@@ -134,7 +135,7 @@
      */
     protected var showRippleEffect = true
 
-    private lateinit var ripple: RippleDrawable
+    private lateinit var qsTileBackground: LayerDrawable
     private lateinit var backgroundDrawable: LayerDrawable
     private lateinit var backgroundBaseDrawable: Drawable
     private lateinit var backgroundOverlayDrawable: Drawable
@@ -283,15 +284,20 @@
         addView(sideView)
     }
 
-    fun createTileBackground(): Drawable {
-        ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
-        backgroundDrawable = ripple.findDrawableByLayerId(R.id.background) as LayerDrawable
+    private fun createTileBackground(): Drawable {
+        qsTileBackground = if (Flags.qsTileFocusState()) {
+            mContext.getDrawable(R.drawable.qs_tile_background_flagged) as LayerDrawable
+        } else {
+            mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
+        }
+        backgroundDrawable =
+            qsTileBackground.findDrawableByLayerId(R.id.background) as LayerDrawable
         backgroundBaseDrawable =
             backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_base)
         backgroundOverlayDrawable =
             backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_overlay)
         backgroundOverlayDrawable.mutate().setTintMode(PorterDuff.Mode.SRC)
-        return ripple
+        return qsTileBackground
     }
 
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
@@ -366,14 +372,16 @@
 
     override fun setClickable(clickable: Boolean) {
         super.setClickable(clickable)
-        background = if (clickable && showRippleEffect) {
-            ripple.also {
-                // In case that the colorBackgroundDrawable was used as the background, make sure
-                // it has the correct callback instead of null
-                backgroundDrawable.callback = it
+        if (!Flags.qsTileFocusState()){
+            background = if (clickable && showRippleEffect) {
+                qsTileBackground.also {
+                    // In case that the colorBackgroundDrawable was used as the background, make sure
+                    // it has the correct callback instead of null
+                    backgroundDrawable.callback = it
+                }
+            } else {
+                backgroundDrawable
             }
-        } else {
-            backgroundDrawable
         }
     }