[TP] Clock Settings

Created the clock settings screen
The feature is gated by flag FLAG_NAME_REVAMPED_WALLPAPER_UI

Test: Manually tested that the UI shows as expected
Bug: 262924055
Change-Id: I4ad5ff0c4fab0759e2da9e478ce8118a22a00a8e
diff --git a/res/layout/clock_size_radio_button_group.xml b/res/layout/clock_size_radio_button_group.xml
new file mode 100644
index 0000000..d520756
--- /dev/null
+++ b/res/layout/clock_size_radio_button_group.xml
@@ -0,0 +1,77 @@
+<?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"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/button_container_dynamic"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="8dp"
+        android:orientation="horizontal">
+
+        <RadioButton
+            android:id="@+id/radio_button_dynamic"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginEnd="8dp"
+            android:clickable="false" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                style="@style/SectionTitleTextStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/clock_size_dynamic" />
+
+            <TextView
+                style="@style/SectionSubtitleTextStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/clock_size_dynamic_description" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/button_container_large"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <RadioButton
+            android:id="@+id/radio_button_large"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginEnd="8dp"
+            android:clickable="false" />
+
+        <TextView
+            style="@style/SectionTitleTextStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:text="@string/clock_size_large" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/fragment_clock_settings.xml b/res/layout/fragment_clock_settings.xml
new file mode 100644
index 0000000..7268b34
--- /dev/null
+++ b/res/layout/fragment_clock_settings.xml
@@ -0,0 +1,103 @@
+<?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"
+    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="wrap_content"
+            android:layout_height="wrap_content">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/tabs"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:clipToPadding="false"
+                android:paddingHorizontal="16dp"
+                android:layout_gravity="center_horizontal"/>
+
+            <!--
+            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>
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@id/affordances"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:clipToPadding="false"
+                android:paddingHorizontal="16dp"
+                android:visibility="gone"/>
+
+            <com.android.customization.picker.clock.ui.view.ClockSizeRadioButtonGroup
+                android:id="@+id/clock_size_radio_button_group"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="16dp" />
+        </FrameLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5da2c33..313fea8 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,6 +36,21 @@
     <!-- 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 tab to change the clock color. [CHAR LIMIT=15] -->
