/* * 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.wallpaper.picker.preview.ui.binder import android.content.Context import android.graphics.Point import android.view.SurfaceView import android.view.View import androidx.cardview.widget.CardView import androidx.core.view.ViewCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.transition.Transition import androidx.transition.TransitionListenerAdapter import com.android.wallpaper.R import com.android.wallpaper.config.BaseFlags import com.android.wallpaper.model.Screen import com.android.wallpaper.model.wallpaper.DeviceDisplayType import com.android.wallpaper.picker.data.WallpaperModel import com.android.wallpaper.picker.preview.ui.fragment.SmallPreviewFragment import com.android.wallpaper.picker.preview.ui.viewmodel.FullPreviewConfigViewModel import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel.Companion.PreviewScreen import com.android.wallpaper.util.wallpaperconnection.WallpaperConnectionUtils import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch object SmallPreviewBinder { interface Binding { fun showSurfaces() fun hideSurfaces() } // TODO(b/339081035): Remove null case for isFoldable with the flag fun bind( applicationContext: Context, view: View, viewModel: WallpaperPreviewViewModel, screen: Screen, displaySize: Point, deviceDisplayType: DeviceDisplayType, mainScope: CoroutineScope, viewLifecycleOwner: LifecycleOwner, currentNavDestId: Int, navigate: ((View) -> Unit)? = null, transition: Transition? = null, transitionConfig: FullPreviewConfigViewModel? = null, wallpaperConnectionUtils: WallpaperConnectionUtils, isFirstBindingDeferred: CompletableDeferred, onPreviewReady: ((Screen) -> Unit)? = null, onStartTransition: (() -> Unit)? = null, onPreviewSurfaceDestroyed: ((Screen) -> Unit)? = null, isFoldable: Boolean? = null, ): Binding { val previewCard: CardView = view.requireViewById(R.id.preview_card) val foldedStateDescription = when (deviceDisplayType) { DeviceDisplayType.FOLDED -> view.context.getString(R.string.folded_device_state_description) DeviceDisplayType.UNFOLDED -> view.context.getString(R.string.unfolded_device_state_description) else -> "" } val screenName = when (screen) { Screen.LOCK_SCREEN -> view.context.getString(R.string.lock_screen_tab) Screen.HOME_SCREEN -> view.context.getString(R.string.home_screen_tab) } val isWallpaperEditable = if (viewModel.wallpaper.value is WallpaperModel.LiveWallpaperModel) { false } else { !(viewModel.wallpaper.value is WallpaperModel.StaticWallpaperModel && (viewModel.wallpaper.value as? WallpaperModel.StaticWallpaperModel) ?.downloadableWallpaperData != null) } previewCard.contentDescription = if (isWallpaperEditable) { view.context.getString( R.string.wallpaper_preview_card_content_description_editable, screenName, foldedStateDescription, ) } else { view.context.getString( R.string.wallpaper_preview_card_content_description_non_editable, screenName, foldedStateDescription, ) } val wallpaperSurface = view.requireViewById(R.id.wallpaper_surface) val workspaceSurface = view.requireViewById(R.id.workspace_surface) // Don't set radius for set wallpaper dialog if (!viewModel.showSetWallpaperDialog.value) { // When putting the surface on top for full transition, the card view is behind the // surface view so we need to apply radius on surface view instead, posting to get the // final radius previewCard.post { wallpaperSurface.cornerRadius = previewCard.radius workspaceSurface.cornerRadius = previewCard.radius } } // Set transition names to enable the small to full preview enter and return shared // element transitions. val transitionName = when (screen) { Screen.LOCK_SCREEN -> when (deviceDisplayType) { DeviceDisplayType.SINGLE -> SmallPreviewFragment.SMALL_PREVIEW_LOCK_SHARED_ELEMENT_ID DeviceDisplayType.FOLDED -> SmallPreviewFragment.SMALL_PREVIEW_LOCK_FOLDED_SHARED_ELEMENT_ID DeviceDisplayType.UNFOLDED -> SmallPreviewFragment.SMALL_PREVIEW_LOCK_UNFOLDED_SHARED_ELEMENT_ID } Screen.HOME_SCREEN -> when (deviceDisplayType) { DeviceDisplayType.SINGLE -> SmallPreviewFragment.SMALL_PREVIEW_HOME_SHARED_ELEMENT_ID DeviceDisplayType.FOLDED -> SmallPreviewFragment.SMALL_PREVIEW_HOME_FOLDED_SHARED_ELEMENT_ID DeviceDisplayType.UNFOLDED -> SmallPreviewFragment.SMALL_PREVIEW_HOME_UNFOLDED_SHARED_ELEMENT_ID } } ViewCompat.setTransitionName(previewCard, transitionName) var transitionDisposableHandle: DisposableHandle? = null val transitionListener = if (transition == null || transitionConfig == null) null else object : TransitionListenerAdapter() { // All surface views are initially visible in the XML to enable smoother // transitions. Only hide the surface views not used in the shared element // transition until the transition ends to avoid issues with multiple surface // views // overlapping. override fun onTransitionStart(transition: Transition) { super.onTransitionStart(transition) if ( transitionConfig.screen == screen && transitionConfig.deviceDisplayType == deviceDisplayType ) { wallpaperSurface.setZOrderOnTop(true) workspaceSurface.setZOrderOnTop(true) } } override fun onTransitionEnd(transition: Transition) { super.onTransitionEnd(transition) if ( transitionConfig.screen == screen && transitionConfig.deviceDisplayType == deviceDisplayType ) { wallpaperSurface.setZOrderMediaOverlay(true) workspaceSurface.setZOrderMediaOverlay(true) } transition.removeListener(this) transitionDisposableHandle = null } } viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { transitionListener?.let { // If transitionListener is not null so do transition and transitionConfig wallpaperSurface.visibility = View.VISIBLE workspaceSurface.visibility = View.VISIBLE transition!!.addListener(it) transitionDisposableHandle = DisposableHandle { transition.removeListener(it) } } if (R.id.smallPreviewFragment == currentNavDestId) { combine( viewModel.onSmallPreviewClicked(screen, deviceDisplayType) { navigate?.invoke(previewCard) }, viewModel.currentPreviewScreen, viewModel.smallPreviewSelectedTab, ) { onClick, previewScreen, tab -> Triple(onClick, previewScreen, tab) } .collect { (onClick, previewScreen, tab) -> if ( BaseFlags.get().isNewPickerUi() && previewScreen != PreviewScreen.SMALL_PREVIEW ) { view.setOnClickListener(null) } else { onClick?.let { view.setOnClickListener { // If tab != screen, it's pager switching tab and no need to // set z order if (BaseFlags.get().isNewPickerUi() && tab == screen) { // Set top z order for shared element transition wallpaperSurface.setZOrderOnTop(true) workspaceSurface.setZOrderOnTop(true) } it() } } ?: view.setOnClickListener(null) } } } else if (R.id.setWallpaperDialog == currentNavDestId) { previewCard.radius = previewCard.resources.getDimension( R.dimen.set_wallpaper_dialog_preview_corner_radius ) } } // Remove transition listeners on destroy transitionDisposableHandle?.dispose() transitionDisposableHandle = null // Remove on click listener when on destroyed view.setOnClickListener(null) } val config = viewModel.getWorkspacePreviewConfig(screen, deviceDisplayType) WorkspacePreviewBinder.bind(workspaceSurface, config, viewModel, viewLifecycleOwner) val binding = SmallWallpaperPreviewBinder.bind( surface = wallpaperSurface, viewModel = viewModel, screen = screen, displaySize = displaySize, applicationContext = applicationContext, mainScope = mainScope, viewLifecycleOwner = viewLifecycleOwner, deviceDisplayType = deviceDisplayType, wallpaperConnectionUtils = wallpaperConnectionUtils, isFirstBindingDeferred = isFirstBindingDeferred, onPreviewReady = onPreviewReady, onStartTransition = onStartTransition, onPreviewSurfaceDestroyed = onPreviewSurfaceDestroyed, isFoldable = isFoldable, ) return object : Binding { override fun showSurfaces() { wallpaperSurface.visibility = View.VISIBLE workspaceSurface.visibility = View.VISIBLE } override fun hideSurfaces() { wallpaperSurface.visibility = View.INVISIBLE workspaceSurface.visibility = View.INVISIBLE binding.destroy() } } } private fun SurfaceView.startFadeInAnimation(duration: Long) { animate().alpha(1f).setDuration(duration).start() } }