1 /*
<lambda>null2  * Copyright (C) 2019 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.customization.module
17 
18 import android.app.Activity
19 import android.app.WallpaperColors
20 import android.app.WallpaperManager
21 import android.content.Context
22 import android.content.Intent
23 import android.content.res.Resources
24 import android.net.Uri
25 import androidx.activity.ComponentActivity
26 import androidx.lifecycle.DefaultLifecycleObserver
27 import androidx.lifecycle.LifecycleOwner
28 import androidx.lifecycle.ViewModelProvider
29 import com.android.customization.model.color.ColorCustomizationManager
30 import com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET
31 import com.android.customization.model.color.ThemedWallpaperColorResources
32 import com.android.customization.model.color.WallpaperColorResources
33 import com.android.customization.model.grid.GridOptionsManager
34 import com.android.customization.model.mode.DarkModeSnapshotRestorer
35 import com.android.customization.model.theme.OverlayManagerCompat
36 import com.android.customization.model.themedicon.ThemedIconSwitchProvider
37 import com.android.customization.model.themedicon.data.repository.ThemeIconRepository
38 import com.android.customization.model.themedicon.domain.interactor.ThemedIconInteractor
39 import com.android.customization.model.themedicon.domain.interactor.ThemedIconSnapshotRestorer
40 import com.android.customization.module.logging.ThemesUserEventLogger
41 import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
42 import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer
43 import com.android.customization.picker.clock.ui.view.ClockViewFactory
44 import com.android.customization.picker.clock.ui.view.ThemePickerClockViewFactory
45 import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
46 import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
47 import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
48 import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer
49 import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
50 import com.android.customization.picker.grid.data.repository.GridRepositoryImpl
51 import com.android.customization.picker.grid.domain.interactor.GridInteractor
52 import com.android.customization.picker.grid.domain.interactor.GridSnapshotRestorer
53 import com.android.customization.picker.grid.ui.viewmodel.GridScreenViewModel
54 import com.android.customization.picker.notifications.domain.interactor.NotificationsSnapshotRestorer
55 import com.android.customization.picker.notifications.ui.viewmodel.NotificationSectionViewModel
56 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
57 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordanceSnapshotRestorer
58 import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
59 import com.android.customization.picker.settings.ui.viewmodel.ColorContrastSectionViewModel
60 import com.android.systemui.shared.clocks.ClockRegistry
61 import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
62 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
63 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
64 import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
65 import com.android.wallpaper.config.BaseFlags
66 import com.android.wallpaper.module.CustomizationSections
67 import com.android.wallpaper.module.FragmentFactory
68 import com.android.wallpaper.module.NetworkStatusNotifier
69 import com.android.wallpaper.module.PackageStatusNotifier
70 import com.android.wallpaper.module.PartnerProvider
71 import com.android.wallpaper.module.WallpaperPicker2Injector
72 import com.android.wallpaper.module.WallpaperPreferences
73 import com.android.wallpaper.module.logging.UserEventLogger
74 import com.android.wallpaper.network.Requester
75 import com.android.wallpaper.picker.CustomizationPickerActivity
76 import com.android.wallpaper.picker.category.wrapper.WallpaperCategoryWrapper
77 import com.android.wallpaper.picker.customization.data.content.WallpaperClient
78 import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository
79 import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
80 import com.android.wallpaper.picker.di.modules.BackgroundDispatcher
81 import com.android.wallpaper.picker.di.modules.MainDispatcher
82 import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
83 import com.android.wallpaper.system.UiModeManagerWrapper
84 import com.android.wallpaper.util.DisplayUtils
85 import dagger.Lazy
86 import javax.inject.Inject
87 import javax.inject.Singleton
88 import kotlinx.coroutines.CoroutineDispatcher
89 import kotlinx.coroutines.CoroutineScope
90 
91 @Singleton
92 open class ThemePickerInjector
93 @Inject
94 constructor(
95     @MainDispatcher private val mainScope: CoroutineScope,
96     @BackgroundDispatcher private val bgScope: CoroutineScope,
97     @BackgroundDispatcher private val bgDispatcher: CoroutineDispatcher,
98     private val colorContrastSectionViewModelFactory: Lazy<ColorContrastSectionViewModel.Factory>,
99     private val keyguardQuickAffordancePickerInteractor:
100         Lazy<KeyguardQuickAffordancePickerInteractor>,
101     private val keyguardQuickAffordanceSnapshotRestorer:
102         Lazy<KeyguardQuickAffordanceSnapshotRestorer>,
103     private val themesUserEventLogger: Lazy<ThemesUserEventLogger>,
104     private val colorPickerInteractor: Lazy<ColorPickerInteractor>,
105     private val colorPickerSnapshotRestorer: Lazy<ColorPickerSnapshotRestorer>,
106     private val clockRegistry: Lazy<ClockRegistry>,
107     private val secureSettingsRepository: Lazy<SecureSettingsRepository>,
108     private val systemSettingsRepository: Lazy<SystemSettingsRepository>,
109     private val clockPickerInteractor: Lazy<ClockPickerInteractor>,
110     private val clockPickerSnapshotRestorer: Lazy<ClockPickerSnapshotRestorer>,
111     displayUtils: Lazy<DisplayUtils>,
112     requester: Lazy<Requester>,
113     networkStatusNotifier: Lazy<NetworkStatusNotifier>,
114     partnerProvider: Lazy<PartnerProvider>,
115     val uiModeManager: Lazy<UiModeManagerWrapper>,
116     userEventLogger: Lazy<UserEventLogger>,
117     injectedWallpaperClient: Lazy<WallpaperClient>,
118     private val injectedWallpaperInteractor: Lazy<WallpaperInteractor>,
119     prefs: Lazy<WallpaperPreferences>,
120     wallpaperColorsRepository: Lazy<WallpaperColorsRepository>,
121     defaultWallpaperCategoryWrapper: Lazy<WallpaperCategoryWrapper>,
122     packageNotifier: Lazy<PackageStatusNotifier>,
123 ) :
124     WallpaperPicker2Injector(
125         mainScope,
126         displayUtils,
127         requester,
128         networkStatusNotifier,
129         partnerProvider,
130         uiModeManager,
131         userEventLogger,
132         injectedWallpaperClient,
133         injectedWallpaperInteractor,
134         prefs,
135         wallpaperColorsRepository,
136         defaultWallpaperCategoryWrapper,
137         packageNotifier,
138     ),
139     CustomizationInjector {
140     private var customizationSections: CustomizationSections? = null
141     private var keyguardQuickAffordancePickerViewModelFactory:
142         KeyguardQuickAffordancePickerViewModel.Factory? =
143         null
144     private var fragmentFactory: FragmentFactory? = null
145     private var notificationsSnapshotRestorer: NotificationsSnapshotRestorer? = null
146     private var clockCarouselViewModelFactory: ClockCarouselViewModel.Factory? = null
147     private var clockViewFactory: ClockViewFactory? = null
148     private var notificationSettingsInteractor: NotificationSettingsInteractor? = null
149     private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null
150     private var colorPickerViewModelFactory: ColorPickerViewModel.Factory? = null
151     private var colorCustomizationManager: ColorCustomizationManager? = null
152     private var darkModeSnapshotRestorer: DarkModeSnapshotRestorer? = null
153     private var themedIconSnapshotRestorer: ThemedIconSnapshotRestorer? = null
154     private var themedIconInteractor: ThemedIconInteractor? = null
155     private var clockSettingsViewModelFactory: ClockSettingsViewModel.Factory? = null
156     private var gridInteractor: GridInteractor? = null
157     private var gridSnapshotRestorer: GridSnapshotRestorer? = null
158     private var gridScreenViewModelFactory: GridScreenViewModel.Factory? = null
159 
160     override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
161         val appContext = activity.applicationContext
162         val clockViewFactory = getClockViewFactory(activity)
163         val resources = activity.resources
164         return customizationSections
165             ?: DefaultCustomizationSections(
166                     getColorPickerViewModelFactory(appContext),
167                     getKeyguardQuickAffordancePickerViewModelFactory(appContext),
168                     colorContrastSectionViewModelFactory.get(),
169                     getNotificationSectionViewModelFactory(appContext),
170                     getFlags(),
171                     getClockCarouselViewModelFactory(
172                         interactor = clockPickerInteractor.get(),
173                         clockViewFactory = clockViewFactory,
174                         resources = resources,
175                     ),
176                     clockViewFactory,
177                     getThemedIconSnapshotRestorer(appContext),
178                     getThemedIconInteractor(),
179                     getGridInteractor(appContext),
180                     colorPickerInteractor.get(),
181                     getUserEventLogger(),
182                 )
183                 .also { customizationSections = it }
184     }
185 
186     override fun getDeepLinkRedirectIntent(context: Context, uri: Uri): Intent {
187         val intent = Intent()
188         intent.setClass(context, CustomizationPickerActivity::class.java)
189         intent.data = uri
190         intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
191         return intent
192     }
193 
194     override fun getDownloadableIntentAction(): String? {
195         return null
196     }
197 
198     @Synchronized
199     override fun getUserEventLogger(): ThemesUserEventLogger {
200         return themesUserEventLogger.get()
201     }
202 
203     override fun getFragmentFactory(): FragmentFactory? {
204         return fragmentFactory ?: ThemePickerFragmentFactory().also { fragmentFactory }
205     }
206 
207     override fun getSnapshotRestorers(context: Context): Map<Int, SnapshotRestorer> {
208         return super<WallpaperPicker2Injector>.getSnapshotRestorers(context).toMutableMap().apply {
209             this[KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER] =
210                 keyguardQuickAffordanceSnapshotRestorer.get()
211             // TODO(b/285047815): Enable after adding wallpaper id for default static wallpaper
212             if (getFlags().isWallpaperRestorerEnabled()) {
213                 this[KEY_WALLPAPER_SNAPSHOT_RESTORER] = getWallpaperSnapshotRestorer(context)
214             }
215             this[KEY_NOTIFICATIONS_SNAPSHOT_RESTORER] = getNotificationsSnapshotRestorer(context)
216             this[KEY_DARK_MODE_SNAPSHOT_RESTORER] = getDarkModeSnapshotRestorer(context)
217             this[KEY_THEMED_ICON_SNAPSHOT_RESTORER] = getThemedIconSnapshotRestorer(context)
218             this[KEY_APP_GRID_SNAPSHOT_RESTORER] = getGridSnapshotRestorer(context)
219             this[KEY_COLOR_PICKER_SNAPSHOT_RESTORER] = colorPickerSnapshotRestorer.get()
220             this[KEY_CLOCKS_SNAPSHOT_RESTORER] = clockPickerSnapshotRestorer.get()
221         }
222     }
223 
224     override fun getCustomizationPreferences(context: Context): CustomizationPreferences {
225         return getPreferences(context) as CustomizationPreferences
226     }
227 
228     override fun getWallpaperInteractor(context: Context): WallpaperInteractor {
229         return injectedWallpaperInteractor.get()
230     }
231 
232     override fun getKeyguardQuickAffordancePickerInteractor(
233         context: Context
234     ): KeyguardQuickAffordancePickerInteractor {
235         return keyguardQuickAffordancePickerInteractor.get()
236     }
237 
238     fun getKeyguardQuickAffordancePickerViewModelFactory(
239         context: Context
240     ): KeyguardQuickAffordancePickerViewModel.Factory {
241         return keyguardQuickAffordancePickerViewModelFactory
242             ?: KeyguardQuickAffordancePickerViewModel.Factory(
243                     context.applicationContext,
244                     getKeyguardQuickAffordancePickerInteractor(context),
245                     getWallpaperInteractor(context),
246                     getCurrentWallpaperInfoFactory(context),
247                     getUserEventLogger(),
248                 )
249                 .also { keyguardQuickAffordancePickerViewModelFactory = it }
250     }
251 
252     fun getNotificationSectionViewModelFactory(
253         context: Context
254     ): NotificationSectionViewModel.Factory {
255         return notificationSectionViewModelFactory
256             ?: NotificationSectionViewModel.Factory(
257                     interactor = getNotificationsInteractor(context),
258                     logger = getUserEventLogger(),
259                 )
260                 .also { notificationSectionViewModelFactory = it }
261     }
262 
263     private fun getNotificationsInteractor(context: Context): NotificationSettingsInteractor {
264         return notificationSettingsInteractor
265             ?: NotificationSettingsInteractor(
266                     repository =
267                         NotificationSettingsRepository(
268                             backgroundScope = bgScope,
269                             backgroundDispatcher = bgDispatcher,
270                             secureSettingsRepository = secureSettingsRepository.get(),
271                             systemSettingsRepository = systemSettingsRepository.get(),
272                         )
273                 )
274                 .also { notificationSettingsInteractor = it }
275     }
276 
277     private fun getNotificationsSnapshotRestorer(context: Context): NotificationsSnapshotRestorer {
278         return notificationsSnapshotRestorer
279             ?: NotificationsSnapshotRestorer(
280                     interactor = getNotificationsInteractor(context = context),
281                     backgroundScope = bgScope,
282                 )
283                 .also { notificationsSnapshotRestorer = it }
284     }
285 
286     override fun getClockCarouselViewModelFactory(
287         interactor: ClockPickerInteractor,
288         clockViewFactory: ClockViewFactory,
289         resources: Resources,
290     ): ClockCarouselViewModel.Factory {
291         return clockCarouselViewModelFactory
292             ?: ClockCarouselViewModel.Factory(
293                     interactor,
294                     bgDispatcher,
295                     clockViewFactory,
296                     resources,
297                     getUserEventLogger(),
298                 )
299                 .also { clockCarouselViewModelFactory = it }
300     }
301 
302     override fun getClockViewFactory(activity: ComponentActivity): ClockViewFactory {
303         return clockViewFactory
304             ?: ThemePickerClockViewFactory(
305                     activity,
306                     WallpaperManager.getInstance(activity.applicationContext),
307                     clockRegistry.get(),
308                 )
309                 .also {
310                     clockViewFactory = it
311                     activity.lifecycle.addObserver(
312                         object : DefaultLifecycleObserver {
313                             override fun onDestroy(owner: LifecycleOwner) {
314                                 super.onDestroy(owner)
315                                 if ((owner as Activity).isChangingConfigurations()) return
316                                 clockViewFactory?.onDestroy()
317                             }
318                         }
319                     )
320                 }
321     }
322 
323     override fun getWallpaperColorResources(
324         wallpaperColors: WallpaperColors,
325         context: Context,
326     ): WallpaperColorResources {
327         return ThemedWallpaperColorResources(wallpaperColors, secureSettingsRepository.get())
328     }
329 
330     override fun getColorPickerViewModelFactory(context: Context): ColorPickerViewModel.Factory {
331         return colorPickerViewModelFactory
332             ?: ColorPickerViewModel.Factory(
333                     context.applicationContext,
334                     colorPickerInteractor.get(),
335                     getUserEventLogger(),
336                 )
337                 .also { colorPickerViewModelFactory = it }
338     }
339 
340     private fun getColorCustomizationManager(context: Context): ColorCustomizationManager {
341         return colorCustomizationManager
342             ?: ColorCustomizationManager.getInstance(context, OverlayManagerCompat(context)).also {
343                 colorCustomizationManager = it
344             }
345     }
346 
347     fun getDarkModeSnapshotRestorer(context: Context): DarkModeSnapshotRestorer {
348         val appContext = context.applicationContext
349         return darkModeSnapshotRestorer
350             ?: DarkModeSnapshotRestorer(
351                     context = appContext,
352                     manager = uiModeManager.get(),
353                     backgroundDispatcher = bgDispatcher,
354                 )
355                 .also { darkModeSnapshotRestorer = it }
356     }
357 
358     protected fun getThemedIconSnapshotRestorer(context: Context): ThemedIconSnapshotRestorer {
359         val optionProvider = ThemedIconSwitchProvider.getInstance(context)
360         return themedIconSnapshotRestorer
361             ?: ThemedIconSnapshotRestorer(
362                     isActivated = { optionProvider.isThemedIconEnabled },
363                     setActivated = { isActivated ->
364                         optionProvider.isThemedIconEnabled = isActivated
365                     },
366                     interactor = getThemedIconInteractor(),
367                 )
368                 .also { themedIconSnapshotRestorer = it }
369     }
370 
371     protected fun getThemedIconInteractor(): ThemedIconInteractor {
372         return themedIconInteractor
373             ?: ThemedIconInteractor(repository = ThemeIconRepository()).also {
374                 themedIconInteractor = it
375             }
376     }
377 
378     override fun getClockSettingsViewModelFactory(
379         context: Context,
380         wallpaperColorsRepository: WallpaperColorsRepository,
381         clockViewFactory: ClockViewFactory,
382     ): ClockSettingsViewModel.Factory {
383         return clockSettingsViewModelFactory
384             ?: ClockSettingsViewModel.Factory(
385                     context.applicationContext,
386                     clockPickerInteractor.get(),
387                     colorPickerInteractor.get(),
388                     getUserEventLogger(),
389                 ) { clockId ->
390                     clockId?.let { clockViewFactory.getController(clockId).config.isReactiveToTone }
391                         ?: false
392                 }
393                 .also { clockSettingsViewModelFactory = it }
394     }
395 
396     fun getGridScreenViewModelFactory(context: Context): ViewModelProvider.Factory {
397         return gridScreenViewModelFactory
398             ?: GridScreenViewModel.Factory(
399                     context = context,
400                     interactor = getGridInteractor(context),
401                 )
402                 .also { gridScreenViewModelFactory = it }
403     }
404 
405     fun getGridInteractor(context: Context): GridInteractor {
406         val appContext = context.applicationContext
407         return gridInteractor
408             ?: GridInteractor(
409                     applicationScope = getApplicationCoroutineScope(),
410                     repository =
411                         GridRepositoryImpl(
412                             applicationScope = getApplicationCoroutineScope(),
413                             manager = GridOptionsManager.getInstance(context),
414                             backgroundDispatcher = bgDispatcher,
415                             isGridApplyButtonEnabled =
416                                 BaseFlags.get().isGridApplyButtonEnabled(appContext),
417                         ),
418                     snapshotRestorer = { getGridSnapshotRestorer(appContext) },
419                 )
420                 .also { gridInteractor = it }
421     }
422 
423     private fun getGridSnapshotRestorer(context: Context): GridSnapshotRestorer {
424         return gridSnapshotRestorer
425             ?: GridSnapshotRestorer(interactor = getGridInteractor(context)).also {
426                 gridSnapshotRestorer = it
427             }
428     }
429 
430     override fun isCurrentSelectedColorPreset(context: Context): Boolean {
431         val colorManager =
432             ColorCustomizationManager.getInstance(context, OverlayManagerCompat(context))
433         return COLOR_SOURCE_PRESET == colorManager.currentColorSource
434     }
435 
436     companion object {
437         @JvmStatic
438         private val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER =
439             WallpaperPicker2Injector.MIN_SNAPSHOT_RESTORER_KEY
440         @JvmStatic
441         private val KEY_WALLPAPER_SNAPSHOT_RESTORER = KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER + 1
442         @JvmStatic
443         private val KEY_NOTIFICATIONS_SNAPSHOT_RESTORER = KEY_WALLPAPER_SNAPSHOT_RESTORER + 1
444         @JvmStatic
445         private val KEY_DARK_MODE_SNAPSHOT_RESTORER = KEY_NOTIFICATIONS_SNAPSHOT_RESTORER + 1
446         @JvmStatic
447         private val KEY_THEMED_ICON_SNAPSHOT_RESTORER = KEY_DARK_MODE_SNAPSHOT_RESTORER + 1
448         @JvmStatic
449         private val KEY_APP_GRID_SNAPSHOT_RESTORER = KEY_THEMED_ICON_SNAPSHOT_RESTORER + 1
450         @JvmStatic
451         private val KEY_COLOR_PICKER_SNAPSHOT_RESTORER = KEY_APP_GRID_SNAPSHOT_RESTORER + 1
452         @JvmStatic private val KEY_CLOCKS_SNAPSHOT_RESTORER = KEY_COLOR_PICKER_SNAPSHOT_RESTORER + 1
453 
454         /**
455          * When this injector is overridden, this is the minimal value that should be used by
456          * restorers returns in [getSnapshotRestorers].
457          *
458          * It should always be greater than the biggest restorer key.
459          */
460         @JvmStatic protected val MIN_SNAPSHOT_RESTORER_KEY = KEY_CLOCKS_SNAPSHOT_RESTORER + 1
461     }
462 }
463