+    <string name="clock_color">Color</string>
+
+    <!-- Title of a tab to change the clock size. [CHAR LIMIT=15] -->
+    <string name="clock_size">Size</string>
+
+    <!-- Title of a radio button to apply clock size dynamic. [CHAR LIMIT=15] -->
+    <string name="clock_size_dynamic">Dynamic</string>
+
+    <!-- Description of a radio button to apply clock size dynamic. [CHAR LIMIT=NONE] -->
+    <string name="clock_size_dynamic_description">Clock size changes according to lock screen content</string>
+
+    <!-- Title of a radio button to apply clock size large. [CHAR LIMIT=15] -->
+    <string name="clock_size_large">Large</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/picker/clock/ClockFacePickerActivity.java b/src/com/android/customization/picker/clock/ClockFacePickerActivity.java
deleted file mode 100644
index 5e51234..0000000
--- a/src/com/android/customization/picker/clock/ClockFacePickerActivity.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.
- */
-package com.android.customization.picker.clock;
-
-import android.content.Intent;
-import android.os.Bundle;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-import com.android.customization.model.clock.BaseClockManager;
-import com.android.customization.model.clock.Clockface;
-import com.android.customization.model.clock.ContentProviderClockProvider;
-import com.android.customization.picker.clock.ClockFragment.ClockFragmentHost;
-import com.android.wallpaper.R;
-
-/**
- * Activity allowing for the clock face picker to be linked to from other setup flows.
- *
- * This should be used with startActivityForResult. The resulting intent contains an extra
- * "clock_face_name" with the id of the picked clock face.
- */
-public class ClockFacePickerActivity extends FragmentActivity implements ClockFragmentHost {
-
-    private static final String EXTRA_CLOCK_FACE_NAME = "clock_face_name";
-
-    private BaseClockManager mClockManager;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_clock_face_picker);
-
-        // Creating a class that overrides {@link ClockManager#apply} to return the clock id to the
-        // calling activity instead of putting the value into settings.
-        //
-        mClockManager = new BaseClockManager(
-                new ContentProviderClockProvider(ClockFacePickerActivity.this)) {
-
-            @Override
-            protected void handleApply(Clockface option, Callback callback) {
-                Intent result = new Intent();
-                result.putExtra(EXTRA_CLOCK_FACE_NAME, option.getId());
-                setResult(RESULT_OK, result);
-                callback.onSuccess();
-                finish();
-            }
-
-            @Override
-            protected String lookUpCurrentClock() {
-                return getIntent().getStringExtra(EXTRA_CLOCK_FACE_NAME);
-            }
-        };
-        if (!mClockManager.isAvailable()) {
-            finish();
-        } else {
-            final FragmentManager fm = getSupportFragmentManager();
-            final FragmentTransaction fragmentTransaction = fm.beginTransaction();
-            final ClockFragment clockFragment = ClockFragment.newInstance(
-                    getString(R.string.clock_title));
-            fragmentTransaction.replace(R.id.fragment_container, clockFragment);
-            fragmentTransaction.commitNow();
-        }
-    }
-
-    @Override
-    public BaseClockManager getClockManager() {
-        return mClockManager;
-    }
-}
diff --git a/src/com/android/customization/picker/clock/ClockFragment.java b/src/com/android/customization/picker/clock/ClockFragment.java
deleted file mode 100644
index bc02ae3..0000000
--- a/src/com/android/customization/picker/clock/ClockFragment.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.
- */
-package com.android.customization.picker.clock;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.widget.ContentLoadingProgressBar;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.customization.model.CustomizationManager.Callback;
-import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
-import com.android.customization.model.clock.BaseClockManager;
-import com.android.customization.model.clock.Clockface;
-import com.android.customization.module.ThemesUserEventLogger;
-import com.android.customization.picker.BasePreviewAdapter;
-import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
-import com.android.customization.widget.OptionSelectorController;
-import com.android.wallpaper.R;
-import com.android.wallpaper.asset.Asset;
-import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.picker.AppbarFragment;
-import com.android.wallpaper.widget.PreviewPager;
-
-import java.util.List;
-
-/**
- * Fragment that contains the main UI for selecting and applying a Clockface.
- */
-public class ClockFragment extends AppbarFragment {
-
-    private static final String TAG = "ClockFragment";
-
-    /**
-     * Interface to be implemented by an Activity hosting a {@link ClockFragment}
-     */
-    public interface ClockFragmentHost {
-        BaseClockManager getClockManager();
-    }
-
-    public static ClockFragment newInstance(CharSequence title) {
-        ClockFragment fragment = new ClockFragment();
-        fragment.setArguments(AppbarFragment.createArguments(title));
-        return fragment;
-    }
-
-    private RecyclerView mOptionsContainer;
-    private OptionSelectorController<Clockface> mOptionsController;
-    private Clockface mSelectedOption;
-    private BaseClockManager mClockManager;
-    private PreviewPager mPreviewPager;
-    private ContentLoadingProgressBar mLoading;
-    private View mContent;
-    private View mError;
-    private ThemesUserEventLogger mEventLogger;
-
-    @Override
-    public void onAttach(Context context) {
-        super.onAttach(context);
-        mClockManager = ((ClockFragmentHost) context).getClockManager();
-        mEventLogger = (ThemesUserEventLogger)
-                InjectorProvider.getInjector().getUserEventLogger(context);
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-            @Nullable Bundle savedInstanceState) {
-        View view = inflater.inflate(
-                R.layout.fragment_clock_picker, container, /* attachToRoot */ false);
-        setUpToolbar(view);
-        mContent = view.findViewById(R.id.content_section);
-        mPreviewPager = view.findViewById(R.id.clock_preview_pager);
-        mOptionsContainer = view.findViewById(R.id.options_container);
-        mLoading = view.findViewById(R.id.loading_indicator);
-        mError = view.findViewById(R.id.error_section);
-        setUpOptions();
-        view.findViewById(R.id.apply_button).setOnClickListener(v -> {
-            mClockManager.apply(mSelectedOption, new Callback() {
-                @Override
-                public void onSuccess() {
-                    mOptionsController.setAppliedOption(mSelectedOption);
-                    Toast.makeText(getContext(), R.string.applied_clock_msg,
-                            Toast.LENGTH_SHORT).show();
-                }
-
-                @Override
-                public void onError(@Nullable Throwable throwable) {
-                    if (throwable != null) {
-                        Log.e(TAG, "Error loading clockfaces", throwable);
-                    }
-                    //TODO(santie): handle
-                }
-            });
-
-        });
-        return view;
-    }
-
-    private void createAdapter() {
-        mPreviewPager.setAdapter(new ClockPreviewAdapter(getActivity(), mSelectedOption));
-    }
-
-    private void setUpOptions() {
-        hideError();
-        mLoading.show();
-        mClockManager.fetchOptions(new OptionsFetchedListener<Clockface>() {
-           @Override
-           public void onOptionsLoaded(List<Clockface> options) {
-               mLoading.hide();
-               mOptionsController = new OptionSelectorController<>(mOptionsContainer, options);
-
-               mOptionsController.addListener(selected -> {
-                   mSelectedOption = (Clockface) selected;
-                   mEventLogger.logClockSelected(mSelectedOption);
-                   createAdapter();
-               });
-               mOptionsController.initOptions(mClockManager);
-               for (Clockface option : options) {
-                   if (option.isActive(mClockManager)) {
-                       mSelectedOption = option;
-                   }
-               }
-               // For development only, as there should always be a grid set.
-               if (mSelectedOption == null) {
-                   mSelectedOption = options.get(0);
-               }
-               createAdapter();
-           }
-           @Override
-            public void onError(@Nullable Throwable throwable) {
-                if (throwable != null) {
-                   Log.e(TAG, "Error loading clockfaces", throwable);
-                }
-                showError();
-            }
-       }, false);
-    }
-
-    private void hideError() {
-        mContent.setVisibility(View.VISIBLE);
-        mError.setVisibility(View.GONE);
-    }
-
-    private void showError() {
-        mLoading.hide();
-        mContent.setVisibility(View.GONE);
-        mError.setVisibility(View.VISIBLE);
-    }
-
-    private static class ClockfacePreviewPage extends PreviewPage {
-
-        private final Asset mPreviewAsset;
-
-        public ClockfacePreviewPage(String title, Activity activity, Asset previewAsset) {
-            super(title, activity);
-            mPreviewAsset = previewAsset;
-        }
-
-        @Override
-        public void bindPreviewContent() {
-            ImageView previewImage = card.findViewById(R.id.clock_preview_image);
-            Context context = previewImage.getContext();
-            Resources res = previewImage.getResources();
-            mPreviewAsset.loadDrawableWithTransition(context, previewImage,
-                    100 /* transitionDurationMillis */,
-                    null /* drawableLoadedListener */,
-                    res.getColor(android.R.color.transparent, null) /* placeholderColor */);
-            card.setContentDescription(card.getResources().getString(
-                    R.string.clock_preview_content_description, title));
-        }
-    }
-
-    /**
-     * Adapter class for mPreviewPager.
-     * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe,
-     * we don't want to just scroll)
-     */
-    private static class ClockPreviewAdapter extends BasePreviewAdapter<ClockfacePreviewPage> {
-        ClockPreviewAdapter(Activity activity, Clockface clockface) {
-            super(activity, R.layout.clock_preview_card);
-            addPage(new ClockfacePreviewPage(
-                    clockface.getTitle(), activity , clockface.getPreviewAsset()));
-        }
-    }
-}
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
index f160a3d..8f163b7 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -16,6 +16,7 @@
  */
 package com.android.customization.picker.clock.data.repository
 
