Move Slice code to spa/slice folder & add tests.
Bug: 244122804
Test: unit-test & local build gallery
Change-Id: I61baac3e52e1ac3d7b674bc8366bee0074586115
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index a051ffe..71c52d9 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -42,7 +42,7 @@
android:exported="false">
</provider>
- <provider android:name="com.android.settingslib.spa.framework.SpaSliceProvider"
+ <provider android:name="com.android.settingslib.spa.slice.SpaSliceProvider"
android:authorities="com.android.spa.gallery.slice.provider"
android:exported="true" >
<intent-filter>
@@ -52,7 +52,7 @@
</provider>
<receiver
- android:name="com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver"
+ android:name="com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver"
android:exported="false">
</receiver>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 6d20c91..9daa4f7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -17,7 +17,6 @@
package com.android.settingslib.spa.gallery
import android.content.Context
-import com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver
import com.android.settingslib.spa.framework.common.LocalLogger
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironment
@@ -39,6 +38,7 @@
import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
+import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
/**
* Enum to define all SPP name here.
@@ -81,9 +81,12 @@
)
}
+ override val logger = LocalLogger()
+
override val browseActivityClass = GalleryMainActivity::class.java
override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
+
+ // For debugging
override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
- override val logger = LocalLogger()
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 945add4..6d0b810 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -70,11 +70,14 @@
// In Robolectric test, applicationContext is not available. Use context as fallback.
val appContext: Context = context.applicationContext ?: context
+ open val logger: SpaLogger = object : SpaLogger {}
+
open val browseActivityClass: Class<out Activity>? = null
open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
+
+ // Specify provider authorities for debugging purpose.
open val searchProviderAuthorities: String? = null
open val sliceProviderAuthorities: String? = null
- open val logger: SpaLogger = object : SpaLogger {}
// TODO: add other environment setup here.
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
index 58131e6..39cb431 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.slice
import android.content.BroadcastReceiver
import android.content.Context
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
similarity index 98%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
index faa04fd..b809c0f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.slice
import android.net.Uri
import android.util.Log
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
index b8731a3..4df37b1 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
@@ -17,7 +17,9 @@
package com.android.settingslib.spa.framework.common
import android.app.Activity
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
import android.os.Bundle
import androidx.navigation.NavType
import androidx.navigation.navArgument
@@ -57,6 +59,9 @@
}
class MockActivity : BrowseActivity()
+class MockSliceBroadcastReceiver : BroadcastReceiver() {
+ override fun onReceive(p0: Context?, p1: Intent?) {}
+}
object SppHome : SettingsPageProvider {
override val name = "SppHome"
@@ -104,7 +109,13 @@
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val owner = this.createSettingsPage()
return listOf(
- SettingsEntryBuilder.create(owner, "Layer2Entry1").build(),
+ SettingsEntryBuilder.create(owner, "Layer2Entry1")
+ .setSliceDataFn { _, _ ->
+ return@setSliceDataFn object : EntrySliceData() {
+ init { postValue(null) }
+ }
+ }
+ .build(),
SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
)
}
@@ -113,7 +124,9 @@
class SpaEnvironmentForTest(
context: Context,
override val browseActivityClass: Class<out Activity>? = MockActivity::class.java,
- override val logger: SpaLogger = SpaLoggerForTest()
+ override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
+ MockSliceBroadcastReceiver::class.java,
+ override val logger: SpaLogger = object : SpaLogger {}
) : SpaEnvironment(context) {
override val pageProviderRepository = lazy {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
new file mode 100644
index 0000000..c501fa5
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.slice
+
+import android.content.Context
+import android.net.Uri
+import androidx.lifecycle.Observer
+import androidx.slice.Slice
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SpaEnvironmentForTest
+import com.android.settingslib.spa.framework.common.SppLayer2
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.common.getUniqueEntryId
+import com.android.settingslib.spa.testutils.InstantTaskExecutorRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsSliceDataRepositoryTest {
+ @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaEnvironment = SpaEnvironmentForTest(context)
+ private val sliceDataRepository by spaEnvironment.sliceDataRepository
+
+ @Test
+ fun getOrBuildSliceDataTest() {
+ // Slice empty
+ assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
+
+ // Slice supported
+ val page = SppLayer2.createSettingsPage()
+ val entryId = getUniqueEntryId("Layer2Entry1", page)
+ val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build()
+ assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
+ assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
+ val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
+ assertThat(sliceData).isNotNull()
+ assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
+
+ // Slice unsupported
+ val entryId2 = getUniqueEntryId("Layer2Entry2", page)
+ val sliceUri2 = Uri.Builder().appendSliceParams(page.buildRoute(), entryId2).build()
+ assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
+ assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
+ assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
+ }
+
+ @Test
+ fun getActiveSliceDataTest() {
+ val page = SppLayer2.createSettingsPage()
+ val entryId = getUniqueEntryId("Layer2Entry1", page)
+ val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build()
+
+ // build slice data first
+ val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
+
+ // slice data is inactive
+ assertThat(sliceData!!.isActive()).isFalse()
+ assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
+
+ // slice data is active
+ val observer = Observer<Slice?> { }
+ sliceData.observeForever(observer)
+ assertThat(sliceData.isActive()).isTrue()
+ assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isSameInstanceAs(sliceData)
+
+ // slice data is inactive again
+ sliceData.removeObserver(observer)
+ assertThat(sliceData.isActive()).isFalse()
+ assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
new file mode 100644
index 0000000..50705d5
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.slice
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.core.os.bundleOf
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.SpaEnvironmentForTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SliceUtilTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaEnvironment = SpaEnvironmentForTest(context)
+
+ @Test
+ fun sliceUriTest() {
+ assertThat(Uri.EMPTY.getEntryId()).isNull()
+ assertThat(Uri.EMPTY.getDestination()).isNull()
+ assertThat(Uri.EMPTY.getRuntimeArguments().size()).isEqualTo(0)
+ assertThat(Uri.EMPTY.getSliceId()).isNull()
+
+ // valid slice uri
+ val dest = "myRoute"
+ val entryId = "myEntry"
+ val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build()
+ assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
+ assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
+ assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
+ assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
+
+ val sliceUriWithParams =
+ Uri.Builder().appendSliceParams(dest, entryId, bundleOf("p1" to "v1")).build()
+ assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
+ assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
+ assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
+ assertThat(sliceUriWithParams.getSliceId()).isEqualTo("${entryId}_Bundle[{p1=v1}]")
+ }
+
+ @Test
+ fun createBroadcastPendingIntentTest() {
+ SpaEnvironmentFactory.reset(spaEnvironment)
+
+ // Empty Slice Uri
+ assertThat(Uri.EMPTY.createBroadcastPendingIntent()).isNull()
+
+ // Valid Slice Uri
+ val dest = "myRoute"
+ val entryId = "myEntry"
+ val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build()
+ val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
+ assertThat(pendingIntent).isNotNull()
+ assertThat(pendingIntent!!.isBroadcast).isTrue()
+ assertThat(pendingIntent.isImmutable).isFalse()
+ }
+
+ @Test
+ fun createBrowsePendingIntentTest() {
+ SpaEnvironmentFactory.reset(spaEnvironment)
+
+ // Empty Slice Uri
+ assertThat(Uri.EMPTY.createBrowsePendingIntent()).isNull()
+
+ // Empty Intent
+ assertThat(Intent().createBrowsePendingIntent()).isNull()
+
+ // Valid Slice Uri
+ val dest = "myRoute"
+ val entryId = "myEntry"
+ val sliceUri = Uri.Builder().appendSliceParams(dest, entryId).build()
+ val pendingIntent = sliceUri.createBrowsePendingIntent()
+ assertThat(pendingIntent).isNotNull()
+ assertThat(pendingIntent!!.isActivity).isTrue()
+ assertThat(pendingIntent.isImmutable).isTrue()
+
+ // Valid Intent
+ val intent = Intent().apply {
+ putExtra("spaActivityDestination", dest)
+ putExtra("highlightEntry", entryId)
+ }
+ val pendingIntent2 = intent.createBrowsePendingIntent()
+ assertThat(pendingIntent2).isNotNull()
+ assertThat(pendingIntent2!!.isActivity).isTrue()
+ assertThat(pendingIntent2.isImmutable).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 0f618fa..48df569 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -24,6 +24,7 @@
srcs: ["src/**/*.kt"],
static_libs: [
+ "androidx.arch.core_core-runtime",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
"mockito",
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index 58b4d42..be8df43 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -47,6 +47,7 @@
}
dependencies {
+ api "androidx.arch.core:core-runtime:2.1.0"
api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
api "com.google.truth:truth:1.1.3"
api "org.mockito:mockito-core:2.21.0"
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
new file mode 100644
index 0000000..43c18d4
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.testutils
+
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
+ * in LifecycleRegistry.
+
+ * This is a copy of androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be
+ * replaced once the dependency issue is solved.
+ */
+class InstantTaskExecutorRule : TestWatcher() {
+ override fun starting(description: Description) {
+ super.starting(description)
+ ArchTaskExecutor.getInstance().setDelegate(
+ object : TaskExecutor() {
+ override fun executeOnDiskIO(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun postToMainThread(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun isMainThread(): Boolean {
+ return true
+ }
+ }
+ )
+ }
+
+ override fun finished(description: Description) {
+ super.finished(description)
+ ArchTaskExecutor.getInstance().setDelegate(null)
+ }
+}