From e63c91ccde54262ae23403f8357318d5567c101c Mon Sep 17 00:00:00 2001 From: Zekan Qian Date: Thu, 24 Nov 2022 12:03:51 +0800 Subject: [PATCH] Move Slice code to spa/slice folder & add tests. Bug: 244122804 Test: unit-test & local build gallery Change-Id: I61baac3e52e1ac3d7b674bc8366bee0074586115 --- .../Spa/gallery/AndroidManifest.xml | 4 +- .../spa/gallery/GallerySpaEnvironment.kt | 7 +- .../spa/framework/common/SpaEnvironment.kt | 5 +- .../SpaSliceBroadcastReceiver.kt | 2 +- .../{framework => slice}/SpaSliceProvider.kt | 2 +- .../framework/common/SpaEnvironmentForTest.kt | 17 ++- .../slice/SettingsSliceDataRepositoryTest.kt | 90 +++++++++++++++ .../settingslib/spa/slice/SliceUtilTest.kt | 106 ++++++++++++++++++ packages/SettingsLib/Spa/testutils/Android.bp | 1 + .../SettingsLib/Spa/testutils/build.gradle | 1 + .../spa/testutils/InstantTaskExecutorRule.kt | 55 +++++++++ 11 files changed, 281 insertions(+), 9 deletions(-) rename packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/{framework => slice}/SpaSliceBroadcastReceiver.kt (96%) rename packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/{framework => slice}/SpaSliceProvider.kt (98%) create mode 100644 packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt create mode 100644 packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt create mode 100644 packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index a051ffed3696f..71c52d9f4a6a8 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -42,7 +42,7 @@ android:exported="false"> - @@ -52,7 +52,7 @@ 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 6d20c917e214e..9daa4f7c5ee99 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.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() } 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 945add47b9655..6d0b810d0cb6d 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 @@ 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? = null open val sliceBroadcastReceiverClass: Class? = 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 96% 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 58131e69d329b..39cb43180f58f 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 faa04fda19046..b809c0f1ae638 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 b8731a3019f88..4df37b1b12b7e 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 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 { 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? = MockActivity::class.java, - override val logger: SpaLogger = SpaLoggerForTest() + override val sliceBroadcastReceiverClass: Class? = + 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 0000000000000..c501fa57468e9 --- /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 { } + 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 0000000000000..50705d5d8dd5e --- /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 0f618fab9656b..48df569f1c543 100644 --- a/packages/SettingsLib/Spa/testutils/Android.bp +++ b/packages/SettingsLib/Spa/testutils/Android.bp @@ -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", diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle index 58b4d42b604a3..be8df436712d0 100644 --- a/packages/SettingsLib/Spa/testutils/build.gradle +++ b/packages/SettingsLib/Spa/testutils/build.gradle @@ -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" 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 0000000000000..43c18d4a3e931 --- /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) + } +}