/* * Copyright (C) 2019 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.module import android.app.Activity import android.app.UiModeManager import android.app.WallpaperManager import android.content.Context import android.content.Intent import android.content.res.Resources import android.net.Uri import android.text.TextUtils import androidx.activity.ComponentActivity import androidx.fragment.app.FragmentActivity import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelProvider import com.android.customization.model.color.ColorCustomizationManager import com.android.customization.model.color.ColorOptionsProvider import com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET import com.android.customization.model.grid.GridOptionsManager import com.android.customization.model.grid.data.repository.GridRepositoryImpl import com.android.customization.model.grid.domain.interactor.GridInteractor import com.android.customization.model.grid.domain.interactor.GridSnapshotRestorer import com.android.customization.model.grid.ui.viewmodel.GridScreenViewModel import com.android.customization.model.mode.DarkModeSnapshotRestorer import com.android.customization.model.theme.OverlayManagerCompat import com.android.customization.model.theme.ThemeBundleProvider import com.android.customization.model.theme.ThemeManager import com.android.customization.model.themedicon.ThemedIconSwitchProvider import com.android.customization.model.themedicon.data.repository.ThemeIconRepository import com.android.customization.model.themedicon.domain.interactor.ThemedIconInteractor import com.android.customization.model.themedicon.domain.interactor.ThemedIconSnapshotRestorer import com.android.customization.picker.clock.data.repository.ClockPickerRepositoryImpl import com.android.customization.picker.clock.data.repository.ClockRegistryProvider import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer import com.android.customization.picker.clock.ui.view.ClockViewFactory import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel import com.android.customization.picker.clock.utils.ClockDescriptionUtils import com.android.customization.picker.clock.utils.ThemePickerClockDescriptionUtils import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel import com.android.customization.picker.notifications.data.repository.NotificationsRepository import com.android.customization.picker.notifications.domain.interactor.NotificationsInteractor import com.android.customization.picker.notifications.domain.interactor.NotificationsSnapshotRestorer import com.android.customization.picker.notifications.ui.viewmodel.NotificationSectionViewModel import com.android.customization.picker.quickaffordance.data.repository.KeyguardQuickAffordancePickerRepository import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordanceSnapshotRestorer import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.shared.customization.data.content.CustomizationProviderClient import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl import com.android.wallpaper.config.BaseFlags import com.android.wallpaper.dispatchers.BackgroundDispatcher import com.android.wallpaper.dispatchers.MainDispatcher import com.android.wallpaper.model.WallpaperColorsViewModel import com.android.wallpaper.module.CustomizationSections import com.android.wallpaper.module.FragmentFactory import com.android.wallpaper.module.UserEventLogger import com.android.wallpaper.module.WallpaperPicker2Injector import com.android.wallpaper.picker.CustomizationPickerActivity import com.android.wallpaper.picker.customization.data.content.WallpaperClientImpl import com.android.wallpaper.picker.customization.data.repository.WallpaperRepository import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer import com.android.wallpaper.util.ScreenSizeCalculator import javax.inject.Inject import javax.inject.Singleton import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @Singleton open class ThemePickerInjector @Inject internal constructor( @MainDispatcher private val mainScope: CoroutineScope, @MainDispatcher private val mainDispatcher: CoroutineDispatcher, @BackgroundDispatcher private val bgDispatcher: CoroutineDispatcher, ) : WallpaperPicker2Injector(mainScope, bgDispatcher), CustomizationInjector { private var customizationSections: CustomizationSections? = null private var userEventLogger: UserEventLogger? = null private var wallpaperInteractor: WallpaperInteractor? = null private var keyguardQuickAffordancePickerInteractor: KeyguardQuickAffordancePickerInteractor? = null private var keyguardQuickAffordancePickerViewModelFactory: KeyguardQuickAffordancePickerViewModel.Factory? = null private var customizationProviderClient: CustomizationProviderClient? = null private var fragmentFactory: FragmentFactory? = null private var keyguardQuickAffordanceSnapshotRestorer: KeyguardQuickAffordanceSnapshotRestorer? = null private var notificationsSnapshotRestorer: NotificationsSnapshotRestorer? = null private var clockPickerInteractor: ClockPickerInteractor? = null private var clockSectionViewModel: ClockSectionViewModel? = null private var clockCarouselViewModelFactory: ClockCarouselViewModel.Factory? = null private var clockViewFactory: ClockViewFactory? = null private var clockPickerSnapshotRestorer: ClockPickerSnapshotRestorer? = null private var notificationsInteractor: NotificationsInteractor? = null private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null private var colorPickerInteractor: ColorPickerInteractor? = null private var colorPickerViewModelFactory: ColorPickerViewModel.Factory? = null private var colorPickerSnapshotRestorer: ColorPickerSnapshotRestorer? = null private var colorCustomizationManager: ColorCustomizationManager? = null private var darkModeSnapshotRestorer: DarkModeSnapshotRestorer? = null private var themedIconSnapshotRestorer: ThemedIconSnapshotRestorer? = null private var themedIconInteractor: ThemedIconInteractor? = null private var clockSettingsViewModelFactory: ClockSettingsViewModel.Factory? = null private var clockDescriptionUtils: ClockDescriptionUtils? = null private var gridInteractor: GridInteractor? = null private var gridSnapshotRestorer: GridSnapshotRestorer? = null private var gridScreenViewModelFactory: GridScreenViewModel.Factory? = null private var clockRegistryProvider: ClockRegistryProvider? = null override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections { return customizationSections ?: DefaultCustomizationSections( getColorPickerViewModelFactory( context = activity, wallpaperColorsViewModel = getWallpaperColorsViewModel(), ), getKeyguardQuickAffordancePickerInteractor(activity), getKeyguardQuickAffordancePickerViewModelFactory(activity), NotificationSectionViewModel.Factory( interactor = getNotificationsInteractor(activity), ), getFlags(), getClockCarouselViewModelFactory( getClockPickerInteractor(activity.applicationContext), ), getClockViewFactory(activity), getDarkModeSnapshotRestorer(activity), getThemedIconSnapshotRestorer(activity), getThemedIconInteractor(), getColorPickerInteractor(activity, getWallpaperColorsViewModel()), ) .also { customizationSections = it } } override fun getDeepLinkRedirectIntent(context: Context, uri: Uri): Intent { val intent = Intent() intent.setClass(context, CustomizationPickerActivity::class.java) intent.data = uri intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK return intent } override fun getDownloadableIntentAction(): String? { return null } @Synchronized override fun getUserEventLogger(context: Context): ThemesUserEventLogger { return userEventLogger as? ThemesUserEventLogger ?: StatsLogUserEventLogger( getPreferences(context.applicationContext), getWallpaperStatusChecker(context.applicationContext), ) .also { userEventLogger = it } } override fun getFragmentFactory(): FragmentFactory? { return fragmentFactory ?: ThemePickerFragmentFactory().also { fragmentFactory } } override fun getSnapshotRestorers( context: Context, ): Map { return super.getSnapshotRestorers(context).toMutableMap().apply { this[KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER] = getKeyguardQuickAffordanceSnapshotRestorer(context) // TODO(b/285047815): Enable after adding wallpaper id for default static wallpaper if (getFlags().isWallpaperRestorerEnabled()) { this[KEY_WALLPAPER_SNAPSHOT_RESTORER] = getWallpaperSnapshotRestorer(context) } this[KEY_NOTIFICATIONS_SNAPSHOT_RESTORER] = getNotificationsSnapshotRestorer(context) this[KEY_DARK_MODE_SNAPSHOT_RESTORER] = getDarkModeSnapshotRestorer(context) this[KEY_THEMED_ICON_SNAPSHOT_RESTORER] = getThemedIconSnapshotRestorer(context) this[KEY_APP_GRID_SNAPSHOT_RESTORER] = getGridSnapshotRestorer(context) this[KEY_COLOR_PICKER_SNAPSHOT_RESTORER] = getColorPickerSnapshotRestorer(context, getWallpaperColorsViewModel()) this[KEY_CLOCKS_SNAPSHOT_RESTORER] = getClockPickerSnapshotRestorer(context) } } override fun getCustomizationPreferences(context: Context): CustomizationPreferences { return getPreferences(context) as CustomizationPreferences } override fun getThemeManager( provider: ThemeBundleProvider, activity: FragmentActivity, overlayManagerCompat: OverlayManagerCompat, logger: ThemesUserEventLogger ): ThemeManager { return ThemeManager(provider, activity, overlayManagerCompat, logger) } override fun getWallpaperInteractor(context: Context): WallpaperInteractor { val appContext = context.applicationContext return wallpaperInteractor ?: WallpaperInteractor( repository = WallpaperRepository( scope = getApplicationCoroutineScope(), client = WallpaperClientImpl( context = appContext, infoFactory = getCurrentWallpaperInfoFactory(appContext), wallpaperManager = WallpaperManager.getInstance(appContext) ), wallpaperPreferences = getPreferences(context = appContext), backgroundDispatcher = bgDispatcher, ), shouldHandleReload = { TextUtils.equals( getColorCustomizationManager(appContext).currentColorSource, ColorOptionsProvider.COLOR_SOURCE_PRESET ) } ) .also { wallpaperInteractor = it } } override fun getKeyguardQuickAffordancePickerInteractor( context: Context ): KeyguardQuickAffordancePickerInteractor { return keyguardQuickAffordancePickerInteractor ?: getKeyguardQuickAffordancePickerInteractorImpl(context).also { keyguardQuickAffordancePickerInteractor = it } } fun getKeyguardQuickAffordancePickerViewModelFactory( context: Context ): KeyguardQuickAffordancePickerViewModel.Factory { return keyguardQuickAffordancePickerViewModelFactory ?: KeyguardQuickAffordancePickerViewModel.Factory( context.applicationContext, getKeyguardQuickAffordancePickerInteractor(context), getWallpaperInteractor(context), getCurrentWallpaperInfoFactory(context), ) .also { keyguardQuickAffordancePickerViewModelFactory = it } } private fun getKeyguardQuickAffordancePickerInteractorImpl( context: Context ): KeyguardQuickAffordancePickerInteractor { val client = getKeyguardQuickAffordancePickerProviderClient(context) val appContext = context.applicationContext return KeyguardQuickAffordancePickerInteractor( KeyguardQuickAffordancePickerRepository( client, getApplicationCoroutineScope(), getFlags(), appContext ), client ) { getKeyguardQuickAffordanceSnapshotRestorer(appContext) } } private fun getKeyguardQuickAffordancePickerProviderClient( context: Context ): CustomizationProviderClient { return customizationProviderClient ?: CustomizationProviderClientImpl(context.applicationContext, bgDispatcher).also { customizationProviderClient = it } } private fun getKeyguardQuickAffordanceSnapshotRestorer( context: Context ): KeyguardQuickAffordanceSnapshotRestorer { return keyguardQuickAffordanceSnapshotRestorer ?: KeyguardQuickAffordanceSnapshotRestorer( getKeyguardQuickAffordancePickerInteractor(context), getKeyguardQuickAffordancePickerProviderClient(context) ) .also { keyguardQuickAffordanceSnapshotRestorer = it } } fun getNotificationSectionViewModelFactory( context: Context, ): NotificationSectionViewModel.Factory { return notificationSectionViewModelFactory ?: NotificationSectionViewModel.Factory( interactor = getNotificationsInteractor(context), ) .also { notificationSectionViewModelFactory = it } } private fun getNotificationsInteractor( context: Context, ): NotificationsInteractor { val appContext = context.applicationContext return notificationsInteractor ?: NotificationsInteractor( repository = NotificationsRepository( scope = getApplicationCoroutineScope(), backgroundDispatcher = bgDispatcher, secureSettingsRepository = getSecureSettingsRepository(context), ), snapshotRestorer = { getNotificationsSnapshotRestorer(appContext) }, ) .also { notificationsInteractor = it } } private fun getNotificationsSnapshotRestorer(context: Context): NotificationsSnapshotRestorer { return notificationsSnapshotRestorer ?: NotificationsSnapshotRestorer( interactor = getNotificationsInteractor( context = context, ), ) .also { notificationsSnapshotRestorer = it } } override fun getClockRegistry(context: Context): ClockRegistry { return (clockRegistryProvider ?: ClockRegistryProvider( context = context.applicationContext, coroutineScope = getApplicationCoroutineScope(), mainDispatcher = mainDispatcher, backgroundDispatcher = bgDispatcher, ) .also { clockRegistryProvider = it }) .get() } override fun getClockPickerInteractor( context: Context, ): ClockPickerInteractor { val appContext = context.applicationContext return clockPickerInteractor ?: ClockPickerInteractor( repository = ClockPickerRepositoryImpl( secureSettingsRepository = getSecureSettingsRepository(appContext), registry = getClockRegistry(appContext), scope = getApplicationCoroutineScope(), mainDispatcher = mainDispatcher, ), snapshotRestorer = { getClockPickerSnapshotRestorer(appContext) }, ) .also { clockPickerInteractor = it } } override fun getClockSectionViewModel( context: Context, ): ClockSectionViewModel { return clockSectionViewModel ?: ClockSectionViewModel( context.applicationContext, getClockPickerInteractor(context.applicationContext) ) .also { clockSectionViewModel = it } } override fun getClockCarouselViewModelFactory( interactor: ClockPickerInteractor, ): ClockCarouselViewModel.Factory { return clockCarouselViewModelFactory ?: ClockCarouselViewModel.Factory(interactor, bgDispatcher).also { clockCarouselViewModelFactory = it } } override fun getClockViewFactory(activity: ComponentActivity): ClockViewFactory { return clockViewFactory ?: ClockViewFactory( activity.applicationContext, ScreenSizeCalculator.getInstance() .getScreenSize(activity.windowManager.defaultDisplay), WallpaperManager.getInstance(activity.applicationContext), getClockRegistry(activity.applicationContext), ) .also { clockViewFactory = it activity.lifecycle.addObserver( object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { super.onDestroy(owner) if ((owner as Activity).isChangingConfigurations()) return clockViewFactory?.onDestroy() } } ) } } private fun getClockPickerSnapshotRestorer( context: Context, ): ClockPickerSnapshotRestorer { return clockPickerSnapshotRestorer ?: ClockPickerSnapshotRestorer(getClockPickerInteractor(context)).also { clockPickerSnapshotRestorer = it } } override fun getColorPickerInteractor( context: Context, wallpaperColorsViewModel: WallpaperColorsViewModel, ): ColorPickerInteractor { val appContext = context.applicationContext return colorPickerInteractor ?: ColorPickerInteractor( repository = ColorPickerRepositoryImpl( wallpaperColorsViewModel, getColorCustomizationManager(appContext) ), snapshotRestorer = { getColorPickerSnapshotRestorer(appContext, wallpaperColorsViewModel) } ) .also { colorPickerInteractor = it } } override fun getColorPickerViewModelFactory( context: Context, wallpaperColorsViewModel: WallpaperColorsViewModel, ): ColorPickerViewModel.Factory { return colorPickerViewModelFactory ?: ColorPickerViewModel.Factory( context.applicationContext, getColorPickerInteractor(context, wallpaperColorsViewModel), ) .also { colorPickerViewModelFactory = it } } private fun getColorPickerSnapshotRestorer( context: Context, wallpaperColorsViewModel: WallpaperColorsViewModel, ): ColorPickerSnapshotRestorer { return colorPickerSnapshotRestorer ?: ColorPickerSnapshotRestorer( getColorPickerInteractor(context, wallpaperColorsViewModel) ) .also { colorPickerSnapshotRestorer = it } } private fun getColorCustomizationManager(context: Context): ColorCustomizationManager { return colorCustomizationManager ?: ColorCustomizationManager.getInstance(context, OverlayManagerCompat(context)).also { colorCustomizationManager = it } } fun getDarkModeSnapshotRestorer( context: Context, ): DarkModeSnapshotRestorer { val appContext = context.applicationContext return darkModeSnapshotRestorer ?: DarkModeSnapshotRestorer( context = appContext, manager = appContext.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager, backgroundDispatcher = bgDispatcher, ) .also { darkModeSnapshotRestorer = it } } protected fun getThemedIconSnapshotRestorer( context: Context, ): ThemedIconSnapshotRestorer { val optionProvider = ThemedIconSwitchProvider.getInstance(context) return themedIconSnapshotRestorer ?: ThemedIconSnapshotRestorer( isActivated = { optionProvider.isThemedIconEnabled }, setActivated = { isActivated -> optionProvider.isThemedIconEnabled = isActivated }, interactor = getThemedIconInteractor(), ) .also { themedIconSnapshotRestorer = it } } protected fun getThemedIconInteractor(): ThemedIconInteractor { return themedIconInteractor ?: ThemedIconInteractor( repository = ThemeIconRepository(), ) .also { themedIconInteractor = it } } override fun getClockSettingsViewModelFactory( context: Context, wallpaperColorsViewModel: WallpaperColorsViewModel, clockViewFactory: ClockViewFactory, ): ClockSettingsViewModel.Factory { return clockSettingsViewModelFactory ?: ClockSettingsViewModel.Factory( context.applicationContext, getClockPickerInteractor(context), getColorPickerInteractor( context, wallpaperColorsViewModel, ), ) { clockId -> clockId?.let { clockViewFactory.getController(clockId).config.isReactiveToTone } ?: false } .also { clockSettingsViewModelFactory = it } } override fun getClockDescriptionUtils(resources: Resources): ClockDescriptionUtils { return clockDescriptionUtils ?: ThemePickerClockDescriptionUtils().also { clockDescriptionUtils = it } } fun getGridScreenViewModelFactory( context: Context, ): ViewModelProvider.Factory { return gridScreenViewModelFactory ?: GridScreenViewModel.Factory( context = context, interactor = getGridInteractor(context), ) .also { gridScreenViewModelFactory = it } } fun getGridInteractor(context: Context): GridInteractor { val appContext = context.applicationContext return gridInteractor ?: GridInteractor( applicationScope = getApplicationCoroutineScope(), repository = GridRepositoryImpl( applicationScope = getApplicationCoroutineScope(), manager = GridOptionsManager.getInstance(context), backgroundDispatcher = bgDispatcher, isGridApplyButtonEnabled = BaseFlags.get().isGridApplyButtonEnabled(appContext), ), snapshotRestorer = { getGridSnapshotRestorer(appContext) }, ) .also { gridInteractor = it } } private fun getGridSnapshotRestorer( context: Context, ): GridSnapshotRestorer { return gridSnapshotRestorer ?: GridSnapshotRestorer( interactor = getGridInteractor(context), ) .also { gridSnapshotRestorer = it } } override fun isCurrentSelectedColorPreset(context: Context): Boolean { val colorManager = ColorCustomizationManager.getInstance(context, OverlayManagerCompat(context)) return COLOR_SOURCE_PRESET == colorManager.currentColorSource } companion object { @JvmStatic private val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER = WallpaperPicker2Injector.MIN_SNAPSHOT_RESTORER_KEY @JvmStatic private val KEY_WALLPAPER_SNAPSHOT_RESTORER = KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER + 1 @JvmStatic private val KEY_NOTIFICATIONS_SNAPSHOT_RESTORER = KEY_WALLPAPER_SNAPSHOT_RESTORER + 1 @JvmStatic private val KEY_DARK_MODE_SNAPSHOT_RESTORER = KEY_NOTIFICATIONS_SNAPSHOT_RESTORER + 1 @JvmStatic private val KEY_THEMED_ICON_SNAPSHOT_RESTORER = KEY_DARK_MODE_SNAPSHOT_RESTORER + 1 @JvmStatic private val KEY_APP_GRID_SNAPSHOT_RESTORER = KEY_THEMED_ICON_SNAPSHOT_RESTORER + 1 @JvmStatic private val KEY_COLOR_PICKER_SNAPSHOT_RESTORER = KEY_APP_GRID_SNAPSHOT_RESTORER + 1 @JvmStatic private val KEY_CLOCKS_SNAPSHOT_RESTORER = KEY_COLOR_PICKER_SNAPSHOT_RESTORER + 1 /** * When this injector is overridden, this is the minimal value that should be used by * restorers returns in [getSnapshotRestorers]. * * It should always be greater than the biggest restorer key. */ @JvmStatic protected val MIN_SNAPSHOT_RESTORER_KEY = KEY_CLOCKS_SNAPSHOT_RESTORER + 1 } }