Merge "Long-press to configure quick affordances (2/2)." into tm-qpr-dev
diff --git a/res/color/keyguard_quick_affordance_slot_tab_text_color.xml b/res/color/picker_fragment_tab_text_color.xml
similarity index 100%
rename from res/color/keyguard_quick_affordance_slot_tab_text_color.xml
rename to res/color/picker_fragment_tab_text_color.xml
diff --git a/res/drawable/color_option_section_selected.xml b/res/drawable/color_option_section_selected.xml
new file mode 100644
index 0000000..13451a5
--- /dev/null
+++ b/res/drawable/color_option_section_selected.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:height="@dimen/component_color_selected_small_diameter_default"
+ android:width="@dimen/component_color_selected_small_diameter_default"
+ android:gravity="center">
+ <shape
+ android:shape="ring"
+ android:innerRadius="@dimen/component_color_overflow_small_radius_default"
+ android:thickness="2dp"
+ android:useLevel="false">
+ <solid android:color="@color/text_color_primary"/>
+ </shape>
+ </item>
+ <item
+ android:height="@dimen/component_color_selected_small_diameter_default"
+ android:width="@dimen/component_color_selected_small_diameter_default"
+ android:gravity="center">
+ <shape
+ android:shape="ring"
+ android:innerRadius="@dimen/component_color_overflow_small_radius_default"
+ android:thickness="-3dp"
+ android:useLevel="false">
+ <solid android:color="@color/color_surface"/>
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/color_overflow.xml b/res/drawable/color_overflow.xml
new file mode 100644
index 0000000..90c2684
--- /dev/null
+++ b/res/drawable/color_overflow.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:height="@dimen/component_color_overflow_small_diameter_default"
+ android:width="@dimen/component_color_overflow_small_diameter_default"
+ android:gravity="center">
+ <shape
+ android:shape="ring"
+ android:innerRadius="@dimen/component_color_overflow_small_radius_default"
+ android:thickness="-1dp"
+ android:useLevel="false">
+ <solid android:color="@color/color_surface_variant"/>
+ </shape>
+ </item>
+ <item
+ android:drawable="@drawable/ic_more_horiz"
+ android:gravity="center"/>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/color_section_background.xml b/res/drawable/color_section_background.xml
new file mode 100644
index 0000000..f96824b
--- /dev/null
+++ b/res/drawable/color_section_background.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ ~
+ -->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+
+ <solid android:color="@color/color_surface" />
+
+ <corners
+ android:topLeftRadius="32dp"
+ android:topRightRadius="32dp"
+ android:bottomLeftRadius="8dp"
+ android:bottomRightRadius="8dp" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_clock_24px.xml b/res/drawable/ic_clock_24px.xml
index b3f1fee..946bc51 100644
--- a/res/drawable/ic_clock_24px.xml
+++ b/res/drawable/ic_clock_24px.xml
@@ -20,6 +20,6 @@
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
- android:fillColor="@android:color/white"
+ android:fillColor="?android:textColorPrimary"
android:pathData="M15.3,16.7 L16.7,15.3 13,11.6V7H11V12.4ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12ZM12,20Q15.325,20 17.663,17.663Q20,15.325 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,15.325 6.338,17.663Q8.675,20 12,20Z"/>
</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_more_horiz.xml b/res/drawable/ic_more_horiz.xml
new file mode 100644
index 0000000..7c17df2
--- /dev/null
+++ b/res/drawable/ic_more_horiz.xml
@@ -0,0 +1,25 @@
+<!--
+ 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 xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:fillColor="@color/text_color_primary"
+ android:pathData="M6,14Q5.175,14 4.588,13.412Q4,12.825 4,12Q4,11.175 4.588,10.587Q5.175,10 6,10Q6.825,10 7.412,10.587Q8,11.175 8,12Q8,12.825 7.412,13.412Q6.825,14 6,14ZM12,14Q11.175,14 10.588,13.412Q10,12.825 10,12Q10,11.175 10.588,10.587Q11.175,10 12,10Q12.825,10 13.413,10.587Q14,11.175 14,12Q14,12.825 13.413,13.412Q12.825,14 12,14ZM18,14Q17.175,14 16.587,13.412Q16,12.825 16,12Q16,11.175 16.587,10.587Q17.175,10 18,10Q18.825,10 19.413,10.587Q20,11.175 20,12Q20,12.825 19.413,13.412Q18.825,14 18,14Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/keyguard_quick_affordance_picker_background.xml b/res/drawable/picker_fragment_background.xml
similarity index 100%
rename from res/drawable/keyguard_quick_affordance_picker_background.xml
rename to res/drawable/picker_fragment_background.xml
diff --git a/res/drawable/keyguard_quick_affordance_slot_tab_background.xml b/res/drawable/picker_fragment_tab_background.xml
similarity index 100%
rename from res/drawable/keyguard_quick_affordance_slot_tab_background.xml
rename to res/drawable/picker_fragment_tab_background.xml
diff --git a/res/layout/clock_section_view.xml b/res/layout/clock_section_view.xml
index 8512498..4a651ca 100644
--- a/res/layout/clock_section_view.xml
+++ b/res/layout/clock_section_view.xml
@@ -33,10 +33,11 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/clock_title"
+ android:text="@string/clock_settings_title"
style="@style/SectionTitleTextStyle" />
<TextView
+ android:id="@+id/selected_clock_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/clock_description"
@@ -49,7 +50,5 @@
android:scaleType="center"
android:src="@drawable/ic_clock_24px"
android:background="@drawable/option_border_color"
- android:contentDescription="@string/clock_picker_entry_content_description"
- android:tint="@color/text_color_primary" />
-
+ android:contentDescription="@string/clock_picker_entry_content_description" />
</com.android.customization.picker.clock.ClockSectionView>
\ No newline at end of file
diff --git a/res/layout/color_option_section.xml b/res/layout/color_option_section.xml
new file mode 100644
index 0000000..d10f8e5
--- /dev/null
+++ b/res/layout/color_option_section.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+-->
+<!--
+ This displays the selection outline. It is dynamically sized, constrained remain square and
+ have a maximum size, and be centered within its parent. Defines a layout_weight of 1, and should
+ be used with a Linear Layout parent where weightSum is defined.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:clipChildren="false"
+ android:gravity="center"
+ android:padding="@dimen/option_tile_grid_tile_padding_min"
+ android:layout_weight="1"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintWidth_max="@dimen/component_color_chip_small_diameter_default">
+ <!--
+ This is the color wheel itself, constrained to a maximum size and centered. The
+ constraint percentage is sized to leave a padding for the outer selection outline,
+ proportionate to 2dp for a 40dp color wheel
+ -->
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/option_tile"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_gravity="center"
+ android:gravity="center"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_percent="0.909">
+
+ <ImageView
+ android:id="@+id/color_preview_0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:adjustViewBounds="true"
+ android:src="@drawable/color_chip_seed_filled0"
+ app:layout_constraintHeight_percent=".50"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_percent=".50" />
+
+ <ImageView
+ android:id="@+id/color_preview_1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:adjustViewBounds="true"
+ android:src="@drawable/color_chip_seed_filled2"
+ app:layout_constraintHeight_percent=".50"
+ app:layout_constraintLeft_toRightOf="@id/color_preview_0"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_percent=".50" />
+
+ <ImageView
+ android:id="@+id/color_preview_2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:adjustViewBounds="true"
+ android:src="@drawable/color_chip_seed_filled1"
+ app:layout_constraintHeight_percent=".50"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/color_preview_0"
+ app:layout_constraintWidth_percent=".50" />
+
+ <ImageView
+ android:id="@+id/color_preview_3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:adjustViewBounds="true"
+ android:src="@drawable/color_chip_seed_filled3"
+ app:layout_constraintHeight_percent=".50"
+ app:layout_constraintLeft_toRightOf="@id/color_preview_2"
+ app:layout_constraintTop_toBottomOf="@id/color_preview_1"
+ app:layout_constraintWidth_percent=".50" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <ImageView
+ android:id="@+id/option_selected"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:adjustViewBounds="true"
+ android:src="@drawable/color_option_section_selected"
+ android:visibility="gone"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/color_option_section_overflow.xml b/res/layout/color_option_section_overflow.xml
new file mode 100644
index 0000000..fb25c35
--- /dev/null
+++ b/res/layout/color_option_section_overflow.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+-->
+<!--
+ This is added to keep sizing consistent with the color section options. For the color
+ section options, this displays the selection outline.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:clipChildren="false"
+ android:gravity="center"
+ android:padding="@dimen/option_tile_grid_tile_padding_min"
+ android:layout_weight="1"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintWidth_max="@dimen/component_color_chip_small_diameter_default">
+
+ <!--
+ This is added to keep sizing consistent with the color section options. For the color
+ section options, this is the color wheel itself.
+ -->
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/option_tile"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_gravity="center"
+ android:gravity="center"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_percent="0.909"
+ android:clipChildren="false">
+
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:adjustViewBounds="true"
+ android:src="@drawable/color_overflow" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/color_section_view2.xml b/res/layout/color_section_view2.xml
new file mode 100644
index 0000000..9ad140b
--- /dev/null
+++ b/res/layout/color_section_view2.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<com.android.customization.picker.color.ColorSectionView2
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/section_bottom_padding"
+ android:paddingHorizontal="@dimen/section_horizontal_padding"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/color_section_option_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:background="@drawable/color_section_background"
+ android:paddingVertical="24dp"
+ android:paddingHorizontal="24dp"
+ android:weightSum="@integer/color_section_num_columns">
+
+ <!--
+ This is just an invisible placeholder put in place so that the parent keeps its height
+ stable as the RecyclerView updates from 0 items to N items. Keeping it stable allows the
+ layout logic to keep the size of the preview container stable as well, which bodes well
+ for setting up the SurfaceView for remote rendering without changing its size after the
+ content is loaded into the RecyclerView.
+
+ It's critical for any TextViews inside the included layout to have text.
+ -->
+ <include
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ layout="@layout/color_option_section_overflow"
+ android:visibility="invisible"
+ android:layout_weight="1"/>
+ </LinearLayout>
+</com.android.customization.picker.color.ColorSectionView2>
diff --git a/res/layout/fragment_clock_custom_picker_demo.xml b/res/layout/fragment_clock_custom_picker_demo.xml
new file mode 100644
index 0000000..c05c4a8
--- /dev/null
+++ b/res/layout/fragment_clock_custom_picker_demo.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <FrameLayout
+ android:id="@+id/section_header_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toTopOf="@+id/clock_preview_card_list_demo"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+ <include layout="@layout/section_header" />
+ </FrameLayout>
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/clock_preview_card_list_demo"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/section_header_container"
+ android:clipToPadding="false" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/layout/fragment_color_picker.xml b/res/layout/fragment_color_picker.xml
new file mode 100644
index 0000000..d91ac5e
--- /dev/null
+++ b/res/layout/fragment_color_picker.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ ~
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/section_header_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/section_header" />
+
+ </FrameLayout>
+
+ <com.android.wallpaper.picker.DisplayAspectRatioFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:paddingTop="36dp"
+ android:paddingBottom="40dp">
+
+ <include
+ android:id="@+id/preview"
+ layout="@layout/wallpaper_preview_card"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"/>
+
+ </com.android.wallpaper.picker.DisplayAspectRatioFrameLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginBottom="28dp"
+ android:background="@drawable/picker_fragment_background"
+ android:paddingTop="22dp"
+ android:paddingBottom="62dp">
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/color_type_tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:paddingHorizontal="16dp" />
+
+ <!--
+ This is just an invisible placeholder put in place so that the parent keeps its height
+ stable as the RecyclerView updates from 0 items to N items. Keeping it stable allows the
+ layout logic to keep the size of the preview container stable as well, which bodes well
+ for setting up the SurfaceView for remote rendering without changing its size after the
+ content is loaded into the RecyclerView.
+
+ It's critical for any TextViews inside the included layout to have text.
+ -->
+ <include
+ layout="@layout/picker_fragment_tab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="invisible" />
+
+ </FrameLayout>
+
+ <View
+ android:layout_width="0dp"
+ android:layout_height="22dp" />
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <androidx.viewpager2.widget.ViewPager2
+ android:id="@+id/color_section_view_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/color_options_container_top_margin"
+ android:clipChildren="false"
+ android:clipToPadding="false"/>
+
+ <!--
+ This is just an invisible placeholder put in place so that the parent keeps its height
+ stable as the RecyclerView updates from 0 items to N items. Keeping it stable allows the
+ layout logic to keep the size of the preview container stable as well, which bodes well
+ for setting up the SurfaceView for remote rendering without changing its size after the
+ content is loaded into the RecyclerView.
+
+ It's critical for any TextViews inside the included layout to have text.
+ -->
+ <include
+ layout="@layout/color_option"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="invisible" />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/fragment_lock_screen_quick_affordances.xml b/res/layout/fragment_lock_screen_quick_affordances.xml
index 331d501..3fc7275 100644
--- a/res/layout/fragment_lock_screen_quick_affordances.xml
+++ b/res/layout/fragment_lock_screen_quick_affordances.xml
@@ -51,7 +51,7 @@
android:orientation="vertical"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="28dp"
- android:background="@drawable/keyguard_quick_affordance_picker_background"
+ android:background="@drawable/picker_fragment_background"
android:paddingTop="22dp"
android:paddingBottom="62dp">
@@ -77,7 +77,7 @@
It's critical for any TextViews inside the included layout to have text.
-->
<include
- layout="@layout/keyguard_quick_affordance_slot_tab"
+ layout="@layout/picker_fragment_tab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible" />
diff --git a/res/layout/keyguard_quick_affordance_enablement_dialog.xml b/res/layout/keyguard_quick_affordance_enablement_dialog.xml
index d6ba105..f3d4d8c 100644
--- a/res/layout/keyguard_quick_affordance_enablement_dialog.xml
+++ b/res/layout/keyguard_quick_affordance_enablement_dialog.xml
@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/keyguard_quick_affordance_picker_background"
+ android:background="@drawable/picker_fragment_background"
android:orientation="vertical"
android:padding="24dp">
diff --git a/res/layout/keyguard_quick_affordance_slot_tab.xml b/res/layout/picker_fragment_tab.xml
similarity index 87%
rename from res/layout/keyguard_quick_affordance_slot_tab.xml
rename to res/layout/picker_fragment_tab.xml
index c4934d8..cd04758 100644
--- a/res/layout/keyguard_quick_affordance_slot_tab.xml
+++ b/res/layout/picker_fragment_tab.xml
@@ -21,12 +21,12 @@
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/keyguard_quick_affordance_slot_tab_text_color"
+ android:textColor="@color/picker_fragment_tab_text_color"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:minWidth="48dp"
android:minHeight="48dp"
android:gravity="center"
- android:background="@drawable/keyguard_quick_affordance_slot_tab_background"
+ android:background="@drawable/picker_fragment_tab_background"
android:text="Placeholder for stable size calculation, please do not remove."
tools:ignore="HardcodedText" />
diff --git a/res/values-w800dp/integers.xml b/res/values-w800dp/integers.xml
index 23fa48a..cef5c90 100644
--- a/res/values-w800dp/integers.xml
+++ b/res/values-w800dp/integers.xml
@@ -16,4 +16,5 @@
-->
<resources>
<integer name="options_grid_num_columns">6</integer>
+ <integer name="color_section_num_columns">5</integer>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 64d9941..9099c93 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -133,6 +133,11 @@
<!-- For the color option section -->
<dimen name="color_options_container_top_margin">24dp</dimen>
+ <dimen name="component_color_overflow_small_radius_default">20dp</dimen>
+ <dimen name="component_color_overflow_small_diameter_default">40dp</dimen>
+ <dimen name="component_color_selected_small_radius_default">22dp</dimen>
+ <dimen name="component_color_selected_small_diameter_default">44dp</dimen>
+
<!-- For the color page. -->
<dimen name="color_page_indicator_margin_top">16dp</dimen>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index 6e2db17..2129ccd 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -16,4 +16,5 @@
-->
<resources>
<integer name="options_grid_num_columns">4</integer>
+ <integer name="color_section_num_columns">6</integer>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 13fc8a0..125bda1 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -33,6 +33,9 @@
<!-- The content description of clock entry. [CHAR LIMIT=NONE] -->
<string name="clock_picker_entry_content_description">Change a custom clock</string>
+ <!-- Title of a section of the customization picker where the user can configure Clock face. [CHAR LIMIT=15] -->
+ <string name="clock_settings_title">Clock Settings</string>
+
<!-- Title of a section of the customization picker where the user can select a Grid size for
the home screen. [CHAR LIMIT=15] -->
<string name="grid_title">App grid</string>
diff --git a/src/com/android/customization/model/color/ColorSectionController2.java b/src/com/android/customization/model/color/ColorSectionController2.java
new file mode 100644
index 0000000..257bb94
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorSectionController2.java
@@ -0,0 +1,241 @@
+/*
+ * 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.customization.model.color;
+
+import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_HOME;
+import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_LOCK;
+import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET;
+
+import android.app.Activity;
+import android.app.WallpaperColors;
+import android.content.Context;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.stats.style.StyleEnums;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.module.CustomizationInjector;
+import com.android.customization.module.ThemesUserEventLogger;
+import com.android.customization.picker.color.ColorPickerFragment;
+import com.android.customization.picker.color.ColorSectionView2;
+import com.android.wallpaper.R;
+import com.android.wallpaper.model.CustomizationSectionController;
+import com.android.wallpaper.model.WallpaperColorsViewModel;
+import com.android.wallpaper.module.InjectorProvider;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Color section view's controller for the logic of color customization.
+ */
+public class ColorSectionController2 implements CustomizationSectionController<ColorSectionView2> {
+
+ private static final String TAG = "ColorSectionController";
+ private static final long MIN_COLOR_APPLY_PERIOD = 500L;
+
+ private final ThemesUserEventLogger mEventLogger;
+ private final ColorCustomizationManager mColorManager;
+ private final WallpaperColorsViewModel mWallpaperColorsViewModel;
+ private final LifecycleOwner mLifecycleOwner;
+ private final CustomizationSectionNavigationController mSectionNavigationController;
+
+ private List<ColorOption> mWallpaperColorOptions = new ArrayList<>();
+ private List<ColorOption> mPresetColorOptions = new ArrayList<>();
+ private ColorOption mSelectedColor;
+ @Nullable private WallpaperColors mHomeWallpaperColors;
+ @Nullable private WallpaperColors mLockWallpaperColors;
+ // Uses a boolean value to indicate whether wallpaper color is ready because WallpaperColors
+ // maybe be null when it's ready.
+ private boolean mHomeWallpaperColorsReady;
+ private boolean mLockWallpaperColorsReady;
+ private long mLastColorApplyingTime = 0L;
+ private ColorSectionView2 mColorSectionView;
+
+ public ColorSectionController2(Activity activity, WallpaperColorsViewModel viewModel,
+ LifecycleOwner lifecycleOwner,
+ CustomizationSectionNavigationController sectionNavigationController) {
+ CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
+ mEventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(activity);
+ mColorManager = ColorCustomizationManager.getInstance(activity,
+ new OverlayManagerCompat(activity));
+ mWallpaperColorsViewModel = viewModel;
+ mLifecycleOwner = lifecycleOwner;
+ mSectionNavigationController = sectionNavigationController;
+ }
+
+ @Override
+ public boolean isAvailable(@Nullable Context context) {
+ return context != null && ColorUtils.isMonetEnabled(context) && mColorManager.isAvailable();
+ }
+
+ @Override
+ public ColorSectionView2 createView(Context context) {
+ mColorSectionView = (ColorSectionView2) LayoutInflater.from(context).inflate(
+ R.layout.color_section_view2, /* root= */ null);
+
+ mWallpaperColorsViewModel.getHomeWallpaperColors().observe(mLifecycleOwner,
+ homeColors -> {
+ mHomeWallpaperColors = homeColors;
+ mHomeWallpaperColorsReady = true;
+ maybeLoadColors();
+ });
+ mWallpaperColorsViewModel.getLockWallpaperColors().observe(mLifecycleOwner,
+ lockColors -> {
+ mLockWallpaperColors = lockColors;
+ mLockWallpaperColorsReady = true;
+ maybeLoadColors();
+ });
+ return mColorSectionView;
+ }
+
+ private void maybeLoadColors() {
+ if (mHomeWallpaperColorsReady && mLockWallpaperColorsReady) {
+ mColorManager.setWallpaperColors(mHomeWallpaperColors, mLockWallpaperColors);
+ loadColorOptions(/* reload= */ false);
+ }
+ }
+
+ private void loadColorOptions(boolean reload) {
+ mColorManager.fetchOptions(new CustomizationManager.OptionsFetchedListener<ColorOption>() {
+ @Override
+ public void onOptionsLoaded(List<ColorOption> options) {
+ List<ColorOption> wallpaperColorOptions = new ArrayList<>();
+ List<ColorOption> presetColorOptions = new ArrayList<>();
+ for (ColorOption option : options) {
+ if (option instanceof ColorSeedOption) {
+ wallpaperColorOptions.add(option);
+ } else if (option instanceof ColorBundle) {
+ presetColorOptions.add(option);
+ }
+ }
+ mWallpaperColorOptions = wallpaperColorOptions;
+ mPresetColorOptions = presetColorOptions;
+ mSelectedColor = findActiveColorOption(mWallpaperColorOptions,
+ mPresetColorOptions);
+
+ mColorSectionView.post(() -> setUpColorSectionView(mWallpaperColorOptions,
+ mPresetColorOptions));
+ }
+
+ @Override
+ public void onError(@Nullable Throwable throwable) {
+ if (throwable != null) {
+ Log.e(TAG, "Error loading theme bundles", throwable);
+ }
+ }
+ }, reload);
+ }
+
+ private void setUpColorSectionView(List<ColorOption> wallpaperColorOptions,
+ List<ColorOption> presetColorOptions) {
+ int wallpaperOptionSize = wallpaperColorOptions.size();
+
+ List<ColorOption> subOptions = wallpaperColorOptions.subList(0,
+ Math.min(5, wallpaperOptionSize));
+ // add additional options based on preset colors if there are less than 5 wallpaper colors
+ List<ColorOption> additionalSubOptions = presetColorOptions.subList(0,
+ Math.min(Math.max(0, 5 - wallpaperOptionSize), presetColorOptions.size()));
+ subOptions.addAll(additionalSubOptions);
+
+ mColorSectionView.setOverflowOnClick(() -> {
+ mSectionNavigationController.navigateTo(new ColorPickerFragment());
+ return null;
+ });
+ mColorSectionView.setColorOptionOnClick(selectedOption -> {
+ if (mSelectedColor.equals(selectedOption)) {
+ return null;
+ }
+ mSelectedColor = (ColorOption) selectedOption;
+ // Post with delay for color option to run ripple.
+ new Handler().postDelayed(()-> applyColor(mSelectedColor), /* delayMillis= */ 100);
+ return null;
+ });
+ mColorSectionView.setItems(subOptions, mColorManager);
+ }
+
+ private ColorOption findActiveColorOption(List<ColorOption> wallpaperColorOptions,
+ List<ColorOption> presetColorOptions) {
+ ColorOption activeColorOption = null;
+ for (ColorOption colorOption : Lists.newArrayList(
+ Iterables.concat(wallpaperColorOptions, presetColorOptions))) {
+ if (colorOption.isActive(mColorManager)) {
+ activeColorOption = colorOption;
+ break;
+ }
+ }
+ // Use the first one option by default. This should not happen as above should have an
+ // active option found.
+ if (activeColorOption == null) {
+ activeColorOption = wallpaperColorOptions.isEmpty()
+ ? presetColorOptions.get(0)
+ : wallpaperColorOptions.get(0);
+ }
+ return activeColorOption;
+ }
+
+ private void applyColor(ColorOption colorOption) {
+ if (SystemClock.elapsedRealtime() - mLastColorApplyingTime < MIN_COLOR_APPLY_PERIOD) {
+ return;
+ }
+ mLastColorApplyingTime = SystemClock.elapsedRealtime();
+ mColorManager.apply(colorOption, new CustomizationManager.Callback() {
+ @Override
+ public void onSuccess() {
+ mColorSectionView.announceForAccessibility(
+ mColorSectionView.getContext().getString(R.string.color_changed));
+ mEventLogger.logColorApplied(getColorAction(colorOption), colorOption);
+ }
+
+ @Override
+ public void onError(@Nullable Throwable throwable) {
+ Log.w(TAG, "Apply theme with error: " + throwable);
+ }
+ });
+ }
+
+ private int getColorAction(ColorOption colorOption) {
+ int action = StyleEnums.DEFAULT_ACTION;
+ boolean isForBoth = mLockWallpaperColors == null || mLockWallpaperColors.equals(
+ mHomeWallpaperColors);
+
+ if (TextUtils.equals(colorOption.getSource(), COLOR_SOURCE_PRESET)) {
+ action = StyleEnums.COLOR_PRESET_APPLIED;
+ } else if (isForBoth) {
+ action = StyleEnums.COLOR_WALLPAPER_HOME_LOCK_APPLIED;
+ } else {
+ switch (colorOption.getSource()) {
+ case COLOR_SOURCE_HOME:
+ action = StyleEnums.COLOR_WALLPAPER_HOME_APPLIED;
+ break;
+ case COLOR_SOURCE_LOCK:
+ action = StyleEnums.COLOR_WALLPAPER_LOCK_APPLIED;
+ break;
+ }
+ }
+ return action;
+ }
+}
diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt
index 3cf8393..2772e56 100644
--- a/src/com/android/customization/module/CustomizationInjector.kt
+++ b/src/com/android/customization/module/CustomizationInjector.kt
@@ -21,6 +21,8 @@
import com.android.customization.model.theme.ThemeBundleProvider
import com.android.customization.model.theme.ThemeManager
import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.shared.clocks.ClockRegistry
import com.android.wallpaper.module.Injector
interface CustomizationInjector : Injector {
@@ -36,4 +38,8 @@
fun getKeyguardQuickAffordancePickerInteractor(
context: Context
): KeyguardQuickAffordancePickerInteractor
+
+ fun getClockRegistry(context: Context): ClockRegistry
+
+ fun getPluginManager(context: Context): PluginManager
}
diff --git a/src/com/android/customization/module/DefaultCustomizationSections.java b/src/com/android/customization/module/DefaultCustomizationSections.java
index 2fb530a..c5affd1 100644
--- a/src/com/android/customization/module/DefaultCustomizationSections.java
+++ b/src/com/android/customization/module/DefaultCustomizationSections.java
@@ -8,6 +8,7 @@
import androidx.lifecycle.ViewModelProvider;
import com.android.customization.model.color.ColorSectionController;
+import com.android.customization.model.color.ColorSectionController2;
import com.android.customization.model.grid.GridOptionsManager;
import com.android.customization.model.grid.GridSectionController;
import com.android.customization.model.mode.DarkModeSectionController;
@@ -50,7 +51,7 @@
}
@Override
- public List<CustomizationSectionController<?>> getSectionControllersForScreen(
+ public List<CustomizationSectionController<?>> getRevampedUISectionControllersForScreen(
Screen screen,
FragmentActivity activity,
LifecycleOwner lifecycleOwner,
@@ -76,8 +77,8 @@
displayUtils));
// Theme color section.
- sectionControllers.add(new ColorSectionController(
- activity, wallpaperColorsViewModel, lifecycleOwner, savedInstanceState));
+ sectionControllers.add(new ColorSectionController2(
+ activity, wallpaperColorsViewModel, lifecycleOwner, sectionNavigationController));
// Wallpaper quick switch section.
sectionControllers.add(
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index 2d932b8..ed4e935 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -16,10 +16,15 @@
package com.android.customization.module
import android.app.Activity
+import android.app.NotificationManager
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
+import android.os.Handler
+import android.os.UserHandle
+import android.view.LayoutInflater
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.android.customization.model.theme.OverlayManagerCompat
@@ -29,8 +34,18 @@
import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordanceSnapshotRestorer
import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
+import com.android.systemui.plugins.Plugin
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.shared.clocks.DefaultClockProvider
import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl
+import com.android.systemui.shared.plugins.PluginActionManager
+import com.android.systemui.shared.plugins.PluginEnabler
+import com.android.systemui.shared.plugins.PluginInstance
+import com.android.systemui.shared.plugins.PluginManagerImpl
+import com.android.systemui.shared.plugins.PluginPrefs
+import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager_Factory
import com.android.wallpaper.model.LiveWallpaperInfo
import com.android.wallpaper.model.WallpaperInfo
import com.android.wallpaper.module.CustomizationSections
@@ -43,6 +58,7 @@
import com.android.wallpaper.picker.LivePreviewFragment
import com.android.wallpaper.picker.PreviewFragment
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
+import java.util.concurrent.Executors
import kotlinx.coroutines.Dispatchers.IO
open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInjector {
@@ -58,6 +74,8 @@
private var fragmentFactory: FragmentFactory? = null
private var keyguardQuickAffordanceSnapshotRestorer: KeyguardQuickAffordanceSnapshotRestorer? =
null
+ private var clockRegistry: ClockRegistry? = null
+ private var pluginManager: PluginManager? = null
override fun getCustomizationSections(activity: Activity): CustomizationSections {
return customizationSections
@@ -193,6 +211,80 @@
.also { keyguardQuickAffordanceSnapshotRestorer = it }
}
+ override fun getClockRegistry(context: Context): ClockRegistry {
+ return clockRegistry
+ ?: ClockRegistry(
+ context,
+ getPluginManager(context),
+ Handler.getMain(),
+ isEnabled = true,
+ userHandle = UserHandle.USER_SYSTEM,
+ DefaultClockProvider(context, LayoutInflater.from(context), context.resources)
+ )
+ .also { clockRegistry = it }
+ }
+
+ override fun getPluginManager(context: Context): PluginManager {
+ return pluginManager ?: createPluginManager(context).also { pluginManager = it }
+ }
+
+ private fun createPluginManager(context: Context): PluginManager {
+ val privilegedPlugins = listOf<String>()
+ val isDebugDevice = true
+
+ val instanceFactory =
+ PluginInstance.Factory(
+ this::class.java.classLoader,
+ PluginInstance.InstanceFactory<Plugin>(),
+ PluginInstance.VersionChecker(),
+ privilegedPlugins,
+ isDebugDevice,
+ )
+
+ /*
+ * let SystemUI handle plugin, in this class assume plugins are enabled
+ */
+ val pluginEnabler =
+ object : PluginEnabler {
+ override fun setEnabled(component: ComponentName) = Unit
+
+ override fun setDisabled(
+ component: ComponentName,
+ @PluginEnabler.DisableReason reason: Int
+ ) = Unit
+
+ override fun isEnabled(component: ComponentName): Boolean {
+ return true
+ }
+
+ @PluginEnabler.DisableReason
+ override fun getDisableReason(componentName: ComponentName): Int {
+ return PluginEnabler.ENABLED
+ }
+ }
+
+ val pluginActionManager =
+ PluginActionManager.Factory(
+ context,
+ context.packageManager,
+ context.mainExecutor,
+ Executors.newSingleThreadExecutor(),
+ context.getSystemService(NotificationManager::class.java),
+ pluginEnabler,
+ privilegedPlugins,
+ instanceFactory,
+ )
+ return PluginManagerImpl(
+ context,
+ pluginActionManager,
+ isDebugDevice,
+ UncaughtExceptionPreHandlerManager_Factory.create().get(),
+ pluginEnabler,
+ PluginPrefs(context),
+ listOf(),
+ )
+ }
+
companion object {
@JvmStatic
private val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER =
diff --git a/src/com/android/customization/picker/clock/ClockCustomDemoFragment.kt b/src/com/android/customization/picker/clock/ClockCustomDemoFragment.kt
index 8648dca..4f65080 100644
--- a/src/com/android/customization/picker/clock/ClockCustomDemoFragment.kt
+++ b/src/com/android/customization/picker/clock/ClockCustomDemoFragment.kt
@@ -1,11 +1,8 @@
package com.android.customization.picker.clock
-import android.app.NotificationManager
-import android.content.ComponentName
import android.content.Context
import android.os.Bundle
-import android.os.Handler
-import android.os.UserHandle
+import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
@@ -14,36 +11,23 @@
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import android.widget.TextView
+import android.widget.Toast
import androidx.core.view.setPadding
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.module.ThemePickerInjector
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProviderPlugin
-import com.android.systemui.plugins.Plugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.systemui.shared.clocks.DefaultClockProvider
-import com.android.systemui.shared.plugins.PluginActionManager
-import com.android.systemui.shared.plugins.PluginEnabler
-import com.android.systemui.shared.plugins.PluginEnabler.ENABLED
-import com.android.systemui.shared.plugins.PluginInstance
-import com.android.systemui.shared.plugins.PluginManagerImpl
-import com.android.systemui.shared.plugins.PluginPrefs
-import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager_Factory
import com.android.wallpaper.R
+import com.android.wallpaper.module.InjectorProvider
import com.android.wallpaper.picker.AppbarFragment
-import java.util.concurrent.Executors
-
-private val TAG = ClockCustomDemoFragment::class.simpleName
class ClockCustomDemoFragment : AppbarFragment() {
@VisibleForTesting lateinit var clockRegistry: ClockRegistry
- val isDebugDevice = true
- val privilegedPlugins = listOf<String>()
- val action = ClockProviderPlugin.ACTION
- lateinit var view: ViewGroup
@VisibleForTesting lateinit var recyclerView: RecyclerView
lateinit var pluginManager: PluginManager
@VisibleForTesting
@@ -51,24 +35,19 @@
object : PluginListener<ClockProviderPlugin> {
override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) {
val listInUse = clockRegistry.getClocks().filter { "NOT_IN_USE" !in it.clockId }
- recyclerView.adapter = ClockRecyclerAdapter(listInUse, context, clockRegistry)
+ recyclerView.adapter =
+ ClockRecyclerAdapter(listInUse, context) {
+ clockRegistry.currentClockId = it.clockId
+ Toast.makeText(context, "${it.name} selected", Toast.LENGTH_SHORT).show()
+ }
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
- val defaultClockProvider =
- DefaultClockProvider(context, LayoutInflater.from(context), context.resources)
- pluginManager = createPluginManager(context)
- clockRegistry =
- ClockRegistry(
- context,
- pluginManager,
- Handler.getMain(),
- isEnabled = true,
- userHandle = UserHandle.USER_OWNER,
- defaultClockProvider
- )
+ val injector = InjectorProvider.getInjector() as ThemePickerInjector
+ pluginManager = injector.getPluginManager(context)
+ clockRegistry = injector.getClockRegistry(context)
pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java, true)
}
@@ -92,69 +71,10 @@
return getString(R.string.clock_title)
}
- private fun createPluginManager(context: Context): PluginManager {
- val instanceFactory =
- PluginInstance.Factory(
- this::class.java.classLoader,
- PluginInstance.InstanceFactory<Plugin>(),
- PluginInstance.VersionChecker(),
- privilegedPlugins,
- isDebugDevice
- )
-
- /*
- * let SystemUI handle plugin, in this class assume plugins are enabled
- */
- val pluginEnabler =
- object : PluginEnabler {
- override fun setEnabled(component: ComponentName) {}
-
- override fun setDisabled(
- component: ComponentName,
- @PluginEnabler.DisableReason reason: Int
- ) {}
-
- override fun isEnabled(component: ComponentName): Boolean {
- return true
- }
-
- @PluginEnabler.DisableReason
- override fun getDisableReason(componentName: ComponentName): Int {
- return ENABLED
- }
- }
-
- val pluginActionManager =
- PluginActionManager.Factory(
- context,
- context.packageManager,
- context.mainExecutor,
- Executors.newSingleThreadExecutor(),
- context.getSystemService(NotificationManager::class.java),
- pluginEnabler,
- privilegedPlugins,
- instanceFactory
- )
- return PluginManagerImpl(
- context,
- pluginActionManager,
- isDebugDevice,
- uncaughtExceptionPreHandlerManager,
- pluginEnabler,
- PluginPrefs(context),
- listOf()
- )
- }
-
- companion object {
- private val uncaughtExceptionPreHandlerManager =
- UncaughtExceptionPreHandlerManager_Factory.create().get()
- }
-
internal class ClockRecyclerAdapter(
val list: List<ClockMetadata>,
val context: Context,
- val clockRegistry: ClockRegistry
+ val onClockSelected: (ClockMetadata) -> Unit
) : RecyclerView.Adapter<ClockRecyclerAdapter.ViewHolder>() {
class ViewHolder(val view: View, val textView: TextView, val onItemClicked: (Int) -> Unit) :
RecyclerView.ViewHolder(view) {
@@ -169,13 +89,12 @@
TextView(ContextThemeWrapper(viewGroup.context, R.style.SectionTitleTextStyle))
textView.setPadding(ITEM_PADDING)
rootView.addView(textView)
+ val outValue = TypedValue()
+ context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
+ rootView.setBackgroundResource(outValue.resourceId)
val lp = RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
- rootView.setLayoutParams(lp)
- return ViewHolder(
- rootView,
- textView,
- { clockRegistry.currentClockId = list[it].clockId }
- )
+ rootView.layoutParams = lp
+ return ViewHolder(rootView, textView) { onClockSelected(list[it]) }
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
new file mode 100644
index 0000000..52c2430
--- /dev/null
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.customization.picker.clock.data.repository
+
+import android.util.Log
+import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.shared.clocks.ClockRegistry
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+
+/**
+ * Repository for accessing application clock settings, as well as selecting and configuring custom
+ * clocks.
+ */
+class ClockPickerRepository(registry: ClockRegistry) {
+
+ /** The currently-selected clock. */
+ val selectedClock: Flow<ClockMetadataModel?> = callbackFlow {
+ fun send() {
+ val model =
+ registry
+ .getClocks()
+ .find { clockMetadata -> clockMetadata.clockId == registry.currentClockId }
+ ?.toModel()
+ if (model == null) {
+ Log.e(TAG, "Currently selected clock ID is not one of the available clocks.")
+ }
+ trySend(model)
+ }
+
+ val listener = ClockRegistry.ClockChangeListener { send() }
+ registry.registerClockChangeListener(listener)
+ send()
+ awaitClose { registry.unregisterClockChangeListener(listener) }
+ }
+
+ private fun ClockMetadata.toModel(): ClockMetadataModel {
+ return ClockMetadataModel(clockId = clockId, name = name)
+ }
+
+ companion object {
+ private const val TAG = "ClockPickerRepository"
+ }
+}
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
new file mode 100644
index 0000000..63b3638
--- /dev/null
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.customization.picker.clock.domain.interactor
+
+import com.android.customization.picker.clock.data.repository.ClockPickerRepository
+import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interactor for accessing application clock settings, as well as selecting and configuring custom
+ * clocks.
+ */
+class ClockPickerInteractor(private val repository: ClockPickerRepository) {
+
+ val selectedClock: Flow<ClockMetadataModel?> = repository.selectedClock
+}
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt b/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt
index 63b4a9b..904d2f9 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
diff --git a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
new file mode 100644
index 0000000..ea84d2b
--- /dev/null
+++ b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.customization.picker.clock.shared.model
+
+/** Model for clock metadata. */
+data class ClockMetadataModel(
+ val clockId: String,
+ val name: String,
+)
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt
new file mode 100644
index 0000000..5a3286d
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.customization.picker.clock.ui.binder
+
+import android.view.View
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
+import com.android.wallpaper.R
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+object ClockSectionViewBinder {
+ fun bind(
+ view: View,
+ viewModel: ClockSectionViewModel,
+ lifecycleOwner: LifecycleOwner,
+ onClicked: () -> Unit,
+ ) {
+ view.setOnClickListener { onClicked() }
+
+ val selectedClockTextView: TextView = view.requireViewById(R.id.selected_clock_text)
+
+ lifecycleOwner.lifecycleScope.launch {
+ viewModel.selectedClockName
+ .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
+ .collectLatest { selectedClockName ->
+ selectedClockTextView.text = selectedClockName
+ }
+ }
+ }
+}
diff --git a/src/com/android/customization/model/clock/ClockSectionController.kt b/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt
similarity index 78%
rename from src/com/android/customization/model/clock/ClockSectionController.kt
rename to src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt
index 1e339bb..848f226 100644
--- a/src/com/android/customization/model/clock/ClockSectionController.kt
+++ b/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt
@@ -13,12 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.customization.model.clock
+package com.android.customization.picker.clock.ui.section
import android.content.Context
import android.view.LayoutInflater
+import androidx.lifecycle.LifecycleOwner
import com.android.customization.picker.clock.ClockCustomDemoFragment
import com.android.customization.picker.clock.ClockSectionView
+import com.android.customization.picker.clock.ui.binder.ClockSectionViewBinder
+import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.wallpaper.R
@@ -30,6 +33,8 @@
class ClockSectionController(
private val navigationController: CustomizationSectionNavigationController,
private val customizationProviderClient: CustomizationProviderClient,
+ private val viewModel: ClockSectionViewModel,
+ private val lifecycleOwner: LifecycleOwner,
) : CustomizationSectionController<ClockSectionView?> {
override fun isAvailable(context: Context?): Boolean {
return runBlocking { customizationProviderClient.queryFlags() }
@@ -44,7 +49,11 @@
R.layout.clock_section_view,
null,
) as ClockSectionView
- view.setOnClickListener { navigationController.navigateTo(ClockCustomDemoFragment()) }
+ ClockSectionViewBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ lifecycleOwner = lifecycleOwner
+ ) { navigationController.navigateTo(ClockCustomDemoFragment()) }
return view
}
}
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt
new file mode 100644
index 0000000..f50bb9c
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.customization.picker.clock.ui.viewmodel
+
+import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View model for the clock section view on the lockscreen customization surface. */
+class ClockSectionViewModel(interactor: ClockPickerInteractor) {
+
+ val selectedClockName: Flow<String?> = interactor.selectedClock.map { it?.name }
+}
diff --git a/src/com/android/customization/picker/color/ColorPickerFragment.kt b/src/com/android/customization/picker/color/ColorPickerFragment.kt
new file mode 100644
index 0000000..c8ecb7f
--- /dev/null
+++ b/src/com/android/customization/picker/color/ColorPickerFragment.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.customization.picker.color
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.AppbarFragment
+
+// TODO (b/262924623): Color Picker Fragment
+class ColorPickerFragment : AppbarFragment() {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val view =
+ inflater.inflate(
+ R.layout.fragment_color_picker,
+ container,
+ false,
+ )
+ setUpToolbar(view)
+ return view
+ }
+}
diff --git a/src/com/android/customization/picker/color/ColorSectionView2.kt b/src/com/android/customization/picker/color/ColorSectionView2.kt
new file mode 100644
index 0000000..3e4e4bc
--- /dev/null
+++ b/src/com/android/customization/picker/color/ColorSectionView2.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.customization.picker.color
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.ImageView
+import android.widget.LinearLayout
+import com.android.customization.model.color.ColorCustomizationManager
+import com.android.customization.model.color.ColorOption
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.SectionView
+
+/**
+ * The class inherits from {@link SectionView} as the view representing the color section of the
+ * customization picker. It displays a list of color options and an overflow option.
+ */
+class ColorSectionView2(context: Context, attrs: AttributeSet?) : SectionView(context, attrs) {
+
+ private val items = mutableListOf<ColorOption>()
+ private var onClick: ((ColorOption) -> Unit)? = null
+ private var overflowOnClick: (() -> Unit)? = null
+
+ // TODO (b/262924623): make adjustments for large screen
+ fun setItems(items: List<ColorOption>, manager: ColorCustomizationManager) {
+ this.items.clear()
+ this.items.addAll(items)
+ val optionContainer = findViewById<LinearLayout>(R.id.color_section_option_container)
+ optionContainer.removeAllViews()
+ // Last color option is either the last index of the items list, or the second last index
+ // of column count. Save the last column of the option container for the overflow option
+ val lastOptionIndex = minOf(optionContainer.weightSum.toInt() - 2, items.size - 1)
+ if (items.isNotEmpty()) {
+ for (position in 0..lastOptionIndex) {
+ val item = items[position]
+ val itemView =
+ LayoutInflater.from(context)
+ .inflate(R.layout.color_option_section, optionContainer, false)
+ item.bindThumbnailTile(itemView.findViewById(R.id.option_tile))
+ if (item.isActive(manager)) {
+ val optionSelectedView = itemView.findViewById<ImageView>(R.id.option_selected)
+ optionSelectedView.visibility = VISIBLE
+ }
+ itemView.setOnClickListener { onClick?.invoke(item) }
+ optionContainer.addView(itemView)
+ }
+ }
+ // add overflow option
+ val itemView =
+ LayoutInflater.from(context)
+ .inflate(R.layout.color_option_section_overflow, optionContainer, false)
+ itemView.setOnClickListener { overflowOnClick?.invoke() }
+ optionContainer.addView(itemView)
+ }
+
+ /** Sets the on click callback for a color option. */
+ fun setColorOptionOnClick(onClick: (ColorOption) -> Unit) {
+ this.onClick = onClick
+ if (items.isNotEmpty()) {
+ val optionContainer = findViewById<LinearLayout>(R.id.color_section_option_container)
+ val lastOptionIndex = minOf(optionContainer.childCount - 2, items.size - 1)
+ for (position in 0..lastOptionIndex) {
+ val item = items[position]
+ val itemView = optionContainer.getChildAt(position)
+ itemView.setOnClickListener { onClick.invoke(item) }
+ }
+ }
+ }
+
+ /** Sets the on click callback for the overflow option. */
+ fun setOverflowOnClick(onClick: () -> Unit) {
+ this.overflowOnClick = onClick
+ val optionContainer = findViewById<LinearLayout>(R.id.color_section_option_container)
+ if (optionContainer.childCount > 0) {
+ val itemView = optionContainer.getChildAt(optionContainer.childCount - 1)
+ itemView.setOnClickListener { onClick.invoke() }
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt b/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt
index acafef4..5203ed3 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt
@@ -44,7 +44,7 @@
return ViewHolder(
LayoutInflater.from(parent.context)
.inflate(
- R.layout.keyguard_quick_affordance_slot_tab,
+ R.layout.picker_fragment_tab,
parent,
false,
)
diff --git a/src/com/android/customization/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java
index 8805caf..fc665b9 100644
--- a/src/com/android/customization/widget/OptionSelectorController.java
+++ b/src/com/android/customization/widget/OptionSelectorController.java
@@ -183,6 +183,16 @@
@Override
public TileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+ // Provide width constraint when a grid layout manager is not use and width is set
+ // to match parent
+ if (!mUseGrid
+ && v.getLayoutParams().width == RecyclerView.LayoutParams.MATCH_PARENT) {
+ Resources res = mContainer.getContext().getResources();
+ RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(
+ res.getDimensionPixelSize(R.dimen.option_tile_width),
+ RecyclerView.LayoutParams.WRAP_CONTENT);
+ v.setLayoutParams(layoutParams);
+ }
return new TileViewHolder(v);
}
diff --git a/tests/robotests/src/com/android/customization/model/clock/BaseClockManagerTest.java b/tests/robotests/src/com/android/customization/model/clock/BaseClockManagerTest.java
index c96e7f8..eeff531 100644
--- a/tests/robotests/src/com/android/customization/model/clock/BaseClockManagerTest.java
+++ b/tests/robotests/src/com/android/customization/model/clock/BaseClockManagerTest.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/tests/robotests/src/com/android/customization/model/grid/GridOptionsManagerTest.java b/tests/robotests/src/com/android/customization/model/grid/GridOptionsManagerTest.java
index 89ca676..04ac024 100644
--- a/tests/robotests/src/com/android/customization/model/grid/GridOptionsManagerTest.java
+++ b/tests/robotests/src/com/android/customization/model/grid/GridOptionsManagerTest.java
@@ -17,7 +17,7 @@
import static junit.framework.TestCase.fail;
-import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/tests/src/com/android/customization/testing/TestCustomizationInjector.java b/tests/src/com/android/customization/testing/TestCustomizationInjector.java
deleted file mode 100644
index d609335..0000000
--- a/tests/src/com/android/customization/testing/TestCustomizationInjector.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.android.customization.testing;
-
-import android.content.Context;
-
-import androidx.fragment.app.FragmentActivity;
-
-import com.android.customization.model.theme.OverlayManagerCompat;
-import com.android.customization.model.theme.ThemeBundleProvider;
-import com.android.customization.model.theme.ThemeManager;
-import com.android.customization.module.CustomizationInjector;
-import com.android.customization.module.CustomizationPreferences;
-import com.android.customization.module.ThemesUserEventLogger;
-import com.android.customization.picker.quickaffordance.data.repository.KeyguardQuickAffordancePickerRepository;
-import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor;
-import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordanceSnapshotRestorer;
-import com.android.systemui.shared.customization.data.content.CustomizationProviderClient;
-import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl;
-import com.android.wallpaper.config.BaseFlags;
-import com.android.wallpaper.module.DrawableLayerResolver;
-import com.android.wallpaper.module.PackageStatusNotifier;
-import com.android.wallpaper.module.UserEventLogger;
-import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer;
-import com.android.wallpaper.testing.TestInjector;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import kotlinx.coroutines.Dispatchers;
-
-/**
- * Test implementation of the dependency injector.
- */
-public class TestCustomizationInjector extends TestInjector implements CustomizationInjector {
- private CustomizationPreferences mCustomizationPreferences;
- private ThemeManager mThemeManager;
- private PackageStatusNotifier mPackageStatusNotifier;
- private DrawableLayerResolver mDrawableLayerResolver;
- private UserEventLogger mUserEventLogger;
- private KeyguardQuickAffordancePickerInteractor mKeyguardQuickAffordancePickerInteractor;
- private BaseFlags mFlags;
- private CustomizationProviderClient mCustomizationProviderClient;
- private KeyguardQuickAffordanceSnapshotRestorer mKeyguardQuickAffordanceSnapshotRestorer;
-
- @Override
- public CustomizationPreferences getCustomizationPreferences(Context context) {
- if (mCustomizationPreferences == null) {
- mCustomizationPreferences = new TestDefaultCustomizationPreferences(context);
- }
- return mCustomizationPreferences;
- }
-
- @Override
- public ThemeManager getThemeManager(
- ThemeBundleProvider provider,
- FragmentActivity activity,
- OverlayManagerCompat overlayManagerCompat,
- ThemesUserEventLogger logger) {
- if (mThemeManager == null) {
- mThemeManager = new TestThemeManager(provider, activity, overlayManagerCompat, logger);
- }
- return mThemeManager;
- }
-
- @Override
- public PackageStatusNotifier getPackageStatusNotifier(Context context) {
- if (mPackageStatusNotifier == null) {
- mPackageStatusNotifier = new TestPackageStatusNotifier();
- }
- return mPackageStatusNotifier;
- }
-
- @Override
- public DrawableLayerResolver getDrawableLayerResolver() {
- if (mDrawableLayerResolver == null) {
- mDrawableLayerResolver = new TestDrawableLayerResolver();
- }
- return mDrawableLayerResolver;
- }
-
- @Override
- public UserEventLogger getUserEventLogger(Context unused) {
- if (mUserEventLogger == null) {
- mUserEventLogger = new TestThemesUserEventLogger();
- }
- return mUserEventLogger;
- }
-
- @Override
- public KeyguardQuickAffordancePickerInteractor getKeyguardQuickAffordancePickerInteractor(
- Context context) {
- if (mKeyguardQuickAffordancePickerInteractor == null) {
- final CustomizationProviderClient client =
- new CustomizationProviderClientImpl(context, Dispatchers.getIO());
- mKeyguardQuickAffordancePickerInteractor = new KeyguardQuickAffordancePickerInteractor(
- new KeyguardQuickAffordancePickerRepository(client, Dispatchers.getIO()),
- client,
- () -> getKeyguardQuickAffordanceSnapshotRestorer(context));
- }
- return mKeyguardQuickAffordancePickerInteractor;
- }
-
- @Override
- public BaseFlags getFlags() {
- if (mFlags == null) {
- mFlags = new BaseFlags() {};
- }
-
- return mFlags;
- }
-
- @Override
- public Map<Integer, SnapshotRestorer> getSnapshotRestorers(Context context) {
- final Map<Integer, SnapshotRestorer> restorers = new HashMap<>();
- restorers.put(
- KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER,
- getKeyguardQuickAffordanceSnapshotRestorer(context));
- return restorers;
- }
-
- /** Returns the {@link CustomizationProviderClient}. */
- private CustomizationProviderClient getKeyguardQuickAffordancePickerProviderClient(
- Context context) {
- if (mCustomizationProviderClient == null) {
- mCustomizationProviderClient =
- new CustomizationProviderClientImpl(context, Dispatchers.getIO());
- }
-
- return mCustomizationProviderClient;
- }
-
- private KeyguardQuickAffordanceSnapshotRestorer getKeyguardQuickAffordanceSnapshotRestorer(
- Context context) {
- if (mKeyguardQuickAffordanceSnapshotRestorer == null) {
- mKeyguardQuickAffordanceSnapshotRestorer = new KeyguardQuickAffordanceSnapshotRestorer(
- getKeyguardQuickAffordancePickerInteractor(context),
- getKeyguardQuickAffordancePickerProviderClient(context));
- }
-
- return mKeyguardQuickAffordanceSnapshotRestorer;
- }
-
- private static final int KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER = 1;
-}
diff --git a/tests/src/com/android/customization/testing/TestCustomizationInjector.kt b/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
new file mode 100644
index 0000000..afc7131
--- /dev/null
+++ b/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
@@ -0,0 +1,151 @@
+package com.android.customization.testing
+
+import android.content.Context
+import android.os.Handler
+import android.os.UserHandle
+import android.view.LayoutInflater
+import androidx.fragment.app.FragmentActivity
+import com.android.customization.model.theme.OverlayManagerCompat
+import com.android.customization.model.theme.ThemeBundleProvider
+import com.android.customization.model.theme.ThemeManager
+import com.android.customization.module.CustomizationInjector
+import com.android.customization.module.CustomizationPreferences
+import com.android.customization.module.ThemesUserEventLogger
+import com.android.customization.picker.quickaffordance.data.repository.KeyguardQuickAffordancePickerRepository
+import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
+import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordanceSnapshotRestorer
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.shared.clocks.DefaultClockProvider
+import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
+import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl
+import com.android.wallpaper.config.BaseFlags
+import com.android.wallpaper.module.DrawableLayerResolver
+import com.android.wallpaper.module.PackageStatusNotifier
+import com.android.wallpaper.module.UserEventLogger
+import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
+import com.android.wallpaper.testing.TestInjector
+import java.util.HashMap
+import kotlinx.coroutines.Dispatchers.IO
+
+/** Test implementation of the dependency injector. */
+class TestCustomizationInjector : TestInjector(), CustomizationInjector {
+ private var customizationPreferences: CustomizationPreferences? = null
+ private var themeManager: ThemeManager? = null
+ private var packageStatusNotifier: PackageStatusNotifier? = null
+ private var drawableLayerResolver: DrawableLayerResolver? = null
+ private var userEventLogger: UserEventLogger? = null
+ private var keyguardQuickAffordancePickerInteractor: KeyguardQuickAffordancePickerInteractor? =
+ null
+ private var flags: BaseFlags? = null
+ private var customizationProviderClient: CustomizationProviderClient? = null
+ private var keyguardQuickAffordanceSnapshotRestorer: KeyguardQuickAffordanceSnapshotRestorer? =
+ null
+ private var clockRegistry: ClockRegistry? = null
+ private var pluginManager: PluginManager? = null
+
+ override fun getCustomizationPreferences(context: Context): CustomizationPreferences {
+ return customizationPreferences
+ ?: TestDefaultCustomizationPreferences(context).also { customizationPreferences = it }
+ }
+
+ override fun getThemeManager(
+ provider: ThemeBundleProvider,
+ activity: FragmentActivity,
+ overlayManagerCompat: OverlayManagerCompat,
+ logger: ThemesUserEventLogger
+ ): ThemeManager {
+ return themeManager
+ ?: TestThemeManager(provider, activity, overlayManagerCompat, logger).also {
+ themeManager = it
+ }
+ }
+
+ override fun getPackageStatusNotifier(context: Context): PackageStatusNotifier {
+ return packageStatusNotifier
+ ?: TestPackageStatusNotifier().also {
+ packageStatusNotifier = TestPackageStatusNotifier()
+ }
+ }
+
+ override fun getDrawableLayerResolver(): DrawableLayerResolver {
+ return drawableLayerResolver
+ ?: TestDrawableLayerResolver().also { drawableLayerResolver = it }
+ }
+
+ override fun getUserEventLogger(context: Context): UserEventLogger {
+ return userEventLogger ?: TestThemesUserEventLogger().also { userEventLogger = it }
+ }
+
+ override fun getKeyguardQuickAffordancePickerInteractor(
+ context: Context
+ ): KeyguardQuickAffordancePickerInteractor {
+ return keyguardQuickAffordancePickerInteractor
+ ?: createCustomizationProviderClient(context).also {
+ keyguardQuickAffordancePickerInteractor = it
+ }
+ }
+
+ private fun createCustomizationProviderClient(
+ context: Context
+ ): KeyguardQuickAffordancePickerInteractor {
+ val client: CustomizationProviderClient = CustomizationProviderClientImpl(context, IO)
+ return KeyguardQuickAffordancePickerInteractor(
+ KeyguardQuickAffordancePickerRepository(client, IO),
+ client
+ ) { getKeyguardQuickAffordanceSnapshotRestorer(context) }
+ }
+
+ override fun getFlags(): BaseFlags {
+ return flags ?: object : BaseFlags() {}.also { flags = it }
+ }
+
+ override fun getSnapshotRestorers(context: Context): Map<Int, SnapshotRestorer> {
+ val restorers: MutableMap<Int, SnapshotRestorer> = HashMap()
+ restorers[KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER] =
+ getKeyguardQuickAffordanceSnapshotRestorer(context)
+ return restorers
+ }
+
+ /** Returns the [CustomizationProviderClient]. */
+ private fun getKeyguardQuickAffordancePickerProviderClient(
+ context: Context
+ ): CustomizationProviderClient {
+ return customizationProviderClient
+ ?: CustomizationProviderClientImpl(context, IO).also {
+ customizationProviderClient = it
+ }
+ }
+
+ private fun getKeyguardQuickAffordanceSnapshotRestorer(
+ context: Context
+ ): KeyguardQuickAffordanceSnapshotRestorer {
+ return keyguardQuickAffordanceSnapshotRestorer
+ ?: KeyguardQuickAffordanceSnapshotRestorer(
+ getKeyguardQuickAffordancePickerInteractor(context),
+ getKeyguardQuickAffordancePickerProviderClient(context)
+ )
+ .also { keyguardQuickAffordanceSnapshotRestorer = it }
+ }
+
+ override fun getClockRegistry(context: Context): ClockRegistry {
+ return clockRegistry
+ ?: ClockRegistry(
+ context,
+ getPluginManager(context),
+ Handler.getMain(),
+ isEnabled = true,
+ userHandle = UserHandle.USER_SYSTEM,
+ DefaultClockProvider(context, LayoutInflater.from(context), context.resources)
+ )
+ .also { clockRegistry = it }
+ }
+
+ override fun getPluginManager(context: Context): PluginManager {
+ return pluginManager ?: TestPluginManager().also { pluginManager = it }
+ }
+
+ companion object {
+ private const val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER = 1
+ }
+}
diff --git a/tests/src/com/android/customization/testing/TestPluginManager.kt b/tests/src/com/android/customization/testing/TestPluginManager.kt
new file mode 100644
index 0000000..167d8dd
--- /dev/null
+++ b/tests/src/com/android/customization/testing/TestPluginManager.kt
@@ -0,0 +1,36 @@
+package com.android.customization.testing
+
+import com.android.systemui.plugins.Plugin
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.plugins.PluginManager
+
+class TestPluginManager : PluginManager {
+ override fun getPrivilegedPlugins(): Array<String> {
+ return emptyArray()
+ }
+
+ override fun <T : Plugin?> addPluginListener(listener: PluginListener<T>, cls: Class<T>) {}
+ override fun <T : Plugin?> addPluginListener(
+ listener: PluginListener<T>,
+ cls: Class<T>,
+ allowMultiple: Boolean
+ ) {}
+
+ override fun <T : Plugin?> addPluginListener(
+ action: String,
+ listener: PluginListener<T>,
+ cls: Class<T>
+ ) {}
+
+ override fun <T : Plugin?> addPluginListener(
+ action: String,
+ listener: PluginListener<T>,
+ cls: Class<T>,
+ allowMultiple: Boolean
+ ) {}
+
+ override fun removePluginListener(listener: PluginListener<*>?) {}
+ override fun <T> dependsOn(p: Plugin, cls: Class<T>): Boolean {
+ return false
+ }
+}