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) }
},
)
}