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:
Zekan Qian
2022-11-24 12:03:51 +08:00
parent 7b57978a86
commit e63c91ccde
11 changed files with 281 additions and 9 deletions

View File

@@ -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>

View File

@@ -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()
}

View File

@@ -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.
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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",

View File

@@ -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"

View File

@@ -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)
}
}