+import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import kotlinx.coroutines.flow.Flow
 
@@ -25,4 +26,8 @@
  */
 interface ClockPickerRepository {
     val selectedClock: Flow<ClockMetadataModel?>
+
+    val selectedClockSize: Flow<ClockSize>
+
+    fun setClockSize(size: ClockSize)
 }
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
index c307ca6..1c50517 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -17,11 +17,14 @@
 package com.android.customization.picker.clock.data.repository
 
 import android.util.Log
+import com.android.customization.picker.clock.shared.ClockSize
 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.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.callbackFlow
 
 /** Implementation of [ClockPickerRepository], using [ClockRegistry]. */
@@ -47,6 +50,14 @@
         awaitClose { registry.unregisterClockChangeListener(listener) }
     }
 
+    // TODO(b/262924055): Use the shared system UI component to query the clock size
+    private val _selectedClockSize = MutableStateFlow(ClockSize.DYNAMIC)
+    override val selectedClockSize: Flow<ClockSize> = _selectedClockSize.asStateFlow()
+
+    override fun setClockSize(size: ClockSize) {
+        _selectedClockSize.value = size
+    }
+
     private fun ClockMetadata.toModel(): ClockMetadataModel {
         return ClockMetadataModel(clockId = clockId, name = name)
     }
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
index 63b3638..a0bf14e 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
@@ -18,6 +18,7 @@
 package com.android.customization.picker.clock.domain.interactor
 
 import com.android.customization.picker.clock.data.repository.ClockPickerRepository
