Notification toggle section (2/3). am: 34be33efa3
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/ThemePicker/+/20978705
Change-Id: Iab8438d3ceb87311d45a4771897ef3c16521aaff
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/res/layout/notification_section.xml b/res/layout/notification_section.xml
new file mode 100644
index 0000000..713624e
--- /dev/null
+++ b/res/layout/notification_section.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+<com.android.customization.picker.notifications.ui.view.NotificationSectionView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?selectableItemBackground"
+ android:clickable="true"
+ android:paddingVertical="@dimen/section_top_padding"
+ android:paddingHorizontal="@dimen/section_horizontal_padding"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/notifications_section_title"
+ style="@style/SectionTitleTextStyle" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/SectionSubtitleTextStyle"/>
+ </LinearLayout>
+
+ <Switch
+ android:id="@+id/switcher"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ style="@style/Switch.SettingsLib"
+ tools:ignore="UseSwitchCompatOrMaterialXml" />
+
+</com.android.customization.picker.notifications.ui.view.NotificationSectionView>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 72a013a..af8dfe1 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -347,4 +347,30 @@
[CHAR LIMIT=60].
-->
<string name="keyguard_quick_affordance_none_selected">None</string>
+
+ <!--
+ Title for a section in a list of sections in the settings app that allows the user to modify
+ how notifications behave whilst the device is locked.
+
+ [CHAR LIMIT=32].
+ -->
+ <string name="notifications_section_title">Notifications</string>
+
+ <!--
+ Summary for a setting that lets the user toggle between showing or hiding notifications on their
+ device's lock screen. This one is shown when the user is allowing notifications to show on their
+ device's lock screen.
+
+ [CHAR LIMIT=64].
+ -->
+ <string name="show_notifications_on_lock_screen">Show notifications on the lock screen</string>
+
+ <!--
+ Summary for a setting that lets the user toggle between showing or hiding notifications on their
+ device's lock screen. This one is shown when the user is not allowing notifications to show on
+ their device's lock screen.
+
+ [CHAR LIMIT=64].
+ -->
+ <string name="hide_notifications_on_lock_screen">Hide notifications on the lock screen</string>
</resources>
diff --git a/src/com/android/customization/module/DefaultCustomizationSections.java b/src/com/android/customization/module/DefaultCustomizationSections.java
index 2bf36be..49062d1 100644
--- a/src/com/android/customization/module/DefaultCustomizationSections.java
+++ b/src/com/android/customization/module/DefaultCustomizationSections.java
@@ -4,6 +4,7 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.AbstractSavedStateViewModelFactory;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
@@ -14,6 +15,8 @@
import com.android.customization.model.mode.DarkModeSectionController;
import com.android.customization.model.themedicon.ThemedIconSectionController;
import com.android.customization.model.themedicon.ThemedIconSwitchProvider;
+import com.android.customization.picker.notifications.ui.section.NotificationSectionController;
+import com.android.customization.picker.notifications.ui.viewmodel.NotificationSectionViewModel;
import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor;
import com.android.customization.picker.quickaffordance.ui.section.KeyguardQuickAffordanceSectionController;
import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel;
@@ -40,14 +43,17 @@
private final KeyguardQuickAffordancePickerInteractor mKeyguardQuickAffordancePickerInteractor;
private final KeyguardQuickAffordancePickerViewModel.Factory
mKeyguardQuickAffordancePickerViewModelFactory;
+ private final AbstractSavedStateViewModelFactory mNotificationSectionViewModelFactory;
public DefaultCustomizationSections(
KeyguardQuickAffordancePickerInteractor keyguardQuickAffordancePickerInteractor,
KeyguardQuickAffordancePickerViewModel.Factory
- keyguardQuickAffordancePickerViewModelFactory) {
+ keyguardQuickAffordancePickerViewModelFactory,
+ AbstractSavedStateViewModelFactory notificationSectionViewModelFactory) {
mKeyguardQuickAffordancePickerInteractor = keyguardQuickAffordancePickerInteractor;
mKeyguardQuickAffordancePickerViewModelFactory =
keyguardQuickAffordancePickerViewModelFactory;
+ mNotificationSectionViewModelFactory = notificationSectionViewModelFactory;
}
@Override
@@ -100,6 +106,15 @@
mKeyguardQuickAffordancePickerViewModelFactory)
.get(KeyguardQuickAffordancePickerViewModel.class),
lifecycleOwner));
+
+ // Notifications section.
+ sectionControllers.add(
+ new NotificationSectionController(
+ new ViewModelProvider(
+ activity,
+ mNotificationSectionViewModelFactory)
+ .get(NotificationSectionViewModel.class),
+ lifecycleOwner));
break;
case HOME_SCREEN:
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index ed4e935..3de1b43 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -15,7 +15,6 @@
*/
package com.android.customization.module
-import android.app.Activity
import android.app.NotificationManager
import android.content.ComponentName
import android.content.Context
@@ -25,11 +24,15 @@
import android.os.Handler
import android.os.UserHandle
import android.view.LayoutInflater
+import androidx.activity.ComponentActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
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.picker.notifications.data.repository.NotificationsRepository
+import com.android.customization.picker.notifications.domain.interactor.NotificationsInteractor
+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
@@ -59,7 +62,8 @@
import com.android.wallpaper.picker.PreviewFragment
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
import java.util.concurrent.Executors
-import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInjector {
private var customizationSections: CustomizationSections? = null
@@ -76,12 +80,18 @@
null
private var clockRegistry: ClockRegistry? = null
private var pluginManager: PluginManager? = null
+ private var notificationsInteractor: NotificationsInteractor? = null
- override fun getCustomizationSections(activity: Activity): CustomizationSections {
+ override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
return customizationSections
?: DefaultCustomizationSections(
getKeyguardQuickAffordancePickerInteractor(activity),
- getKeyguardQuickAffordancePickerViewModelFactory(activity)
+ getKeyguardQuickAffordancePickerViewModelFactory(activity),
+ NotificationSectionViewModel.newFactory(
+ owner = activity,
+ defaultArgs = null,
+ interactor = getNotificationsInteractor(activity),
+ ),
)
.also { customizationSections = it }
}
@@ -186,7 +196,7 @@
): KeyguardQuickAffordancePickerInteractor {
val client = getKeyguardQuickAffordancePickerProviderClient(context)
return KeyguardQuickAffordancePickerInteractor(
- KeyguardQuickAffordancePickerRepository(client, IO),
+ KeyguardQuickAffordancePickerRepository(client, Dispatchers.IO),
client
) { getKeyguardQuickAffordanceSnapshotRestorer(context) }
}
@@ -195,7 +205,7 @@
context: Context
): CustomizationProviderClient {
return customizationProviderClient
- ?: CustomizationProviderClientImpl(context, IO).also {
+ ?: CustomizationProviderClientImpl(context, Dispatchers.IO).also {
customizationProviderClient = it
}
}
@@ -285,6 +295,21 @@
)
}
+ protected fun getNotificationsInteractor(
+ context: Context,
+ ): NotificationsInteractor {
+ return notificationsInteractor
+ ?: NotificationsInteractor(
+ repository =
+ NotificationsRepository(
+ scope = GlobalScope,
+ backgroundDispatcher = Dispatchers.IO,
+ secureSettingsRepository = getSecureSettingsRepository(context),
+ )
+ )
+ .also { notificationsInteractor = it }
+ }
+
companion object {
@JvmStatic
private val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER =
diff --git a/src/com/android/customization/picker/notifications/data/repository/NotificationsRepository.kt b/src/com/android/customization/picker/notifications/data/repository/NotificationsRepository.kt
new file mode 100644
index 0000000..5b03cbe
--- /dev/null
+++ b/src/com/android/customization/picker/notifications/data/repository/NotificationsRepository.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.customization.picker.notifications.data.repository
+
+import android.provider.Settings
+import com.android.customization.picker.notifications.shared.model.NotificationSettingsModel
+import com.android.wallpaper.settings.data.repository.SecureSettingsRepository
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.withContext
+
+/** Provides access to state related to notifications. */
+class NotificationsRepository(
+ scope: CoroutineScope,
+ private val backgroundDispatcher: CoroutineDispatcher,
+ private val secureSettingsRepository: SecureSettingsRepository,
+) {
+ /** The current state of the notification setting. */
+ val settings: SharedFlow<NotificationSettingsModel> =
+ secureSettingsRepository
+ .intSetting(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ )
+ .map { lockScreenShowNotificationsInt ->
+ NotificationSettingsModel(
+ isShowNotificationsOnLockScreenEnabled = lockScreenShowNotificationsInt == 1,
+ )
+ }
+ .shareIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
+
+ /** Updates the setting to show or hide notifications on the lock screen. */
+ suspend fun setShowNotificationsOnLockScreenEnabled(isEnabled: Boolean) {
+ withContext(backgroundDispatcher) {
+ secureSettingsRepository.set(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ value = if (isEnabled) 1 else 0,
+ )
+ }
+ }
+
+ suspend fun isShowNotificationsOnLockScreenEnabled(): Boolean {
+ return withContext(backgroundDispatcher) {
+ secureSettingsRepository.get(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ defaultValue = 0,
+ ) == 1
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/notifications/domain/interactor/NotificationsInteractor.kt b/src/com/android/customization/picker/notifications/domain/interactor/NotificationsInteractor.kt
new file mode 100644
index 0000000..24860fb
--- /dev/null
+++ b/src/com/android/customization/picker/notifications/domain/interactor/NotificationsInteractor.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.customization.picker.notifications.domain.interactor
+
+import com.android.customization.picker.notifications.data.repository.NotificationsRepository
+import com.android.customization.picker.notifications.shared.model.NotificationSettingsModel
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business logic for interacting with notifications. */
+class NotificationsInteractor(
+ private val repository: NotificationsRepository,
+) {
+ /** The current state of the notification setting. */
+ val settings: Flow<NotificationSettingsModel> = repository.settings
+
+ /** Toggles the setting to show or hide notifications on the lock screen. */
+ suspend fun toggleShowNotificationsOnLockScreenEnabled() {
+ repository.setShowNotificationsOnLockScreenEnabled(
+ isEnabled = !repository.isShowNotificationsOnLockScreenEnabled(),
+ )
+ }
+}
diff --git a/src/com/android/customization/picker/notifications/shared/model/NotificationSettingsModel.kt b/src/com/android/customization/picker/notifications/shared/model/NotificationSettingsModel.kt
new file mode 100644
index 0000000..7ce388b
--- /dev/null
+++ b/src/com/android/customization/picker/notifications/shared/model/NotificationSettingsModel.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.customization.picker.notifications.shared.model
+
+/** Models notification settings. */
+data class NotificationSettingsModel(
+ /** Whether notifications are shown on the lock screen. */
+ val isShowNotificationsOnLockScreenEnabled: Boolean = false,
+)
diff --git a/src/com/android/customization/picker/notifications/ui/binder/NotificationSectionBinder.kt b/src/com/android/customization/picker/notifications/ui/binder/NotificationSectionBinder.kt
new file mode 100644
index 0000000..54f9bf6
--- /dev/null
+++ b/src/com/android/customization/picker/notifications/ui/binder/NotificationSectionBinder.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.customization.picker.notifications.ui.binder
+
+import android.annotation.SuppressLint
+import android.view.View
+import android.widget.Switch
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.customization.picker.notifications.ui.viewmodel.NotificationSectionViewModel
+import com.android.wallpaper.R
+import kotlinx.coroutines.launch
+
+/**
+ * Binds between view and view-model for a section that lets the user control notification settings.
+ */
+object NotificationSectionBinder {
+ @SuppressLint("UseSwitchCompatOrMaterialCode") // We're using Switch and that's okay for SysUI.
+ fun bind(
+ view: View,
+ viewModel: NotificationSectionViewModel,
+ lifecycleOwner: LifecycleOwner,
+ ) {
+ val subtitle: TextView = view.requireViewById(R.id.subtitle)
+ val switch: Switch = view.requireViewById(R.id.switcher)
+
+ view.setOnClickListener { viewModel.onClicked() }
+
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.subtitleStringResourceId.collect {
+ subtitle.text = view.context.getString(it)
+ }
+ }
+
+ launch { viewModel.isSwitchOn.collect { switch.isChecked = it } }
+ }
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/notifications/ui/section/NotificationSectionController.kt b/src/com/android/customization/picker/notifications/ui/section/NotificationSectionController.kt
new file mode 100644
index 0000000..6e30c6b
--- /dev/null
+++ b/src/com/android/customization/picker/notifications/ui/section/NotificationSectionController.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.customization.picker.notifications.ui.section
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.LayoutInflater
+import androidx.lifecycle.LifecycleOwner
+import com.android.customization.picker.notifications.ui.binder.NotificationSectionBinder
+import com.android.customization.picker.notifications.ui.view.NotificationSectionView
+import com.android.customization.picker.notifications.ui.viewmodel.NotificationSectionViewModel
+import com.android.wallpaper.R
+import com.android.wallpaper.model.CustomizationSectionController
+
+/** Controls a section with UI that lets the user toggle notification settings. */
+class NotificationSectionController(
+ private val viewModel: NotificationSectionViewModel,
+ private val lifecycleOwner: LifecycleOwner,
+) : CustomizationSectionController<NotificationSectionView> {
+
+ override fun isAvailable(context: Context?): Boolean {
+ return true
+ }
+
+ @SuppressLint("InflateParams") // We don't care that the parent is null.
+ override fun createView(context: Context?): NotificationSectionView {
+ val view =
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.notification_section,
+ /* parent= */ null,
+ ) as NotificationSectionView
+
+ NotificationSectionBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ lifecycleOwner = lifecycleOwner,
+ )
+
+ return view
+ }
+}
diff --git a/src/com/android/customization/picker/notifications/ui/view/NotificationSectionView.kt b/src/com/android/customization/picker/notifications/ui/view/NotificationSectionView.kt
new file mode 100644
index 0000000..29cce0a
--- /dev/null
+++ b/src/com/android/customization/picker/notifications/ui/view/NotificationSectionView.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.customization.picker.notifications.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.wallpaper.picker.SectionView
+
+class NotificationSectionView(
+ context: Context?,
+ attrs: AttributeSet?,
+) :
+ SectionView(
+ context,
+ attrs,
+ )
diff --git a/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt b/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt
new file mode 100644
index 0000000..f33b10b
--- /dev/null
+++ b/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModel.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.customization.picker.notifications.ui.viewmodel
+
+import android.os.Bundle
+import androidx.annotation.StringRes
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.AbstractSavedStateViewModelFactory
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.savedstate.SavedStateRegistryOwner
+import com.android.customization.picker.notifications.domain.interactor.NotificationsInteractor
+import com.android.wallpaper.R
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/** Models UI state for a section that lets the user control the notification settings. */
+class NotificationSectionViewModel
+@VisibleForTesting
+constructor(
+ private val interactor: NotificationsInteractor,
+) : ViewModel() {
+
+ /** A string resource ID for the subtitle. */
+ @StringRes
+ val subtitleStringResourceId: Flow<Int> =
+ interactor.settings.map { model ->
+ when (model.isShowNotificationsOnLockScreenEnabled) {
+ true -> R.string.show_notifications_on_lock_screen
+ false -> R.string.hide_notifications_on_lock_screen
+ }
+ }
+
+ /** Whether the switch should be on. */
+ val isSwitchOn: Flow<Boolean> =
+ interactor.settings.map { model -> model.isShowNotificationsOnLockScreenEnabled }
+
+ /** Notifies that the section has been clicked. */
+ fun onClicked() {
+ viewModelScope.launch { interactor.toggleShowNotificationsOnLockScreenEnabled() }
+ }
+
+ companion object {
+ @JvmStatic
+ fun newFactory(
+ owner: SavedStateRegistryOwner,
+ defaultArgs: Bundle? = null,
+ interactor: NotificationsInteractor,
+ ): AbstractSavedStateViewModelFactory =
+ object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ key: String,
+ modelClass: Class<T>,
+ handle: SavedStateHandle,
+ ): T {
+ return NotificationSectionViewModel(
+ interactor = interactor,
+ )
+ as T
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/customization/picker/notifications/data/repository/NotificationsRepositoryTest.kt b/tests/src/com/android/customization/picker/notifications/data/repository/NotificationsRepositoryTest.kt
new file mode 100644
index 0000000..e29bd33
--- /dev/null
+++ b/tests/src/com/android/customization/picker/notifications/data/repository/NotificationsRepositoryTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.customization.picker.notifications.data.repository
+
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.customization.picker.notifications.shared.model.NotificationSettingsModel
+import com.android.wallpaper.testing.FakeSecureSettingsRepository
+import com.android.wallpaper.testing.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class NotificationsRepositoryTest {
+
+ private lateinit var underTest: NotificationsRepository
+
+ private lateinit var testScope: TestScope
+ private lateinit var secureSettingsRepository: FakeSecureSettingsRepository
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ secureSettingsRepository = FakeSecureSettingsRepository()
+
+ underTest =
+ NotificationsRepository(
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
+ secureSettingsRepository = secureSettingsRepository,
+ )
+ }
+
+ @Test
+ fun settings() =
+ testScope.runTest {
+ val settings = collectLastValue(underTest.settings)
+
+ secureSettingsRepository.set(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ value = 1,
+ )
+ assertThat(settings())
+ .isEqualTo(NotificationSettingsModel(isShowNotificationsOnLockScreenEnabled = true))
+
+ secureSettingsRepository.set(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ value = 0,
+ )
+ assertThat(settings())
+ .isEqualTo(
+ NotificationSettingsModel(isShowNotificationsOnLockScreenEnabled = false)
+ )
+ }
+
+ @Test
+ fun setShowNotificationsOnLockScreenEnabled() =
+ testScope.runTest {
+ val settings = collectLastValue(underTest.settings)
+
+ underTest.setShowNotificationsOnLockScreenEnabled(isEnabled = true)
+ assertThat(settings())
+ .isEqualTo(NotificationSettingsModel(isShowNotificationsOnLockScreenEnabled = true))
+
+ underTest.setShowNotificationsOnLockScreenEnabled(isEnabled = false)
+ assertThat(settings())
+ .isEqualTo(
+ NotificationSettingsModel(isShowNotificationsOnLockScreenEnabled = false)
+ )
+ }
+}
diff --git a/tests/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModelTest.kt b/tests/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModelTest.kt
new file mode 100644
index 0000000..c74f848
--- /dev/null
+++ b/tests/src/com/android/customization/picker/notifications/ui/viewmodel/NotificationSectionViewModelTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.customization.picker.notifications.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.customization.picker.notifications.data.repository.NotificationsRepository
+import com.android.customization.picker.notifications.domain.interactor.NotificationsInteractor
+import com.android.wallpaper.testing.FakeSecureSettingsRepository
+import com.android.wallpaper.testing.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class NotificationSectionViewModelTest {
+
+ private lateinit var underTest: NotificationSectionViewModel
+
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ Dispatchers.setMain(testDispatcher)
+ testScope = TestScope(testDispatcher)
+ underTest =
+ NotificationSectionViewModel(
+ interactor =
+ NotificationsInteractor(
+ repository =
+ NotificationsRepository(
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
+ secureSettingsRepository = FakeSecureSettingsRepository(),
+ )
+ )
+ )
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun `toggles back and forth`() =
+ testScope.runTest {
+ val subtitleStringResId = collectLastValue(underTest.subtitleStringResourceId)
+ val isSwitchOn = collectLastValue(underTest.isSwitchOn)
+
+ val initialSubtitleStringRes = subtitleStringResId()
+ val initialIsSwitchOn = isSwitchOn()
+
+ underTest.onClicked()
+ assertThat(subtitleStringResId()).isNotEqualTo(initialSubtitleStringRes)
+ assertThat(isSwitchOn()).isNotEqualTo(initialIsSwitchOn)
+
+ underTest.onClicked()
+ assertThat(subtitleStringResId()).isEqualTo(initialSubtitleStringRes)
+ assertThat(isSwitchOn()).isEqualTo(initialIsSwitchOn)
+ }
+}