Merge "Import translations. DO NOT MERGE ANYWHERE" into tm-qpr-dev
diff --git a/res/layout/fragment_lock_screen_quick_affordances.xml b/res/layout/fragment_lock_screen_quick_affordances.xml
index 9927e6a..331d501 100644
--- a/res/layout/fragment_lock_screen_quick_affordances.xml
+++ b/res/layout/fragment_lock_screen_quick_affordances.xml
@@ -15,8 +15,7 @@
   ~
   -->
 
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
@@ -30,11 +29,21 @@
 
     </FrameLayout>
 
-    <!-- TODO(b/254858701): plug in the preview here. -->
-    <View
+    <com.android.wallpaper.picker.DisplayAspectRatioFrameLayout
         android:layout_width="match_parent"
         android:layout_height="0dp"
-        android:layout_weight="1" />
+        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"
@@ -46,23 +55,66 @@
         android:paddingTop="22dp"
         android:paddingBottom="62dp">
 
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@id/slot_tabs"
+        <FrameLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:clipToPadding="false"
-            android:paddingHorizontal="16dp" />
+            >
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@id/slot_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/keyguard_quick_affordance_slot_tab"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="invisible" />
+
+        </FrameLayout>
 
         <View
             android:layout_width="0dp"
             android:layout_height="22dp" />
 
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@id/affordances"
+        <FrameLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:clipToPadding="false"
-            android:paddingHorizontal="16dp" />
+            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" />
+
+            <!--
+            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/keyguard_quick_affordance"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="invisible" />
+
+        </FrameLayout>
 
     </LinearLayout>
 
diff --git a/res/layout/keyguard_quick_affordance.xml b/res/layout/keyguard_quick_affordance.xml
index 90ba68e..b3b6893 100644
--- a/res/layout/keyguard_quick_affordance.xml
+++ b/res/layout/keyguard_quick_affordance.xml
@@ -18,7 +18,7 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="@dimen/keyguard_quick_affordance_picker_item_width"
     android:layout_height="wrap_content"
     android:orientation="vertical"
@@ -48,6 +48,8 @@
         android:layout_height="wrap_content"
         android:textColor="@color/text_color_primary"
         android:singleLine="true"
-        android:ellipsize="end"/>
+        android:ellipsize="end"
+        android:text="Placeholder for stable size calculation, please do not remove."
+        tools:ignore="HardcodedText" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/keyguard_quick_affordance_slot_tab.xml b/res/layout/keyguard_quick_affordance_slot_tab.xml
index ba233cd..c4934d8 100644
--- a/res/layout/keyguard_quick_affordance_slot_tab.xml
+++ b/res/layout/keyguard_quick_affordance_slot_tab.xml
@@ -17,6 +17,7 @@
 
 <TextView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/text"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
@@ -26,4 +27,6 @@
     android:minWidth="48dp"
     android:minHeight="48dp"
     android:gravity="center"
-    android:background="@drawable/keyguard_quick_affordance_slot_tab_background" />
+    android:background="@drawable/keyguard_quick_affordance_slot_tab_background"
+    android:text="Placeholder for stable size calculation, please do not remove."
+    tools:ignore="HardcodedText" />
diff --git a/src/com/android/customization/picker/grid/GridOptionPreviewer.java b/src/com/android/customization/picker/grid/GridOptionPreviewer.java
index 5cf327e..7786d35 100644
--- a/src/com/android/customization/picker/grid/GridOptionPreviewer.java
+++ b/src/com/android/customization/picker/grid/GridOptionPreviewer.java
@@ -22,6 +22,7 @@
 
 import com.android.customization.model.grid.GridOption;
 import com.android.customization.model.grid.GridOptionsManager;
+import com.android.wallpaper.R;
 import com.android.wallpaper.picker.WorkspaceSurfaceHolderCallback;
 import com.android.wallpaper.util.PreviewUtils;
 import com.android.wallpaper.util.SurfaceViewUtils;
