1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.wallpaper.picker.preview.ui
17 
18 import android.content.Context
19 import android.content.Intent
20 import android.content.pm.ActivityInfo
21 import android.graphics.Color
22 import android.os.Bundle
23 import android.view.Window
24 import android.widget.Toast
25 import androidx.activity.result.contract.ActivityResultContracts
26 import androidx.activity.viewModels
27 import androidx.core.view.WindowCompat
28 import androidx.lifecycle.lifecycleScope
29 import androidx.navigation.fragment.NavHostFragment
30 import com.android.wallpaper.R
31 import com.android.wallpaper.model.ImageWallpaperInfo
32 import com.android.wallpaper.model.WallpaperInfo
33 import com.android.wallpaper.module.InjectorProvider
34 import com.android.wallpaper.picker.AppbarFragment
35 import com.android.wallpaper.picker.BasePreviewActivity
36 import com.android.wallpaper.picker.data.WallpaperModel
37 import com.android.wallpaper.picker.di.modules.MainDispatcher
38 import com.android.wallpaper.picker.preview.data.repository.CreativeEffectsRepository
39 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository
40 import com.android.wallpaper.picker.preview.data.repository.WallpaperPreviewRepository
41 import com.android.wallpaper.picker.preview.data.util.LiveWallpaperDownloader
42 import com.android.wallpaper.picker.preview.ui.fragment.SmallPreviewFragment
43 import com.android.wallpaper.picker.preview.ui.viewmodel.PreviewActionsViewModel.Companion.getEditActivityIntent
44 import com.android.wallpaper.picker.preview.ui.viewmodel.PreviewActionsViewModel.Companion.isNewCreativeWallpaper
45 import com.android.wallpaper.picker.preview.ui.viewmodel.WallpaperPreviewViewModel
46 import com.android.wallpaper.util.ActivityUtils
47 import com.android.wallpaper.util.DisplayUtils
48 import com.android.wallpaper.util.WallpaperConnection
49 import com.android.wallpaper.util.converter.WallpaperModelFactory
50 import com.android.wallpaper.util.wallpaperconnection.WallpaperConnectionUtils
51 import dagger.hilt.android.AndroidEntryPoint
52 import dagger.hilt.android.qualifiers.ApplicationContext
53 import javax.inject.Inject
54 import kotlinx.coroutines.CoroutineScope
55 import kotlinx.coroutines.launch
56 
57 /** This activity holds the flow for the preview screen. */
58 @AndroidEntryPoint(BasePreviewActivity::class)
59 class WallpaperPreviewActivity :
60     Hilt_WallpaperPreviewActivity(), AppbarFragment.AppbarFragmentHost {
61     @ApplicationContext @Inject lateinit var appContext: Context
62     @Inject lateinit var displayUtils: DisplayUtils
63     @Inject lateinit var wallpaperModelFactory: WallpaperModelFactory
64     @Inject lateinit var wallpaperPreviewRepository: WallpaperPreviewRepository
65     @Inject lateinit var imageEffectsRepository: ImageEffectsRepository
66     @Inject lateinit var creativeEffectsRepository: CreativeEffectsRepository
67     @Inject lateinit var liveWallpaperDownloader: LiveWallpaperDownloader
68     @MainDispatcher @Inject lateinit var mainScope: CoroutineScope
69 
70     private val wallpaperPreviewViewModel: WallpaperPreviewViewModel by viewModels()
71 
72     override fun onCreate(savedInstanceState: Bundle?) {
73         window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
74         super.onCreate(savedInstanceState)
75         enforcePortraitForHandheldAndFoldedDisplay()
76         wallpaperPreviewViewModel.updateDisplayConfiguration()
77         wallpaperPreviewViewModel.setIsWallpaperColorPreviewEnabled(
78             !InjectorProvider.getInjector().isCurrentSelectedColorPreset(appContext)
79         )
80         window.navigationBarColor = Color.TRANSPARENT
81         window.statusBarColor = Color.TRANSPARENT
82         setContentView(R.layout.activity_wallpaper_preview)
83         val wallpaper =
84             checkNotNull(intent.getParcelableExtra(EXTRA_WALLPAPER_INFO, WallpaperInfo::class.java))
85                 .convertToWallpaperModel()
86         val navController =
87             (supportFragmentManager.findFragmentById(R.id.wallpaper_preview_nav_host)
88                     as NavHostFragment)
89                 .navController
90         val graph = navController.navInflater.inflate(R.navigation.wallpaper_preview_nav_graph)
91         val startDestinationArgs: Bundle? =
92             (wallpaper as? WallpaperModel.LiveWallpaperModel)
93                 ?.let {
94                     if (it.isNewCreativeWallpaper()) it.getNewCreativeWallpaperArgs() else null
95                 }
96                 ?.also {
97                     // For creating a new creative wallpaper, replace the default start destination
98                     // with CreativeEditPreviewFragment.
99                     graph.setStartDestination(R.id.creativeEditPreviewFragment)
100                 }
101         navController.setGraph(graph, startDestinationArgs)
102         // Fits screen to navbar and statusbar
103         WindowCompat.setDecorFitsSystemWindows(window, ActivityUtils.isSUWMode(this))
104         val isAssetIdPresent = intent.getBooleanExtra(IS_ASSET_ID_PRESENT, false)
105         wallpaperPreviewViewModel.isNewTask = intent.getBooleanExtra(IS_NEW_TASK, false)
106         if (savedInstanceState == null) {
107             wallpaperPreviewRepository.setWallpaperModel(wallpaper)
108         }
109         val whichPreview =
110             if (isAssetIdPresent) WallpaperConnection.WhichPreview.EDIT_NON_CURRENT
111             else WallpaperConnection.WhichPreview.EDIT_CURRENT
112         wallpaperPreviewViewModel.setWhichPreview(whichPreview)
113         if (wallpaper is WallpaperModel.StaticWallpaperModel) {
114             wallpaper.staticWallpaperData.cropHints?.let {
115                 wallpaperPreviewViewModel.setCropHints(it)
116             }
117         }
118         if (
119             (wallpaper as? WallpaperModel.StaticWallpaperModel)?.downloadableWallpaperData != null
120         ) {
121             liveWallpaperDownloader.initiateDownloadableService(
122                 this,
123                 wallpaper,
124                 registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {}
125             )
126         }
127 
128         val creativeWallpaperEffectData =
129             (wallpaper as? WallpaperModel.LiveWallpaperModel)
130                 ?.creativeWallpaperData
131                 ?.creativeWallpaperEffectsData
132         if (creativeWallpaperEffectData != null) {
133             lifecycleScope.launch {
134                 creativeEffectsRepository.initializeEffect(creativeWallpaperEffectData)
135             }
136         } else if (
137             (wallpaper as? WallpaperModel.StaticWallpaperModel)?.imageWallpaperData != null &&
138                 imageEffectsRepository.areEffectsAvailable()
139         ) {
140             lifecycleScope.launch {
141                 imageEffectsRepository.initializeEffect(
142                     staticWallpaperModel = wallpaper,
143                     onWallpaperModelUpdated = { wallpaper ->
144                         wallpaperPreviewRepository.setWallpaperModel(wallpaper)
145                     },
146                 )
147             }
148         }
149     }
150 
151     override fun onUpArrowPressed() {
152         onBackPressedDispatcher.onBackPressed()
153     }
154 
155     override fun isUpArrowSupported(): Boolean {
156         return !ActivityUtils.isSUWMode(baseContext)
157     }
158 
159     override fun onResume() {
160         super.onResume()
161         if (isInMultiWindowMode) {
162             Toast.makeText(this, R.string.wallpaper_exit_split_screen, Toast.LENGTH_SHORT).show()
163             onBackPressedDispatcher.onBackPressed()
164         }
165     }
166 
167     override fun onDestroy() {
168         imageEffectsRepository.destroy()
169         creativeEffectsRepository.destroy()
170         liveWallpaperDownloader.cleanup()
171         // TODO(b/333879532): Only disconnect when leaving the Activity without introducing black
172         //  preview. If onDestroy is caused by an orientation change, we should keep the connection
173         //  to avoid initiating the engines again.
174         // TODO(b/328302105): MainScope ensures the job gets done non-blocking even if the
175         //   activity has been destroyed already. Consider making this part of
176         //   WallpaperConnectionUtils.
177         (wallpaperPreviewViewModel.wallpaper.value as? WallpaperModel.LiveWallpaperModel)?.let {
178             // Keep a copy of current wallpaperPreviewViewModel.wallpaperDisplaySize as what we want
179             // to disconnect. There's a chance mainScope executes the job not until new activity
180             // is created and the wallpaperDisplaySize is updated to a new one, e.g. when
181             // orientation changed.
182             // TODO(b/328302105): maintain this state in WallpaperConnectionUtils.
183             val currentWallpaperDisplay = wallpaperPreviewViewModel.wallpaperDisplaySize.value
184             mainScope.launch {
185                 WallpaperConnectionUtils.disconnect(
186                     appContext,
187                     it,
188                     wallpaperPreviewViewModel.smallerDisplaySize
189                 )
190                 WallpaperConnectionUtils.disconnect(
191                     appContext,
192                     it,
193                     currentWallpaperDisplay,
194                 )
195             }
196         }
197 
198         super.onDestroy()
199     }
200 
201     private fun WallpaperInfo.convertToWallpaperModel(): WallpaperModel {
202         return wallpaperModelFactory.getWallpaperModel(appContext, this)
203     }
204 
205     companion object {
206         /**
207          * Returns a new [Intent] that can be used to start [WallpaperPreviewActivity].
208          *
209          * @param context application context.
210          * @param wallpaperInfo selected by user for editing preview.
211          * @param isNewTask true to launch at a new task.
212          *
213          * TODO(b/291761856): Use wallpaper model to replace wallpaper info.
214          */
215         fun newIntent(
216             context: Context,
217             wallpaperInfo: WallpaperInfo,
218             isAssetIdPresent: Boolean,
219             isViewAsHome: Boolean = false,
220             isNewTask: Boolean = false,
221         ): Intent {
222             val intent = Intent(context.applicationContext, WallpaperPreviewActivity::class.java)
223             if (isNewTask) {
224                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
225             }
226             intent.putExtra(EXTRA_WALLPAPER_INFO, wallpaperInfo)
227             intent.putExtra(IS_ASSET_ID_PRESENT, isAssetIdPresent)
228             intent.putExtra(EXTRA_VIEW_AS_HOME, isViewAsHome)
229             intent.putExtra(IS_NEW_TASK, isNewTask)
230             return intent
231         }
232 
233         /**
234          * Returns a new [Intent] that can be used to start [WallpaperPreviewActivity], explicitly
235          * propagating any permissions on the wallpaper data to the new [Intent].
236          *
237          * @param context application context.
238          * @param wallpaperInfo selected by user for editing preview.
239          * @param isNewTask true to launch at a new task.
240          *
241          * TODO(b/291761856): Use wallpaper model to replace wallpaper info.
242          */
243         fun newIntent(
244             context: Context,
245             originalIntent: Intent,
246             isAssetIdPresent: Boolean,
247             isViewAsHome: Boolean = false,
248             isNewTask: Boolean = false,
249         ): Intent {
250             val data = originalIntent.data
251             val intent =
252                 newIntent(
253                     context,
254                     ImageWallpaperInfo(data),
255                     isAssetIdPresent,
256                     isViewAsHome,
257                     isNewTask
258                 )
259             // Both these lines are required for permission propagation
260             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
261             intent.setData(data)
262             return intent
263         }
264 
265         private fun WallpaperModel.LiveWallpaperModel.getNewCreativeWallpaperArgs() =
266             Bundle().apply {
267                 putParcelable(
268                     SmallPreviewFragment.ARG_EDIT_INTENT,
269                     liveWallpaperData.getEditActivityIntent(true),
270                 )
271             }
272     }
273 
274     /**
275      * If the display is a handheld display or a folded display from a foldable, we enforce the
276      * activity to be portrait.
277      *
278      * This method should be called upon initialization of this activity, and whenever there is a
279      * configuration change.
280      */
281     private fun enforcePortraitForHandheldAndFoldedDisplay() {
282         val wantedOrientation =
283             if (displayUtils.isLargeScreenOrUnfoldedDisplay(this))
284                 ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
285             else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
286         if (requestedOrientation != wantedOrientation) {
287             requestedOrientation = wantedOrientation
288         }
289     }
290 }
291