+import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import kotlinx.coroutines.flow.Flow
 
@@ -26,6 +27,11 @@
  * clocks.
  */
 class ClockPickerInteractor(private val repository: ClockPickerRepository) {
-
     val selectedClock: Flow<ClockMetadataModel?> = repository.selectedClock
+
+    val selectedClockSize: Flow<ClockSize> = repository.selectedClockSize
+
+    fun setClockSize(size: ClockSize) {
+        repository.setClockSize(size)
+    }
 }
diff --git a/src/com/android/customization/picker/clock/shared/ClockSize.kt b/src/com/android/customization/picker/clock/shared/ClockSize.kt
new file mode 100644
index 0000000..91c5cd4
--- /dev/null
+++ b/src/com/android/customization/picker/clock/shared/ClockSize.kt
@@ -0,0 +1,22 @@
+/*
+ * 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
+
+enum class ClockSize {
+    DYNAMIC,
+    LARGE,
+}
diff --git a/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt b/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt
new file mode 100644
index 0000000..746fdb3
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsTabViewModel
+import com.android.wallpaper.R
+
+/** Adapter for the tab recycler view on the clock settings screen. */
+class ClockSettingsTabAdapter : RecyclerView.Adapter<ClockSettingsTabAdapter.ViewHolder>() {
+
+    private val items = mutableListOf<ClockSettingsTabViewModel>()
+
+    fun setItems(items: List<ClockSettingsTabViewModel>) {
+        this.items.clear()
+        this.items.addAll(items)
+        notifyDataSetChanged()
+    }
+
+    override fun getItemCount(): Int {
+        return items.size
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        return ViewHolder(
+            LayoutInflater.from(parent.context)
+                .inflate(
+                    R.layout.picker_fragment_tab,
+                    parent,
+                    false,
+                )
+        )
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        val item = items[position]
+        holder.itemView.isSelected = item.isSelected
+        holder.textView.text = item.name
+        holder.textView.setOnClickListener(
+            if (item.onClicked != null) {
+                View.OnClickListener { item.onClicked.invoke() }
+            } else {
+                null
+            }
+        )
+    }
+
+    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+        val textView: TextView = itemView.requireViewById(R.id.text)
+    }
+}
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
new file mode 100644
index 0000000..9a94a69
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
@@ -0,0 +1,92 @@
+/*
+ * 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 androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.customization.picker.clock.ui.adapter.ClockSettingsTabAdapter
+import com.android.customization.picker.clock.ui.view.ClockSizeRadioButtonGroup
+import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
+import com.android.customization.picker.common.ui.view.ItemSpacing
+import com.android.wallpaper.R
+import kotlinx.coroutines.launch
+
+/** Bind between the clock settings screen and its view model. */
+object ClockSettingsBinder {
+    fun bind(
+        view: View,
+        viewModel: ClockSettingsViewModel,
+        lifecycleOwner: LifecycleOwner,
+    ) {
+        val tabView: RecyclerView = view.requireViewById(R.id.tabs)
+        val sizeOptions =
+            view.requireViewById<ClockSizeRadioButtonGroup>(R.id.clock_size_radio_button_group)
+
+        val tabAdapter = ClockSettingsTabAdapter()
+        tabView.adapter = tabAdapter
+        tabView.layoutManager = LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
+        tabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP))
+
+        sizeOptions.onRadioButtonClickListener =
+            object : ClockSizeRadioButtonGroup.OnRadioButtonClickListener {
+                override fun onClick(size: ClockSize) {
+                    viewModel.setClockSize(size)
+                }
+            }
+
+        lifecycleOwner.lifecycleScope.launch {
+            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch { viewModel.tabs.collect { tabAdapter.setItems(it) } }
+
+                launch {
+                    viewModel.selectedTabPosition.collect { tab ->
+                        when (tab) {
+                            ClockSettingsViewModel.Tab.COLOR -> {
+                                sizeOptions.isInvisible = true
+                            }
+                            ClockSettingsViewModel.Tab.SIZE -> {
+                                sizeOptions.isVisible = true
+                            }
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.selectedClockSize.collect { size ->
+                        when (size) {
+                            ClockSize.DYNAMIC -> {
+                                sizeOptions.radioButtonDynamic.isChecked = true
+                                sizeOptions.radioButtonLarge.isChecked = false
+                            }
+                            ClockSize.LARGE -> {
+                                sizeOptions.radioButtonDynamic.isChecked = false
+                                sizeOptions.radioButtonLarge.isChecked = true
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/customization/picker/clock/ClockCustomFragment.java b/src/com/android/customization/picker/clock/ui/fragment/ClockCustomFragment.java
similarity index 97%
rename from src/com/android/customization/picker/clock/ClockCustomFragment.java
rename to src/com/android/customization/picker/clock/ui/fragment/ClockCustomFragment.java
index 56860fe..ea267ab 100644
--- a/src/com/android/customization/picker/clock/ClockCustomFragment.java
+++ b/src/com/android/customization/picker/clock/ui/fragment/ClockCustomFragment.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.customization.picker.clock;
+package com.android.customization.picker.clock.ui.fragment;
 
 import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
 import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
diff --git a/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
new file mode 100644
index 0000000..c8d2434
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/fragment/ClockSettingsFragment.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.get
+import androidx.lifecycle.lifecycleScope
+import com.android.customization.module.ThemePickerInjector
+import com.android.customization.picker.clock.ui.binder.ClockSettingsBinder
+import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
+import com.android.customization.picker.quickaffordance.ui.binder.KeyguardQuickAffordancePreviewBinder
+import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
+import com.android.wallpaper.R
+import com.android.wallpaper.module.InjectorProvider
+import com.android.wallpaper.picker.AppbarFragment
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class ClockSettingsFragment : AppbarFragment() {
+    companion object {
+        const val DESTINATION_ID = "clock_settings"
+
+        @JvmStatic
+        fun newInstance(): ClockSettingsFragment {
+            return ClockSettingsFragment()
+        }
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val view =
+            inflater.inflate(
+                R.layout.fragment_clock_settings,
+                container,
+                false,
+            )
+        setUpToolbar(view)
+        val injector = InjectorProvider.getInjector() as ThemePickerInjector
+
+        // TODO(b/262924055): Modify to render the lockscreen properly
+        val viewModel: KeyguardQuickAffordancePickerViewModel =
+            ViewModelProvider(
+                    requireActivity(),
+                    injector.getKeyguardQuickAffordancePickerViewModelFactory(requireContext()),
+                )
+                .get()
+        KeyguardQuickAffordancePreviewBinder.bind(
+            activity = requireActivity(),
+            previewView = view.requireViewById(R.id.preview),
+            viewModel = viewModel,
+            lifecycleOwner = this,
+            offsetToStart =
+                injector.getDisplayUtils(requireActivity()).isOnWallpaperDisplay(requireActivity())
+        )
+
+        lifecycleScope.launch {
+            val clockRegistry =
+                withContext(Dispatchers.IO) {
+                    injector.getClockRegistryProvider(requireContext()).get()
+                }
+            ClockSettingsBinder.bind(
+                view,
+                ClockSettingsViewModel(
+                    requireContext(),
+                    injector.getClockPickerInteractor(requireContext(), clockRegistry)
+                ),
+                this@ClockSettingsFragment,
+            )
+        }
+
+        return view
+    }
+
+    override fun getDefaultTitle(): CharSequence {
+        return requireContext().getString(R.string.clock_settings_title)
+    }
+}
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt b/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.kt
new file mode 100644
index 0000000..fcf8904
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/view/ClockSizeRadioButtonGroup.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.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.RadioButton
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.wallpaper.R
+
+/** The radio button group to pick the clock size. */
+class ClockSizeRadioButtonGroup(
+    context: Context,
+    attrs: AttributeSet?,
+) : FrameLayout(context, attrs) {
+
+    interface OnRadioButtonClickListener {
+        fun onClick(size: ClockSize)
+    }
+
+    val radioButtonDynamic: RadioButton
+    val radioButtonLarge: RadioButton
+    var onRadioButtonClickListener: OnRadioButtonClickListener? = null
+
+    init {
+        LayoutInflater.from(context).inflate(R.layout.clock_size_radio_button_group, this, true)
+        radioButtonDynamic = requireViewById(R.id.radio_button_dynamic)
+        val buttonDynamic = requireViewById<View>(R.id.button_container_dynamic)
+        buttonDynamic.setOnClickListener { onRadioButtonClickListener?.onClick(ClockSize.DYNAMIC) }
+        radioButtonLarge = requireViewById(R.id.radio_button_large)
+        val buttonLarge = requireViewById<View>(R.id.button_container_large)
+        buttonLarge.setOnClickListener { onRadioButtonClickListener?.onClick(ClockSize.LARGE) }
+    }
+}
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModel.kt
new file mode 100644
index 0000000..7c30ca2
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModel.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
+
+/** View model for the tabs on the clock settings screen. */
+class ClockSettingsTabViewModel(
+    /** User-visible name for the tab. */
+    val name: String,
+
+    /** Whether this is the currently-selected tab in the picker. */
+    val isSelected: Boolean,
+
+    /** Notifies that the tab has been clicked by the user. */
+    val onClicked: (() -> Unit)?,
+)
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
new file mode 100644
index 0000000..8c0925f
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
@@ -0,0 +1,69 @@
+/*
+ * 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 android.content.Context
+import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.wallpaper.R
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+
+/** View model for the clock settings screen. */
+class ClockSettingsViewModel(val context: Context, val interactor: ClockPickerInteractor) {
+
+    enum class Tab {
+        COLOR,
+        SIZE,
+    }
+
+    val selectedClockSize: Flow<ClockSize> = interactor.selectedClockSize
+
+    fun setClockSize(size: ClockSize) {
+        interactor.setClockSize(size)
+    }
+
+    private val _selectedTabPosition = MutableStateFlow(Tab.COLOR)
+    val selectedTabPosition: StateFlow<Tab> = _selectedTabPosition.asStateFlow()
+    val tabs: Flow<List<ClockSettingsTabViewModel>> =
+        selectedTabPosition.map {
+            listOf(
+                ClockSettingsTabViewModel(
+                    name = context.resources.getString(R.string.clock_color),
+                    isSelected = it == Tab.COLOR,
+                    onClicked =
+                        if (it == Tab.COLOR) {
+                            null
+                        } else {
+                            { _selectedTabPosition.tryEmit(Tab.COLOR) }
+                        }
+                ),
+                ClockSettingsTabViewModel(
+                    name = context.resources.getString(R.string.clock_size),
+                    isSelected = it == Tab.SIZE,
+                    onClicked =
+                        if (it == Tab.SIZE) {
+                            null
+                        } else {
+                            { _selectedTabPosition.tryEmit(Tab.SIZE) }
+                        }
+                ),
+            )
+        }
+}
diff --git a/src/com/android/customization/picker/common/ui/view/ItemSpacing.kt b/src/com/android/customization/picker/common/ui/view/ItemSpacing.kt
new file mode 100644
index 0000000..cbf9e97
--- /dev/null
+++ b/src/com/android/customization/picker/common/ui/view/ItemSpacing.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.common.ui.view
+
+import android.graphics.Rect
+import androidx.core.view.ViewCompat
+import androidx.recyclerview.widget.RecyclerView
+
+/** Item spacing used by the RecyclerView. */
+class ItemSpacing(
+    private val itemSpacingDp: Int,
+) : RecyclerView.ItemDecoration() {
+    override fun getItemOffsets(outRect: Rect, itemPosition: Int, parent: RecyclerView) {
+        val addSpacingToStart = itemPosition > 0
+        val addSpacingToEnd = itemPosition < (parent.adapter?.itemCount ?: 0) - 1
+        val isRtl = parent.layoutManager?.layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL
+        val density = parent.context.resources.displayMetrics.density
+        val halfItemSpacingPx = itemSpacingDp.toPx(density) / 2
+        if (!isRtl) {
+            outRect.left = if (addSpacingToStart) halfItemSpacingPx else 0
+            outRect.right = if (addSpacingToEnd) halfItemSpacingPx else 0
+        } else {
+            outRect.left = if (addSpacingToEnd) halfItemSpacingPx else 0
+            outRect.right = if (addSpacingToStart) halfItemSpacingPx else 0
+        }
+    }
+
+    private fun Int.toPx(density: Float): Int {
+        return (this * density).toInt()
+    }
+
+    companion object {
+        const val TAB_ITEM_SPACING_DP = 12
+        const val AFFORDANCE_ITEM_SPACING_DP = 8
+    }
+}
diff --git a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt
index 30372fe..efa090b 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerBinder.kt
@@ -19,15 +19,14 @@
 
 import android.app.Dialog
 import android.content.Context
-import android.graphics.Rect
 import android.view.View
-import androidx.core.view.ViewCompat
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.common.ui.view.ItemSpacing
 import com.android.customization.picker.quickaffordance.ui.adapter.AffordancesAdapter
 import com.android.customization.picker.quickaffordance.ui.adapter.SlotTabAdapter
 import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
@@ -54,12 +53,12 @@
         slotTabView.adapter = slotTabAdapter
         slotTabView.layoutManager =
             LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
-        slotTabView.addItemDecoration(ItemSpacing(SLOT_TAB_ITEM_SPACING_DP))
+        slotTabView.addItemDecoration(ItemSpacing(ItemSpacing.TAB_ITEM_SPACING_DP))
         val affordancesAdapter = AffordancesAdapter()
         affordancesView.adapter = affordancesAdapter
         affordancesView.layoutManager =
             LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
-        affordancesView.addItemDecoration(ItemSpacing(AFFORDANCE_ITEM_SPACING_DP))
+        affordancesView.addItemDecoration(ItemSpacing(ItemSpacing.AFFORDANCE_ITEM_SPACING_DP))
 
         var dialog: Dialog? = null
 
@@ -117,30 +116,4 @@
             onDismissed = onDismissed,
         )
     }
-
-    private class ItemSpacing(
-        private val itemSpacingDp: Int,
-    ) : RecyclerView.ItemDecoration() {
-        override fun getItemOffsets(outRect: Rect, itemPosition: Int, parent: RecyclerView) {
-            val addSpacingToStart = itemPosition > 0
-            val addSpacingToEnd = itemPosition < (parent.adapter?.itemCount ?: 0) - 1
-            val isRtl = parent.layoutManager?.layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL
-            val density = parent.context.resources.displayMetrics.density
-            val halfItemSpacingPx = itemSpacingDp.toPx(density) / 2
-            if (!isRtl) {
-                outRect.left = if (addSpacingToStart) halfItemSpacingPx else 0
-                outRect.right = if (addSpacingToEnd) halfItemSpacingPx else 0
-            } else {
-                outRect.left = if (addSpacingToEnd) halfItemSpacingPx else 0
-                outRect.right = if (addSpacingToStart) halfItemSpacingPx else 0
-            }
-        }
-
-        private fun Int.toPx(density: Float): Int {
-            return (this * density).toInt()
-        }
-    }
-
-    private const val SLOT_TAB_ITEM_SPACING_DP = 12
-    private const val AFFORDANCE_ITEM_SPACING_DP = 8
 }
diff --git a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
index ea97c9a..0ff0cab 100644
--- a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
+++ b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
@@ -15,11 +15,20 @@
  */
 package com.android.customization.picker.clock.data.repository
 
+import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 class FakeClockPickerRepository : ClockPickerRepository {
 
     override val selectedClock: Flow<ClockMetadataModel?> = MutableStateFlow(null)
+
+    private val _selectedClockSize = MutableStateFlow(ClockSize.LARGE)
+    override val selectedClockSize: Flow<ClockSize> = _selectedClockSize.asStateFlow()
+
+    override fun setClockSize(size: ClockSize) {
+        _selectedClockSize.value = size
+    }
 }
diff --git a/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt b/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
new file mode 100644
index 0000000..a6ad6de
--- /dev/null
+++ b/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
@@ -0,0 +1,48 @@
+package com.android.customization.picker.clock.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.wallpaper.testing.collectLastValue
+import com.google.common.truth.Truth
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ClockPickerInteractorTest {
+
+    private lateinit var underTest: ClockPickerInteractor
+
+    @Before
+    fun setUp() {
+        val testDispatcher = StandardTestDispatcher()
+        Dispatchers.setMain(testDispatcher)
+        underTest = ClockPickerInteractor(FakeClockPickerRepository())
+    }
+
+    @After
+    fun tearDown() {
+        Dispatchers.resetMain()
+    }
+
+    @Test
+    fun setClockSize() = runTest {
+        val observedClockSize = collectLastValue(underTest.selectedClockSize)
+        underTest.setClockSize(ClockSize.DYNAMIC)
+        Truth.assertThat(observedClockSize()).isEqualTo(ClockSize.DYNAMIC)
+
+        underTest.setClockSize(ClockSize.LARGE)
+        Truth.assertThat(observedClockSize()).isEqualTo(ClockSize.LARGE)
+    }
+}
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
new file mode 100644
index 0000000..63bdd7b
--- /dev/null
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
@@ -0,0 +1,68 @@
+package com.android.customization.picker.clock.ui.viewmodel
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
+import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import com.android.customization.picker.clock.shared.ClockSize
+import com.android.wallpaper.testing.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ClockSettingsViewModelTest {
+
+    private lateinit var underTest: ClockSettingsViewModel
+
+    private lateinit var context: Context
+
+    @Before
+    fun setUp() {
+        val testDispatcher = StandardTestDispatcher()
+        Dispatchers.setMain(testDispatcher)
+        context = InstrumentationRegistry.getInstrumentation().targetContext
+        underTest =
+            ClockSettingsViewModel(context, ClockPickerInteractor(FakeClockPickerRepository()))
+    }
+
+    @After
+    fun tearDown() {
+        Dispatchers.resetMain()
+    }
+
+    @Test
+    fun setClockSize() = runTest {
+        val observedClockSize = collectLastValue(underTest.selectedClockSize)
+        underTest.setClockSize(ClockSize.DYNAMIC)
+        assertThat(observedClockSize()).isEqualTo(ClockSize.DYNAMIC)
+
+        underTest.setClockSize(ClockSize.LARGE)
+        assertThat(observedClockSize()).isEqualTo(ClockSize.LARGE)
+    }
+
+    @Test
+    fun `Click on a picker tab`() = runTest {
+        val tabs = collectLastValue(underTest.tabs)
+        assertThat(tabs()?.get(0)?.name).isEqualTo("Color")
+        assertThat(tabs()?.get(0)?.isSelected).isTrue()
+        assertThat(tabs()?.get(1)?.name).isEqualTo("Size")
+        assertThat(tabs()?.get(1)?.isSelected).isFalse()
+
+        tabs()?.get(1)?.onClicked?.invoke()
+        assertThat(tabs()?.get(0)?.isSelected).isFalse()
+        assertThat(tabs()?.get(1)?.isSelected).isTrue()
+    }
+}