Move Slice code to spa/slice folder & add tests.
Bug: 244122804 Test: unit-test & local build gallery Change-Id: I61baac3e52e1ac3d7b674bc8366bee0074586115
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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.SwitchPreferencePageProvid
|
||||
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 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -70,11 +70,14 @@ abstract class SpaEnvironment(context: Context) {
|
||||
// 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.
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 SpaLoggerForTest : SpaLogger {
|
||||
}
|
||||
|
||||
class MockActivity : BrowseActivity()
|
||||
class MockSliceBroadcastReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(p0: Context?, p1: Intent?) {}
|
||||
}
|
||||
|
||||
object SppHome : SettingsPageProvider {
|
||||
override val name = "SppHome"
|
||||
@@ -104,7 +109,13 @@ object SppLayer2 : SettingsPageProvider {
|
||||
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 @@ object SppLayer2 : SettingsPageProvider {
|
||||
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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ android_library {
|
||||
srcs: ["src/**/*.kt"],
|
||||
|
||||
static_libs: [
|
||||
"androidx.arch.core_core-runtime",
|
||||
"androidx.compose.ui_ui-test-junit4",
|
||||
"androidx.compose.ui_ui-test-manifest",
|
||||
"mockito",
|
||||
|
||||
@@ -47,6 +47,7 @@ android {
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user