Initial porting of View and Res for AccessibilityMenu
Bug: 261252772
Test: adb install the APK, then enable the menu from the Settings Accessibility page
Change-Id: I20b11bcaa631a5c78f00e870bef5d25d02fe86dc
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index a494f5e..0b1a3e2 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -20,6 +20,17 @@
android_app {
name: "AccessibilityMenu",
+
+ static_libs: [
+ "androidx.coordinatorlayout_coordinatorlayout",
+ "androidx.core_core",
+ "androidx.viewpager_viewpager",
+ ],
+
+ uses_libs: [
+ "org.apache.http.legacy",
+ ],
+
srcs: [
"src/**/*.java",
],
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/color/footer_icon_tint_color.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/color/footer_icon_tint_color.xml
new file mode 100644
index 0000000..c89e4c3
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/color/footer_icon_tint_color.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/footer_icon_disabled_color" /> <!-- disabled -->
+ <item android:color="@color/footer_icon_enabled_color" /> <!-- default -->
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/a11ymenu_intro.png b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/a11ymenu_intro.png
new file mode 100644
index 0000000..6149ee4
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/a11ymenu_intro.png
Binary files differ
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/footer_button_background_left.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/footer_button_background_left.xml
new file mode 100644
index 0000000..5ff245d
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/footer_button_background_left.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item>
+ <ripple
+ android:color="@color/ripple_material_color">
+ <item android:id="@android:id/mask">
+ <color android:color="@color/overlay_bg_color"/>
+ </item>
+ </ripple>
+ </item>
+
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/footer_button_background_right.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/footer_button_background_right.xml
new file mode 100644
index 0000000..5ff245d
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/footer_button_background_right.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item>
+ <ripple
+ android:color="@color/ripple_material_color">
+ <item android:id="@android:id/mask">
+ <color android:color="@color/overlay_bg_color"/>
+ </item>
+ </ripple>
+ </item>
+
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_a11y_menu_round.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_a11y_menu_round.xml
new file mode 100644
index 0000000..a2eaf95
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_a11y_menu_round.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<vector android:height="108dp" android:viewportHeight="24"
+ android:viewportWidth="24" android:width="108dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#ffffff" android:fillType="evenOdd" android:pathData="M7.875,10.625C7.1188,10.625 6.5,11.2437 6.5,12C6.5,12.7562 7.1188,13.375 7.875,13.375C8.6313,13.375 9.25,12.7562 9.25,12C9.25,11.2437 8.6313,10.625 7.875,10.625ZM16.125,10.625C15.3687,10.625 14.75,11.2437 14.75,12C14.75,12.7562 15.3687,13.375 16.125,13.375C16.8813,13.375 17.5,12.7562 17.5,12C17.5,11.2437 16.8813,10.625 16.125,10.625ZM10.625,12C10.625,11.2437 11.2438,10.625 12,10.625C12.7563,10.625 13.375,11.2437 13.375,12C13.375,12.7562 12.7563,13.375 12,13.375C11.2438,13.375 10.625,12.7562 10.625,12Z"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_add_32dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_add_32dp.xml
new file mode 100644
index 0000000..7e1262c
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_add_32dp.xml
@@ -0,0 +1,5 @@
+<vector android:height="32dp" android:tint="#FFFFFF"
+ android:viewportHeight="24.0" android:viewportWidth="24.0"
+ android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_arrow_back_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_arrow_back_24dp.xml
new file mode 100644
index 0000000..f6af270
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_arrow_back_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/footer_arrow_length"
+ android:height="@dimen/footer_arrow_length"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@color/footer_icon_color"
+ android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_arrow_forward_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_arrow_forward_24dp.xml
new file mode 100644
index 0000000..2f7b632
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_arrow_forward_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/footer_arrow_length"
+ android:height="@dimen/footer_arrow_length"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@color/footer_icon_color"
+ android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_menu.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_menu.xml
new file mode 100644
index 0000000..79e0e08d
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_menu.xml
@@ -0,0 +1,33 @@
+<vector android:height="48dp" android:viewportHeight="192.0"
+ android:viewportWidth="192.0" android:width="48dp"
+ xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#34a853" android:pathData="M37.14,173.74l-28.53,-90a14.53,14.53 0,0 1,5 -15.63L87.15,11a14.21,14.21 0,0 1,17.61 0.12l73.81,58.94a14.53,14.53 0,0 1,4.8 15.57l-28.48,88.18A14.32,14.32 0,0 1,141.22 184H50.84A14.33,14.33 0,0 1,37.14 173.74Z"/>
+ <path android:pathData="M137.61,94.07l-17,17 -17,-17 -17,17L70.3,94.72l-17,17L125.66,184h15.56a14.32,14.32 0,0 0,13.67 -10.19l15.25,-47.21Z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="27152.64"
+ android:endY="32745.600000000002"
+ android:startX="20910.72"
+ android:startY="21934.079999999998" android:type="linear">
+ <item android:color="#33263238" android:offset="0.0"/>
+ <item android:color="#11205432" android:offset="0.47"/>
+ <item android:color="#051E6130" android:offset="1.0"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:fillAlpha="0.2" android:fillColor="#263238" android:pathData="M50.14,100.11a12,12 0,1 1,11.39 15.77,11.72 11.72,0 0,1 -5,-1.1l-3.41,-3.4ZM129.4,91.88a12,12 0,1 1,-12 12A12,12 0,0 1,129.4 91.88ZM95.4,91.88a12,12 0,1 1,-12 12A12,12 0,0 1,95.42 91.88Z"/>
+ <path android:fillColor="#fff"
+ android:pathData="M61.53,90.88a12,12 0,1 1,-12 12A12,12 0,0 1,61.53 90.88ZM129.41,90.88a12,12 0,1 1,-12 12A12,12 0,0 1,129.41 90.88ZM95.41,90.88a12,12 0,1 1,-12 12A12,12 0,0 1,95.42 90.88Z"
+ android:strokeAlpha="0" android:strokeColor="#000"
+ android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.5"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238" android:pathData="M184,80.91a14.33,14.33 0,0 1,-0.63 4.7l-28.48,88.18A14.33,14.33 0,0 1,141.21 184H50.84a14.33,14.33 0,0 1,-13.7 -10.26l-28.53,-90A14.49,14.49 0,0 1,8 79.11a14.3,14.3 0,0 0,0.61 3.64l28.53,90A14.33,14.33 0,0 0,50.84 183h90.37a14.33,14.33 0,0 0,13.67 -10.19l28.48,-88.18A14.79,14.79 0,0 0,184 80.91Z"/>
+ <path android:fillAlpha="0.2" android:fillColor="#fff" android:pathData="M184,81.89A14.46,14.46 0,0 0,178.57 71L104.76,12.1A14.21,14.21 0,0 0,87.15 12L13.58,69.12A14.5,14.5 0,0 0,8 80.09a14.5,14.5 0,0 1,5.57 -12L87.15,11a14.21,14.21 0,0 1,17.61 0.12L178.57,70A14.48,14.48 0,0 1,184 81.89Z"/>
+ <path android:pathData="M37.14,173.74l-28.53,-90a14.53,14.53 0,0 1,5 -15.63L87.15,11a14.21,14.21 0,0 1,17.61 0.12l73.81,58.94a14.53,14.53 0,0 1,4.8 15.57l-28.48,88.18A14.32,14.32 0,0 1,141.22 184H50.84A14.33,14.33 0,0 1,37.14 173.74Z"/>
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/colorAccessibilityMenuIcon" />
+ <foreground>
+ <inset
+ android:drawable="@drawable/ic_a11y_menu_round"
+ android:inset="21.88%" />
+ </foreground>
+ </adaptive-icon>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_assistant_32dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_assistant_32dp.xml
new file mode 100644
index 0000000..ebeebf8
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_assistant_32dp.xml
@@ -0,0 +1,8 @@
+<vector android:height="32dp"
+ android:viewportHeight="192.0" android:viewportWidth="192.0"
+ android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#34A853" android:pathData="M172,60m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"/>
+ <path android:fillColor="#EA4335" android:pathData="M136,88m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
+ <path android:fillColor="#FBBC05" android:pathData="M136,148m-28,0a28,28 0,1 1,56 0a28,28 0,1 1,-56 0"/>
+ <path android:fillColor="#4285F4" android:pathData="M56,64m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/shadow_0deg.9.png b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/shadow_0deg.9.png
new file mode 100644
index 0000000..b0d1696
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/shadow_0deg.9.png
Binary files differ
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/shadow_270deg.9.png b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/shadow_270deg.9.png
new file mode 100644
index 0000000..b777ffe
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/shadow_270deg.9.png
Binary files differ
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/shadow_90deg.9.png b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/shadow_90deg.9.png
new file mode 100644
index 0000000..998bd90
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/shadow_90deg.9.png
Binary files differ
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/view_background.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/view_background.xml
new file mode 100644
index 0000000..c1f76f37
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/view_background.xml
@@ -0,0 +1,5 @@
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/overlay_bg_color" />
+</shape>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
new file mode 100644
index 0000000..658c03b
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/footerlayout"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/grid_item_btn_view_height"
+ android:layout_alignParentBottom="true"
+ android:layout_gravity="bottom"
+ android:layoutDirection="ltr"
+ android:orientation="vertical"
+ android:visibility="gone">
+
+ <View
+ android:id="@+id/top_listDivider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?android:attr/listDivider"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="horizontal">
+
+ <ImageButton
+ android:id="@+id/menu_prev_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/footer_button_background_left"
+ android:contentDescription="@string/previous_button_content_description"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_arrow_back_24dp"
+ android:tint="@color/footer_icon_tint_color"/>
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:background="?android:attr/listDivider"/>
+
+ <ImageButton
+ android:id="@+id/menu_next_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/footer_button_background_right"
+ android:contentDescription="@string/next_button_content_description"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_arrow_forward_24dp"
+ android:tint="@color/footer_icon_tint_color"/>
+
+ </LinearLayout>
+
+ <View
+ android:id="@+id/bottom_listDivider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?android:attr/listDivider"/>
+
+</LinearLayout>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
new file mode 100644
index 0000000..39e5a8c
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/grid_item_padding"
+ android:paddingBottom="@dimen/grid_item_padding"
+ android:gravity="center">
+
+ <ImageButton
+ android:id="@+id/shortcutIconBtn"
+ android:layout_width="@dimen/image_button_width"
+ android:layout_height="@dimen/image_button_height"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:scaleType="fitCenter"></ImageButton>
+
+<TextView
+ android:id="@+id/shortcutLabel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/grid_item_text_view_margin_top"
+ android:layout_below="@+id/shortcutIconBtn"
+ android:layout_centerHorizontal="true"
+ android:ellipsize="end"
+ android:gravity="center_horizontal"
+ android:importantForAccessibility="no"
+ android:lines="2"
+ android:textSize="@dimen/label_text_size"
+ android:textAlignment="center"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Widget.Button"/>
+
+</RelativeLayout>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_view.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_view.xml
new file mode 100644
index 0000000..c198443
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_view.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<GridView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/gridview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:horizontalSpacing="@dimen/a11ymenu_grid_layout_margin"
+ android:listSelector="@android:color/transparent"
+ android:numColumns="3"
+ android:overScrollMode="never"
+ android:stretchMode="columnWidth">
+</GridView>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml
new file mode 100644
index 0000000..28a633e
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/row_width"
+ android:layout_height="match_parent"
+ android:id="@+id/coordinatorLayout"
+ android:background="@drawable/view_background"
+ >
+ <LinearLayout
+ android:layout_width="@dimen/row_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <androidx.viewpager.widget.ViewPager
+ android:id="@+id/view_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/table_margin_top"
+ android:paddingBottom="@dimen/a11ymenu_layout_margin"
+ android:paddingLeft="@dimen/a11ymenu_layout_margin"
+ android:paddingRight="@dimen/a11ymenu_layout_margin"
+ android:layout_gravity="center"
+ android:gravity="center"
+ />
+
+ <include layout="@layout/footerlayout_switch_page"/>
+ </LinearLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-land/dimens.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-land/dimens.xml
new file mode 100644
index 0000000..69f0934
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-land/dimens.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <dimen name="table_margin_top">0dp</dimen>
+ <dimen name="row_width">388dp</dimen>
+ <dimen name="image_button_height">45dp</dimen>
+ <dimen name="image_button_width">45dp</dimen>
+ <dimen name="image_button_marginBottom">1dp</dimen>
+
+ <!-- dimens for gridview layout. -->
+ <dimen name="grid_item_padding">4dp</dimen>
+
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
new file mode 100644
index 0000000..33c0cca
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+ <color name="power_color">#dadce0</color>
+ <color name="quick_settings_color">#78d9ec</color>
+ <color name="a11y_settings_color">#d9affe</color>
+ <color name="recent_apps_color">#f0a5dd</color>
+ <color name="lockscreen_color">#85e4a0</color>
+ <color name="volume_color">#7ae3d4</color>
+ <color name="notifications_color">#f496ac</color>
+ <color name="screenshot_color">#adcbff</color>
+ <color name="assistant_color">#F1F3F4</color>
+ <color name="brightness_color">#fdd663</color>
+
+ <color name="ripple_material_color">#10FFFFFF</color>
+
+ <color name="overlay_bg_color">#313235</color>
+ <color name="footer_icon_color">#E8EAED</color>
+ <color name="footer_icon_enabled_color">#E8EAED</color>
+ <color name="footer_icon_disabled_color">#5F6368</color>
+ <color name="colorControlNormal">#202124</color>
+
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
new file mode 100644
index 0000000..81b3152
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+
+<resources>
+ <!--Adds the theme to support SnackBar component and user configurable theme. -->
+ <style name="ServiceTheme" parent="android:Theme.DeviceDefault.DayNight">
+ <item name="android:colorControlNormal">@color/colorControlNormal</item>
+ </style>
+
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/bool.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/bool.xml
new file mode 100644
index 0000000..2f9d6b5
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/bool.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="isAtLeastP">true</bool>
+
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
new file mode 100644
index 0000000..36d1fc1
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+ <color name="power_color">#757575</color>
+ <color name="quick_settings_color">#2196F3</color>
+ <color name="a11y_settings_color">#5806C9</color>
+ <color name="recent_apps_color">#AD2EC6</color>
+ <color name="lockscreen_color">#0F9D58</color>
+ <color name="volume_color">#01A2A0</color>
+ <color name="notifications_color">#F15B8D</color>
+ <color name="screenshot_color">#26459C</color>
+ <color name="assistant_color">#F1F3F4</color>
+ <color name="brightness_color">#E59810</color>
+ <color name="colorAccent">#1a73e8</color>
+
+ <color name="ripple_material_color">#1f000000</color>
+
+ <color name="overlay_bg_color">@android:color/white</color>
+ <color name="footer_icon_color">@android:color/black</color>
+ <color name="footer_icon_enabled_color">@android:color/black</color>
+ <color name="footer_icon_disabled_color">#ddd</color>
+ <color name="colorControlNormal">@android:color/white</color>
+
+ <color name="colorAccessibilityMenuIcon">#3AA757</color>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/dimens.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/dimens.xml
new file mode 100644
index 0000000..7ed1897
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- the curve radius for the background of the complete layout -->
+ <dimen name="table_margin_top">22dp</dimen>
+ <dimen name="row_width">@dimen/custom_match_parent</dimen>
+ <dimen name="image_button_height">60dp</dimen>
+ <dimen name="image_button_width">60dp</dimen>
+ <dimen name="image_button_marginBottom">2dp</dimen>
+ <dimen name="a11ymenu_layout_margin">4dp</dimen>
+ <dimen name="custom_match_parent">-1px</dimen>
+
+ <!-- dimens for gridview layout. -->
+ <dimen name="grid_item_text_view_margin_top">2dp</dimen>
+ <dimen name="grid_item_padding">10dp</dimen>
+ <dimen name="grid_item_btn_view_height">48dp</dimen>
+ <dimen name="a11ymenu_grid_layout_margin">8dp</dimen>
+
+ <!-- dimens for a11y menu footer layout. -->
+ <dimen name="footer_arrow_length">24dp</dimen>
+
+ <!-- text size for shortcut label when large button settings in on. -->
+ <dimen name="large_label_text_size">18sp</dimen>
+ <dimen name="label_text_size">14sp</dimen>
+
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/donottranslate.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/donottranslate.xml
new file mode 100644
index 0000000..0c25ec4
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/donottranslate.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- user customized shortcuts preference -->
+ <string name="pref_user_shortcuts">accessibility_menu_user_shortcuts</string>
+ <!-- key for user customized shortcuts -->
+ <string name="pref_user_shortcuts_key">pref_user_shortcuts_key</string>
+ <!-- value for empty shortcut -->
+ <string name="pref_user_shortcuts_value_empty">[]</string>
+ <!-- empty string for shortcut label -->
+ <string name="empty_content"></string>
+
+ <string name="pref_large_buttons">pref_large_buttons</string>
+
+ <!-- key for Help&feedback settings [CHAR_LIMIT=NONE] -->
+ <string name="pref_help">pref_help</string>
+
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
new file mode 100644
index 0000000..30fd017
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- String defining the service name -->
+ <string name="accessibility_menu_service_name">Accessibility Menu</string>
+ <!-- Accessibility Menu detail intro. [CHAR_LIMIT=NONE] -->
+ <string name="accessibility_menu_intro">
+ The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots, and more.
+ </string>
+ <!-- String defining the label for the assistant button -->
+ <string name="assistant_label">Assistant</string>
+ <!-- String defining utterance for the assistant button for screen readers -->
+ <string name="assistant_utterance">Google Assistant</string>
+ <!-- String defining the label for the accessibility settings button -->
+ <string name="a11y_settings_label">Accessibility Settings</string>
+ <!-- String defining the label for the volume button -->
+ <string name="volume_label">Volume</string>
+ <!-- String defining utterance for the volume button for screen readers -->
+ <string name="volume_utterance">Volume controls</string>
+ <!-- String defining the label for the power button -->
+ <string name="power_label">Power</string>
+ <!-- String defining utterance for the power button for screen readers -->
+ <string name="power_utterance">Power options</string>
+ <!-- String defining the label for the recent apps button -->
+ <string name="recent_apps_label">Recent apps</string>
+ <!-- String defining the label for the lockscreen button -->
+ <string name="lockscreen_label">Lock screen</string>
+ <!-- String defining the label for the quick settings button -->
+ <string name="quick_settings_label">Quick Settings</string>
+ <!-- String defining the label for the notifications button -->
+ <string name="notifications_label">Notifications</string>
+ <!-- String defining the label for the screenshot button -->
+ <string name="screenshot_label">Screenshot</string>
+ <!-- String defining the utterance for the screenshot button for screen readers -->
+ <string name="screenshot_utterance">Take screenshot</string>
+ <!-- String defining the label for the volume up/down button -->
+ <string name="volume_up_label">Volume up</string>
+ <string name="volume_down_label">Volume down</string>
+ <!-- String defining the label for the brightness up/down button -->
+ <string name="brightness_up_label">Brightness up</string>
+ <string name="brightness_down_label">Brightness down</string>
+ <!-- String defining the content description for the footer previous/next button -->
+ <string name="previous_button_content_description">Go to previous screen</string>
+ <string name="next_button_content_description">Go to next screen</string>
+
+ <string name="accessibility_menu_description">
+ The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots, and more.
+ </string>
+ <!-- Short summary of app that appears as subtext on the service preference in Settings -->
+ <string name="accessibility_menu_summary">Control device via large menu</string>
+
+ <!-- TODO(b/113371047): string need to be reviewed -->
+ <!-- String defining the settings name -->
+ <string name="accessibility_menu_settings_name">Accessibility Menu Settings</string>
+
+ <!-- String defining the title of Large button setting -->
+ <string name="accessibility_menu_large_buttons_title">Large buttons</string>
+ <!-- String defining the summary of Large button setting -->
+ <string name="accessibility_menu_large_buttons_summary">Increase size of Accessibility Menu Buttons</string>
+ <!-- String defining the title of the preference to show help and feedback menu [CHAR LIMIT=40] -->
+ <string name="pref_help_and_feedback_title">Help & feedback</string>
+ <!-- String defining the title of the preference to show help menu [CHAR LIMIT=40] -->
+ <string name="pref_help_title">Help</string>
+
+ <!-- The percentage of the brightness, and double "%" is required to represent the symbol "%" -->
+ <string name="brightness_percentage_label">Brightness <xliff:g id="percentage">%1$s</xliff:g> %%</string>
+ <!-- The percentage of the music volume, and double "%" is required to represent the symbol "%" -->
+ <string name="music_volume_percentage_label">Music volume <xliff:g id="percentage">%1$s</xliff:g> %%</string>
+
+ <!-- The label of a settings item that displays legal information about the licenses used in this app. [CHAR LIMIT=NONE] -->
+ <string name="pref_item_licenses">Open Source Licenses</string>
+
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
new file mode 100644
index 0000000..a2cf267
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+-->
+
+<resources>
+ <!--The theme is for preference CollapsingToolbarBaseActivity settings-->
+ <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.Light" />
+
+ <!--Adds the theme to support SnackBar component and user configurable theme. -->
+ <style name="ServiceTheme" parent="android:Theme.DeviceDefault.Light">
+ <item name="android:colorControlNormal">@color/colorControlNormal</item>
+ </style>
+
+ <!--The basic theme for service and test case only-->
+ <style name="A11yMenuBaseTheme" parent="android:Theme.DeviceDefault.Light">
+ <item name="android:windowActionBar">false</item>
+ </style>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml
index 96882d33..3dbbb1a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml
@@ -13,4 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"/>
\ No newline at end of file
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagRequestAccessibilityButton|flagRequestFilterKeyEvents"
+ android:canRequestFilterKeyEvents="true"
+ android:summary="@string/accessibility_menu_summary"
+ android:intro="@string/accessibility_menu_intro"
+ android:animatedImageDrawable="@drawable/a11ymenu_intro"
+ android:isAccessibilityTool="true"
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 8b75900..5c4fdcc 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -17,16 +17,39 @@
package com.android.systemui.accessibility.accessibilitymenu;
import android.accessibilityservice.AccessibilityService;
+import android.view.MotionEvent;
+import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuOverlayLayout;
+
/** @hide */
-public class AccessibilityMenuService extends AccessibilityService {
+public class AccessibilityMenuService extends AccessibilityService implements View.OnTouchListener {
+ private static final String TAG = "A11yMenuService";
+
+ private A11yMenuOverlayLayout mA11yMenuLayout;
@Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
+ public void onCreate() {
+ super.onCreate();
}
@Override
+ protected void onServiceConnected() {
+ mA11yMenuLayout = new A11yMenuOverlayLayout(this);
+ super.onServiceConnected();
+ mA11yMenuLayout.toggleVisibility();
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {}
+
+ @Override
public void onInterrupt() {
}
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return false;
+ }
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
new file mode 100644
index 0000000..fa42e61
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.accessibility.accessibilitymenu.model;
+
+import com.android.systemui.accessibility.accessibilitymenu.R;
+
+/** Provides a data structure for a11y menu shortcuts. */
+public class A11yMenuShortcut {
+
+ public enum ShortcutId {
+ UNSPECIFIED_ID_VALUE,
+ ID_ASSISTANT_VALUE,
+ ID_A11YSETTING_VALUE,
+ ID_POWER_VALUE,
+ ID_VOLUME_DOWN_VALUE,
+ ID_VOLUME_UP_VALUE,
+ ID_RECENT_VALUE,
+ ID_BRIGHTNESS_DOWN_VALUE,
+ ID_BRIGHTNESS_UP_VALUE,
+ ID_LOCKSCREEN_VALUE,
+ ID_QUICKSETTING_VALUE,
+ ID_NOTIFICATION_VALUE,
+ ID_SCREENSHOT_VALUE
+ }
+
+ private static final String TAG = "A11yMenuShortcut";
+
+ /** Shortcut id used to identify. */
+ private int mShortcutId = ShortcutId.UNSPECIFIED_ID_VALUE.ordinal();
+
+ // Resource IDs of shortcut button and label.
+ public int imageSrc;
+ public int imageColor;
+ public int imgContentDescription;
+ public int labelText;
+
+ public A11yMenuShortcut(int id) {
+ setId(id);
+ }
+
+ /**
+ * Sets Id to shortcut, checks the value first and updates shortcut resources. It will set id to
+ *
+ * @param id id set to shortcut
+ */
+ public void setId(int id) {
+ mShortcutId = id;
+
+ // TODO(jonesriley) load the proper resources based on id
+ imageSrc = R.drawable.ic_logo_assistant_32dp;
+ imageColor = android.R.color.darker_gray;
+ imgContentDescription = R.string.empty_content;
+ labelText = R.string.empty_content;
+ }
+
+ public int getId() {
+ return mShortcutId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof A11yMenuShortcut)) {
+ return false;
+ }
+
+ A11yMenuShortcut targetObject = (A11yMenuShortcut) o;
+
+ return mShortcutId == targetObject.mShortcutId;
+ }
+
+ @Override
+ public int hashCode() {
+ return mShortcutId;
+ }
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/utils/ShortcutDrawableUtils.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/utils/ShortcutDrawableUtils.java
new file mode 100644
index 0000000..28ba4b5
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/utils/ShortcutDrawableUtils.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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.accessibility.accessibilitymenu.utils;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RippleDrawable;
+
+import com.android.systemui.accessibility.accessibilitymenu.R;
+
+/** Creates background drawable for a11y menu shortcut. */
+public class ShortcutDrawableUtils {
+
+ /**
+ * To make the circular background of shortcut icons have higher resolution. The higher value of
+ * LENGTH is, the higher resolution of the circular background are.
+ */
+ private static final int LENGTH = 480;
+
+ private static final int RADIUS = LENGTH / 2;
+ private static final int COORDINATE = LENGTH / 2;
+ private static final int RIPPLE_COLOR_ID = R.color.ripple_material_color;
+
+ private final Context mContext;
+ private final ColorStateList mRippleColorStateList;
+
+ // Placeholder of drawable to prevent NullPointerException
+ private final ColorDrawable mTransparentDrawable = new ColorDrawable(Color.TRANSPARENT);
+
+ public ShortcutDrawableUtils(Context context) {
+ this.mContext = context;
+
+ int rippleColor = context.getColor(RIPPLE_COLOR_ID);
+ mRippleColorStateList = ColorStateList.valueOf(rippleColor);
+ }
+
+ /**
+ * Creates a circular drawable in specific color for shortcut.
+ *
+ * @param colorResId color resource ID
+ * @return drawable circular drawable
+ */
+ public Drawable createCircularDrawable(int colorResId) {
+ Bitmap output = Bitmap.createBitmap(LENGTH, LENGTH, Config.ARGB_8888);
+ Canvas canvas = new Canvas(output);
+ int color = mContext.getColor(colorResId);
+ Paint paint = new Paint();
+ paint.setColor(color);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStyle(Style.FILL);
+ canvas.drawCircle(COORDINATE, COORDINATE, RADIUS, paint);
+
+ BitmapDrawable drawable = new BitmapDrawable(mContext.getResources(), output);
+ return drawable;
+ }
+
+ /**
+ * Creates an adaptive icon drawable in specific color for shortcut.
+ *
+ * @param colorResId color resource ID
+ * @return drawable for adaptive icon
+ */
+ public Drawable createAdaptiveIconDrawable(int colorResId) {
+ Drawable circleLayer = createCircularDrawable(colorResId);
+ RippleDrawable rippleLayer = new RippleDrawable(mRippleColorStateList, null, null);
+
+ AdaptiveIconDrawable adaptiveIconDrawable =
+ new AdaptiveIconDrawable(circleLayer, mTransparentDrawable);
+
+ Drawable[] layers = {adaptiveIconDrawable, rippleLayer};
+ LayerDrawable drawable = new LayerDrawable(layers);
+ return drawable;
+ }
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
new file mode 100644
index 0000000..e3401a9
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 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.accessibility.accessibilitymenu.view;
+
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+import android.view.TouchDelegate;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
+import com.android.systemui.accessibility.accessibilitymenu.R;
+import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
+import com.android.systemui.accessibility.accessibilitymenu.utils.ShortcutDrawableUtils;
+
+import java.util.List;
+
+/** GridView Adapter for a11y menu overlay. */
+public class A11yMenuAdapter extends BaseAdapter {
+
+ // The large scale of shortcut icon and label.
+ private static final float LARGE_BUTTON_SCALE = 1.5f;
+ private final int mLargeTextSize;
+
+ private final AccessibilityMenuService mService;
+ private final LayoutInflater mInflater;
+ private final List<A11yMenuShortcut> mShortcutDataList;
+ private final ShortcutDrawableUtils mShortcutDrawableUtils;
+
+ public A11yMenuAdapter(
+ AccessibilityMenuService service, List<A11yMenuShortcut> shortcutDataList) {
+ this.mService = service;
+ this.mShortcutDataList = shortcutDataList;
+ mInflater = LayoutInflater.from(service);
+
+ mShortcutDrawableUtils = new ShortcutDrawableUtils(service);
+
+ mLargeTextSize =
+ service.getResources().getDimensionPixelOffset(R.dimen.large_label_text_size);
+ }
+
+ @Override
+ public int getCount() {
+ return mShortcutDataList.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mShortcutDataList.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mShortcutDataList.get(position).getId();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ convertView = mInflater.inflate(R.layout.grid_item, null);
+
+ A11yMenuShortcut shortcutItem = (A11yMenuShortcut) getItem(position);
+ // Sets shortcut icon and label resource.
+ configureShortcutView(convertView, shortcutItem);
+
+ expandIconTouchArea(convertView);
+ setActionForMenuShortcut(convertView);
+ return convertView;
+ }
+
+ /**
+ * Expand shortcut icon touch area to the border of grid item.
+ * The height is from the top of icon to the bottom of label.
+ * The width is from the left border of grid item to the right border of grid item.
+ */
+ private void expandIconTouchArea(View convertView) {
+ ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
+ TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
+
+ shortcutIconButton.post(
+ () -> {
+ Rect iconHitRect = new Rect();
+ shortcutIconButton.getHitRect(iconHitRect);
+ Rect labelHitRect = new Rect();
+ shortcutLabel.getHitRect(labelHitRect);
+
+ final int widthAdjustment = iconHitRect.left;
+ iconHitRect.left = 0;
+ iconHitRect.right += widthAdjustment;
+ iconHitRect.top = 0;
+ iconHitRect.bottom = labelHitRect.bottom;
+ ((View) shortcutIconButton.getParent())
+ .setTouchDelegate(new TouchDelegate(iconHitRect, shortcutIconButton));
+ });
+ }
+
+ private void setActionForMenuShortcut(View convertView) {
+ ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
+
+ shortcutIconButton.setOnClickListener(
+ (View v) -> {
+ // Handles shortcut click event by AccessibilityMenuService.
+ // service.handleClick(v);
+ });
+ }
+
+ private void configureShortcutView(View convertView, A11yMenuShortcut shortcutItem) {
+ ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
+ TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
+
+ // TODO: Enlarge shortcut icon & label when large button setting is on.
+
+ if (shortcutItem.getId() == A11yMenuShortcut.ShortcutId.UNSPECIFIED_ID_VALUE.ordinal()) {
+ // Sets empty shortcut icon and label when the shortcut is ADD_ITEM.
+ shortcutIconButton.setImageResource(android.R.color.transparent);
+ shortcutIconButton.setBackground(null);
+ } else {
+ // Sets shortcut ID as tagId, to handle menu item click in AccessibilityMenuService.
+ shortcutIconButton.setTag(shortcutItem.getId());
+ shortcutIconButton.setContentDescription(
+ mService.getString(shortcutItem.imgContentDescription));
+ shortcutLabel.setText(shortcutItem.labelText);
+ shortcutIconButton.setImageResource(shortcutItem.imageSrc);
+
+ shortcutIconButton.setBackground(
+ mShortcutDrawableUtils.createAdaptiveIconDrawable(shortcutItem.imageColor));
+ }
+ }
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java
new file mode 100644
index 0000000..20c63df
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 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.accessibility.accessibilitymenu.view;
+
+import android.graphics.Rect;
+import android.view.TouchDelegate;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.ImageButton;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.accessibility.accessibilitymenu.R;
+
+/**
+ * This class is for Accessibility menu footer layout. Handles switching between a11y menu pages.
+ */
+public class A11yMenuFooter {
+
+ /** Provides an interface for footer of a11yMenu. */
+ public interface A11yMenuFooterCallBack {
+
+ /** Calls back when user clicks the left button. */
+ void onLeftButtonClicked();
+
+ /** Calls back when user clicks the right button. */
+ void onRightButtonClicked();
+ }
+
+ private final FooterButtonClickListener mFooterButtonClickListener;
+
+ private ImageButton mPreviousPageBtn;
+ private ImageButton mNextPageBtn;
+ private View mTopListDivider;
+ private View mBottomListDivider;
+ private final A11yMenuFooterCallBack mCallBack;
+
+ public A11yMenuFooter(ViewGroup menuLayout, A11yMenuFooterCallBack callBack) {
+ this.mCallBack = callBack;
+ mFooterButtonClickListener = new FooterButtonClickListener();
+ configureFooterLayout(menuLayout);
+ }
+
+ public @Nullable ImageButton getPreviousPageBtn() {
+ return mPreviousPageBtn;
+ }
+
+ public @Nullable ImageButton getNextPageBtn() {
+ return mNextPageBtn;
+ }
+
+ private void configureFooterLayout(ViewGroup menuLayout) {
+ ViewGroup footerContainer = menuLayout.findViewById(R.id.footerlayout);
+ footerContainer.setVisibility(View.VISIBLE);
+
+ mPreviousPageBtn = menuLayout.findViewById(R.id.menu_prev_button);
+ mNextPageBtn = menuLayout.findViewById(R.id.menu_next_button);
+ mTopListDivider = menuLayout.findViewById(R.id.top_listDivider);
+ mBottomListDivider = menuLayout.findViewById(R.id.bottom_listDivider);
+
+ // Registers listeners for footer buttons.
+ setListener(mPreviousPageBtn);
+ setListener(mNextPageBtn);
+
+ menuLayout
+ .getViewTreeObserver()
+ .addOnGlobalLayoutListener(
+ new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ menuLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ expandBtnTouchArea(mPreviousPageBtn, menuLayout);
+ expandBtnTouchArea(mNextPageBtn, (View) mNextPageBtn.getParent());
+ }
+ });
+ }
+
+ private void expandBtnTouchArea(ImageButton btn, View btnParent) {
+ Rect btnRect = new Rect();
+ btn.getHitRect(btnRect);
+ btnRect.top -= getHitRectHeight(mTopListDivider);
+ btnRect.bottom += getHitRectHeight(mBottomListDivider);
+ btnParent.setTouchDelegate(new TouchDelegate(btnRect, btn));
+ }
+
+ private static int getHitRectHeight(View listDivider) {
+ Rect hitRect = new Rect();
+ listDivider.getHitRect(hitRect);
+ return hitRect.height();
+ }
+
+ private void setListener(@Nullable View view) {
+ if (view != null) {
+ view.setOnClickListener(mFooterButtonClickListener);
+ }
+ }
+
+ /** Handles click event for footer buttons. */
+ private class FooterButtonClickListener implements OnClickListener {
+ @Override
+ public void onClick(View view) {
+ if (view.getId() == R.id.menu_prev_button) {
+ mCallBack.onLeftButtonClicked();
+ } else if (view.getId() == R.id.menu_next_button) {
+ mCallBack.onRightButtonClicked();
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
new file mode 100644
index 0000000..740bc8a
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 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.accessibility.accessibilitymenu.view;
+
+import static java.lang.Math.max;
+
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
+import com.android.systemui.accessibility.accessibilitymenu.R;
+import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides functionality for Accessibility menu layout in a11y menu overlay. There are functions to
+ * configure or update Accessibility menu layout when orientation and display size changed, and
+ * functions to toggle menu visibility when button clicked or screen off.
+ */
+public class A11yMenuOverlayLayout {
+
+ /** Predefined default shortcuts when large button setting is off. */
+ private static final int[] SHORTCUT_LIST_DEFAULT = {
+ A11yMenuShortcut.ShortcutId.ID_ASSISTANT_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_A11YSETTING_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_POWER_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_VOLUME_UP_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_RECENT_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_LOCKSCREEN_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_QUICKSETTING_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_NOTIFICATION_VALUE.ordinal(),
+ A11yMenuShortcut.ShortcutId.ID_SCREENSHOT_VALUE.ordinal()
+ };
+
+ private final AccessibilityMenuService mService;
+ private final WindowManager mWindowManager;
+ private ViewGroup mLayout;
+ private WindowManager.LayoutParams mLayoutParameter;
+ private A11yMenuViewPager mA11yMenuViewPager;
+
+ public A11yMenuOverlayLayout(AccessibilityMenuService service) {
+ mService = service;
+ mWindowManager = mService.getSystemService(WindowManager.class);
+ configureLayout();
+ }
+
+ /** Creates Accessibility menu layout and configure layout parameters. */
+ public View configureLayout() {
+ return configureLayout(A11yMenuViewPager.DEFAULT_PAGE_INDEX);
+ }
+
+ // TODO(b/78292783): Find a better way to inflate layout in the test.
+ /**
+ * Creates Accessibility menu layout, configure layout parameters and apply index to ViewPager.
+ *
+ * @param pageIndex the index of the ViewPager to show.
+ */
+ public View configureLayout(int pageIndex) {
+
+ int lastVisibilityState = View.GONE;
+ if (mLayout != null) {
+ lastVisibilityState = mLayout.getVisibility();
+ mWindowManager.removeView(mLayout);
+ mLayout = null;
+ }
+
+ if (mLayoutParameter == null) {
+ initLayoutParams();
+ }
+
+ mLayout = new FrameLayout(mService);
+ updateLayoutPosition();
+ inflateLayoutAndSetOnTouchListener(mLayout);
+ mA11yMenuViewPager = new A11yMenuViewPager(mService);
+ mA11yMenuViewPager.configureViewPagerAndFooter(mLayout, createShortcutList(), pageIndex);
+ mWindowManager.addView(mLayout, mLayoutParameter);
+ mLayout.setVisibility(lastVisibilityState);
+
+ return mLayout;
+ }
+
+ /** Updates view layout with new layout parameters only. */
+ public void updateViewLayout() {
+ if (mLayout == null || mLayoutParameter == null) {
+ return;
+ }
+ updateLayoutPosition();
+ mWindowManager.updateViewLayout(mLayout, mLayoutParameter);
+ }
+
+ private void initLayoutParams() {
+ mLayoutParameter = new WindowManager.LayoutParams();
+ mLayoutParameter.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+ mLayoutParameter.format = PixelFormat.TRANSLUCENT;
+ mLayoutParameter.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ mLayoutParameter.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+ mLayoutParameter.setTitle(mService.getString(R.string.accessibility_menu_service_name));
+ }
+
+ private void inflateLayoutAndSetOnTouchListener(ViewGroup view) {
+ LayoutInflater inflater = LayoutInflater.from(mService);
+ inflater.inflate(R.layout.paged_menu, view);
+ view.setOnTouchListener(mService);
+ }
+
+ /**
+ * Loads shortcut data from default shortcut ID array.
+ *
+ * @return A list of default shortcuts
+ */
+ private List<A11yMenuShortcut> createShortcutList() {
+ List<A11yMenuShortcut> shortcutList = new ArrayList<>();
+ for (int shortcutId : SHORTCUT_LIST_DEFAULT) {
+ shortcutList.add(new A11yMenuShortcut(shortcutId));
+ }
+ return shortcutList;
+ }
+
+ /** Updates a11y menu layout position by configuring layout params. */
+ private void updateLayoutPosition() {
+ Display display = mLayout.getDisplay();
+ final int orientation = mService.getResources().getConfiguration().orientation;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ switch (display.getRotation()) {
+ case Surface.ROTATION_90:
+ case Surface.ROTATION_180:
+ mLayoutParameter.gravity =
+ Gravity.END | Gravity.BOTTOM
+ | Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL;
+ mLayoutParameter.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ mLayoutParameter.height = WindowManager.LayoutParams.MATCH_PARENT;
+ mLayoutParameter.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ mLayoutParameter.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+ mLayout.setBackgroundResource(R.drawable.shadow_90deg);
+ break;
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_270:
+ mLayoutParameter.gravity =
+ Gravity.START | Gravity.BOTTOM
+ | Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL;
+ mLayoutParameter.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ mLayoutParameter.height = WindowManager.LayoutParams.MATCH_PARENT;
+ mLayoutParameter.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ mLayoutParameter.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+ mLayout.setBackgroundResource(R.drawable.shadow_270deg);
+ break;
+ default:
+ break;
+ }
+ } else {
+ mLayoutParameter.gravity = Gravity.BOTTOM;
+ mLayoutParameter.width = WindowManager.LayoutParams.MATCH_PARENT;
+ mLayoutParameter.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ mLayout.setBackgroundResource(R.drawable.shadow_0deg);
+ }
+
+ // Adjusts the y position of a11y menu layout to make the layout not to overlap bottom
+ // navigation bar window.
+ updateLayoutByWindowInsetsIfNeeded();
+ mLayout.setOnApplyWindowInsetsListener(
+ (view, insets) -> {
+ if (updateLayoutByWindowInsetsIfNeeded()) {
+ mWindowManager.updateViewLayout(mLayout, mLayoutParameter);
+ }
+ return view.onApplyWindowInsets(insets);
+ });
+ }
+
+ /**
+ * Returns {@code true} if the a11y menu layout params
+ * should be updated by {@link WindowManager} immediately due to window insets change.
+ * This method adjusts the layout position and size to
+ * make a11y menu not to overlap navigation bar window.
+ */
+ private boolean updateLayoutByWindowInsetsIfNeeded() {
+ boolean shouldUpdateLayout = false;
+ WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+ Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
+ WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+ int xOffset = max(windowInsets.left, windowInsets.right);
+ int yOffset = windowInsets.bottom;
+ Rect windowBound = windowMetrics.getBounds();
+ if (mLayoutParameter.x != xOffset || mLayoutParameter.y != yOffset) {
+ mLayoutParameter.x = xOffset;
+ mLayoutParameter.y = yOffset;
+ shouldUpdateLayout = true;
+ }
+ // for gestural navigation mode and the landscape mode,
+ // the layout height should be decreased by system bar
+ // and display cutout inset to fit the new
+ // frame size that doesn't overlap the navigation bar window.
+ int orientation = mService.getResources().getConfiguration().orientation;
+ if (mLayout.getHeight() != mLayoutParameter.height
+ && orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ mLayoutParameter.height = windowBound.height() - yOffset;
+ shouldUpdateLayout = true;
+ }
+ return shouldUpdateLayout;
+ }
+
+ /**
+ * Gets the current page index when device configuration changed. {@link
+ * AccessibilityMenuService#onConfigurationChanged(Configuration)}
+ *
+ * @return the current index of the ViewPager.
+ */
+ public int getPageIndex() {
+ if (mA11yMenuViewPager != null) {
+ return mA11yMenuViewPager.mViewPager.getCurrentItem();
+ }
+ return A11yMenuViewPager.DEFAULT_PAGE_INDEX;
+ }
+
+ /**
+ * Hides a11y menu layout. And return if layout visibility has been changed.
+ *
+ * @return {@code true} layout visibility is toggled off; {@code false} is unchanged
+ */
+ public boolean hideMenu() {
+ if (mLayout.getVisibility() == View.VISIBLE) {
+ mLayout.setVisibility(View.GONE);
+ return true;
+ }
+ return false;
+ }
+
+ /** Toggles a11y menu layout visibility. */
+ public void toggleVisibility() {
+ mLayout.setVisibility((mLayout.getVisibility() == View.VISIBLE) ? View.GONE : View.VISIBLE);
+ }
+
+ /** Shows hint text on Toast. */
+ public void showToast(String text) {
+ final View viewPos = mLayout.findViewById(R.id.coordinatorLayout);
+ Toast.makeText(viewPos.getContext(), text, Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
new file mode 100644
index 0000000..c510b87
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2023 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.accessibility.accessibilitymenu.view;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+import android.widget.GridView;
+
+import androidx.viewpager.widget.ViewPager;
+
+import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
+import com.android.systemui.accessibility.accessibilitymenu.R;
+import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
+import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuFooter.A11yMenuFooterCallBack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class handles UI for viewPager and footer.
+ * It displays grid pages containing all shortcuts in viewPager,
+ * and handles the click events from footer to switch between pages.
+ */
+public class A11yMenuViewPager {
+
+ /** The default index of the ViewPager. */
+ public static final int DEFAULT_PAGE_INDEX = 0;
+
+ /**
+ * The class holds the static parameters for grid view when large button settings is on/off.
+ */
+ public static final class GridViewParams {
+ /** Total shortcuts count in the grid view when large button settings is off. */
+ public static final int GRID_ITEM_COUNT = 9;
+
+ /** The number of columns in the grid view when large button settings is off. */
+ public static final int GRID_COLUMN_COUNT = 3;
+
+ /** Total shortcuts count in the grid view when large button settings is on. */
+ public static final int LARGE_GRID_ITEM_COUNT = 4;
+
+ /** The number of columns in the grid view when large button settings is on. */
+ public static final int LARGE_GRID_COLUMN_COUNT = 2;
+
+ /** Temporary measure to test both item types. */
+ private static final boolean USE_LARGE_ITEMS = true;
+
+ /**
+ * Returns the number of items in the grid view.
+ *
+ * @param context The parent context
+ * @return Grid item count
+ */
+ public static int getGridItemCount(Context context) {
+ return USE_LARGE_ITEMS
+ ? LARGE_GRID_ITEM_COUNT
+ : GRID_ITEM_COUNT;
+ }
+
+ /**
+ * Returns the number of columns in the grid view.
+ *
+ * @param context The parent context
+ * @return Grid column count
+ */
+ public static int getGridColumnCount(Context context) {
+ return USE_LARGE_ITEMS
+ ? LARGE_GRID_COLUMN_COUNT
+ : GRID_COLUMN_COUNT;
+ }
+
+ /**
+ * Returns the number of rows in the grid view.
+ *
+ * @param context The parent context
+ * @return Grid row count
+ */
+ public static int getGridRowCount(Context context) {
+ return USE_LARGE_ITEMS
+ ? (LARGE_GRID_ITEM_COUNT / LARGE_GRID_COLUMN_COUNT)
+ : (GRID_ITEM_COUNT / GRID_COLUMN_COUNT);
+ }
+
+ /**
+ * Separates a provided list of accessibility shortcuts into multiple sub-lists.
+ * Does not modify the original list.
+ *
+ * @param pageItemCount The maximum size of an individual sub-list.
+ * @param shortcutList The list of shortcuts to be separated into sub-lists.
+ * @return A list of shortcut sub-lists.
+ */
+ public static List<List<A11yMenuShortcut>> generateShortcutSubLists(
+ int pageItemCount, List<A11yMenuShortcut> shortcutList) {
+ int start = 0;
+ int end;
+ int shortcutListSize = shortcutList.size();
+ List<List<A11yMenuShortcut>> subLists = new ArrayList<>();
+ while (start < shortcutListSize) {
+ end = Math.min(start + pageItemCount, shortcutListSize);
+ subLists.add(shortcutList.subList(start, end));
+ start = end;
+ }
+ return subLists;
+ }
+
+ private GridViewParams() {}
+ }
+
+ private final AccessibilityMenuService mService;
+
+ /**
+ * The pager widget, which handles animation and allows swiping horizontally to access previous
+ * and next gridView pages.
+ */
+ protected ViewPager mViewPager;
+
+ private ViewPagerAdapter<GridView> mViewPagerAdapter;
+ private final List<GridView> mGridPageList = new ArrayList<>();
+
+ /** The footer, which provides buttons to switch between pages */
+ protected A11yMenuFooter mA11yMenuFooter;
+
+ /** The shortcut list intended to show in grid pages of viewPager */
+ private List<A11yMenuShortcut> mA11yMenuShortcutList;
+
+ /** The container layout for a11y menu. */
+ private ViewGroup mA11yMenuLayout;
+
+ public A11yMenuViewPager(AccessibilityMenuService service) {
+ this.mService = service;
+ }
+
+ /**
+ * Configures UI for view pager and footer.
+ *
+ * @param a11yMenuLayout the container layout for a11y menu
+ * @param shortcutDataList the data list need to show in view pager
+ * @param pageIndex the index of ViewPager to show
+ */
+ public void configureViewPagerAndFooter(
+ ViewGroup a11yMenuLayout, List<A11yMenuShortcut> shortcutDataList, int pageIndex) {
+ this.mA11yMenuLayout = a11yMenuLayout;
+ mA11yMenuShortcutList = shortcutDataList;
+ initViewPager();
+ initChildPage();
+ mA11yMenuFooter = new A11yMenuFooter(a11yMenuLayout, mFooterCallbacks);
+ updateFooterState();
+ registerOnGlobalLayoutListener();
+ goToPage(pageIndex);
+ }
+
+ /** Initializes viewPager and its adapter. */
+ private void initViewPager() {
+ mViewPager = mA11yMenuLayout.findViewById(R.id.view_pager);
+ mViewPagerAdapter = new ViewPagerAdapter<>();
+ mViewPager.setAdapter(mViewPagerAdapter);
+ mViewPager.setOverScrollMode(View.OVER_SCROLL_NEVER);
+ mViewPager.addOnPageChangeListener(
+ new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrollStateChanged(int state) {}
+
+ @Override
+ public void onPageScrolled(
+ int position, float positionOffset, int positionOffsetPixels) {}
+
+ @Override
+ public void onPageSelected(int position) {
+ updateFooterState();
+ }
+ });
+ }
+
+ /** Creates child pages of viewPager by the length of shortcuts and initializes them. */
+ private void initChildPage() {
+ if (mA11yMenuShortcutList == null || mA11yMenuShortcutList.isEmpty()) {
+ return;
+ }
+
+ if (!mGridPageList.isEmpty()) {
+ mGridPageList.clear();
+ }
+
+ // Generate pages by calculating # of items per grid.
+ for (List<A11yMenuShortcut> page : GridViewParams.generateShortcutSubLists(
+ GridViewParams.getGridItemCount(mService), mA11yMenuShortcutList)
+ ) {
+ addGridPage(page);
+ }
+
+ mViewPagerAdapter.set(mGridPageList);
+ }
+
+ private void addGridPage(List<A11yMenuShortcut> shortcutDataListInPage) {
+ LayoutInflater inflater = LayoutInflater.from(mService);
+ View view = inflater.inflate(R.layout.grid_view, null);
+ GridView gridView = view.findViewById(R.id.gridview);
+ A11yMenuAdapter adapter = new A11yMenuAdapter(mService, shortcutDataListInPage);
+ gridView.setNumColumns(GridViewParams.getGridColumnCount(mService));
+ gridView.setAdapter(adapter);
+ mGridPageList.add(gridView);
+ }
+
+ /** Updates footer's state by index of current page in view pager. */
+ private void updateFooterState() {
+ int currentPage = mViewPager.getCurrentItem();
+ int lastPage = mViewPager.getAdapter().getCount() - 1;
+ mA11yMenuFooter.getPreviousPageBtn().setEnabled(currentPage > 0);
+ mA11yMenuFooter.getNextPageBtn().setEnabled(currentPage < lastPage);
+ }
+
+ private void goToPage(int pageIndex) {
+ if (mViewPager == null) {
+ return;
+ }
+ if ((pageIndex >= 0) && (pageIndex < mViewPager.getAdapter().getCount())) {
+ mViewPager.setCurrentItem(pageIndex);
+ }
+ }
+
+ /** Registers OnGlobalLayoutListener to adjust menu UI by running callback at first time. */
+ private void registerOnGlobalLayoutListener() {
+ mA11yMenuLayout
+ .getViewTreeObserver()
+ .addOnGlobalLayoutListener(
+ new OnGlobalLayoutListener() {
+
+ boolean mIsFirstTime = true;
+
+ @Override
+ public void onGlobalLayout() {
+ if (!mIsFirstTime) {
+ return;
+ }
+
+ if (mGridPageList.isEmpty()) {
+ return;
+ }
+
+ GridView firstGridView = mGridPageList.get(0);
+ if (firstGridView == null
+ || firstGridView.getChildAt(0) == null) {
+ return;
+ }
+
+ mIsFirstTime = false;
+
+ int gridItemHeight = firstGridView.getChildAt(0)
+ .getMeasuredHeight();
+ adjustMenuUISize(gridItemHeight);
+ }
+ });
+ }
+
+ /**
+ * Adjusts menu UI to fit both landscape and portrait mode.
+ *
+ * <ol>
+ * <li>Adjust view pager's height.
+ * <li>Adjust vertical interval between grid items.
+ * <li>Adjust padding in view pager.
+ * </ol>
+ */
+ private void adjustMenuUISize(int gridItemHeight) {
+ final int rowsInGridView = GridViewParams.getGridRowCount(mService);
+ final int defaultMargin =
+ (int) mService.getResources().getDimension(R.dimen.a11ymenu_layout_margin);
+ final int topMargin = (int) mService.getResources().getDimension(R.dimen.table_margin_top);
+ final int displayMode = mService.getResources().getConfiguration().orientation;
+ int viewPagerHeight = mViewPager.getMeasuredHeight();
+
+ if (displayMode == Configuration.ORIENTATION_PORTRAIT) {
+ // In portrait mode, we only need to adjust view pager's height to match its
+ // child's height.
+ viewPagerHeight = gridItemHeight * rowsInGridView + defaultMargin + topMargin;
+ } else if (displayMode == Configuration.ORIENTATION_LANDSCAPE) {
+ // In landscape mode, we need to adjust view pager's height to match screen height
+ // and adjust its child too,
+ // because a11y menu layout height is limited by the screen height.
+ DisplayMetrics displayMetrics = mService.getResources().getDisplayMetrics();
+ float densityScale = (float) displayMetrics.densityDpi
+ / DisplayMetrics.DENSITY_DEVICE_STABLE;
+ View footerLayout = mA11yMenuLayout.findViewById(R.id.footerlayout);
+ // Keeps footer window height unchanged no matter the density is changed.
+ footerLayout.getLayoutParams().height =
+ (int) (footerLayout.getLayoutParams().height / densityScale);
+ // Adjust the view pager height for system bar and display cutout insets.
+ WindowManager windowManager = mService.getSystemService(WindowManager.class);
+ WindowMetrics windowMetric = windowManager.getCurrentWindowMetrics();
+ Insets windowInsets = windowMetric.getWindowInsets().getInsetsIgnoringVisibility(
+ WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+ viewPagerHeight =
+ windowMetric.getBounds().height()
+ - footerLayout.getLayoutParams().height
+ - windowInsets.bottom;
+ // Sets vertical interval between grid items.
+ int interval =
+ (viewPagerHeight - topMargin - defaultMargin
+ - (rowsInGridView * gridItemHeight))
+ / (rowsInGridView + 1);
+ for (GridView gridView : mGridPageList) {
+ gridView.setVerticalSpacing(interval);
+ }
+
+ // Sets padding to view pager.
+ final int finalMarginTop = interval + topMargin;
+ mViewPager.setPadding(defaultMargin, finalMarginTop, defaultMargin, defaultMargin);
+ }
+ final ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams();
+ layoutParams.height = viewPagerHeight;
+ mViewPager.setLayoutParams(layoutParams);
+ }
+
+ /** Callback object to handle click events from A11yMenuFooter */
+ protected A11yMenuFooterCallBack mFooterCallbacks =
+ new A11yMenuFooterCallBack() {
+ @Override
+ public void onLeftButtonClicked() {
+ // Moves to previous page.
+ int targetPage = mViewPager.getCurrentItem() - 1;
+ goToPage(targetPage);
+ updateFooterState();
+ }
+
+ @Override
+ public void onRightButtonClicked() {
+ // Moves to next page.
+ int targetPage = mViewPager.getCurrentItem() + 1;
+ goToPage(targetPage);
+ updateFooterState();
+ }
+ };
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java
new file mode 100644
index 0000000..5670d72
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 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.accessibility.accessibilitymenu.view;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.viewpager.widget.PagerAdapter;
+
+import java.util.List;
+
+/** The pager adapter, which provides the pages to the view pager widget. */
+class ViewPagerAdapter<T extends View> extends PagerAdapter {
+
+ /** The widget list in each page of view pager. */
+ private List<T> mWidgetList;
+
+ ViewPagerAdapter() {}
+
+ public void set(List<T> tList) {
+ mWidgetList = tList;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ if (mWidgetList == null) {
+ return 0;
+ }
+ return mWidgetList.size();
+ }
+
+ @Override
+ public int getItemPosition(Object object) {
+ return POSITION_NONE;
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ if (mWidgetList == null) {
+ return null;
+ }
+ container.addView(mWidgetList.get(position));
+ return mWidgetList.get(position);
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ container.removeView((View) object);
+ }
+}