@@ -80,7 +81,10 @@
 
     private class GridOptionSurfaceHolderCallback extends WorkspaceSurfaceHolderCallback {
         private GridOptionSurfaceHolderCallback(SurfaceView workspaceSurface, Context context) {
-            super(workspaceSurface, context);
+            super(
+                    workspaceSurface,
+                    new PreviewUtils(
+                            context, context.getString(R.string.grid_control_metadata_name)));
         }
 
         @Override
diff --git a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerPreviewBinder.kt b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerPreviewBinder.kt
deleted file mode 100644
index 13ee553..0000000
--- a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePickerPreviewBinder.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.customization.picker.quickaffordance.ui.binder
-
-import android.view.View
-import android.widget.ImageView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.wallpaper.R
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.launch
-
-object KeyguardQuickAffordancePickerPreviewBinder {
-
-    /** Binds view with view-model for a lock screen quick affordance preview experience. */
-    @JvmStatic
-    fun bind(
-        view: View,
-        viewModel: KeyguardQuickAffordancePickerViewModel,
-        lifecycleOwner: LifecycleOwner,
-    ) {
-        val startView: ImageView = view.requireViewById(R.id.start_affordance)
-        val endView: ImageView = view.requireViewById(R.id.end_affordance)
-        lifecycleOwner.lifecycleScope.launch {
-            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-                launch {
-                    updateView(
-                        view = startView,
-                        viewModel = viewModel,
-                        slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                    )
-                }
-
-                launch {
-                    updateView(
-                        view = endView,
-                        viewModel = viewModel,
-                        slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                    )
-                }
-            }
-        }
-    }
-
-    private suspend fun updateView(
-        view: ImageView,
-        viewModel: KeyguardQuickAffordancePickerViewModel,
-        slotId: String,
-    ) {
-        viewModel.slots
-            .mapNotNull { slotById -> slotById[slotId] }
-            .map { slot -> slot.selectedQuickAffordances.firstOrNull() }
-            .collect { affordance ->
-                view.setImageDrawable(affordance?.icon)
-                view.contentDescription = affordance?.contentDescription
-            }
-    }
-}
diff --git a/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePreviewBinder.kt b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePreviewBinder.kt
new file mode 100644
index 0000000..0af6bf4
--- /dev/null
+++ b/src/com/android/customization/picker/quickaffordance/ui/binder/KeyguardQuickAffordancePreviewBinder.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.customization.picker.quickaffordance.ui.binder
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.service.wallpaper.WallpaperService
+import android.view.SurfaceView
+import androidx.cardview.widget.CardView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
+import com.android.wallpaper.R
+import com.android.wallpaper.asset.Asset
+import com.android.wallpaper.asset.BitmapCachingAsset
+import com.android.wallpaper.model.LiveWallpaperInfo
+import com.android.wallpaper.model.WallpaperInfo
+import com.android.wallpaper.picker.WorkspaceSurfaceHolderCallback
+import com.android.wallpaper.util.PreviewUtils
+import com.android.wallpaper.util.ResourceUtils
+import com.android.wallpaper.util.WallpaperConnection
+import com.android.wallpaper.util.WallpaperSurfaceCallback
+import java.util.concurrent.CompletableFuture
+import kotlinx.coroutines.launch
+
+object KeyguardQuickAffordancePreviewBinder {
+
+    /** Binds view for the preview of the lock screen. */
+    @JvmStatic
+    fun bind(
+        activity: Activity,
+        previewView: CardView,
+        viewModel: KeyguardQuickAffordancePickerViewModel,
+        lifecycleOwner: LifecycleOwner,
+        wallpaperInfoProvider: suspend () -> WallpaperInfo?,
+    ) {
+        val workspaceSurface: SurfaceView = previewView.requireViewById(R.id.workspace_surface)
+        val wallpaperSurface: SurfaceView = previewView.requireViewById(R.id.wallpaper_surface)
+
+        previewView.radius =
+            previewView.resources.getDimension(R.dimen.wallpaper_picker_entry_card_corner_radius)
+        previewView.contentDescription =
+            previewView.context.getString(
+                R.string.lockscreen_wallpaper_preview_card_content_description
+            )
+
+        var previewSurfaceCallback: WorkspaceSurfaceHolderCallback? = null
+        var wallpaperSurfaceCallback: WallpaperSurfaceCallback? = null
+        var wallpaperConnection: WallpaperConnection? = null
+        var wallpaperInfo: WallpaperInfo? = null
+
+        lifecycleOwner.lifecycle.addObserver(
+            LifecycleEventObserver { _, event ->
+                when (event) {
+                    Lifecycle.Event.ON_CREATE -> {
+                        previewSurfaceCallback =
+                            WorkspaceSurfaceHolderCallback(
+                                workspaceSurface,
+                                PreviewUtils(
+                                    context = previewView.context,
+                                    authority =
+                                        previewView.context.getString(
+                                            R.string.lock_screen_preview_provider_authority
+                                        ),
+                                ),
+                                Bundle().apply {
+                                    putString(
+                                        KeyguardQuickAffordancePreviewConstants
+                                            .KEY_INITIALLY_SELECTED_SLOT_ID,
+                                        viewModel.selectedSlotId.value,
+                                    )
+                                },
+                            )
+                        workspaceSurface.holder.addCallback(previewSurfaceCallback)
+                        workspaceSurface.setZOrderMediaOverlay(true)
+
+                        wallpaperSurfaceCallback =
+                            WallpaperSurfaceCallback(
+                                previewView.context,
+                                previewView,
+                                wallpaperSurface,
+                                CompletableFuture.completedFuture(
+                                    WallpaperInfo.ColorInfo(
+                                        /* wallpaperColors= */ null,
+                                        ResourceUtils.getColorAttr(
+                                            previewView.context,
+                                            android.R.attr.colorSecondary,
+                                        )
+                                    )
+                                ),
+                            ) {
+                                maybeLoadThumbnail(
+                                    activity = activity,
+                                    wallpaperInfo = wallpaperInfo,
+                                    surfaceCallback = wallpaperSurfaceCallback,
+                                )
+                            }
+                        wallpaperSurface.holder.addCallback(wallpaperSurfaceCallback)
+                        wallpaperSurface.setZOrderMediaOverlay(true)
+                    }
+                    Lifecycle.Event.ON_DESTROY -> {
+                        workspaceSurface.holder.removeCallback(previewSurfaceCallback)
+                        previewSurfaceCallback?.cleanUp()
+                        wallpaperSurface.holder.removeCallback(wallpaperSurfaceCallback)
+                        wallpaperSurfaceCallback?.cleanUp()
+                    }
+                    Lifecycle.Event.ON_RESUME -> {
+                        lifecycleOwner.lifecycleScope.launch {
+                            wallpaperInfo = wallpaperInfoProvider()
+                            (wallpaperInfo as? LiveWallpaperInfo)?.let { liveWallpaperInfo ->
+                                if (WallpaperConnection.isPreviewAvailable()) {
+                                    wallpaperConnection =
+                                        WallpaperConnection(
+                                            Intent(WallpaperService.SERVICE_INTERFACE).apply {
+                                                setClassName(
+                                                    liveWallpaperInfo.wallpaperComponent
+                                                        .packageName,
+                                                    liveWallpaperInfo.wallpaperComponent.serviceName
+                                                )
+                                            },
+                                            previewView.context,
+                                            null,
+                                            wallpaperSurface,
+                                            null,
+                                        )
+
+                                    wallpaperConnection?.connect()
+                                    wallpaperConnection?.setVisibility(true)
+                                }
+                            }
+                            maybeLoadThumbnail(
+                                activity = activity,
+                                wallpaperInfo = wallpaperInfo,
+                                surfaceCallback = wallpaperSurfaceCallback,
+                            )
+                        }
+                    }
+                    Lifecycle.Event.ON_PAUSE -> {
+                        wallpaperConnection?.setVisibility(false)
+                    }
+                    Lifecycle.Event.ON_STOP -> {
+                        wallpaperConnection?.disconnect()
+                    }
+                    else -> Unit
+                }
+            }
+        )
+
+        lifecycleOwner.lifecycleScope.launch {
+            viewModel.selectedSlotId
+                .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
+                .collect { slotId ->
+                    previewSurfaceCallback?.send(
+                        KeyguardQuickAffordancePreviewConstants.MESSAGE_ID_SLOT_SELECTED,
+                        Bundle().apply {
+                            putString(KeyguardQuickAffordancePreviewConstants.KEY_SLOT_ID, slotId)
+                        },
+                    )
+                }
+        }
+    }
+
+    private fun maybeLoadThumbnail(
+        activity: Activity,
+        wallpaperInfo: WallpaperInfo?,
+        surfaceCallback: WallpaperSurfaceCallback?,
+    ) {
+        if (wallpaperInfo == null || surfaceCallback == null) {
+            return
+        }
+
+        val imageView = surfaceCallback.homeImageWallpaper
+        val thumbAsset: Asset = BitmapCachingAsset(activity, wallpaperInfo.getThumbAsset(activity))
+        if (imageView != null && imageView.drawable == null) {
+            thumbAsset.loadPreviewImage(
+                activity,
+                imageView,
+                ResourceUtils.getColorAttr(activity, android.R.attr.colorSecondary)
+            )
+        }
+    }
+}
diff --git a/src/com/android/customization/picker/quickaffordance/ui/fragment/KeyguardQuickAffordancePickerFragment.kt b/src/com/android/customization/picker/quickaffordance/ui/fragment/KeyguardQuickAffordancePickerFragment.kt
index c99c6e8..251cdca 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/fragment/KeyguardQuickAffordancePickerFragment.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/fragment/KeyguardQuickAffordancePickerFragment.kt
@@ -25,10 +25,15 @@
 import androidx.lifecycle.get
 import com.android.customization.module.ThemePickerInjector
 import com.android.customization.picker.quickaffordance.ui.binder.KeyguardQuickAffordancePickerBinder
+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.ExperimentalCoroutinesApi
+import kotlinx.coroutines.suspendCancellableCoroutine
 
+@OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardQuickAffordancePickerFragment : AppbarFragment() {
     companion object {
         const val DESTINATION_ID = "quick_affordances"
@@ -51,14 +56,32 @@
             )
         setUpToolbar(view)
         val injector = InjectorProvider.getInjector() as ThemePickerInjector
+        val wallpaperInfoFactory = injector.getCurrentWallpaperInfoFactory(requireContext())
+        val viewModel: KeyguardQuickAffordancePickerViewModel =
+            ViewModelProvider(
+                    requireActivity(),
+                    injector.getKeyguardQuickAffordancePickerViewModelFactory(requireContext()),
+                )
+                .get()
+        KeyguardQuickAffordancePreviewBinder.bind(
+            activity = requireActivity(),
+            previewView = view.requireViewById(R.id.preview),
+            viewModel = viewModel,
+            lifecycleOwner = this,
+            wallpaperInfoProvider = {
+                suspendCancellableCoroutine { continuation ->
+                    wallpaperInfoFactory.createCurrentWallpaperInfos(
+                        { homeWallpaper, lockWallpaper, _ ->
+                            continuation.resume(lockWallpaper ?: homeWallpaper, null)
+                        },
+                        /* forceRefresh= */ true,
+                    )
+                }
+            },
+        )
         KeyguardQuickAffordancePickerBinder.bind(
             view = view,
-            viewModel =
-                ViewModelProvider(
-                        requireActivity(),
-                        injector.getKeyguardQuickAffordancePickerViewModelFactory(requireContext()),
-                    )
-                    .get(),
+            viewModel = viewModel,
             lifecycleOwner = this,
         )
         return view
diff --git a/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt b/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt
index 7ea776b..774ff22 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/viewmodel/KeyguardQuickAffordancePickerViewModel.kt
@@ -32,6 +32,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
@@ -47,7 +48,8 @@
 
     @SuppressLint("StaticFieldLeak") private val applicationContext = context.applicationContext
 
-    private val selectedSlotId = MutableStateFlow<String?>(null)
+    private val _selectedSlotId = MutableStateFlow<String?>(null)
+    val selectedSlotId: StateFlow<String?> = _selectedSlotId.asStateFlow()
 
     /** View-models for each slot, keyed by slot ID. */
     val slots: Flow<Map<String, KeyguardQuickAffordanceSlotViewModel>> =
@@ -90,7 +92,7 @@
                                 if (isSelected) {
                                     null
                                 } else {
-                                    { this.selectedSlotId.value = slot.id }
+                                    { _selectedSlotId.tryEmit(slot.id) }
                                 },
                         )
                 }