Merge "Fixed problem of methodBinder nullability in DaoWriter.kt" into androidx-main
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarMenuHostTest.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarMenuHostTest.kt
index 09d44b2..317fcc3 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarMenuHostTest.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarMenuHostTest.kt
@@ -96,7 +96,7 @@
@Test
fun providedOnPrepareMenu() {
with(ActivityScenario.launch(ToolbarTestActivity::class.java)) {
- var menuPrepared: Boolean
+ var menuPrepared = false
val toolbar: Toolbar = withActivity {
findViewById(androidx.appcompat.test.R.id.toolbar)
}
@@ -117,8 +117,11 @@
})
}
- menuPrepared = false
- withActivity { toolbar.invalidateMenu() }
+ assertThat(menuPrepared).isFalse()
+
+ toolbar.showOverflowMenu()
+ PollingCheck.waitFor { toolbar.isOverflowMenuShowing }
+
assertThat(menuPrepared).isTrue()
}
}
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
index 205bfd6..9993998 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
@@ -158,7 +158,7 @@
public class Toolbar extends ViewGroup implements MenuHost {
private static final String TAG = "Toolbar";
- private ActionMenuView mMenuView;
+ ActionMenuView mMenuView;
private TextView mTitleTextView;
private TextView mSubtitleTextView;
private ImageButton mNavButtonView;
@@ -232,7 +232,7 @@
private ActionMenuPresenter mOuterActionMenuPresenter;
private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
private MenuPresenter.Callback mActionMenuPresenterCallback;
- private MenuBuilder.Callback mMenuBuilderCallback;
+ MenuBuilder.Callback mMenuBuilderCallback;
private boolean mCollapsible;
@@ -1253,7 +1253,34 @@
mMenuView = new ActionMenuView(getContext());
mMenuView.setPopupTheme(mPopupTheme);
mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener);
- mMenuView.setMenuCallbacks(mActionMenuPresenterCallback, mMenuBuilderCallback);
+ mMenuView.setMenuCallbacks(mActionMenuPresenterCallback,
+ // Have Toolbar insert a Callback to ensure onPrepareMenu is called properly
+ new MenuBuilder.Callback() {
+ // The mMenuView item does not call into the mMenuBuilderCallback when
+ // menuItems are selected, so this should not get called, but we implement it
+ // anyway
+ @Override
+ public boolean onMenuItemSelected(@NonNull MenuBuilder menu,
+ @NonNull MenuItem item) {
+ // Check if there is a mMenuBuilderCallback and if so, forward the call.
+ return mMenuBuilderCallback != null
+ && mMenuBuilderCallback.onMenuItemSelected(menu, item);
+ }
+
+ @Override
+ public void onMenuModeChange(@NonNull MenuBuilder menu) {
+ // If the menu is not showing, we are about to show it, so we need to
+ // make the prepare call.
+ if (!mMenuView.isOverflowMenuShowing()) {
+ mMenuHostHelper.onPrepareMenu(menu);
+ }
+ // If there is a mMenuBuilderCallback, forward the onMenuModeChanged call.
+ if (mMenuBuilderCallback != null) {
+ mMenuBuilderCallback.onMenuModeChange(menu);
+ }
+ }
+ }
+ );
final LayoutParams lp = generateDefaultLayoutParams();
lp.gravity = GravityCompat.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
mMenuView.setLayoutParams(lp);
@@ -2445,8 +2472,6 @@
ArrayList<MenuItem> newMenuItemList = getCurrentMenuItems();
newMenuItemList.removeAll(oldMenuItemList);
mProvidedMenuItems = newMenuItemList;
-
- mMenuHostHelper.onPrepareMenu(menu);
}
@Override
diff --git a/benchmark/benchmark-macro/api/current.txt b/benchmark/benchmark-macro/api/current.txt
index 4a1e12c..68a4a8c 100644
--- a/benchmark/benchmark-macro/api/current.txt
+++ b/benchmark/benchmark-macro/api/current.txt
@@ -64,6 +64,7 @@
method public androidx.test.uiautomator.UiDevice getDevice();
method public Integer? getIteration();
method public String getPackageName();
+ method public void killProcess(optional boolean useKillAll);
method public void killProcess();
method public void pressHome(optional long delayDurationMs);
method public void pressHome();
diff --git a/benchmark/benchmark-macro/api/public_plus_experimental_current.txt b/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
index eb7d8a1..f2aa08e 100644
--- a/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
+++ b/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
@@ -81,6 +81,7 @@
method public androidx.test.uiautomator.UiDevice getDevice();
method public Integer? getIteration();
method public String getPackageName();
+ method public void killProcess(optional boolean useKillAll);
method public void killProcess();
method public void pressHome(optional long delayDurationMs);
method public void pressHome();
diff --git a/benchmark/benchmark-macro/api/restricted_current.txt b/benchmark/benchmark-macro/api/restricted_current.txt
index 53fcd5a..c548035 100644
--- a/benchmark/benchmark-macro/api/restricted_current.txt
+++ b/benchmark/benchmark-macro/api/restricted_current.txt
@@ -79,6 +79,7 @@
method public androidx.test.uiautomator.UiDevice getDevice();
method public Integer? getIteration();
method public String getPackageName();
+ method public void killProcess(optional boolean useKillAll);
method public void killProcess();
method public void pressHome(optional long delayDurationMs);
method public void pressHome();
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
index e11d2c8..697fc15 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
@@ -59,7 +59,7 @@
)
// always kill the process at beginning of a collection.
- scope.killProcess()
+ scope.killProcess(useKillAll = Shell.isSessionRooted())
try {
userspaceTrace("compile $packageName") {
compilationMode.resetAndCompile(
@@ -114,7 +114,7 @@
Log.d(TAG, "Total Run Time Ns: $totalRunTime")
}
} finally {
- scope.killProcess()
+ scope.killProcess(useKillAll = Shell.isSessionRooted())
}
}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
index a78f45e..f0d8b34 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
@@ -215,10 +215,13 @@
/**
* Force-stop the process being measured.
+ *
+ *@param useKillAll should be set to `true` for System apps or pre-installed apps.
*/
- public fun killProcess() {
+ @JvmOverloads
+ public fun killProcess(useKillAll: Boolean = false) {
Log.d(TAG, "Killing process $packageName")
- if (Shell.isSessionRooted()) {
+ if (useKillAll) {
device.executeShellCommand("killall $packageName")
} else {
device.executeShellCommand("am force-stop $packageName")
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
index ff35c32..30c2a30 100644
--- a/buildSrc-tests/build.gradle
+++ b/buildSrc-tests/build.gradle
@@ -25,10 +25,22 @@
id("kotlin")
}
+apply from: "../buildSrc/kotlin-dsl-dependency.gradle"
+
+def buildSrcJar(jarName) {
+ return project.files(
+ new File(
+ BuildServerConfigurationKt.getRootOutDirectory(project),
+ "buildSrc/$jarName/build/libs/${jarName}.jar"
+ )
+ )
+}
+
dependencies {
implementation(gradleApi())
- implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/private/build/libs/private.jar")))
- implementation(project.files(new File(BuildServerConfigurationKt.getRootOutDirectory(project), "buildSrc/public/build/libs/public.jar")))
+ implementation(buildSrcJar("private"))
+ implementation(buildSrcJar("public"))
+ implementation(buildSrcJar("jetpad-integration"))
implementation("com.googlecode.json-simple:json-simple:1.1")
implementation(libs.gson)
implementation(libs.dom4j) {
@@ -43,6 +55,9 @@
testImplementation(project(":internal-testutils-gradle-plugin"))
testImplementation(gradleTestKit())
testImplementation(libs.checkmark)
+ testImplementation(libs.kotlinGradlePluginz)
+ testImplementation(libs.toml)
+ testImplementation(findGradleKotlinDsl())
}
SdkResourceGenerator.generateForHostTest(project)
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXSelfTestProject.kt b/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXSelfTestProject.kt
index d29af2c..1a243cc 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXSelfTestProject.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/AndroidXSelfTestProject.kt
@@ -29,7 +29,8 @@
companion object {
fun cubaneBuildGradleText(
plugins: List<String> = listOf("java-library", "kotlin", "AndroidXPlugin"),
- version: String? = "1.2.3"
+ version: String? = "1.2.3",
+ moreConfig: String = ""
): String {
val mavenVersionLine = if (version != null) {
" mavenVersion = new Version(\"$version\")"
@@ -48,6 +49,8 @@
| api(libs.kotlinStdlib)
|}
|
+ |$moreConfig
+ |
|androidx {
| publish = Publish.SNAPSHOT_AND_RELEASE
|$mavenVersionLine
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/SdkResourceGeneratorTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/SdkResourceGeneratorTest.kt
index c58d39f..a571f1a 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/SdkResourceGeneratorTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/SdkResourceGeneratorTest.kt
@@ -31,8 +31,8 @@
val project = ProjectBuilder.builder().build()
+ project.setSupportRootFolder(File("files/support"))
val extension = project.rootProject.property("ext") as ExtraPropertiesExtension
- extension.set("supportRootFolder", File("files/support"))
extension.set("buildSrcOut", project.projectDir.resolve("relative/path"))
SdkResourceGenerator.registerSdkResourceGeneratorTask(project)
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/buildFiles.kt b/buildSrc-tests/src/test/kotlin/androidx/build/buildFiles.kt
index f2adaf6..df67b65 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/buildFiles.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/buildFiles.kt
@@ -22,38 +22,47 @@
writeBuildFiles(projects.toList())
}
-fun AndroidXPluginTestContext.writeBuildFiles(projects: List<AndroidXSelfTestProject>) {
+fun AndroidXPluginTestContext.writeBuildFiles(
+ projects: List<AndroidXSelfTestProject>,
+ groupLines: List<String> = listOf()
+) {
writeRootSettingsFile(projects.map { it.gradlePath })
writeRootBuildFile()
- writeApplyPluginScript()
+ writeApplyPluginScripts()
- File(supportRoot, "libraryversions.toml").writeText(
- """|[groups]
- |[versions]
- |""".trimMargin()
- )
+ writeLibraryVersionsFile(supportRoot, groupLines)
// Matches behavior of root properties
File(supportRoot, "gradle.properties").writeText(
"""|# Do not automatically include stdlib
- |kotlin.stdlib.default.dependency=false
- |
- |# Avoid OOM in subgradle
- |# (https://github.com/gradle/gradle/issues/10527#issuecomment-887704062)
- |org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=1g
- |""".trimMargin()
+ |kotlin.stdlib.default.dependency=false
+ |
+ |# Avoid OOM in subgradle
+ |# (https://github.com/gradle/gradle/issues/10527#issuecomment-887704062)
+ |org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=1g
+ |""".trimMargin()
)
projects.forEach { it.writeFiles() }
}
+fun writeLibraryVersionsFile(supportFolder: File, groupLines: List<String>) {
+ File(supportFolder, "libraryversions.toml").writeText(
+ buildString {
+ appendLine("[groups]")
+ groupLines.forEach { appendLine(it) }
+ appendLine("[versions]")
+ }
+ )
+}
+
fun AndroidXPluginTestContext.writeRootSettingsFile(projectPaths: List<String>) {
val settingsString = buildString {
append(
"""|pluginManagement {
- | ${setup.repositories}
- |}
- |""".trimMargin()
+ | ${setup.repositories}
+ |}
+ |""".trimMargin()
)
appendLine()
projectPaths.forEach {
@@ -63,15 +72,20 @@
File(setup.rootDir, "settings.gradle").writeText(settingsString)
}
-fun AndroidXPluginTestContext.writeApplyPluginScript() {
- setup.rootDir.resolve("buildSrc/apply").also { it.mkdirs() }
- .resolve("applyAndroidXImplPlugin.gradle").writeText(
- """|import androidx.build.AndroidXImplPlugin
- |buildscript {
- | ${setup.repositories}
- | $buildScriptDependencies
- |}
- |apply plugin: AndroidXImplPlugin
- |""".trimMargin()
- )
+fun AndroidXPluginTestContext.writeApplyPluginScripts() {
+ setup.rootDir.resolve("buildSrc/apply").also {
+ it.mkdirs()
+
+ listOf("AndroidXImplPlugin", "AndroidXComposeImplPlugin").forEach { pluginName ->
+ it.resolve("apply$pluginName.gradle").writeText(
+ """|import androidx.build.$pluginName
+ |buildscript {
+ | ${setup.repositories}
+ | $buildScriptDependencies
+ |}
+ |apply plugin: $pluginName
+ |""".trimMargin()
+ )
+ }
+ }
}
\ No newline at end of file
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTaskTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTaskTest.kt
new file mode 100644
index 0000000..4035dac
--- /dev/null
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTaskTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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 androidx.build.buildInfo
+
+import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.junit.Test
+import androidx.build.buildInfo.CreateLibraryBuildInfoFileTask.Companion.asBuildInfoDependencies
+import net.saff.checkmark.Checkmark.Companion.check
+
+class CreateLibraryBuildInfoFileTaskTest {
+ @Test
+ fun buildInfoDependencies() {
+ val deps: List<ModuleDependency> =
+ listOf(DefaultExternalModuleDependency("androidx.group", "artifact", "version"))
+ deps.asBuildInfoDependencies().single().check { it.groupId == "androidx.group" }
+ .check { it.artifactId == "artifact" }.check { it.version == "version" }
+ .check { !it.isTipOfTree }
+ }
+
+ @Test
+ fun suffix() {
+ computeTaskSuffix("cubane").check { it == "" }
+ computeTaskSuffix("cubane-jvm").check { it == "Jvm" }
+ computeTaskSuffix("cubane-jvm-linux-x64").check { it == "JvmLinuxX64" }
+ }
+}
\ No newline at end of file
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/integrationtests/BuildInfoTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/integrationtests/BuildInfoTest.kt
index d7fae0d..e4a83e6 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/integrationtests/BuildInfoTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/integrationtests/BuildInfoTest.kt
@@ -26,6 +26,32 @@
class BuildInfoTest {
@Test
+ fun kmpBuildInfoTasks() = pluginTest {
+ val env = environmentForExplicitChangeInfo()
+
+ writeBuildFiles(
+ AndroidXSelfTestProject.cubaneKmpProject.copy(
+ buildGradleText = AndroidXSelfTestProject.buildGradleForKmp(
+ withJava = true,
+ addJvmDependency = true
+ )
+ )
+ )
+
+ // Run to generate build_info file to examine.
+ runGradle(":cubane:cubanekmp:createLibraryBuildInfoFilesJvm", "--stacktrace", env = env)
+
+ // Generated by command above. See CreateLibraryBuildInfoFileTask doc for derivation
+ // of filename
+ val buildInfoPath = "dist/build-info/cubane_cubanekmp-jvm_build_info.txt"
+ outDir.resolve(buildInfoPath).readText().check {
+ it.contains("\"artifactId\": \"cubanekmp-jvm\"")
+ }.check {
+ it.contains("jvmdep")
+ }
+ }
+
+ @Test
fun androidLibraryBuildInfoTasks() = pluginTest {
val env = environmentForExplicitChangeInfo()
@@ -48,6 +74,91 @@
}
}
+ @Test
+ fun androidLibraryMppBuildInfoTasks() = pluginTest {
+ val env = environmentForExplicitChangeInfo()
+
+ writeBuildFiles(
+ AndroidXSelfTestProject.cubaneProject.copy(
+ buildGradleText = AndroidXSelfTestProject.cubaneBuildGradleText(
+ plugins = listOf(
+ "AndroidXPlugin",
+ "com.android.library",
+ "AndroidXComposePlugin"
+ ),
+ moreConfig =
+ """|import androidx.build.AndroidXComposePlugin
+ |AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+ |
+ |android {
+ | namespace "androidx.compose.animation"
+ |}
+ |
+ |androidXComposeMultiplatform {
+ | android()
+ | desktop()
+ |}
+ |""".trimMargin()
+ )
+ ),
+
+ AndroidXSelfTestProject(
+ groupId = "compose",
+ artifactId = "compiler:compiler",
+ version = null,
+ buildGradleText = ""
+ )
+ )
+
+ // Run to generate build_info file to examine.
+ runGradle(
+ "-Pandroidx.compose.multiplatformEnabled=true",
+ ":cubane:cubane:createLibraryBuildInfoFilesAndroid",
+ "--stacktrace",
+ env = env
+ )
+
+ // Generated by command above. See CreateLibraryBuildInfoFileTask doc for derivation
+ // of filename
+ val buildInfoPath = "dist/build-info/cubane_cubane-android_build_info.txt"
+ outDir.resolve(buildInfoPath).assertExists().readText().check {
+ it.contains("\"artifactId\": \"cubane-android\"")
+ }
+ }
+
+ @Test
+ fun pluginBuildInfoTasks() = pluginTest {
+ val env = environmentForExplicitChangeInfo()
+
+ writeBuildFiles(
+ AndroidXSelfTestProject.cubaneProject.copy(
+ buildGradleText = AndroidXSelfTestProject.cubaneBuildGradleText(
+ plugins = listOf("AndroidXPlugin", "kotlin", "java-gradle-plugin"),
+ moreConfig =
+ """|gradlePlugin {
+ | plugins {
+ | benchmark {
+ | id = "androidx.benchmark"
+ | implementationClass = "androidx.benchmark.gradle.BenchmarkPlugin"
+ | }
+ | }
+ |}
+ |""".trimMargin()
+ )
+ )
+ )
+
+ // Run to generate build_info file to examine.
+ runGradle(":cubane:cubane:createLibraryBuildInfoFiles", "--stacktrace", env = env)
+
+ // Generated by command above. See CreateLibraryBuildInfoFileTask doc for derivation
+ // of filename
+ val buildInfoPath = "dist/build-info/cubane_cubane_build_info.txt"
+ outDir.resolve(buildInfoPath).assertExists().readText().check {
+ it.contains("\"artifactId\": \"cubane\"")
+ }
+ }
+
/**
* Avoid calling git in tests by taking advantage of environment variables with changelist info
* and manifest of changed files. (These are usually set by busytown and detected in our
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index 46f55a3..376419d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -194,17 +194,19 @@
}
}
- // TODO: figure out how to apply this to multiplatform modules
- dependencies.add(
- "lintChecks",
- project.dependencies.project(
- mapOf(
- "path" to ":compose:lint:internal-lint-checks",
- // TODO(b/206617878) remove this shadow configuration
- "configuration" to "shadow"
+ if (!allowMissingLintProject()) {
+ // TODO: figure out how to apply this to multiplatform modules
+ dependencies.add(
+ "lintChecks",
+ project.dependencies.project(
+ mapOf(
+ "path" to ":compose:lint:internal-lint-checks",
+ // TODO(b/206617878) remove this shadow configuration
+ "configuration" to "shadow"
+ )
)
)
- )
+ }
}
private fun Project.configureManifests() {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index ac01224..c12c696 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -17,6 +17,7 @@
package androidx.build
import androidx.build.checkapi.shouldConfigureApiTasks
+import com.android.build.gradle.internal.crash.afterEvaluate
import groovy.lang.Closure
import org.gradle.api.GradleException
import org.gradle.api.Project
@@ -185,6 +186,14 @@
false
}
+ internal fun ifReleasing(action: () -> Unit) {
+ project.afterEvaluate {
+ if (shouldRelease()) {
+ action()
+ }
+ }
+ }
+
internal fun isPublishConfigured(): Boolean = (
publish != Publish.UNSET ||
type.publish != Publish.UNSET
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index f1067803..373b053 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -25,7 +25,7 @@
import androidx.build.SupportConfig.DEFAULT_MIN_SDK_VERSION
import androidx.build.SupportConfig.INSTRUMENTATION_RUNNER
import androidx.build.SupportConfig.TARGET_SDK_VERSION
-import androidx.build.buildInfo.addCreateLibraryBuildInfoFileTask
+import androidx.build.buildInfo.addCreateLibraryBuildInfoFileTasks
import androidx.build.checkapi.JavaApiTaskConfig
import androidx.build.checkapi.KmpApiTaskConfig
import androidx.build.checkapi.LibraryApiTaskConfig
@@ -418,7 +418,7 @@
project.configurePublicResourcesStub(libraryExtension)
project.configureSourceJarForAndroid(libraryExtension)
project.configureVersionFileWriter(libraryExtension, androidXExtension)
- project.addCreateLibraryBuildInfoFileTask(androidXExtension)
+ project.addCreateLibraryBuildInfoFileTasks(androidXExtension)
project.configureJavaCompilationWarnings(androidXExtension)
project.configureDependencyVerification(androidXExtension) { taskProvider ->
@@ -482,7 +482,7 @@
}
}
- project.addCreateLibraryBuildInfoFileTask(extension)
+ project.addCreateLibraryBuildInfoFileTasks(extension)
// Standard lint, docs, and Metalava configuration for AndroidX projects.
project.configureNonAndroidProjectForLint(extension)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index aad479ab..b298c5b 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -16,6 +16,7 @@
package androidx.build
+import androidx.build.AndroidXImplPlugin.Companion.CREATE_LIBRARY_BUILD_INFO_FILES_TASK
import androidx.build.AndroidXImplPlugin.Companion.ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK
import androidx.build.AndroidXImplPlugin.Companion.ZIP_TEST_CONFIGS_WITH_APKS_TASK
import androidx.build.buildInfo.CreateAggregateLibraryBuildInfoFileTask
@@ -26,8 +27,8 @@
import androidx.build.playground.VerifyPlaygroundGradleConfigurationTask
import androidx.build.studio.StudioTask.Companion.registerStudioTask
import androidx.build.testConfiguration.registerOwnersServiceTasks
-import androidx.build.uptodatedness.cacheEvenIfNoOutputs
import androidx.build.uptodatedness.TaskUpToDateValidator
+import androidx.build.uptodatedness.cacheEvenIfNoOutputs
import com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.LibraryPlugin
@@ -94,7 +95,7 @@
)
)
buildOnServerTask.dependsOn(
- tasks.register(AndroidXImplPlugin.CREATE_LIBRARY_BUILD_INFO_FILES_TASK)
+ tasks.register(CREATE_LIBRARY_BUILD_INFO_FILES_TASK)
)
VerifyPlaygroundGradleConfigurationTask.createIfNecessary(project)?.let {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index 6774607..8702293 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -46,6 +46,7 @@
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.publish.maven.MavenPublication
+import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal
import org.gradle.api.publish.maven.tasks.GenerateMavenPom
import org.gradle.api.publish.tasks.GenerateModuleMetadata
import org.gradle.kotlin.dsl.configure
@@ -350,6 +351,12 @@
}
})
}
+
+ // mark original publication as an alias, so we do not try to publish it.
+ pubs.named("kotlinMultiplatform").configure {
+ it as MavenPublicationInternal
+ it.isAlias = true
+ }
}
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateAggregateLibraryBuildInfoFileTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateAggregateLibraryBuildInfoFileTask.kt
index 7e7ac69..d4ebdb8 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateAggregateLibraryBuildInfoFileTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateAggregateLibraryBuildInfoFileTask.kt
@@ -24,10 +24,10 @@
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.TaskProvider
import org.gradle.work.DisableCachingByDefault
/**
@@ -114,7 +114,7 @@
}
fun Project.addTaskToAggregateBuildInfoFileTask(
- task: TaskProvider<CreateLibraryBuildInfoFileTask>
+ task: Provider<CreateLibraryBuildInfoFileTask>
) {
rootProject.tasks.named(CREATE_AGGREGATE_BUILD_INFO_FILES_TASK).configure {
val aggregateLibraryBuildInfoFileTask = it as CreateAggregateLibraryBuildInfoFileTask
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
index fc1f69c..486ee23 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
@@ -27,20 +27,25 @@
import androidx.build.gitclient.GitClient
import androidx.build.gitclient.GitCommitRange
import androidx.build.jetpad.LibraryBuildInfoFile
+import com.google.common.annotations.VisibleForTesting
import com.google.gson.GsonBuilder
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectComponentPublication
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
+import org.gradle.kotlin.dsl.configure
import org.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
@@ -253,29 +258,55 @@
}
}
-// Task that creates a json file of a project's dependencies
-fun Project.addCreateLibraryBuildInfoFileTask(extension: AndroidXExtension) {
- afterEvaluate {
- if (extension.shouldRelease()) {
- // Only generate build info files for published libraries.
- val task = CreateLibraryBuildInfoFileTask.setup(
- project,
- extension.mavenGroup,
- VariantPublishPlan(
- artifactId = project.name.toString(),
- dependencies = project.provider {
- val config = project.configurations.findByName("releaseRuntimeElements")
- config?.allDependencies.orEmpty().toList()
- }),
- project.provider {
- project.getFrameworksSupportCommitShaAtHead()
+// Tasks that create a json files of a project's variant's dependencies
+fun Project.addCreateLibraryBuildInfoFileTasks(extension: AndroidXExtension) {
+ extension.ifReleasing {
+ configure<PublishingExtension> {
+ // Unfortunately, dependency information is only available through internal API
+ // (See https://github.com/gradle/gradle/issues/21345).
+ publications.withType(MavenPublicationInternal::class.java).configureEach { mavenPub ->
+ // Ideally we would be able to inspect each publication after initial configuration
+ // without using afterEvaluate, but there is not a clean gradle API for doing
+ // that (see https://github.com/gradle/gradle/issues/21424)
+ afterEvaluate {
+ // java-gradle-plugin creates marker publications that are aliases of the
+ // main publication. We do not track these aliases.
+ if (!mavenPub.isAlias) {
+ createTaskForComponent(mavenPub, extension.mavenGroup, mavenPub.artifactId)
+ }
}
- )
-
- rootProject.tasks.named(CreateLibraryBuildInfoFileTask.TASK_NAME).configure {
- it.dependsOn(task)
}
- addTaskToAggregateBuildInfoFileTask(task)
}
}
}
+
+private fun Project.createTaskForComponent(
+ pub: ProjectComponentPublication,
+ libraryGroup: LibraryGroup?,
+ artifactId: String
+) {
+ val task: TaskProvider<CreateLibraryBuildInfoFileTask> =
+ CreateLibraryBuildInfoFileTask.setup(
+ project = project,
+ mavenGroup = libraryGroup,
+ variant = VariantPublishPlan(
+ artifactId = artifactId,
+ taskSuffix = computeTaskSuffix(artifactId),
+ dependencies = project.provider {
+ pub.component?.usages?.flatMap { it.dependencies }.orEmpty()
+ }
+ ),
+ shaProvider = project.provider {
+ project.getFrameworksSupportCommitShaAtHead()
+ }
+ )
+
+ rootProject.tasks.named(CreateLibraryBuildInfoFileTask.TASK_NAME)
+ .configure { it.dependsOn(task) }
+ addTaskToAggregateBuildInfoFileTask(task)
+}
+
+// For examples, see CreateLibraryBuildInfoFileTaskTest
+@VisibleForTesting
+fun computeTaskSuffix(artifactId: String) = artifactId.split("-").drop(1)
+ .joinToString("") { word -> word.replaceFirstChar { it.uppercase() } }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
index 5e62e91..760e4e8 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
@@ -18,6 +18,9 @@
import androidx.camera.camera2.pipe.graph.GraphListener
import kotlinx.coroutines.Deferred
+/**
+ * This is used to uniquely identify a specific backend implementation.
+ */
@JvmInline
value class CameraBackendId(public val value: String)
@@ -94,7 +97,7 @@
* returned instances is managed by [CameraPipe] unless the application asks [CameraPipe] to close
* and release previously created [CameraBackend]s.
*/
-interface CameraBackendFactory {
+fun interface CameraBackendFactory {
/**
* Create a new [CameraBackend] instance based on the provided [CameraContext].
*/
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index 3b83c1f..6c03ad4 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -90,6 +90,12 @@
* @param defaultListeners A default set of listeners that will be added to every [Request].
* @param requiredParameters Will override any other configured parameter, and can be used
* to enforce that specific keys are always set to specific value for every [CaptureRequest].
+ * @param cameraBackendId If defined, this tells the [CameraGraph] to use a specific
+ * [CameraBackend] to open and operate the camera. The defined [camera] parameter must be a
+ * camera that can be opened by this [CameraBackend]. If this value is null it will use the
+ * default backend that has been configured by [CameraPipe].
+ * @param customCameraBackend If defined, this [customCameraBackend] will be created an used for
+ * _only_ this [CameraGraph]. This cannot be defined if [cameraBackendId] is defined.
*/
public data class Config(
val camera: CameraId,
@@ -104,10 +110,18 @@
val defaultListeners: List<Request.Listener> = listOf(),
val requiredParameters: Map<*, Any?> = emptyMap<Any, Any?>(),
+ val cameraBackendId: CameraBackendId? = null,
+ val customCameraBackend: CameraBackendFactory? = null,
val metadataTransform: MetadataTransform = MetadataTransform(),
val flags: Flags = Flags()
// TODO: Internal error handling. May be better at the CameraPipe level.
- )
+ ) {
+ init {
+ check(cameraBackendId == null || customCameraBackend == null) {
+ "Setting both cameraBackendId and customCameraBackend is not supported."
+ }
+ }
+ }
/**
* Flags define boolean values that are used to adjust the behavior and interactions with
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
index 0f41232..2348cf6 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
@@ -117,17 +117,17 @@
/**
* Configure the default and available [CameraBackend] instances that are available.
*
- * @param camera2Backend will override the internal camera2 backend defined by [CameraPipe].
+ * @param internalBackend will override the default camera backend defined by [CameraPipe].
* This may be used to mock and replace all interactions with camera2.
* @param defaultBackend defines which camera backend instance should be used by default. If
* this value is specified, it must appear in the list of [cameraBackends]. If no value is
- * specified, the [camera2Backend] instance OR the internal camera2 backend will be used as
- * the default camera backend.
- * @param cameraBackends defines a map of unique camera backend factories that may be used via
- * [CameraPipe].
+ * specified, the [internalBackend] instance will be used. If [internalBackend] is null, the
+ * default backend will use the pre-defined [CameraPipe] internal backend.
+ * @param cameraBackends defines a map of unique [CameraBackendFactory] that may be used to
+ * create, query, and operate cameras via [CameraPipe].
*/
class CameraBackendConfig(
- val camera2Backend: CameraBackend? = null,
+ val internalBackend: CameraBackend? = null,
val defaultBackend: CameraBackendId? = null,
val cameraBackends: Map<CameraBackendId, CameraBackendFactory> = emptyMap()
) {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
new file mode 100644
index 0000000..f2a53d3
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 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 androidx.camera.camera2.pipe.compat
+
+import android.hardware.camera2.CameraAccessException
+import android.hardware.camera2.CameraManager
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraBackend
+import androidx.camera.camera2.pipe.CameraBackendId
+import androidx.camera.camera2.pipe.CameraContext
+import androidx.camera.camera2.pipe.CameraController
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.StreamGraph
+import androidx.camera.camera2.pipe.config.Camera2ControllerComponent
+import androidx.camera.camera2.pipe.config.Camera2ControllerConfig
+import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.graph.GraphListener
+import androidx.camera.camera2.pipe.graph.StreamGraphImpl
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
+
+/**
+ * This is the default [CameraBackend] implementation for CameraPipe based on Camera2.
+ */
+@RequiresApi(21)
+internal class Camera2Backend @Inject constructor(
+ private val cameraManager: Provider<CameraManager>,
+ private val camera2MetadataCache: Camera2MetadataCache,
+ private val virtualCameraManager: VirtualCameraManager,
+ private val camera2CameraControllerComponent: Camera2ControllerComponent.Builder,
+) : CameraBackend {
+ override val id: CameraBackendId
+ get() = CameraBackendId("CXCP-Camera2")
+
+ override fun readCameraIdList(): List<CameraId> {
+ val cameraManager = cameraManager.get()
+ val cameraIdArray = try {
+ // WARNING: This method can, at times, return an empty list of cameras on devices that
+ // will normally return a valid list of cameras (b/159052778)
+ cameraManager.cameraIdList
+ } catch (e: CameraAccessException) {
+ Log.warn(e) { "Failed to query CameraManager#getCameraIdList!" }
+ null
+ }
+ if (cameraIdArray?.isEmpty() == true) {
+ Log.warn { "Failed to query CameraManager#getCameraIdList: No values returned." }
+ }
+
+ return cameraIdArray?.map { CameraId(it) } ?: listOf()
+ }
+
+ override fun readCameraMetadata(cameraId: CameraId): CameraMetadata =
+ camera2MetadataCache.readCameraMetadata(cameraId)
+
+ override fun disconnectAllAsync(): Deferred<Unit> {
+ // TODO: VirtualCameraManager needs to be extended to support a suspendable future that can
+ // be used to wait until close has been called on all camera devices.
+ virtualCameraManager.closeAll()
+ return CompletableDeferred(Unit)
+ }
+
+ override fun shutdownAsync(): Deferred<Unit> {
+ // TODO: VirtualCameraManager needs to be extended to support a suspendable future that can
+ // be used to wait until close has been called on all camera devices.
+ virtualCameraManager.closeAll()
+ return CompletableDeferred(Unit)
+ }
+
+ override fun createCameraController(
+ cameraContext: CameraContext,
+ graphConfig: CameraGraph.Config,
+ graphListener: GraphListener,
+ streamGraph: StreamGraph
+ ): CameraController {
+ // Use Dagger to create the camera2 controller component, then create the CameraController.
+ val cameraControllerComponent = camera2CameraControllerComponent.camera2ControllerConfig(
+ Camera2ControllerConfig(
+ this,
+ graphConfig,
+ graphListener,
+ streamGraph as StreamGraphImpl
+ )
+ ).build()
+
+ // Create and return a Camera2 CameraController object.
+ return cameraControllerComponent.cameraController()
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Camera2Controller.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
similarity index 71%
rename from camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Camera2Controller.kt
rename to camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
index b726377..5c58947 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Camera2Controller.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
@@ -16,18 +16,19 @@
package androidx.camera.camera2.pipe.compat
+import android.view.Surface
import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraController
import androidx.camera.camera2.pipe.CameraGraph
-import androidx.camera.camera2.pipe.config.CameraGraphScope
-import androidx.camera.camera2.pipe.config.ForCameraGraph
+import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.config.Camera2ControllerScope
import androidx.camera.camera2.pipe.graph.GraphListener
-import androidx.camera.camera2.pipe.graph.StreamGraphImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
- * This represents the core state loop for a Camera Graph instance.
+ * This represents the core state loop for a CameraGraph instance.
*
* A camera graph will receive start / stop signals from the application. When started, it will do
* everything possible to bring up and maintain an active camera instance with the given
@@ -36,18 +37,18 @@
* TODO: Reorganize these constructor parameters.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-@CameraGraphScope
-internal class Camera2Camera2Controller @Inject constructor(
- @ForCameraGraph private val scope: CoroutineScope,
+@Camera2ControllerScope
+internal class Camera2CameraController @Inject constructor(
+ private val scope: CoroutineScope,
private val config: CameraGraph.Config,
private val graphListener: GraphListener,
private val captureSessionFactory: CaptureSessionFactory,
private val requestProcessorFactory: Camera2RequestProcessorFactory,
- private val virtualCameraManager: VirtualCameraManager,
- private val streamGraph: StreamGraphImpl
-) : Camera2Controller {
+ private val virtualCameraManager: VirtualCameraManager
+) : CameraController {
private var currentCamera: VirtualCamera? = null
private var currentSession: VirtualSessionState? = null
+ private var surfaceMap: Map<StreamId, Surface>? = null
override fun start() {
val camera = virtualCameraManager.open(
@@ -59,14 +60,20 @@
check(currentSession == null)
currentCamera = camera
- currentSession = VirtualSessionState(
+ val session = VirtualSessionState(
graphListener,
captureSessionFactory,
requestProcessorFactory,
scope
)
+ currentSession = session
+
+ val surfaces: Map<StreamId, Surface>? = surfaceMap
+ if (surfaces != null) {
+ session.onSurfaceMapUpdated(surfaces)
+ }
}
- scope.launch { configure() }
+ scope.launch { bindSessionToCamera() }
}
override fun stop() {
@@ -86,30 +93,20 @@
}
}
- override fun restart() {
- val oldSession: VirtualSessionState?
- val newSession: VirtualSessionState?
+ override fun close() {
+ // TODO: Consider changing the behavior so that start / stop are not invokable after calling
+ // close.
+ stop()
+ }
+ override fun updateSurfaceMap(surfaceMap: Map<StreamId, Surface>) {
synchronized(this) {
- check(currentCamera != null) { "Cannot invoke reconfigure while stopped." }
-
- oldSession = currentSession
- newSession = VirtualSessionState(
- graphListener,
- captureSessionFactory,
- requestProcessorFactory,
- scope
- )
- currentSession = newSession
- }
-
- scope.launch {
- oldSession?.disconnect()
- configure()
+ this.surfaceMap = surfaceMap
+ currentSession?.onSurfaceMapUpdated(surfaceMap)
}
}
- private suspend fun configure() {
+ private suspend fun bindSessionToCamera() {
val camera: VirtualCamera?
val session: VirtualSessionState?
@@ -119,7 +116,6 @@
}
if (camera != null && session != null) {
- streamGraph.listener = session
camera.state.collect {
if (it is CameraStateOpen) {
session.cameraDevice = it.cameraDevice
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Controller.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Controller.kt
deleted file mode 100644
index 32a9395..0000000
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Controller.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2021 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 androidx.camera.camera2.pipe.compat
-
-import androidx.annotation.RequiresApi
-import androidx.camera.camera2.pipe.RequestProcessor
-
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-internal interface Camera2Controller {
- /**
- * Tell the graph to start and initialize a [RequestProcessor] instances.
- */
- fun start()
-
- /**
- * Tell the [Camera2Controller] to stop initialization and to tear down any existing
- * [RequestProcessor] instance.
- */
- fun stop()
-
- /**
- * Signals the [Camera2Controller] that a [RequestProcessor] may need to be recreated.
- */
- fun restart()
-}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
index c781ed2..40b16d8 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
@@ -81,6 +81,10 @@
}
}
+ fun readCameraMetadata(cameraId: CameraId): CameraMetadata {
+ return createCameraMetadata(cameraId, isMetadataRedacted())
+ }
+
private fun createCameraMetadata(cameraId: CameraId, redacted: Boolean): Camera2CameraMetadata {
val start = Timestamps.now()
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt
index 1e232b8..275b27a 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactory.kt
@@ -26,7 +26,7 @@
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.compat.OutputConfigurationWrapper.Companion.SURFACE_GROUP_ID_NONE
-import androidx.camera.camera2.pipe.config.CameraGraphScope
+import androidx.camera.camera2.pipe.config.Camera2ControllerScope
import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.core.Threads
import androidx.camera.camera2.pipe.graph.StreamGraphImpl
@@ -51,9 +51,9 @@
}
@Module
-internal object SessionFactoryModule {
+internal object Camera2CaptureSessionsModule {
@SuppressLint("ObsoleteSdkInt")
- @CameraGraphScope
+ @Camera2ControllerScope
@Provides
fun provideSessionFactory(
androidLProvider: Provider<AndroidLSessionFactory>,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
new file mode 100644
index 0000000..6af025d
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 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 androidx.camera.camera2.pipe.config
+
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.StreamGraph
+import androidx.camera.camera2.pipe.CameraBackend
+import androidx.camera.camera2.pipe.CameraController
+import androidx.camera.camera2.pipe.compat.Camera2Backend
+import androidx.camera.camera2.pipe.compat.Camera2CameraController
+import androidx.camera.camera2.pipe.compat.Camera2CaptureSessionsModule
+import androidx.camera.camera2.pipe.compat.Camera2RequestProcessorFactory
+import androidx.camera.camera2.pipe.compat.StandardCamera2RequestProcessorFactory
+import androidx.camera.camera2.pipe.core.Threads
+import androidx.camera.camera2.pipe.graph.GraphListener
+import androidx.camera.camera2.pipe.graph.StreamGraphImpl
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import javax.inject.Scope
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@Module(
+ subcomponents = [
+ Camera2ControllerComponent::class
+ ]
+)
+internal abstract class Camera2Module {
+ @Binds
+ @CameraPipeCameraBackend
+ abstract fun bindCameraPipeCameraBackend(camera2Backend: Camera2Backend): CameraBackend
+}
+
+@Scope
+internal annotation class Camera2ControllerScope
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@Camera2ControllerScope
+@Subcomponent(
+ modules = [
+ Camera2ControllerConfig::class,
+ Camera2ControllerModule::class,
+ Camera2CaptureSessionsModule::class
+ ]
+)
+internal interface Camera2ControllerComponent {
+ fun cameraController(): CameraController
+
+ @Subcomponent.Builder
+ interface Builder {
+ fun camera2ControllerConfig(config: Camera2ControllerConfig): Builder
+ fun build(): Camera2ControllerComponent
+ }
+}
+
+@Module
+internal class Camera2ControllerConfig(
+ private val cameraBackend: CameraBackend,
+ private val graphConfig: CameraGraph.Config,
+ private val graphListener: GraphListener,
+ private val streamGraph: StreamGraph,
+) {
+ @Provides
+ fun provideCameraGraphConfig() = graphConfig
+
+ @Provides
+ fun provideCameraBackend() = cameraBackend
+
+ @Provides
+ fun provideStreamGraph() = streamGraph as StreamGraphImpl
+
+ @Provides
+ fun provideGraphListener() = graphListener
+}
+
+@Module
+internal abstract class Camera2ControllerModule {
+ @Binds
+ abstract fun bindCamera2RequestProcessorFactory(
+ factoryStandard: StandardCamera2RequestProcessorFactory
+ ): Camera2RequestProcessorFactory
+
+ @Binds
+ abstract fun bindCameraController(
+ camera2CameraController: Camera2CameraController
+ ): CameraController
+
+ companion object {
+ @Camera2ControllerScope
+ @Provides
+ fun provideCoroutineScope(threads: Threads): CoroutineScope {
+ return CoroutineScope(
+ threads.lightweightDispatcher.plus(CoroutineName("CXCP-Camera2Controller"))
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
index 413553e..b83853a 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
@@ -19,21 +19,20 @@
package androidx.camera.camera2.pipe.config
import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraBackend
+import androidx.camera.camera2.pipe.CameraBackends
+import androidx.camera.camera2.pipe.CameraContext
+import androidx.camera.camera2.pipe.CameraController
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraMetadata
import androidx.camera.camera2.pipe.Request
-import androidx.camera.camera2.pipe.compat.Camera2Camera2Controller
-import androidx.camera.camera2.pipe.compat.Camera2MetadataCache
-import androidx.camera.camera2.pipe.compat.Camera2RequestProcessorFactory
-import androidx.camera.camera2.pipe.compat.Camera2Controller
-import androidx.camera.camera2.pipe.compat.SessionFactoryModule
-import androidx.camera.camera2.pipe.compat.StandardCamera2RequestProcessorFactory
import androidx.camera.camera2.pipe.core.Threads
import androidx.camera.camera2.pipe.graph.CameraGraphImpl
import androidx.camera.camera2.pipe.graph.GraphListener
import androidx.camera.camera2.pipe.graph.GraphProcessor
import androidx.camera.camera2.pipe.graph.GraphProcessorImpl
import androidx.camera.camera2.pipe.graph.Listener3A
+import androidx.camera.camera2.pipe.graph.StreamGraphImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -52,9 +51,9 @@
@CameraGraphScope
@Subcomponent(
modules = [
- CameraGraphModules::class,
+ SharedCameraGraphModules::class,
+ InternalCameraGraphModules::class,
CameraGraphConfigModule::class,
- Camera2CameraGraphModules::class,
]
)
internal interface CameraGraphComponent {
@@ -76,7 +75,7 @@
}
@Module
-internal abstract class CameraGraphModules {
+internal abstract class SharedCameraGraphModules {
@Binds
abstract fun bindCameraGraph(cameraGraph: CameraGraphImpl): CameraGraph
@@ -91,7 +90,9 @@
@Provides
@ForCameraGraph
fun provideCameraGraphCoroutineScope(threads: Threads): CoroutineScope {
- return CoroutineScope(threads.lightweightDispatcher.plus(CoroutineName("CXCP-Graph")))
+ return CoroutineScope(
+ threads.lightweightDispatcher.plus(CoroutineName("CXCP-Graph"))
+ )
}
@CameraGraphScope
@@ -115,27 +116,54 @@
}
}
-@Module(
- includes = [
- SessionFactoryModule::class
- ]
-)
-internal abstract class Camera2CameraGraphModules {
- @Binds
- abstract fun bindRequestProcessorFactory(
- factoryStandard: StandardCamera2RequestProcessorFactory
- ): Camera2RequestProcessorFactory
-
- @Binds
- abstract fun bindGraphState(camera2CameraState: Camera2Camera2Controller): Camera2Controller
-
+@Module
+internal abstract class InternalCameraGraphModules {
companion object {
+ @CameraGraphScope
@Provides
- fun provideCamera2Metadata(
+ fun provideCameraBackend(
+ cameraBackends: CameraBackends,
graphConfig: CameraGraph.Config,
- metadataCache: Camera2MetadataCache
+ cameraContext: CameraContext
+ ): CameraBackend {
+ val customCameraBackend = graphConfig.customCameraBackend
+ if (customCameraBackend != null) {
+ return customCameraBackend.create(cameraContext)
+ }
+
+ val cameraBackendId = graphConfig.cameraBackendId
+ if (cameraBackendId != null) {
+ cameraBackends[cameraBackendId]
+ }
+ return cameraBackends.default
+ }
+
+ @CameraGraphScope
+ @Provides
+ fun provideCameraMetadata(
+ graphConfig: CameraGraph.Config,
+ cameraBackend: CameraBackend
): CameraMetadata {
- return metadataCache.awaitMetadata(graphConfig.camera)
+ // TODO: It might be a good idea to cache and go through caches for some of these calls
+ // instead of reading it directly from the backend.
+ return cameraBackend.readCameraMetadata(graphConfig.camera)
+ }
+
+ @CameraGraphScope
+ @Provides
+ fun provideCameraController(
+ graphConfig: CameraGraph.Config,
+ cameraBackend: CameraBackend,
+ cameraContext: CameraContext,
+ graphProcessor: GraphProcessorImpl,
+ streamGraph: StreamGraphImpl,
+ ): CameraController {
+ return cameraBackend.createCameraController(
+ cameraContext,
+ graphConfig,
+ graphProcessor,
+ streamGraph
+ )
}
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
index de31e8c..081771c 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
@@ -21,22 +21,36 @@
import android.content.Context
import android.hardware.camera2.CameraManager
import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraBackend
+import androidx.camera.camera2.pipe.CameraBackendFactory
+import androidx.camera.camera2.pipe.CameraBackendId
+import androidx.camera.camera2.pipe.CameraBackends
+import androidx.camera.camera2.pipe.internal.CameraBackendsImpl
+import androidx.camera.camera2.pipe.CameraContext
import androidx.camera.camera2.pipe.CameraDevices
import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.CameraPipe.CameraMetadataConfig
import androidx.camera.camera2.pipe.compat.Camera2CameraDevices
+import androidx.camera.camera2.pipe.core.Debug
+import androidx.camera.camera2.pipe.core.Threads
import dagger.Binds
import dagger.Component
import dagger.Module
import dagger.Provides
import dagger.Reusable
+import javax.inject.Provider
+import javax.inject.Qualifier
import javax.inject.Singleton
+@Qualifier
+internal annotation class CameraPipeCameraBackend
+
@Singleton
@Component(
modules = [
CameraPipeConfigModule::class,
- Camera2CameraPipeModules::class,
+ CameraPipeModules::class,
+ Camera2Module::class,
]
)
internal interface CameraPipeComponent {
@@ -54,7 +68,7 @@
}
@Module
-internal abstract class Camera2CameraPipeModules {
+internal abstract class CameraPipeModules {
@Binds
abstract fun bindCameras(impl: Camera2CameraDevices): CameraDevices
@@ -70,5 +84,50 @@
@Provides
fun provideCameraManager(context: Context): CameraManager =
context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+
+ @Singleton
+ @Provides
+ fun provideCameraContext(
+ context: Context,
+ threads: Threads,
+ cameraBackends: CameraBackends
+ ): CameraContext =
+ object : CameraContext {
+ override val appContext: Context = context
+ override val threads: Threads = threads
+ override val cameraBackends: CameraBackends = cameraBackends
+ }
+
+ @Singleton
+ @Provides
+ fun provideCameraBackends(
+ config: CameraPipe.Config,
+ @CameraPipeCameraBackend cameraPipeCameraBackend: Provider<CameraBackend>,
+ appContext: Context,
+ threads: Threads,
+ ): CameraBackends {
+ // This is intentionally lazy. If an internalBackend is defined as part of the
+ // CameraPipe configuration, we will never create the default cameraPipeCameraBackend.
+ val internalBackend = config.cameraBackendConfig.internalBackend
+ ?: Debug.trace("Initialize cameraPipeCameraBackend") {
+ cameraPipeCameraBackend.get()
+ }
+
+ // Make sure that the list of additional backends does not contain the
+ check(!config.cameraBackendConfig.cameraBackends.containsKey(internalBackend.id)) {
+ "CameraBackendConfig#cameraBackends should not contain a backend with " +
+ "${internalBackend.id}. Use CameraBackendConfig#internalBackend field instead."
+ }
+ val allBackends: Map<CameraBackendId, CameraBackendFactory> =
+ config.cameraBackendConfig.cameraBackends +
+ (internalBackend.id to CameraBackendFactory { internalBackend })
+
+ val defaultBackendId = config.cameraBackendConfig.defaultBackend ?: internalBackend.id
+ check(allBackends.containsKey(defaultBackendId)) {
+ "Failed to find $defaultBackendId in the list of available CameraPipe backends! " +
+ "Available values are ${allBackends.keys}"
+ }
+ return CameraBackendsImpl(defaultBackendId, allBackends, appContext, threads)
+ }
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
index 544c072..bb13f5f 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
@@ -18,11 +18,13 @@
package androidx.camera.camera2.pipe.config
+import android.view.Surface
import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraController
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraMetadata
import androidx.camera.camera2.pipe.RequestProcessor
-import androidx.camera.camera2.pipe.compat.Camera2Controller
+import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.graph.GraphListener
import dagger.Module
import dagger.Provides
@@ -32,7 +34,7 @@
@CameraGraphScope
@Subcomponent(
modules = [
- CameraGraphModules::class,
+ SharedCameraGraphModules::class,
ExternalCameraGraphConfigModule::class
]
)
@@ -59,8 +61,8 @@
fun provideCameraMetadata(): CameraMetadata = cameraMetadata
@Provides
- fun provideGraphController(graphListener: GraphListener): Camera2Controller =
- object : Camera2Controller {
+ fun provideGraphController(graphListener: GraphListener): CameraController =
+ object : CameraController {
var started = atomic(false)
override fun start() {
if (started.compareAndSet(expect = false, update = true)) {
@@ -74,9 +76,10 @@
}
}
- override fun restart() {
- stop()
- start()
+ override fun close() {
+ }
+
+ override fun updateSurfaceMap(surfaceMap: Map<StreamId, Surface>) {
}
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
index 3f8796a..74ca0c5 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-
package androidx.camera.camera2.pipe.graph
import android.view.Surface
import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraController
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraMetadata
import androidx.camera.camera2.pipe.StreamGraph
import androidx.camera.camera2.pipe.StreamId
-import androidx.camera.camera2.pipe.compat.Camera2Controller
import androidx.camera.camera2.pipe.config.CameraGraphScope
import androidx.camera.camera2.pipe.core.Debug
import androidx.camera.camera2.pipe.core.Log
@@ -36,13 +34,15 @@
internal val cameraGraphIds = atomic(0)
+@RequiresApi(21)
@CameraGraphScope
internal class CameraGraphImpl @Inject constructor(
graphConfig: CameraGraph.Config,
metadata: CameraMetadata,
private val graphProcessor: GraphProcessor,
private val streamGraph: StreamGraphImpl,
- private val camera2Controller: Camera2Controller,
+ private val surfaceGraph: SurfaceGraph,
+ private val cameraController: CameraController,
private val graphState3A: GraphState3A,
private val listener3A: Listener3A
) : CameraGraph {
@@ -66,14 +66,14 @@
override fun start() {
Debug.traceStart { "$this#start" }
Log.info { "Starting $this" }
- camera2Controller.start()
+ cameraController.start()
Debug.traceStop()
}
override fun stop() {
Debug.traceStart { "$this#stop" }
Log.info { "Stopping $this" }
- camera2Controller.stop()
+ cameraController.stop()
Debug.traceStop()
}
@@ -98,7 +98,7 @@
check(surface == null || surface.isValid) {
"Failed to set $surface to $stream: The surface was not valid."
}
- streamGraph[stream] = surface
+ surfaceGraph[stream] = surface
Debug.traceStop()
}
@@ -107,7 +107,7 @@
Log.info { "Closing $this" }
sessionLock.close()
graphProcessor.close()
- camera2Controller.stop()
+ cameraController.close()
Debug.traceStop()
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
index d684d72..12050cf 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
@@ -39,7 +39,6 @@
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.compat.Api24Compat
import androidx.camera.camera2.pipe.config.CameraGraphScope
-import androidx.camera.camera2.pipe.core.Log
import javax.inject.Inject
import kotlinx.atomicfu.atomic
@@ -65,7 +64,6 @@
cameraMetadata: CameraMetadata,
graphConfig: CameraGraph.Config
) : StreamGraph {
- private val surfaceMap: MutableMap<StreamId, Surface> = mutableMapOf()
private val _streamMap: Map<CameraStream.Config, CameraStream>
internal val outputConfigs: List<OutputConfig>
@@ -168,63 +166,6 @@
outputConfigs = outputConfigListBuilder
}
- private var _listener: SurfaceListener? = null
- var listener: SurfaceListener?
- get() = _listener
- set(value) {
- _listener = value
- if (value != null) {
- maybeUpdateSurfaces()
- }
- }
-
- operator fun set(stream: StreamId, surface: Surface?) {
- Log.info {
- if (surface != null) {
- "Configured $stream to use $surface"
- } else {
- "Removed surface for $stream"
- }
- }
- if (surface == null) {
- // TODO: Tell the graph processor that it should resubmit the repeating request or
- // reconfigure the camera2 captureSession
- surfaceMap.remove(stream)
- } else {
- surfaceMap[stream] = surface
- }
- maybeUpdateSurfaces()
- }
-
- private fun maybeUpdateSurfaces() {
- val surfaceListener = _listener ?: return
- val surfaces = buildSurfaceMap()
- if (surfaces.isEmpty()) {
- return
- }
- surfaceListener.onSurfaceMapUpdated(surfaces)
- }
-
- private fun buildSurfaceMap(): Map<StreamId, Surface> {
- // Rules:
- // 1. There must be at least one non-null, valid surface.
- // 2. All non-deferrable streams must have a valid surface.
- val surfaces = mutableMapOf<StreamId, Surface>()
- for (outputConfig in outputConfigs) {
- for (stream in outputConfig.streamBuilder) {
- val surface = surfaceMap[stream.id]
- if (surface == null) {
- if (!outputConfig.deferrable) {
- return emptyMap()
- }
- } else {
- surfaces[stream.id] = surface
- }
- }
- }
- return surfaces
- }
-
@Suppress("SyntheticAccessor") // StreamId generates a synthetic constructor
class OutputConfig(
val id: OutputConfigId,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt
new file mode 100644
index 0000000..c107afa
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 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 androidx.camera.camera2.pipe.graph
+
+import android.view.Surface
+import androidx.annotation.GuardedBy
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraController
+import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.config.CameraGraphScope
+import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.CameraGraph
+import javax.inject.Inject
+
+/**
+ * A SurfaceGraph tracks the current stream-to-surface mapping state for a [CameraGraph] instance.
+ *
+ * It's primary responsibility is aggregating the current stream-to-surface mapping and passing the
+ * most up to date version to the [CameraController] instance.
+ */
+@RequiresApi(21)
+@CameraGraphScope
+internal class SurfaceGraph @Inject constructor(
+ private val streamGraph: StreamGraphImpl,
+ private val cameraController: CameraController
+) {
+ private val lock = Any()
+
+ @GuardedBy("lock")
+ private val surfaceMap: MutableMap<StreamId, Surface> = mutableMapOf()
+
+ operator fun set(stream: StreamId, surface: Surface?) {
+ Log.info {
+ if (surface != null) {
+ "Configured $stream to use $surface"
+ } else {
+ "Removed surface for $stream"
+ }
+ }
+
+ synchronized(lock) {
+ if (surface == null) {
+ // TODO: Tell the graph processor that it should resubmit the repeating request or
+ // reconfigure the camera2 captureSession
+ surfaceMap.remove(stream)
+ } else {
+ surfaceMap[stream] = surface
+ }
+ }
+
+ maybeUpdateSurfaces()
+ }
+
+ private fun maybeUpdateSurfaces() {
+ // Rules:
+ // 1. There must be at least one non-null surface.
+ // 2. All non-deferrable streams must have a non-null surface.
+
+ val surfaces = buildSurfaceMap()
+ if (surfaces.isEmpty()) {
+ return
+ }
+ cameraController.updateSurfaceMap(surfaces)
+ }
+
+ private fun buildSurfaceMap(): Map<StreamId, Surface> = synchronized(lock) {
+ val surfaces = mutableMapOf<StreamId, Surface>()
+ for (outputConfig in streamGraph.outputConfigs) {
+ for (stream in outputConfig.streamBuilder) {
+ val surface = surfaceMap[stream.id]
+ if (surface == null) {
+ if (!outputConfig.deferrable) {
+ // If output is non-deferrable, a surface must be available or the config
+ // is not yet valid. Exit now with an empty map.
+ return emptyMap()
+ }
+ } else {
+ surfaces[stream.id] = surface
+ }
+ }
+ }
+ return surfaces
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt
new file mode 100644
index 0000000..e7ed8fc
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 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 androidx.camera.camera2.pipe.internal
+
+import android.content.Context
+import androidx.annotation.GuardedBy
+import androidx.camera.camera2.pipe.CameraBackend
+import androidx.camera.camera2.pipe.CameraBackendFactory
+import androidx.camera.camera2.pipe.CameraBackendId
+import androidx.camera.camera2.pipe.CameraBackends
+import androidx.camera.camera2.pipe.CameraContext
+import androidx.camera.camera2.pipe.core.Threads
+
+/**
+ * Provides an implementation for interacting with CameraBackends.
+ */
+internal class CameraBackendsImpl(
+ private val defaultBackendId: CameraBackendId,
+ private val cameraBackends: Map<CameraBackendId, CameraBackendFactory>,
+ private val appContext: Context,
+ private val threads: Threads
+) : CameraBackends {
+ private val lock = Any()
+
+ @GuardedBy("lock")
+ private val activeCameraBackends = mutableMapOf<CameraBackendId, CameraBackend>()
+
+ override val default: CameraBackend = checkNotNull(get(defaultBackendId)) {
+ "Failed to load the default backend for $defaultBackendId! Available backends are " +
+ "${cameraBackends.keys}"
+ }
+
+ override val allIds: Set<CameraBackendId>
+ get() = cameraBackends.keys
+ override val activeIds: Set<CameraBackendId>
+ get() = synchronized(lock) { activeCameraBackends.keys }
+
+ override fun get(backendId: CameraBackendId): CameraBackend? {
+ synchronized(lock) {
+ val existing = activeCameraBackends[backendId]
+ if (existing != null) return existing
+
+ val backend = cameraBackends[backendId]?.create(
+ CameraBackendContext(appContext, threads, this)
+ )
+ if (backend != null) {
+ check(backendId == backend.id) {
+ "Unexpected backend id! Expected $backendId but it was actually ${backend.id}"
+ }
+ activeCameraBackends[backendId] = backend
+ }
+ return backend
+ }
+ }
+
+ internal class CameraBackendContext(
+ override val appContext: Context,
+ override val threads: Threads,
+ override val cameraBackends: CameraBackends
+ ) : CameraContext
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt
index c6c2ef3..3ed5505 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/CaptureSessionFactoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 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.
@@ -28,9 +28,9 @@
import androidx.camera.camera2.pipe.RequestProcessor
import androidx.camera.camera2.pipe.StreamFormat
import androidx.camera.camera2.pipe.StreamId
-import androidx.camera.camera2.pipe.config.Camera2CameraGraphModules
-import androidx.camera.camera2.pipe.config.Camera2CameraPipeModules
-import androidx.camera.camera2.pipe.config.CameraGraphModules
+import androidx.camera.camera2.pipe.config.Camera2ControllerScope
+import androidx.camera.camera2.pipe.config.CameraPipeModules
+import androidx.camera.camera2.pipe.config.SharedCameraGraphModules
import androidx.camera.camera2.pipe.config.CameraGraphScope
import androidx.camera.camera2.pipe.config.ThreadConfigModule
import androidx.camera.camera2.pipe.graph.StreamGraphImpl
@@ -69,10 +69,11 @@
@Test
fun canCreateSessionFactoryTestComponent() = runTest {
- val component: CameraSessionTestComponent = DaggerCameraSessionTestComponent.builder()
- .fakeCameraPipeModule(FakeCameraPipeModule(context, testCamera))
- .threadConfigModule(ThreadConfigModule(CameraPipe.ThreadConfig()))
- .build()
+ val component: Camera2CaptureSessionTestComponent =
+ DaggerCamera2CaptureSessionTestComponent.builder()
+ .fakeCameraPipeModule(FakeCameraPipeModule(context, testCamera))
+ .threadConfigModule(ThreadConfigModule(CameraPipe.ThreadConfig()))
+ .build()
val sessionFactory = component.sessionFactory()
assertThat(sessionFactory).isNotNull()
@@ -80,10 +81,11 @@
@Test
fun createCameraCaptureSession() = runTest {
- val component: CameraSessionTestComponent = DaggerCameraSessionTestComponent.builder()
- .fakeCameraPipeModule(FakeCameraPipeModule(context, testCamera))
- .threadConfigModule(ThreadConfigModule(CameraPipe.ThreadConfig()))
- .build()
+ val component: Camera2CaptureSessionTestComponent =
+ DaggerCamera2CaptureSessionTestComponent.builder()
+ .fakeCameraPipeModule(FakeCameraPipeModule(context, testCamera))
+ .threadConfigModule(ThreadConfigModule(CameraPipe.ThreadConfig()))
+ .build()
val sessionFactory = component.sessionFactory()
val streamMap = component.streamMap()
@@ -125,13 +127,15 @@
@Singleton
@CameraGraphScope
+@Camera2ControllerScope
@Component(
modules = [
FakeCameraGraphModule::class,
- FakeCameraPipeModule::class
+ FakeCameraPipeModule::class,
+ Camera2CaptureSessionsModule::class
]
)
-internal interface CameraSessionTestComponent {
+internal interface Camera2CaptureSessionTestComponent {
fun graphConfig(): CameraGraph.Config
fun sessionFactory(): CaptureSessionFactory
fun streamMap(): StreamGraphImpl
@@ -140,7 +144,7 @@
/**
* Utility module for testing the Dagger generated graph with a a reasonable default config.
*/
-@Module(includes = [ThreadConfigModule::class, Camera2CameraPipeModules::class])
+@Module(includes = [ThreadConfigModule::class, CameraPipeModules::class])
class FakeCameraPipeModule(
private val context: Context,
private val fakeCamera: RobolectricCameras.FakeCamera
@@ -153,10 +157,14 @@
fun provideFakeCameraPipeConfig() = CameraPipe.Config(context)
}
-@Module(includes = [CameraGraphModules::class, Camera2CameraGraphModules::class])
+@Module(includes = [SharedCameraGraphModules::class])
class FakeCameraGraphModule {
@Provides
@CameraGraphScope
+ fun provideFakeCameraMetadata(fakeCamera: RobolectricCameras.FakeCamera) = fakeCamera.metadata
+
+ @Provides
+ @CameraGraphScope
fun provideFakeGraphConfig(fakeCamera: RobolectricCameras.FakeCamera): CameraGraph.Config {
val stream = CameraStream.Config.create(
Size(640, 480),
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
index a7094ff..33f40d7 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
@@ -21,7 +21,7 @@
import android.os.Build
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.Request
-import androidx.camera.camera2.pipe.testing.FakeCamera2Controller
+import androidx.camera.camera2.pipe.testing.FakeCameraController
import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
@@ -44,7 +44,7 @@
cameraId = fakeCameraId
)
private val fakeGraphProcessor = FakeGraphProcessor()
- private val fakeCameraController = FakeCamera2Controller()
+ private val fakeCameraController = FakeCameraController()
private lateinit var impl: CameraGraphImpl
@Before
@@ -53,14 +53,18 @@
camera = fakeCameraId,
streams = listOf(),
)
+ val streamGraph = StreamGraphImpl(
+ fakeMetadata,
+ config
+ )
+ val surfaceGraph = SurfaceGraph(streamGraph, fakeCameraController)
+
impl = CameraGraphImpl(
config,
fakeMetadata,
fakeGraphProcessor,
- StreamGraphImpl(
- fakeMetadata,
- config
- ),
+ streamGraph,
+ surfaceGraph,
fakeCameraController,
GraphState3A(),
Listener3A()
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/StreamGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/StreamGraphImplTest.kt
index 17b0ac5..4037146 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/StreamGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/StreamGraphImplTest.kt
@@ -16,19 +16,14 @@
package androidx.camera.camera2.pipe.graph
-import android.graphics.SurfaceTexture
-import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
-import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
import android.os.Build
import android.util.Size
-import android.view.Surface
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.CameraStream
import androidx.camera.camera2.pipe.OutputStream
import androidx.camera.camera2.pipe.StreamFormat
-import androidx.camera.camera2.pipe.StreamId
-import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.camera2.pipe.testing.FakeCameraGraphConfig
import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -40,65 +35,25 @@
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
internal class StreamGraphImplTest {
- private val fakeMetadata = FakeCameraMetadata(
- mapOf(INFO_SUPPORTED_HARDWARE_LEVEL to INFO_SUPPORTED_HARDWARE_LEVEL_FULL)
- )
-
- private val camera1 = CameraId("TestCamera-1")
- private val camera2 = CameraId("TestCamera-2")
-
- private val streamConfig1 = CameraStream.Config.create(
- size = Size(100, 100),
- format = StreamFormat.YUV_420_888
- )
- private val streamConfig2 = CameraStream.Config.create(
- size = Size(123, 321),
- format = StreamFormat.YUV_420_888,
- camera = camera1
- )
- private val streamConfig3 = CameraStream.Config.create(
- size = Size(200, 200),
- format = StreamFormat.YUV_420_888,
- camera = camera2,
- outputType = OutputStream.OutputType.SURFACE_TEXTURE
- )
- private val sharedOutputConfig = OutputStream.Config.create(
- size = Size(200, 200),
- format = StreamFormat.YUV_420_888,
- camera = camera1
- )
- private val sharedStreamConfig1 = CameraStream.Config.create(sharedOutputConfig)
- private val sharedStreamConfig2 = CameraStream.Config.create(sharedOutputConfig)
-
- private val graphConfig = CameraGraph.Config(
- camera = camera1,
- streams = listOf(
- streamConfig1,
- streamConfig2,
- streamConfig3,
- sharedStreamConfig1,
- sharedStreamConfig2
- ),
- streamSharingGroups = listOf(listOf(streamConfig1, streamConfig2))
- )
+ private val config = FakeCameraGraphConfig()
@Test
fun testPrecomputedTestData() {
- val streamGraph = StreamGraphImpl(fakeMetadata, graphConfig)
+ val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
assertThat(streamGraph.streams).hasSize(5)
assertThat(streamGraph.streams).hasSize(5)
assertThat(streamGraph.outputConfigs).hasSize(4)
- val stream1 = streamGraph[streamConfig1]!!
+ val stream1 = streamGraph[config.streamConfig1]!!
val outputStream1 = stream1.outputs.single()
assertThat(outputStream1.format).isEqualTo(StreamFormat.YUV_420_888)
assertThat(outputStream1.size.width).isEqualTo(100)
assertThat(outputStream1.size.height).isEqualTo(100)
- val stream2 = streamGraph[streamConfig2]!!
+ val stream2 = streamGraph[config.streamConfig2]!!
val outputStream2 = stream2.outputs.single()
- assertThat(outputStream2.camera).isEqualTo(graphConfig.camera)
+ assertThat(outputStream2.camera).isEqualTo(config.graphConfig.camera)
assertThat(outputStream2.format).isEqualTo(StreamFormat.YUV_420_888)
assertThat(outputStream2.size.width).isEqualTo(123)
assertThat(outputStream2.size.height).isEqualTo(321)
@@ -106,10 +61,10 @@
@Test
fun testStreamGraphPopulatesCameraId() {
- val streamGraph = StreamGraphImpl(fakeMetadata, graphConfig)
- val stream = streamGraph[streamConfig1]!!
- assertThat(streamConfig1.outputs.single().camera).isNull()
- assertThat(stream.outputs.single().camera).isEqualTo(graphConfig.camera)
+ val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+ val stream = streamGraph[config.streamConfig1]!!
+ assertThat(config.streamConfig1.outputs.single().camera).isNull()
+ assertThat(stream.outputs.single().camera).isEqualTo(config.graphConfig.camera)
}
@Test
@@ -131,11 +86,11 @@
),
)
)
- val config = CameraGraph.Config(
+ val graphConfig = CameraGraph.Config(
camera = CameraId("TestCamera"),
streams = listOf(streamConfig),
)
- val streamGraph = StreamGraphImpl(fakeMetadata, config)
+ val streamGraph = StreamGraphImpl(config.fakeMetadata, graphConfig)
assertThat(streamGraph.streams).hasSize(1)
assertThat(streamGraph.streams).hasSize(1)
@@ -144,32 +99,32 @@
@Test
fun testStreamMapConvertsConfigObjectsToStreamIds() {
- val streamGraph = StreamGraphImpl(fakeMetadata, graphConfig)
+ val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
- assertThat(streamGraph[streamConfig1]).isNotNull()
- assertThat(streamGraph[streamConfig2]).isNotNull()
- assertThat(streamGraph[streamConfig3]).isNotNull()
+ assertThat(streamGraph[config.streamConfig1]).isNotNull()
+ assertThat(streamGraph[config.streamConfig2]).isNotNull()
+ assertThat(streamGraph[config.streamConfig3]).isNotNull()
- val stream1 = streamGraph[streamConfig1]!!
- val stream2 = streamGraph[streamConfig2]!!
- val stream3 = streamGraph[streamConfig3]!!
+ val stream1 = streamGraph[config.streamConfig1]!!
+ val stream2 = streamGraph[config.streamConfig2]!!
+ val stream3 = streamGraph[config.streamConfig3]!!
- assertThat(stream1).isEqualTo(streamGraph[streamConfig1])
- assertThat(stream2).isEqualTo(streamGraph[streamConfig2])
- assertThat(stream3).isEqualTo(streamGraph[streamConfig3])
+ assertThat(stream1).isEqualTo(streamGraph[config.streamConfig1])
+ assertThat(stream2).isEqualTo(streamGraph[config.streamConfig2])
+ assertThat(stream3).isEqualTo(streamGraph[config.streamConfig3])
- assertThat(streamConfig1).isNotEqualTo(streamConfig2)
- assertThat(streamConfig1).isNotEqualTo(streamConfig3)
- assertThat(streamConfig2).isNotEqualTo(streamConfig3)
+ assertThat(config.streamConfig1).isNotEqualTo(config.streamConfig2)
+ assertThat(config.streamConfig1).isNotEqualTo(config.streamConfig3)
+ assertThat(config.streamConfig2).isNotEqualTo(config.streamConfig3)
}
@Test
fun testStreamMapIdsAreNotEqualAcrossMultipleStreamMapInstances() {
- val streamGraphA = StreamGraphImpl(fakeMetadata, graphConfig)
- val streamGraphB = StreamGraphImpl(fakeMetadata, graphConfig)
+ val streamGraphA = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+ val streamGraphB = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
- val stream1A = streamGraphA[streamConfig1]!!
- val stream1B = streamGraphB[streamConfig1]!!
+ val stream1A = streamGraphA[config.streamConfig1]!!
+ val stream1B = streamGraphB[config.streamConfig1]!!
assertThat(stream1A).isNotEqualTo(stream1B)
assertThat(stream1A.id).isNotEqualTo(stream1B.id)
@@ -177,9 +132,9 @@
@Test
fun testSharedStreamsHaveOneOutputConfig() {
- val streamGraph = StreamGraphImpl(fakeMetadata, graphConfig)
- val stream1 = streamGraph[sharedStreamConfig1]!!
- val stream2 = streamGraph[sharedStreamConfig2]!!
+ val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+ val stream1 = streamGraph[config.sharedStreamConfig1]!!
+ val stream2 = streamGraph[config.sharedStreamConfig2]!!
val outputConfigForStream1 =
streamGraph.outputConfigs.filter { it.streams.contains(stream1) }
@@ -193,18 +148,18 @@
@Test
fun testSharedStreamsHaveDifferentOutputStreams() {
- val streamGraph = StreamGraphImpl(fakeMetadata, graphConfig)
- val stream1 = streamGraph[sharedStreamConfig1]!!
- val stream2 = streamGraph[sharedStreamConfig2]!!
+ val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+ val stream1 = streamGraph[config.sharedStreamConfig1]!!
+ val stream2 = streamGraph[config.sharedStreamConfig2]!!
assertThat(stream1.outputs.first()).isNotEqualTo(stream2.outputs.first())
}
@Test
fun testGroupedStreamsHaveSameGroupNumber() {
- val streamGraph = StreamGraphImpl(fakeMetadata, graphConfig)
- val stream1 = streamGraph[streamConfig1]!!
- val stream2 = streamGraph[streamConfig2]!!
+ val streamGraph = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+ val stream1 = streamGraph[config.streamConfig1]!!
+ val stream2 = streamGraph[config.streamConfig2]!!
val outputConfigForStream1 =
streamGraph.outputConfigs.filter { it.streams.contains(stream1) }
@@ -221,170 +176,4 @@
assertThat(config2.groupNumber).isGreaterThan(-1)
assertThat(config1.groupNumber).isEqualTo(config2.groupNumber)
}
-
- @Test
- fun outputSurfacesArePassedToListenerImmediately() {
- val streamMap = StreamGraphImpl(fakeMetadata, graphConfig)
- val stream1 = streamMap[streamConfig1]!!
- val stream2 = streamMap[streamConfig2]!!
- val stream3 = streamMap[streamConfig3]!!
- val stream4 = streamMap[sharedStreamConfig1]!!
- val stream5 = streamMap[sharedStreamConfig2]!!
-
- val fakeSurface1 = Surface(SurfaceTexture(1))
- val fakeSurface2 = Surface(SurfaceTexture(2))
- val fakeSurface3 = Surface(SurfaceTexture(3))
- val fakeSurface4 = Surface(SurfaceTexture(4))
- val fakeSurface5 = Surface(SurfaceTexture(5))
-
- streamMap[stream1.id] = fakeSurface1
- streamMap[stream2.id] = fakeSurface2
- streamMap[stream3.id] = fakeSurface3
- streamMap[stream4.id] = fakeSurface4
- streamMap[stream5.id] = fakeSurface5
-
- val session = FakeSurfaceListener()
-
- streamMap.listener = session
-
- assertThat(session.surfaces).isNotNull()
- assertThat(session.surfaces?.get(stream1.id)).isEqualTo(fakeSurface1)
- assertThat(session.surfaces?.get(stream2.id)).isEqualTo(fakeSurface2)
- assertThat(session.surfaces?.get(stream3.id)).isEqualTo(fakeSurface3)
- }
-
- @Test
- fun outputSurfacesArePassedToListenerWhenAvailable() {
- val streamMap = StreamGraphImpl(fakeMetadata, graphConfig)
- val stream1 = streamMap[streamConfig1]!!
- val stream2 = streamMap[streamConfig2]!!
- val stream3 = streamMap[streamConfig3]!!
- val stream4 = streamMap[sharedStreamConfig1]!!
- val stream5 = streamMap[sharedStreamConfig2]!!
-
- val fakeSurface1 = Surface(SurfaceTexture(1))
- val fakeSurface2 = Surface(SurfaceTexture(2))
- val fakeSurface3 = Surface(SurfaceTexture(3))
- val fakeSurface4 = Surface(SurfaceTexture(4))
- val fakeSurface5 = Surface(SurfaceTexture(5))
-
- val session = FakeSurfaceListener()
- streamMap.listener = session
- assertThat(session.surfaces).isNull()
-
- streamMap[stream1.id] = fakeSurface1
- streamMap[stream2.id] = fakeSurface2
- streamMap[stream3.id] = fakeSurface3
- assertThat(session.surfaces).isNull()
-
- streamMap[stream4.id] = fakeSurface4
- streamMap[stream5.id] = fakeSurface5
-
- assertThat(session.surfaces).isNotNull()
- assertThat(session.surfaces?.get(stream1.id)).isEqualTo(fakeSurface1)
- assertThat(session.surfaces?.get(stream2.id)).isEqualTo(fakeSurface2)
- assertThat(session.surfaces?.get(stream3.id)).isEqualTo(fakeSurface3)
- assertThat(session.surfaces?.get(stream4.id)).isEqualTo(fakeSurface4)
- assertThat(session.surfaces?.get(stream5.id)).isEqualTo(fakeSurface5)
- }
-
- @Test
- fun onlyFinalSurfacesAreSentToSession() {
- val streamMap = StreamGraphImpl(fakeMetadata, graphConfig)
- val stream1 = streamMap[streamConfig1]!!
- val stream2 = streamMap[streamConfig2]!!
- val stream3 = streamMap[streamConfig3]!!
- val stream4 = streamMap[sharedStreamConfig1]!!
- val stream5 = streamMap[sharedStreamConfig2]!!
-
- val fakeSurface1A = Surface(SurfaceTexture(1))
- val fakeSurface1B = Surface(SurfaceTexture(2))
- val fakeSurface2 = Surface(SurfaceTexture(3))
- val fakeSurface3 = Surface(SurfaceTexture(4))
- val fakeSurface4 = Surface(SurfaceTexture(5))
- val fakeSurface5 = Surface(SurfaceTexture(6))
-
- val session = FakeSurfaceListener()
- streamMap.listener = session
- streamMap[stream1.id] = fakeSurface1A
- streamMap[stream1.id] = fakeSurface1B
- assertThat(session.surfaces).isNull()
-
- streamMap[stream2.id] = fakeSurface2
- streamMap[stream3.id] = fakeSurface3
- streamMap[stream4.id] = fakeSurface4
- streamMap[stream5.id] = fakeSurface5
-
- assertThat(session.surfaces).isNotNull()
- assertThat(session.surfaces?.get(stream1.id)).isEqualTo(fakeSurface1B)
- assertThat(session.surfaces?.get(stream2.id)).isEqualTo(fakeSurface2)
- assertThat(session.surfaces?.get(stream3.id)).isEqualTo(fakeSurface3)
- assertThat(session.surfaces?.get(stream4.id)).isEqualTo(fakeSurface4)
- assertThat(session.surfaces?.get(stream5.id)).isEqualTo(fakeSurface5)
- }
-
- @Test
- fun settingListenerToNullDoesNotClearSurfaces() {
- val streamMap = StreamGraphImpl(fakeMetadata, graphConfig)
- val stream1 = streamMap[streamConfig1]!!
- val stream2 = streamMap[streamConfig2]!!
- val stream3 = streamMap[streamConfig3]!!
-
- val fakeSurface1 = Surface(SurfaceTexture(1))
- val fakeSurface2 = Surface(SurfaceTexture(2))
- val fakeSurface3 = Surface(SurfaceTexture(3))
-
- val session = FakeSurfaceListener()
- streamMap.listener = session
- streamMap[stream1.id] = fakeSurface1
- streamMap.listener = null
-
- streamMap[stream2.id] = fakeSurface2
- streamMap[stream3.id] = fakeSurface3
-
- assertThat(session.surfaces).isNull()
- }
-
- @Test
- fun replacingSessionPassesSurfacesToNewSession() {
- val streamMap = StreamGraphImpl(fakeMetadata, graphConfig)
- val stream1 = streamMap[streamConfig1]!!
- val stream2 = streamMap[streamConfig2]!!
- val stream3 = streamMap[streamConfig3]!!
- val stream4 = streamMap[sharedStreamConfig1]!!
- val stream5 = streamMap[sharedStreamConfig2]!!
-
- val fakeSurface1 = Surface(SurfaceTexture(1))
- val fakeSurface2 = Surface(SurfaceTexture(2))
- val fakeSurface3 = Surface(SurfaceTexture(3))
- val fakeSurface4 = Surface(SurfaceTexture(4))
- val fakeSurface5 = Surface(SurfaceTexture(5))
-
- streamMap[stream1.id] = fakeSurface1
- streamMap[stream2.id] = fakeSurface2
- streamMap[stream3.id] = fakeSurface3
- streamMap[stream4.id] = fakeSurface4
- streamMap[stream5.id] = fakeSurface5
-
- val listener1 = FakeSurfaceListener()
- streamMap.listener = listener1
-
- val listener2 = FakeSurfaceListener()
- streamMap.listener = listener2
-
- assertThat(listener2.surfaces).isNotNull()
- assertThat(listener2.surfaces?.get(stream1.id)).isEqualTo(fakeSurface1)
- assertThat(listener2.surfaces?.get(stream2.id)).isEqualTo(fakeSurface2)
- assertThat(listener2.surfaces?.get(stream3.id)).isEqualTo(fakeSurface3)
- assertThat(listener2.surfaces?.get(stream4.id)).isEqualTo(fakeSurface4)
- assertThat(listener2.surfaces?.get(stream5.id)).isEqualTo(fakeSurface5)
- }
-
- class FakeSurfaceListener : StreamGraphImpl.SurfaceListener {
- var surfaces: Map<StreamId, Surface>? = null
-
- override fun onSurfaceMapUpdated(surfaces: Map<StreamId, Surface>) {
- this.surfaces = surfaces
- }
- }
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt
new file mode 100644
index 0000000..dc5def5
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/SurfaceGraphTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 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 androidx.camera.camera2.pipe.graph
+
+import android.graphics.SurfaceTexture
+import android.os.Build
+import android.view.Surface
+import androidx.camera.camera2.pipe.testing.FakeCameraController
+import androidx.camera.camera2.pipe.testing.FakeCameraGraphConfig
+import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class SurfaceGraphTest {
+ private val config = FakeCameraGraphConfig()
+
+ private val streamMap = StreamGraphImpl(config.fakeMetadata, config.graphConfig)
+ private val controller = FakeCameraController()
+ private val surfaceGraph = SurfaceGraph(streamMap, controller)
+
+ private val stream1 = streamMap[config.streamConfig1]!!
+ private val stream2 = streamMap[config.streamConfig2]!!
+ private val stream3 = streamMap[config.streamConfig3]!!
+ private val stream4 = streamMap[config.sharedStreamConfig1]!!
+ private val stream5 = streamMap[config.sharedStreamConfig2]!!
+
+ private val fakeSurface1 = Surface(SurfaceTexture(1))
+ private val fakeSurface2 = Surface(SurfaceTexture(2))
+ private val fakeSurface3 = Surface(SurfaceTexture(3))
+ private val fakeSurface4 = Surface(SurfaceTexture(4))
+ private val fakeSurface5 = Surface(SurfaceTexture(5))
+
+ @Test
+ fun outputSurfacesArePassedToControllerImmediately() {
+ surfaceGraph[stream1.id] = fakeSurface1
+ surfaceGraph[stream2.id] = fakeSurface2
+ surfaceGraph[stream3.id] = fakeSurface3
+ surfaceGraph[stream4.id] = fakeSurface4
+ surfaceGraph[stream5.id] = fakeSurface5
+
+ assertThat(controller.surfaceMap).isNotNull()
+ assertThat(controller.surfaceMap?.get(stream1.id)).isEqualTo(fakeSurface1)
+ assertThat(controller.surfaceMap?.get(stream2.id)).isEqualTo(fakeSurface2)
+ assertThat(controller.surfaceMap?.get(stream3.id)).isEqualTo(fakeSurface3)
+ }
+
+ @Test
+ fun outputSurfacesArePassedToListenerWhenAvailable() {
+ assertThat(controller.surfaceMap).isNull()
+
+ surfaceGraph[stream1.id] = fakeSurface1
+ surfaceGraph[stream2.id] = fakeSurface2
+ surfaceGraph[stream3.id] = fakeSurface3
+ assertThat(controller.surfaceMap).isNull()
+
+ surfaceGraph[stream4.id] = fakeSurface4
+ surfaceGraph[stream5.id] = fakeSurface5
+
+ assertThat(controller.surfaceMap).isNotNull()
+ assertThat(controller.surfaceMap?.get(stream1.id)).isEqualTo(fakeSurface1)
+ assertThat(controller.surfaceMap?.get(stream2.id)).isEqualTo(fakeSurface2)
+ assertThat(controller.surfaceMap?.get(stream3.id)).isEqualTo(fakeSurface3)
+ assertThat(controller.surfaceMap?.get(stream4.id)).isEqualTo(fakeSurface4)
+ assertThat(controller.surfaceMap?.get(stream5.id)).isEqualTo(fakeSurface5)
+ }
+
+ @Test
+ fun onlyMostRecentSurfacesArePassedToSession() {
+ val fakeSurface1A = Surface(SurfaceTexture(7))
+ val fakeSurface1B = Surface(SurfaceTexture(8))
+
+ surfaceGraph[stream1.id] = fakeSurface1A
+ surfaceGraph[stream1.id] = fakeSurface1B
+ assertThat(controller.surfaceMap).isNull()
+
+ surfaceGraph[stream2.id] = fakeSurface2
+ surfaceGraph[stream3.id] = fakeSurface3
+ surfaceGraph[stream4.id] = fakeSurface4
+ surfaceGraph[stream5.id] = fakeSurface5
+
+ assertThat(controller.surfaceMap).isNotNull()
+ assertThat(controller.surfaceMap?.get(stream1.id)).isEqualTo(fakeSurface1B)
+ assertThat(controller.surfaceMap?.get(stream2.id)).isEqualTo(fakeSurface2)
+ assertThat(controller.surfaceMap?.get(stream3.id)).isEqualTo(fakeSurface3)
+ assertThat(controller.surfaceMap?.get(stream4.id)).isEqualTo(fakeSurface4)
+ assertThat(controller.surfaceMap?.get(stream5.id)).isEqualTo(fakeSurface5)
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2Controller.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
similarity index 64%
rename from camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2Controller.kt
rename to camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
index 9d1643c..136fe8e 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2Controller.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
@@ -16,11 +16,14 @@
package androidx.camera.camera2.pipe.testing
-import androidx.camera.camera2.pipe.compat.Camera2Controller
+import android.view.Surface
+import androidx.camera.camera2.pipe.CameraController
+import androidx.camera.camera2.pipe.StreamId
-internal class FakeCamera2Controller : Camera2Controller {
+internal class FakeCameraController : CameraController {
var active = false
- var reconfigured = false
+ var closed = false
+ var surfaceMap: Map<StreamId, Surface>? = null
override fun start() {
active = true
@@ -30,7 +33,12 @@
active = false
}
- override fun restart() {
- reconfigured = true
+ override fun close() {
+ closed = true
+ active = false
+ }
+
+ override fun updateSurfaceMap(surfaceMap: Map<StreamId, Surface>) {
+ this.surfaceMap = surfaceMap
}
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraGraphConfig.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraGraphConfig.kt
new file mode 100644
index 0000000..47f4e62
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraGraphConfig.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 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 androidx.camera.camera2.pipe.testing
+
+import android.hardware.camera2.CameraCharacteristics
+import android.util.Size
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraStream
+import androidx.camera.camera2.pipe.OutputStream
+import androidx.camera.camera2.pipe.StreamFormat
+
+/**
+ * Fake CameraGraph configuration that can be used for more complicated tests that need a realistic
+ * configuration for tests.
+ */
+internal class FakeCameraGraphConfig {
+ private val camera1 = CameraId("TestCamera-1")
+ private val camera2 = CameraId("TestCamera-2")
+
+ val fakeMetadata = FakeCameraMetadata(
+ mapOf(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL to
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
+ ),
+ cameraId = camera1
+ )
+
+ val streamConfig1 = CameraStream.Config.create(
+ size = Size(100, 100),
+ format = StreamFormat.YUV_420_888
+ )
+ val streamConfig2 = CameraStream.Config.create(
+ size = Size(123, 321),
+ format = StreamFormat.YUV_420_888,
+ camera = camera1
+ )
+ val streamConfig3 = CameraStream.Config.create(
+ size = Size(200, 200),
+ format = StreamFormat.YUV_420_888,
+ camera = camera2,
+ outputType = OutputStream.OutputType.SURFACE_TEXTURE
+ )
+ val sharedOutputConfig = OutputStream.Config.create(
+ size = Size(200, 200),
+ format = StreamFormat.YUV_420_888,
+ camera = camera1
+ )
+ val sharedStreamConfig1 = CameraStream.Config.create(sharedOutputConfig)
+ val sharedStreamConfig2 = CameraStream.Config.create(sharedOutputConfig)
+
+ val graphConfig = CameraGraph.Config(
+ camera = camera1,
+ streams = listOf(
+ streamConfig1,
+ streamConfig2,
+ streamConfig3,
+ sharedStreamConfig1,
+ sharedStreamConfig2
+ ),
+ streamSharingGroups = listOf(listOf(streamConfig1, streamConfig2))
+ )
+}
\ No newline at end of file
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index 470370c..6d048c5 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -35,7 +35,9 @@
import android.provider.MediaStore
import android.util.Size
import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
import androidx.camera.core.Preview
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.impl.ImageFormatConstants
@@ -59,7 +61,6 @@
import androidx.camera.video.internal.encoder.InvalidConfigException
import androidx.core.util.Consumer
import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
@@ -90,6 +91,7 @@
import org.junit.Test
import org.junit.rules.TestName
import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.argThat
@@ -106,13 +108,16 @@
private const val TEST_ATTRIBUTION_TAG = "testAttribution"
@LargeTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = 21)
-class RecorderTest {
+class RecorderTest(
+ private val implName: String,
+ private val cameraConfig: CameraXConfig,
+) {
@get:Rule
val cameraRule = CameraUtil.grantCameraPermissionAndPreTest(
- CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+ CameraUtil.PreTestCameraIdList(cameraConfig)
)
@get:Rule
@@ -128,6 +133,15 @@
@get:Rule
val labTest: LabTestRule = LabTestRule()
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = listOf(
+ arrayOf(Camera2Config::class.simpleName, Camera2Config.defaultConfig()),
+ arrayOf(CameraPipeConfig::class.simpleName, CameraPipeConfig.defaultConfig())
+ )
+ }
+
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private val context: Context = ApplicationProvider.getApplicationContext()
private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
@@ -153,7 +167,7 @@
CameraXUtil.initialize(
context,
- Camera2Config.defaultConfig()
+ cameraConfig
).get()
cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/VideoEncoderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/VideoEncoderTest.kt
index b6abcd4..d20a2f9 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/VideoEncoderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/VideoEncoderTest.kt
@@ -26,6 +26,7 @@
import android.util.Size
import android.view.Surface
import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXConfig
import androidx.camera.core.Preview
@@ -45,7 +46,6 @@
import androidx.concurrent.futures.ResolvableFuture
import androidx.core.content.ContextCompat
import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
@@ -61,6 +61,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.atLeast
@@ -78,16 +79,28 @@
private const val I_FRAME_INTERVAL = 1
@LargeTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
@Suppress("DEPRECATION")
@SdkSuppress(minSdkVersion = 21)
-class VideoEncoderTest {
+class VideoEncoderTest(
+ private val implName: String,
+ private val cameraConfig: CameraXConfig,
+) {
@get:Rule
val cameraRule = CameraUtil.grantCameraPermissionAndPreTest(
- CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+ CameraUtil.PreTestCameraIdList(cameraConfig)
)
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = listOf(
+ arrayOf(Camera2Config::class.simpleName, Camera2Config.defaultConfig()),
+ arrayOf(CameraPipeConfig::class.simpleName, CameraPipeConfig.defaultConfig())
+ )
+ }
+
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private val context: Context = ApplicationProvider.getApplicationContext()
private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 81befd7..df01294 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -31,6 +31,7 @@
hilt = "2.42"
incap = "0.2"
kotlin = "1.7.10"
+kotlinBenchmark = "0.4.4"
kotlinNative = "1.7.10"
kotlinCompileTesting = "1.4.1"
kotlinCoroutines = "1.6.1"
@@ -108,6 +109,7 @@
gradleIncapHelper = { module = "net.ltgt.gradle.incap:incap", version.ref = "incap" }
gradleIncapHelperProcessor = { module = "net.ltgt.gradle.incap:incap-processor", version.ref = "incap" }
kotlinAnnotationProcessingEmbeddable = { module = "org.jetbrains.kotlin:kotlin-annotation-processing-embeddable", version.ref = "kotlin" }
+kotlinBenchmarkRuntime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinBenchmark" }
kotlinCompiler = { module = "org.jetbrains.kotlin:kotlin-compiler", version.ref = "kotlin" }
kotlinCompilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" }
kotlinCompileTesting = { module = "com.github.tschuchortdev:kotlin-compile-testing", version.ref = "kotlinCompileTesting" }
@@ -197,6 +199,7 @@
xmlpull = { module = "xmlpull:xmlpull", version = "1.1.3.1" }
[plugins]
+kotlinBenchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "kotlinBenchmark" }
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlinMp = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
diff --git a/gradle/verification-keyring.keys b/gradle/verification-keyring.keys
index cee15ae..53064d2 100644
--- a/gradle/verification-keyring.keys
+++ b/gradle/verification-keyring.keys
@@ -502,6 +502,84 @@
-----END PGP PUBLIC KEY BLOCK-----
+pub 0D3B328562A119A7
+sub C45D01093DCFC371
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mQINBE4rG7gBEADo5n849j3hlKrvFzt6y65grIxTlbLDXEB7+6sw0Xwuh4NrK/Zg
+0+eF0vvCCZrl3lHE2duD2ng9ZXz8EvUSNfwKMQz+cwF0klhP92u6mykKJ3/DZ4yo
+wojLPkIr5tVo4ybeVIbQ3N4+FnqzpNfs571KZHUOa2unwdgGK7OGMTxTkP8oaRwP
+H5VenaKoknbLbp2CUchQT3pkv3Yio+NIDGgZ1VmgOTqczI2KZe1viqRY32rBVKr1
+684Bgygf0ZfzMyKd1xK5UvDGhfQU+uDZrV9f81YMqJ9dZFjbZsyIhsEtIloTp0/Y
+kDtUMlkXF1c8EExpqTEUwEBwV+ow3IKVv3YDNNpZ8g8TQa7wKcpOia7UmEdXraY6
+PdP5tzClCqV0PqOxdNh+En9tw3VNKqAjQ9EE/nSbRz5d0JgdIA6SfPeXqAK5hxuT
+fdOdleywcOa1HRVN4xoEsOljfQiS1dz2xzou0mR1NpnD4PuGRdx4wwYGmkqulbbt
+1wQJRd/psyFfnpxrqk9I4frouzxMcrPUDH+T5qAVfkX3LG5XRGFkScWbZ65SXXB6
+wg6DsFNUXl1is01Wfda0TvBXdjO19RdcVSyD/DlAlXukmQb36Av3pHatR5Y8k7xN
+c1tU4G6dSfiD/JjwnCTzfqmHBnS4c2d1JKscPGRy+Y82Ghj2lPmunn6D9QARAQAB
+uQINBE4rG7gBEADdSXw7MJF5zFEN7siT74kGxyBO40gfhW2HTIbGXyUHKGpknHOH
+V3KYS0GEtvLc8QGOHv5qLfYlCejD5cYpzoDcWIUo1KZiaqG6LulcAy8sDuX3o5z0
+LpUikutXeIxGTgxdpc3SfYPhb43ir6pPI42MhgFOOAAhHLo9yE24G5FYna5S5OZx
+eOWiwelhYUxBMTIyA+vwCUii91ZSO5ByPU9d0QJBS2Y1Xwp0SDDa5z1x+SYRx1XO
+a8aD7/tb+K1G+giuedY5J2eVKvxFB0ABqdTNT1tj9bZmXqfKEjpaziXa9WVSNNU0
+De6IYZFsDJ9yC/3mYBB4rNd3iqQnlH6bTIaSWGA1I13JZQUm0dJet9IwxP7rCgM8
+lSsPXjvdTvHZItIIYQaMihUp3360G16ESiVRXIXwRyUztm9MMNhVzTkFXkJ9G5WX
++3Og8inNjzJViJGfqgZ4KNlXql9/BtqlsPcmTeMoZf2LCz86uTfUrhf18AVJJq2E
+B/R9M+TWQ7R9SEpQ49RoZUfVXb/HKVnWw9OjJyGFhc58MBGyT1VNGpvwDEzceJfG
+ri71lnyCAddNc10wNyfeF069wVoGTZyWklWgC7UB6dUn+9TYN2/ZpHtDzzdAoEOp
+pFdqib0+xfeY0mt9k0/jPvK2wqXMY3Vi2nvVDQNUEEJxMLSFBiqAA88EDwARAQAB
+iQIlBBgBAgAPBQJOKxu4AhsMBQkFJlwAAAoJEA07MoVioRmnkZsQAOG7gJyE2G7O
+6jP+O0O0HpczzcxAt6/z5lmEcdlbFkMv71385JJIKhOQ30dmfp4VfQuEMCai+XgS
+aZvLcsDs6AYqv9Rs85jUaWKIJUUiQAFlCqrUVYSP8Las5jm6XHMX+AZ6ObJmFCWw
+jLluxjA5Q8m+qPSqG/rMi4wEtTAJXVcH4nZ0W4TTUfzFPGHXnkyiqWmYYXMdAat3
+tCyHZ6DEN/CGEpLQJLM/0R2ZWTuI60KpUinlJOhs3GQvcyTwt6EfB8+KeXSc/2hr
+6KW7DlPLSYBO/6GF8VAzya6jjh1XTjnbYX78MxMNxwZyCj5lqQEWvJWwVqP03x9+
+q/invjWn1NRrOHrbFUGUxNH9UPVwwZ9/EqD0A25+T0MFTirzsyKiwvJYmuEWgvrJ
+h+L5LZUI1eETP7BLFeomnqcbZxhfEZqPZxU/sKcHBC68030DbxxySlhmlFHqS4ou
+wKSuGET1G0j2hmK1NBgkDvBtLnz314aSRLPvLIqMxgpU7O2dG0V/kHiK7atkQU+e
+GSXQpgrcNF/dGv9KdwdJMvJz7K51DCD/V/1Anrobba8ezv6IV6JGnxHAUeg/z98j
+o2b2FQQ4bwWKXQklMrz4kXmikwIH7Sjn1FjuQv2LFK/AMUodZJAC5cuxfADby2+O
+WPYQqlvqX5QcgnuZX8E1tLt1v31Ay8j+iQIlBBgBAgAPAhsMBQJTGCn3BQkKkKi7
+AAoJEA07MoVioRmnu1cP/RRb7tIFoQCzgblOZl5G6xXrkrDaUgGcvB/zpdy96vT6
+Sh4Tmdg/kl5EWuxkIk8b9CASLUebQe/DatdRvaV+IzxTJ5vz7uDdw7ieqrz5+ZeG
+yw0l9KUlP7b9kj8DRdVAJUO6Cd9/x7B9LhJpiiLm43JvmH6hmlgpqj+QpE+QInea
+7niX7J2sNd/M1cvYJTAHwbY/m8KTqNhHPwnRwWRTc42BrI8euBCdx3SZ7GZtgKzh
+6JMjIpg+XGtMqIwYdPxlqNZ3pB1oSBdJdkCKVCvd0mU2b+6gtouPfVZz8dio9IdG
+eAopk99k1XV4EvtOqSefAt0iMkjwmZgIcvy2KaLoLGYhUGn1NeoQ9eM4zCGMXL4l
+7EqFKiARlHbOuKv8eeOLL14UKVYoWqiBNo0SyU4D9l18OQ7tnPS7F0GSq5QeYfZT
+qFobrjEMVLXkVmSlfXfryFQYldsj9REmNrdOTKdxHL5aekurnWiP465SY3XgzJR1
++gXnn+j7wCkYYZNe2/OaDYX+4v0/c/AdXJmt4DsnA6279M/1El82edMeRXQi9e4d
+E4BO/OqluHpZcsAufM4bSK3OkmSxj+h976k7Xnl86ZWanhNPI8NcQUpcEDKIfye+
+1tRBjMfDhqdJ1SlwRMVxLiIcdI5cdj0qLUS8nXQLD8aSYK6jY6O/2DRtmsAmNFhE
+iQIlBBgBAgAPAhsMBQJYfp/GBQkOMEkJAAoJEA07MoVioRmnZ0YQAKxOp9KWQQJy
+9+R861eF4tWGBF+fpGAkJUiOQdFECIXnrciqUmEsYw+cn8+TIW8/1O9bbcsRfuxC
+K3/15jm1+042iSgi1UtOfXcubH1dvrWC9XyXhBKTYownWzgK2wDe4VQ7QyXlwCjK
+j9rtvw5v17AHH/uiN5K+wR9BK1bvZy0SS1XUlH1o2lDzWaaQrEP44ZCe32TNiYPe
+hmynSMHUZGDQNgL5nqtQ9V32zKT0bspvcJ4wHA7L9b8pfwDHNHg0Rt4q2aawBfFO
+EC49rkdQxDznEMk1CEqrCGaD5IbsKtnnaonmab3QCHDiiDNGjXpmxguaisJA+/2J
+pGQ89fFT9iG+wl174ygO/gZhBrPN1cWhmtMeMvlHMu6NkJKzYF1fYVR4kpWEGRcg
+zVl9leLAy/n6FgdfivRRqzSsmj/a1B95VBWE0bpbOlxxCCs2OkT4Dtor+gKploRC
+ff2Tzlq0VY4nVYFmmyG0nDSMrBsq+t6uQJEIu4hkQbZEiH2fiOSQloc8Rfj2YDHM
+yWaJBYy7Lzm6k9scT79t3iGzN5739YgsrYRstSG6TbNSbFB2lon2q45/vZfDlgM5
+FwKjBqD5GOcES8f/Q0rof01Gz4GdVk47Ofwv+WSkQa/8s5T6sNFMrJ2sNDVQrwot
+jhnjUrOIQaIMk5zmFCvYnGtY2BzqLVPWiQI8BBgBAgAmAhsMFiEEAZCCvADgMk4q
+70zwDTsyhWKhGacFAlxcD9UFCRPUjp0ACgkQDTsyhWKhGad3rxAAxd/l3iSg42JB
+aclOrrB5Yu6SjjMsZvVpCBC7NgfrWuBlPt3NxHW87J0Me1E0J+OYlnFdaGkKDMQu
+XSfFhoGHgDJ3q+UZYZeaIFb2DGVm6Q6byVL6LXo3pnbeldZt4p+6u1/HMeXKtwRe
+j86o51Aq9tmdxyFs/MHiqLBrk9a+tw24VFeT6GXRJApdsWPPFgf1yFr9chu3uCZH
+kaIlGf9fLaaeslL4++sGGaj6MKYs9VqzGTQOuWwowVBDDFnIrgwpK5v5Etss+vqc
+vdTfyF8B5kT45R0c1whGzt7yDTgFOa65vjoZbnX/4O0um+o+8GfX+uyoZWIV35EW
+qLsk35eHkt6hTu7bHGlk3yXhyFtfaHaxfBqPQFK2yjQMR4RaQht4nqzkvurxc3TT
+kfbxmsVGHXVxvKGCUFM6XdKa+qm3jAl3bWvDMXWU+SKwg32/ddUQr6WgYvSCiRMV
+inkXCvcV3LEMrUBLrS5wyyWOFS8xwQB9dqjMhxZSnfRBs2hoCK0A3DGGev0ibCgY
+vPyRKnQxuVzU/HJTwpISqHKLe4P/sIs/2vv8T1D8n+oor2hRumnf27CLC0pFGK1Q
+Qk+NXBJtX9Lsq8olj55uC5MPnxoMbHBibd8+vaF+HrjL0369SCnwNhLE2bZmtJ7u
+UGyrigQZu0gUujkogzGo8f7UOwHRBoA=
+=7aNS
+-----END PGP PUBLIC KEY BLOCK-----
+
+
pub 0DA8A5EC02D11EAD
sub 71499A87DC1FF84B
-----BEGIN PGP PUBLIC KEY BLOCK-----
@@ -4363,6 +4441,43 @@
-----END PGP PUBLIC KEY BLOCK-----
+pub 685C46769DBB5E5D
+uid Egor Andreevici <egor@squareup.com>
+
+sub CA7AE93399B1ED99
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mQENBFqHCi8BCACgRpCaVCiJ2MccCN01SbHYowmM255nSYKOnfItBmXYAMtc4rL9
+n1y1qFtc4LBbkIrPH8CO2zpEImUTZel4W93BQkluPOO3EX/hLCTCFfXrO89L1u4V
+XL4siS8vZl3DVDdY7m9G4vcpiIsggGF683KNctN2KXZ0D3tu9C1X7eJk3GyDo6W7
+MJwiiCm10968VqFqIFn1rTkvtGtXGP61Vqy7kLTdiBrKbo1HkZDbryl6nvCmT++E
+auJXEDYH9R+vsODy10a5in0n0EzQD4DYadL6cQTbwP2SvMbRRRQ7AnNtys6cNCCb
+CIJ8iySLaHYi65w6FEbiBM2XQ2hzf8Dek9yDABEBAAG0I0Vnb3IgQW5kcmVldmlj
+aSA8ZWdvckBzcXVhcmV1cC5jb20+iQFUBBMBCAA+FiEEaC92XupxjSULvbLxaFxG
+dp27Xl0FAlqHCi8CGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ
+aFxGdp27Xl03AAgAgtnhNsz++UIqtiETzp0Y5v40STwdI+8KYhvbhzL2MARmnYr8
+US+NqeALovuWPNoP8CuzGN6dQP4tFMVr/4GtDJRqzi9EGUHIk6KzliK69Eir04TP
+3CgJb4OqrfgqU/tbXTPFVVKbpyWnrrzg50THhIigMxkezvTWa8iLWswBHb/E/NRn
+1NOfVkPqHY0D0Z+KeAR1/20n+aEh5JrLsxzIp3vNF+qXSN4gKtmdj2fYH9eovM85
+0NzlZnaqwetTtBxeu9mYvCHLCbTLrQTY2MvqBpZbUdVJytJrvOTAddmRrNiBz1w9
+yxzGOKXDSB0t6LVWldwAuTM0eGXqaDgInbe7r7kBDQRahwovAQgAzYHtmnHEZVad
+1Th2c8oFr3/QaH7UP7Xz+lhKyYgq/FsKmkQy88qxcxTX4kMfSB8ssKFdaCtBQI6y
+pFVvaUm8jn1LC0dVPZJHKHOdDEiLdg7B20bPzdY0x5+Yav7SJjYIq53V3pkJGZyH
+P9CvZ/L5uJ5+2sFx1nj1qyK1FWVx2VZk2TsFmL4fpiAi6SOXRoGVLlO0sCqUvlH2
+eR873/1u5Ya387En4krWnu0T+dHyg5/xu9S2Q7XKmO7GyEUXRnXsJPur6CwXpUao
+eoXIcydpdjl+TR70OgxyAGsrfQYVNjPvRVK4yUv839Xz8mCIp21CRscsx1IIeshM
+BqRGXIo8BQARAQABiQE8BBgBCAAmFiEEaC92XupxjSULvbLxaFxGdp27Xl0FAlqH
+Ci8CGwwFCQPCZwAACgkQaFxGdp27Xl2rCAf9EwynJ3Pb70PcSVboGozFCnSayE+c
+/SboSK2Cujfw1Fy2Qr0YizXL4RAE4jXzI+Y3EYZpcJ7XMxUheBTZrxO8ACvMrAwm
+MC2vf1EoM3DQVpyoVCv5d1OIY6rYCjyHeBT4rwsl8GLZZ9vsjGWpI9m8Jcpi3jhM
+ih+zfrdaYJpFzu5MA2y/dV3aYn0kpODuN+WVWRyF/3jhLCZtrNFnZb3pfrS/jcDM
++kdiyqH9wE6iyvIxX8Rk7a+1H0zFmKQNetxdMQPYODUSXQEuygXaZFzm+bfWU+E9
+x+HCpeZqmOpBQgdhFZFEX+ru97s07kBMVUlWM4s4uETNKLIwzkAk2FlbGw==
+=S5Ep
+-----END PGP PUBLIC KEY BLOCK-----
+
+
pub 689CBE64F4BC997F
uid Szczepan Faber <szczepiq@gmail.com>
@@ -6776,6 +6891,1628 @@
-----END PGP PUBLIC KEY BLOCK-----
+pub 9AE296FD02E9F65B
+uid Luc Maisonobe (CODE SIGNING KEY) <luc@apache.org>
+uid Luc Maisonobe <Luc.Maisonobe@c-s.fr>
+uid Luc Maisonobe <luc@orekit.org>
+uid Luc Maisonobe <Luc.Maisonobe@free.fr>
+
+sub 2F3C9EEB05D1D1E3
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+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
+IChSZWxlYXNlIFNpZ25pbmcgS2V5KSA8amltQGFwYWNoZS5vcmc+IBxKaW0gSmFn
+aWVsc2tpIDxqaW1AamFndU5FVC5jb20+HxxKaW0gSmFnaWVsc2tpIDxqaW1Aamlt
+amFnLmNvbT4ACgkQNOp25nkUhagzfA//fDOf2r4R4NmRxMBYksilQGdgtrenELGh
+0WXsPv9ogvau+7nuJ+nIVG9b7S4+rac7Lim9L+15SqWnwASdw+yVOUI7r6VHHaQY
+6mYh3PtjdkuXkT04jCBKnEBXLLUariGTPIYEJEWH4FUzORg6nt/9vQBzXbG0DG4J
+7DzDmFGKS9nQDAqxKRbrRVth9IumhIh4kuwDJbN1PHdJAYHHRyD98f5uTxzMvU7r
+5VmG009oAG4JCVBMW9enj0/qu9mLmNm4IxyvmbuC3ZiJXwQUn2qDJobv9BtDnvJw
+YCNtFJYHtu8lpJZyk2wxP+tpcvny5D39iQRDG0WpymtDZcZt8I5S86PrhxVXEem3
+7INHiXVsMDLGdfNpfdezAqvIkYVmVfK4gYhDLj1oka4ruiergwW3H8BakStpeGqd
+tp2tNMNGQXuoKbyjqK1WFNVUKN3s7efjrgoh2XR+yFjBop4pNLktbjo+LXGc0e4u
+T7exRrpSlHLUuEa2rZeeKtntnT4DKv3y4ebJ5f34VegBAb4e4Xsd6XgtDPYX/HUG
+seSjj72OPN7pAUtbBTO2q1NFG6vUPZBSLiV9ZzOu1lPYWEV0g+cIKPmLmIsRnETP
+346vWx2wq5EIWh7T3fTfYkYnsD3vyApPkG9MqMgotR+FIKlWFglPNEv0V2cZgcgV
+exvw5RD8TMuIygQQEQIAigUCTNK0Qx8cSmltIEphZ2llbHNraSA8amltQGFwYWNo
+ZS5vcmc+IBxKaW0gSmFnaWVsc2tpIDxqaW1AamFndU5FVC5jb20+HxxKaW0gSmFn
+aWVsc2tpIDxqaW1AamltamFnLmNvbT4iHEppbSBKYWdpZWxza2kgPGppbWpAY292
+YWxlbnQubmV0PgAKCRCLOmAfCMl15cvtAKCStVtCjnhZQeccqgGtwJqnGXba+ACd
+E1osWknKnvFCvurObyp6OPRoyuiJAhwEEAEKAAYFAkzStckACgkQgngd5G1ZVPqI
+ehAAihtxeHdh36A6no5dt9LwrV3WDApxM4M9G8zfN9f3sr5RKq/9cebZjiVWOKYu
+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
+wT3/uLspPh8CTR7DkLU0twAr+POjct6QdzOlwv4bCh3EII4e9u5lqLlmlvvp/X7z
+8GzeDwxK2oDyUSIoN+FMPuS8ESVhMV+ST409io7p9On/MpgQ4F471UsYuzpLXfHB
+l4l32Ra3haNIfW477XgRHz3tfLUvFQWQ9PH7OFJV4rXDFyiLPQsJgW7TTgz0zJt4
+N2x/2awenMOp3T3IXnIpO80ENO4VcPmVSjL9aXkMiS6YicZG1YkCXQQQAQIARwUC
+TNLXrEAcSm9zZXBoIEVkd2FyZCBCZXJnbWFyayAoQ09ERSBTSUdOSU5HIEtFWSkg
+PGJlcmdtYXJrQGFwYWNoZS5vcmc+AAoJELDZkbR8QIc3jXIP/1R4765cKL59YnFp
+QuJNwrJmchg6oh4E+C60cWes955cYCitUPzRkHeHCSmC55TcFWwhGtzQH8Ql4Xle
+UJk7AXh8yMv3KSxG7Ic5/w4MTXAaY53r1q/ImTHnlSmF0+iMq4LF2BfLGW1lWXz+
+5D1Fm2fqlyYJ7BfDRwhbQZiQAxeddf+Ydx8d8Zk1CVqQOkhTFWpN9O+oxiEyxfo5
+SNOZzcCKi+cR0E7AB9qet/OTjecnKy4vFqG4adc6RNPU7d+20GOiJAZMOChdgl6k
+nrX3b11ilu41ri1Y2PqHyjBJnm4zSVw8x8HxbpHt5TB/TIy7YwVnZs13mitxRoa5
+CwuMeyDCz/8Ob5K3fTF7/0UAsG7c/dhZBA6ENzjHYSxhlYDDL0mTUG+b8C6ZypLH
+eS00rky8g8mNV901vsGxoJhu/w2diFRf/o4QAbj+9nttxxtYvBKTvuHSEGFErOLV
+35ChixhsMdr+Sl4eBWF3HxRJUsyIT/kRZzL41usXP2KH8CbkerQVaMzOb3M9RfO6
+7VLPfb+q2JW+BdHMlbVsQdfWhLJoMTLHGgxhXCA7Fpd6slI3HrUvlFxd/f9DHmZu
+E63mvtjgI9GRdbDOHdkhJ5yTd1uQe3p+QWXsgq/YOIQj+oIVI5R8ZMXHzsec4vPF
+xok1uajMV/fXeqdDYutbZrQgj56piQKpBBABAgCTBQJM0uOxIBxTYW5kZXIgVGVt
+bWUgPHNhbmRlckB0ZW1tZS5uZXQ+IhxTYW5kZXIgVGVtbWUgPHNjdGVtbWVAYXBh
+Y2hlLm9yZz4dHFNhbmRlciBUZW1tZSA8c2FuZGVyQG1lLmNvbT4qHFNhbmRlciBU
+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
+zuF5mZ85CvswdQE31umGLWlHhgJq5DB2rMY+bIvoeIMPAan3ZgPPAB1rDKNUzNQU
+peO678gaJqS81TIHQbcXKxzfCem5auHz7hVfENZKkKPh11XPzIA1HJTpkYQleOQK
+sc8B5qUwQd2ecATfrfIY1KTAmT7qcTnBpdO/TTwnw7M9wwpZBP1IfJwB4nmlWkI8
+Bnllv289lQWAdw12F/4gjJ46MxQwtHrWOpcxWAUTcF/QJZf7UUT8EkzW6aveTP3P
+7IkAkAvvadEWzZcMwVEy70ceayrufDlMYZ7ia24WKFyzm8zCQKWV5X94uQhPQ1e6
+osaXf1wTpshbnmqhRSqZC1FvAVsWNS4330T78p1bQAMYesmYQQkSa53ecW3/////
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+////////iQEcBBABAgAGBQJQmioNAAoJEO0JbZPu7UfaclUH/RezKHySASl/1t/o
+9TixCcyysDuSeRbhuNlQNZDeOtvnaajMnPpRZEs067uK/YVqNj/BEtDn+vM/sNzs
+Nu69EaaSZZvM7wn2Ig9MywCFaCCpdeuzD8cVG6uuYwO4vIvMrc5dKcFoQQA5bOEQ
+rYofqsOPePChce5dE4kxBN8MjcgHFIBh3WzPs8ohXzNWzNKU/lew0StEKbbR3CPz
+dYwQ5/Xe5IvYlkZtaCWgX+r1vVjxuKK5NUWez+5crHICtuhVy06E3PRHKM4elh/G
+wPVzcP8cspMgofAM9cSHKjFfopynq0RqXEPldG8xJm55uFacOUag4P4PE26t/7Cj
+sY1d+e+JAhwEEAECAAYFAlCaQ9wACgkQNiWQp/XP2RUr5hAAhpLsiBfJJN0wWDh0
+dH3MbXiZ69vWibHd/CNG3WZhxxoxnfT5M1P7Xdu+k1vUjBGCTT8mYuHzMUyZRa2q
+vGIZK1cn/eZzjS1AuV9yR45WIcYtl8eqekvpI9wN200B0oGWkV//9mWO232s79eB
+9j/t9XkozQ7HkFaKevPQ70s6oLfKBT41VTgFSlqylSkoFIeekynwk33Ne3SHwZEj
+kJeJv770unXeTxeGjuIXdYAnk7EkI1E8ysskyGTYzrjDn5Z3m1gqd//msjEXSXXU
+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
+RnQfDzV75TMLdPbRNKk6/9AQtZUDSSf2rmAJDso1/Hw1uXRzhWUSQjP2NKgqDg1h
+kSsrxVcf/HT+/qEDV3c50RxRCkRXWnie+7DE7gV9VxYVGFp/RDcI/CCo7zk8opuy
+we779RU9Tds6ngXMmcbi4HdJBIVavr8ZlCyZ5mj7HSenujIUhnDqKZf/JE2CIxmJ
+UUQbD3LVlOZByPKowa+nR2WzfezkRCNhw20aSxOWch7qb6dFGQBRHdi2E9Ovy3JR
+xAyh+mLc5OMx3yBqe54v0cn+Ov/WYCb5LqtPKc2qd/Oy27pfo0Ly2eE4AU5EVcV2
+PpetTpC5W2xZqVxFd6irpyp8/lFiXb09FpNe4ykad22GA87SIzoGAEUK3Eyx+K06
+CVunXKc3PR/WyK+tM8KPVc3L9g+OzVL9Jnj5HasNO+NJyfNVvCY6f0vmBrZEsRbn
+lG7lGJ7HSbB5kHgTZGQFfxu+ftrXWkg/xg+bUQcPGIGlKh9lMVeZk4kdkt6fRewc
+0GI6h51FD3S5T6kKDBhEt6wXshYaTVxRHNWw0dd5sMzNar6CHkyaFVaJAhwEEAEC
+AAYFAlCiZnMACgkQRmNAdwvZNvFYVw//cKPW7WtU0HIVvkDD4EsXe9ejDOH3Wg4d
+4XT4bYFl8p78/msZipDTX9lTZzYHEW2Hhvl40frtA4mOUfZdwyB8qQSVjgvwnJD2
+Skp6MMuCOOEv1m29PACXjNzt/UJcVKEr8e8A8DdWmZx/ifeNrKx4qB6mKm0f2AU4
+qjMCApncUBPvpTxPTsO2pw+HOObXbApxwva4kC43QEHbqiBVqI4n0YIRQo6bWClN
+/FbxfzF959iaL/JY9hUY0KTkPfPrNlq12CY5M/PoAlOpP0o3l5ZfI6zf2VHhXB46
+XdTGvwxZsJ21fGADLh2RlXRONM5mtkVuY8cgfRmr1u1VzJWCiWsTig/kkGm/OjjV
+qCc9+7rFrCqI7Bs90Kf7+VDrHLEEjLM2Qgr95jqaD7c2QJWmns9DQ2l4ilKSOfkE
+eQWFu+nbXVRhu5qB5X2ZzcvZmTc5kx43bGJTemEettzmdGyLUk8RZvotuCBeWiUs
+X4/Ko56pIEjODjXfCeT6J4ysvCnRscxkcSY02KWJbR77vZP3scrx6rSch/H7V/hX
+xmslCUErHpYsmzSaCoh153fSQaiyfKUT+loPTas3x/1fbJRsDBwhgCJXuVVkE7Kh
+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
+7efjrgoh2XR+yFjBop4pNLktbjo+LXGc0e4uT7exRrpSlHLUuEa2rZeeKtntnT4D
+Kv3y4ebJ5f34VegBAb4e4Xsd6XgtDPYX/HUGseSjj72OPN7pAUtbBTO2q1NFG6vU
+PZBSLiV9ZzOu1lPYWEV0g+cIKPmLmIsRnETP346vWx2wq5EIWh7T3fTfYkYnsD3v
+yApPkG9MqMgotR+FIKlWFglPNEv0V2cZgcgVexvw5RD8TMuIygQQEQIAigUCTNK0
+Qx8cSmltIEphZ2llbHNraSA8amltQGFwYWNoZS5vcmc+IBxKaW0gSmFnaWVsc2tp
+IDxqaW1AamFndU5FVC5jb20+HxxKaW0gSmFnaWVsc2tpIDxqaW1AamltamFnLmNv
+bT4iHEppbSBKYWdpZWxza2kgPGppbWpAY292YWxlbnQubmV0PgAKCRCLOmAfCMl1
+5cvtAKCStVtCjnhZQeccqgGtwJqnGXba+ACdE1osWknKnvFCvurObyp6OPRoyuiJ
+AhwEEAEKAAYFAkzStckACgkQgngd5G1ZVPrnSg/+L4892O4wSVGmIZ7AGHtq08NP
+TLNfzfkcKtMUOBP3hnH9tyiKkxmQKuUAAUAatx3AkxYfsN8e3Rd/w7SfyeqCeQBY
+B/fmBWVegpUkIUmUDnAT89EXParMOBk+NAmTfBo5H+zS/lo8y2MC8WXJYHWI9TE8
+uft0nHn8hLCVqA2YcLD8oxQ9YP4lKCQsbZc0zz1GEZzcxgXFKm3HPHPSymr5sBbh
+TlKAXZ0kjzUgAz8asHbyAuvNmIMstRjZuqhvig/E9JkWgBdGOk9UfLFbQ9p9Hzah
+LnWkutfhSVcs1IJ2kf7P71NpfZl1/sifqojWUkrLcVwYUqfu9A8tICu7zE1xluRs
+cdv/3mx4yyWvMk4Eu0WTOrIWeQyoy6Sg9zt7ymjw/iMvFs0Lq1KgvI4TEL2KR4ln
+FU9BJQYwTVaWIWwWnTP5361nU7ixCYBQu+Vgv0sIQMauu6RZrfb1mQPpBigwFTh4
+GYBhQwgfF32g/4oiELkHQJ+Sf/6Z6oyE/sSdz3vsNFSxF6av4Yr5iSXojBXD/jt7
+ivOUOE/K00mNVNSW/T9oJGy/S/7XkOTV90MvtWSC3CWbLv7YNMYUN/3TgYV8esz7
+wG1Df0ogD4PbLUXwyXHXoFbiwIe2a3+XTHb5zUbl7LUgN+LAGzT7GItaPTjNgj+K
+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
+Ky4vFqG4adc6RNPU7d+20GOiJAZMOChdgl6knrX3b11ilu41ri1Y2PqHyjBJnm4z
+SVw8x8HxbpHt5TB/TIy7YwVnZs13mitxRoa5CwuMeyDCz/8Ob5K3fTF7/0UAsG7c
+/dhZBA6ENzjHYSxhlYDDL0mTUG+b8C6ZypLHeS00rky8g8mNV901vsGxoJhu/w2d
+iFRf/o4QAbj+9nttxxtYvBKTvuHSEGFErOLV35ChixhsMdr+Sl4eBWF3HxRJUsyI
+T/kRZzL41usXP2KH8CbkerQVaMzOb3M9RfO67VLPfb+q2JW+BdHMlbVsQdfWhLJo
+MTLHGgxhXCA7Fpd6slI3HrUvlFxd/f9DHmZuE63mvtjgI9GRdbDOHdkhJ5yTd1uQ
+e3p+QWXsgq/YOIQj+oIVI5R8ZMXHzsec4vPFxok1uajMV/fXeqdDYutbZrQgj56p
+iQKpBBABAgCTBQJM0uOxIBxTYW5kZXIgVGVtbWUgPHNhbmRlckB0ZW1tZS5uZXQ+
+IhxTYW5kZXIgVGVtbWUgPHNjdGVtbWVAYXBhY2hlLm9yZz4dHFNhbmRlciBUZW1t
+ZSA8c2FuZGVyQG1lLmNvbT4qHFNhbmRlciBUZW1tZSA8c2N0ZW1tZUBrZXlzaW50
+aGVjbG91ZC5jb20+AAoJEJu4Y7D1G7iKmpUQAK9EKFrhM2xi5GeTFv/nhzwArK2M
+pncvQTBGousnyJYeqW3dptSl/u0gB2CpOZdtMwvdOciB39B/s0+uvXOV/ZCh8Joz
+Sqsr7u2dJ2tjFw1v18ZwKDk7meZ/M/VJqUkEOC49FjU36Kx3EhPdN58u/V6kR4b6
+G08FSTjozwpCNK70GFNZz5pY4D3Pe70BFZhShVmHmdk3qA0ECbFs0bSk5cF5utQr
+iqvWiAWsO/6dU9NiaB9aZoQsEc+nKwl0gCgzvNhmYGKz6S7bjfdF/pXIgI1UAI2o
+dtHuMCU/E6OCFZIX9zROMJSNADmNSQhzfSlC72NJm2XBgvIKWigDIbHfIxm7To9y
+xB5WiBo9qG8mFH2kfe3Mo896wy0p2Ag369GuAg62c89B5uuV6nO1GVXEyOIEaklr
+ZC5NYb0r0Fh9w+Vndq1Hnd5GEMDDe/s4yJ+5JCTBgNpEv2L8ngabzKFvG7LRag8Q
+HmXm8yR6+CmKM7Ebhgweqdb3zk1wqIwloOEOy7dlJCcs/g9EzDSBZ8nNXQWkZXQP
+8QFhdQwVJGQS/UeFzDJdsD5Uo4VqOYhPgBiRKx5JXtNXxFYpns87l5NtS/ycSIXm
+6hUfXncSIvZ87RyFU0OsuHK/1QU7ev7l8FMAIBOA/9jkLV1HmAHSyL3nQK2W5mf3
+EZxEw1C6R407vyGziQQcBBABCAAGBQJM0vTaAAoJEIqviNbYTkGu8eQgAKwFS7Yu
+0Id1Oz+55zsjzKl7HbpEpD46yug6qrKS533qdVHaAe0/3266vR6iCmkMziGJJW1c
+n+m1mYg8UiKGAB0Crm0N6gU1xhGtTWjHvwbHZVigEac2tfgSDK+3TsuWkBgWJsJT
+Qm7PHUG/uvKAX7UgzDYWblXOx1wXw1YaTwxHkRopoB8Fk8u8LBI3HIqJjAJug4U6
+5zzeZWW0SprXqvoiZuS2d4+c0+SrYGwAkAfQKKK17kBceksVPjKleoCXrJdPCB3l
+O930SN9Cu55HM7YNHvC0zxlXxTkiC1N9xRWLSiC/EvPFhyeCPfAkWCBXPCdi19ln
+jqeTCgIXZ46hpcvk8SFm8etULq8E26lgD3P70fgZj/AHTWozXwChHXnOeCO44XNJ
+TkAnzHHTILRR/JgX7yJ3y62s9oWmTdI0U6NxjpjbH4QYzhtbEfHxoqFBICCSMi6m
+iDooPnYtDrBU1+JucxENCoWfaFV8UEea9F3IXtpvvO/c2ToWsQDvBiLzp9N9Nhuy
+TZewTPxWnyTd/tItT7ORjrFgs0J7IrquH1Gb/Ln7T2uCHDAQeikkfL8EbrpnAWMa
+8/LHk6aAbcAnQaRdbgfkt00kAqOCCh1k4ECJCDus0lgXJkKGgrRrCfpogJ4by/c+
+WnzQc7PhLy6YxDtJ/HLjSE8GIcceasbW58U4VfE3SJFjpLvGAa9tV1/4RO1yl78B
+oFfpeuKd1GYRCUzO7M8tfm6uo62TCyDuKE6W6i4swlQlgxtfz0aZPKzZu4RIQ6cm
+LfhX0+f7SyyHOo7llyjfUZjkOak/80bVx5+V1zL2sEWTMgPlr+t4AFcOUzJio+dj
+rSxtj2TXBNJBJMC7zadopBwG2kIAo88Ydo5+rCTK7UWe7rEfWEolrkcpWAPV1h0u
+hqXkSEQozF2paRUF2a1O0GwwhzBMMC0P+QGbdxD9l05DhEbwMwZIAhf6u1fj9wmy
+XGaOBgbrcYsuRwvtjMwfXSPYRNUNKeTGKIgYeAJ4zq1zGnhhXcc3Gl0tUG8qoqwC
+AlL2WSEokSYU9tOCiNBkZOKIOfVnsRjZatx1A55Twz2Y7FDu5+uTE6lY8UmOT6oQ
+z78ONq7B5OQbGxNmHJZ4b8kUgqX2EytjBzDwCxv1zMNJpXxFAsLTtEjCXBJ/6MoL
+DtmizFfzO7qxaXRdM+OjhFzICk5M2vWSNcWCZ2rLKqc9xZVPVopzzbdNfJ/DL1tP
+l/H8+qTvQsqbtUB++BVASfEV7kcG7kmRfxX6p7xaEwfGTPOICvjf1ObW2dJkXNSM
+Q9tLVfA7R8mcHV0Cz18Um/D/QIJ70nJZ3yEs5P5zC/pq7BMmD3nkEtQcOInJYCzr
+GrFJPofzm6frPHiJAlMEEAECAD0FAkzbbUE2HEJyZXR0IFBvcnRlciAoUmVsZWFz
+ZSBTaWduaW5nIEtleSkgPGJyZXR0QGFwYWNoZS5vcmc+AAoJEOE2CIoYJL3BC+UQ
+AKzr1PvAahX2KKQzI7xZ0SgSl8W5aupggbNa3Ksy+/0ms2JlJGwliv+/dHjC4p+i
+fNzkmqeUKfSED5F4WTp+VQAEncGcXOt7zPnCifwhD692G3ga/xwyG2B9isSOysQu
+/l5SRctmiYp/4SDm8MnwLJbd9eih+rI2kc221yB8fORUAa4YKNGNL6XZL2RXQbHf
+BPBigqz9eaRWSXyiqkKrof1Ec7vujP8BBZJVVTYZERP7DALsZtU22i5tz5BOsewW
+8e95xWd2EmVz/NxvE/oc+pXsKzw9lgO5vrPUKPlD/xbe2aEaFBheQrYPj61qC/Vd
+NfidVqv8o9z4dKUX0v99i7UMmZYTG63cpvvm0GfgIIcraTXXPaN+u2I0E38M/SBD
+zGj6VqsWERMyVRuNQpD5YNfoCa/LTeE8iUf9OP7xB/+0l2b5T2UdhwvCq3GKn1OG
+yeodJsUcOj3r83AltQM816URTCUIq9g0D//AB/ZuLsyPRqaLwxUX5d2E6ExLjj1h
+z7fNVwtsFdpnvnt0vWM5R8q3bEgRJv4y1mnhwDGow9HtAtg+lPRWIMMUHQjFwWrB
+Hf6i4I5swzJZZIrlGJEOYTP6zZlpEecm/EGXR8kEBALs+zmVUj0jMZrsw5QSX7HD
+QE6GS34OJZUzv22W6wMd+DGgSf/KOBgUp4IHwHZ/37TiiQJTBBABAgA9BQJM23Dv
+NhxCcmV0dCBQb3J0ZXIgKFJlbGVhc2UgU2lnbmluZyBLZXkpIDxicmV0dEBhcGFj
+aGUub3JnPgAKCRDhNgiKGCS9wc/6D/92Z/l13FwWdmWuigwx/C0PjJodAsiSbPIR
++N32pLgJ2QTEDDRpEKejLSSGBG6YCj4I8P+BYRVfR0qPAKfcRTQKYbEOD0BiAVLA
+mY2aH6yiZfKdzG0LF0Wahw7QkU5ZJv1lDbHNFKTKJXxZdQdGyP54Z3K5/Tw3i0WN
+eRxzeHYSyXY7fWqPlEp4IS7VMZkOjMEbLeBX6FGglLOOGbm3/nFfOykCm/v+QskL
+48YRGfO6dmfC3Ql3iHNt77wzdvz96IP7SlOihvkpfVQhI7e998oO5GqaCNlZPiZa
+HlaBnyy5a1tL6dB4T9wzLeIX4war41+CMmWT/HiVxFw+jYRMXYMPpUuGCuLGorR3
+u6pPq+o0i2VgppwuRMEb0fBGfS/uk89khQB6aZw1asfazIGdUUmAHrItAGpxL4Ol
+360f/M9H4WiuupjflWQEXy3TrDkCLPnBMAsoEkfODlcnAJhr6q5wkEYN0ZbqiHFq
+ZDOhp66DOMeEDaH2w3aAVPq95rBXbCfAwN1j6ZyY2QcB3epkXbCUZzA6bnndTiS6
+Cd0720qSXdPHlebrmihNsfxM9gpYBGqAyBs8PqJvClSl37rqtMTllDaGGiFRLzsO
+z0eufmUtQlg4kkGrJsh0SBmzVEfTOWFvYzOe5O9wGX6Ntp7z7RgcDYwjwqm/tOSH
+GQWSb+lXs4h1BBARAgA1BQJM4lKiLhxKZWFuLVNlYmFzdGllbiBEZWxmaW5vIDxq
+c2RlbGZpbm9AYXBhY2hlLm9yZz4ACgkQpH0eLdAeDhgwiACglstAuwKrbhX+dLFl
+/cQ0uU2kHCoAn2+ZeuV3z7vlP7l4qV2N0/aRfUCbiEYEEBECAAYFAkz+C1EACgkQ
+hR0KoAzMq0i40ACdEVpB3E3v6+RBBdMk3W9pnb52ZQAAnA//zU+3/3FURmSxcqZZ
+XaPfX6GtiEYEEBECAAYFAkz+C5QACgkQyn669Dr+KM6yWQCgo+m8JQ79z1Ak7XcT
+TWyhW5claQcAoKXHpMcledJJJf82QB7mFhJvLv1iiQIcBBABCgAGBQJNHE3IAAoJ
+ED/PUp/y8noGxAUP/iLhp3BBneLhUhRtOGdMuIv85pY6yKYv6kIIB29Xv4lE5JI3
+Ihl1gIT4W5Vu8iz3EGz38ZmmnhKYJyr0+hOG/0UJhxDVqNENGaNb2Axp34qYyAGl
+Sh5NkXXc3a9HYI5UNpZ/y6pyP7g++MtVw0Cf4IGcVhG+HBgU0h4BsGnW/2lytD/A
+Orm6YgY3UN2Ag1Ms8eEfDMWukrmbsegnXiLok/LZZLVpsU+lVsYJ2iCWmiIS6pps
+SEC39tyE9JqzK0xpBk89MKdraPExqUYrrrJKuPjKpP6wA2gyNzaKY11Ga1aPQnrs
+OyqBIbRtkvhsIkF3xm3iRb8iVR6KjDsbkaTWE8Sdy3AvxIa7UW9+gTAsXJiXaz/D
+reBf7BlZRqFsJxk7ozg8t8GUW1+q7HcLhB4Un60syIAFCEOrg0+ifV4KhNIeB4C6
+WtQWLyc/6wADq4UZE6wILNsIoMs1DuDYYcefnmYXqwzHXtQU9ouW3Xp2dBDzS4ia
+54BE+lrue18s/Bf+rv+U1iUg2GhRmjCQI8+d+N1ghcioWJTAJHwd/DeuTwot8UVa
+LtvUCRF/K0rG59yJQvAeR9MzGmxMxMtCQGP+f+0SVBQXK/wemJVbZ5noG1JO1qbU
++INKLB2wfJpVIe40YLAR2TVd332j7c8dGIeof630c0CeTvVzBa94yfw8nRT6iQIc
+BBABAgAGBQJNY//hAAoJECVRcDYxoboQ59kP/Rd6ON1qG79b7yzKaWm1lnv9obpu
+mTpbRAQsOLkfktJkm7/yEJe7roKyQP+txn78EQ4leo+sOShZZI8OlHVI5WIrXIwx
++q2uBokjSjoWl0QkmyZLBWa9dZE0XMnUoiz7w08AJO+ePin9WF3b572VYWX75ETX
+xEOCWhLC0du7m3xUveu2IcffwtaaYAjk2MqMpSXBu+13s49Z+YzP2F+QmRSLBAx/
+vDjO5LDq7ktcJf7BIQLXUZxuEB14hBPS1lhPcxZsOqPVJaBE/EE0z0AnjmudEBvh
+6mUkmlvcfF4uaadDyp1d+MShIDr2074V6e4GwNtXbf/Y83uBB1TiLKVMEyuL/+KH
+FS/ru7yHRo2qamjrSGDiFCUEpmQRFBMtVALc6hkRt0sWT0R90d0xp64U6VzVNj96
+Sb7E1sGETrxVnkjrFHEq4Mr7OUDCkmJf1dtHVHPzlEDweRUq5NkW3ytrSItyv3Hz
+sJJ57ut8ELAR2nRJ9AVO++gej6slwrS15KpVmBBSbCqeiLjkeaJR/FQEFJbfs8Cy
+efnq37jR3BjTQtm//IstHSmt1xJSdYyupADsl8lMtGD3yGded032lW4/GDF+lsa0
+LQ3oRcEXn1k5lDjznunqga/Cj0WYtPv6N8hdthNwFqw4XmC50xTVQ/HQFANHkqEb
+aKhRTYhHqa478QekiQIcBBABCgAGBQJNp10/AAoJEBVQ/b1jdcM7iasP/AxU3ArK
+R81PIjEVxtWKrWgHYhtOoVjwT/i43NFxXZfOK7oFLm0tGiyRZym9vudzdBOoQ9tB
+zknbn0dho+KOeyixRy8sRmWW0VTq6aQQMQOA/aMelqQr60bAy3bf3Ge2lWMEnDOs
+flIauhk0FZUYatWehqZuvaN4yiKEOeZLC4CB9lmtFdmWt7ZIWP0n0EWo0S0zhQp0
+A17lzz+aHF67pGlm9HnNMQcRKGtbwUCG7VUai/55tfOvKpjctUvgxhGY2mdLq9Wc
+WkysOyDMTsHOMJqiEZcLFHSZKZ+tMAvIcuLoDvj96fl4JfrRqrNXzgubYMtuVUXw
+vIHLi3c1L9i95eNo1XjzjqeaIyd6RKWmbrrDwMjixyNxTzWbxJ8TVoTYgCLQuwyc
+hHoGJ+owLH3GmA1tIn/lv93tBPF4v8OxSRbt2JMtEDreUVjhaL67zX6SbU4/ROLK
+kGFA0TIvWkA0PLyVMm6C8//POCHOwWGmZ3j/Zxx2okhwURhZGnfrtKiXCm1ojrIT
+Kz7zF7Bub3FmbplkV/j2VYxTlE8H/sB4NK1aR2CVINfGqVHDImwfa1HIwqKfhBKf
+meupJDQiZEEjQbdIG8/5NuJySO5rtfKjhneUpWdtn1cpen/QnDcyp0WxhuxdKkq4
+hKccyFhH24TAL/m9XMDl8TZVIzvHYU9NFfqQiQIcBBABAgAGBQJPTS/nAAoJEHA0
+EwEeItW4GA0P/iuDxwCW+mEKravuKZ4xwaa2OagJWUiouG2cbXvyCWe8xgqWeP0O
+9vGYnqmU4cPeBXlWUY+8VRdjRwOQDT2uiYP7fnyunqE4sPZCCFNuxrVNZCmSXeQ/
+HwZnUg2gmUBZfYDzMcTa5ro/229gVkDFt3a0NFcQ7+EFVyJsLF9ZF2Flvr0O6aWl
+cwotbrJb2Z1EDvKaqSnA9CXfbUeQ3ZRJMNZj2Ud+/vLQQBuezJ9hzUukHA4mPynx
+wVMN0vu0IkXdONqH0dsN6T6s9bDxgurT7O6Tc9b3UWjgbEUl/9SqOyoJHbuq9NSk
+TZkVRvyHzmK+Za73PvJ2cOa6RhAnvlFL4INFNLELfLFMLj8q8em9E8UY8UI1FYiq
+Qx6et5YTLPLOjzCtCE/9cjiurvSKKLMWeODrUWXgcdAdEUNBzNwh1cUEcJ6oTbUo
+WIMM4koF0izIeXwk+2fe9naqUy3Vp0PqEj3Hd1/KjM65jK/+8Nf2q0IbvR/WkKMH
++9HaCjHB6jwE4DzySP5PWV2LYcc3Ak9N5NovXuXJUoS/2XeF/XNYUCYxzEms9Saz
+vVNX7Ggnvt9aekdFPlhVIpjhZwoAYCXf0JxdXJJbRp+aEp2kCXH7YKmyiluQqyuZ
+zdo2n0P5T9xtaulfNiLE25B7Kh9LVXHVZGxPssOQ7ttNtsbYYdOAwQpJiQEcBBAB
+AgAGBQJQmioNAAoJEO0JbZPu7UfaRs0H/2bjG/9+Z2Ruq5rEu8DIWVB1u3B45KhL
+Syq4M0CcWKZmtdLHd1fsVLesQHwERsTUICfWrfqi4IWRhxYzkqdF6SELCmku9k9M
+qZZegtVh6frZXoTEohcsE53TmmIdtNhnWqfk9iTF6YSTyREGRZd9yf1t/RGdRQ9W
+ATPa9xXAUe+jARk1EiVBJgHkyvnnGpYB/IkpCTm3zfKCHLvMKadlNh/1sUEhGfBt
+nR1T2VuiUQR6hxFFfnrLasevesqqTKQ86FZQ9+0Ftol58YiJlAM4I2mabEZClR/+
+B+p85HPsfFsLxdivj7leSasKBDcC91JThKtYc13A9xyKzr1Z7bMQH/qJAhwEEAEC
+AAYFAlCaQ9wACgkQNiWQp/XP2RWgwg/+P6qCo6YRtNRFbtpHpLExFZUQilZVy7HI
+02Pb68zBLpoxyf1sHxPaTrjU2Chc4mMGh6BcgQvU+DG9xTENbCvcJPy0dHmoz9FC
+g4+SjTvzl2KfZb3ZoO1HI2R66BTNnRZz2hISqcNvUt0MWQX2g4S2JXLgm2F6jsMo
+5PlDLabbmGMMR7cDuZOoGv9bj/jlCeIUCcLM10z3w/2kumhWVuZorxVWiEusF6kh
+5gGh8cRYA7WA5y+QOIDgV/HA/ljC6APDpJ72igMSYoWrK58BCJ+x9ZqDp7z8DHrS
+NbRszjAdB7Rg2ywp2UZ+jPzHTXtLpcyhwxntH3mDFBN9L7UH38uV8kNDimCJgIRe
+spEMhcVzAHJq3pJLLFGIwO8zJD7FL0+1/ugh0zVCOuGb27ra4lVjUsG6CM6+EJv7
+AqazsXlOWSIjn96Mdatj4laXKnlPymrVeLQMybEf/+S9OVMODvnswXDtJ7zNNk//
+Pcn/R2FLLyS5VwOHkux7JSKi3r1TkW84iPdOlLo9RY9Y6s5B6/hZlRGvXirwb1vR
+aljSHDI+c66SuCIi3X/av4lx/5S+2rbYa/8m4iDMcIjxxITh1avRpqMzbrSzGLaP
+b9T2RMeo9sxIOisom69hNzu+8toDYwKovGpau3QdzYXVxntcBq4BMXFvaGpyiywh
+TJtT2VdmNy6JAhwEEwEKAAYFAlCaavMACgkQcaRaPQ2NC5OIVg//fZMojFppZCGy
+Vgc/Lcc0rr8xhv4AMdtdsRHVMwe7MzaOmPWN8DrgwrUL73M2ImQlKN4oxC/k6MPT
+pk7WvqGLijnrdV19eZJY8Q7IdXBdzDG05mahJHONhJGwsuM+XxRP2UGEKdyEiK5j
+cxHrlPV7oF8rV/J94xlfRdgiOT3xHQZrk4lmIAiQwasWAghCxX9R7gga3vjFSxCt
+rF2suKGaTf0Y1n63GcUatbv5gx94StRfElVj/jCtOoVTi94SM6brz53A2bRMXG44
+2kuJobbx/K7klZVhJmxFFlYmak3pDBu54F65gg0NFqq8cQsE5eOKY4O2FKAKtKK5
+E/HM58nxko50zmP7PT0uCzFgCjc2BhB8aVwWklgetj6hc23YZly5xx6suWw4KbmK
+TNlDQupg1X6gPpY52H0OGd2qXeHSMDYanTMV0abxbWiDvIQWJC4JFNiZkjRX0bpT
+cD6mKk9mjM2LQGzSUpqKWrgyNdkkQWuyOp7+1ISyyB/k/wOt1DYF+oEUpIkMGtDh
+etDAqUBQ1dxVuzfMTg3rvrDaKCkbojFUXcGfhWHAW6MvzbFj4+JAGn1nisSvhw+2
+4fdaADOmTYxs9AQAmWhotLRinYyJWs1bL2/EJ2Td3rckTKP58x25tiolEYZzwbJR
+jspGqCN+zcbO7RBGaBEgBKw6SMQEaoiJAhwEEAECAAYFAlCap+IACgkQ+2M96PXL
+rmso/RAArMayP08W7itj2x1ESwQN49zN08RuWfTt0/zx7clzUybxyoczP/xOJP7y
+am1Y7cXoMa8BH34fIoAKbofGSnc/+42F1aXNIQQzFPicPQupvt9LhLS7MG0weggv
+iRmHX1PDy2S4NI26dDwcQYkcGfAtGVGlht094ZM5B42PVGZZGh/6FuQn+R++9qhg
+jzVcPPDBFP0V/32fP9CnpF4EZpMp9SIvBFfn4PAOeNTPw72Af2wbopxqkRcmmUjk
+RYoQE76b2N6sywiZPiAMig5NGo+UdTsupqATKgh2Jl2ZHgSUuzmiZxYmmLc67j/r
+aKlZnMm8rDWpPa5qSBBmFILTroy/eR9113H2ITEkeGfz3rgx1u9UkP0s+wIXPALR
+Rg1OvPJJnlgRYAavMBEqqXnd5hLVmI44AjtywvsXlBn3PP/JoM22JDcCJ4/bYEdB
+/R9PdqOFKKe23YOdxqdUPOJ4+4VN1raJj1ow7lCEgsl43jXmWSIiPZkCVyJbqdu4
+wGB9WUI/qmnGCTHwFn8OqucUrba45MWr0NC//4CQNG70kC5hiCt2/ZJfY37Kist0
+3x7TefGJGEzAbdE3q5nYmFwSZrWp71Z9pJHT43HJvzPtfY4j1OhXZwWVUrAM7Xqc
+iOAsqZaTQsB1lmJglVg7Xoi6ditxz+8egCX4XRcA/Cdls4C1EmyIRgQTEQIABgUC
+UJv1cgAKCRAuuUaCiIF0Ao5QAJ4o2pI12PE9ZEV5vvt4F6uPLswWQwCfbVCnsOlC
+rqlzMp1GcRP1LbQQhvqJAhwEEAEKAAYFAlCc4IYACgkQz+7zFlG1/ej2eg//TQeN
+F/vG13oJE+arbzJnAR1eS2cSFPN9ih7QafOq2ZCxROWmQmI5KqaUqV2aDrI+IU1b
+Oo5WnDsc2BIThqzxgsTk5sdHSkRDSo3Bojnn2MeuLCZtd4dQmdtuCR+IVVrxuI5W
+6Bdc6KM6EzK1qdBuVPL9NyqrHsYk988NeZtonx16p5BIuiM1pzwI0C5R9XLolzF2
+Dq0PZuyzkFQyEpJOJONhGwGmTjy3QPGzjmtqnxD+ADfjrlDfb/DTVvPh7iVpwzrL
+7eLs97beM7EXs9R8o0XxnoFJXBrh/OZPXWNFIZP+l+K2huGo/TNqgYjPvaxIn3Tw
+KgoraJP5FnkaGKemiRn27PGaEKx1tA/sbe9UDGOkDgPWHPMhyBkeE0dKZUJnPtJp
+6fTUVyT8i/3HPVEGsr/CU4SBc/gTObhzhkSA9qBv3CYrt3kV51myWL5oVyaK5zpq
+PN6/0OgLRXYuh4JfqzLNcA6x6+PkgEjv2kvkDPbflw+TEbPxHI6vlmpASHJZqb31
+k654tOLs0nS7g8XxmJYllCp4j/1/1R2d4VzFVD7M3nttNr3fZBI06ctEUZsq62af
+hoJTkPuASEzA50uKYsCUNMTkg6rwbZi0A1VPSKkXT8K5hCx3n26Mko4XPtugYlY2
+8opNKHzK7ypcK799h+CT7i9TYBfxmqxLFdGuxQ2JARwEEAECAAYFAlCegfQACgkQ
+RxXcAmQovbpKIwf9FP9qd9cGYOGbHiGaHO/pvVZPuWbD6Hj3adjlGFNA4kMn79sm
+eDbxZfs7YpzDA5zeHipfifk3U3DQh5A2p7EaoycvASv1rk7Zs9CG+Yk0+SWFxsST
+LSm7PFf0sTef1ZxjVhrhfqZDZ8ULHLjcKmji3WwDZtPimX1v9ibQ4AZ9c49t4YkQ
+Tj37eVICPc8sVKRTRBJNwQi4vlqUbMtnqGfTI4rniIK4TCh1Bi4ahVL7CvXcn5c+
+zTx1naAEWkPCffl9l4H4fh66AlHSKH6fZi5DlnUS87fKhrMAerUxbg+j2A4ApCW5
+6usFl/LiQspU92Wo13RyTPoHraSuOZphO7KZuIhGBBARAgAGBQJQnsgcAAoJEOYW
+ZyllQio9s6oAoJRF5wFr0EpUCrMxXm0K6yF1TmIPAKCTvm9WNRuGLxbdQ+RA+pyA
+RtpuKYhGBBARAgAGBQJQoUKSAAoJEIvYLm8wuUtcZakAnje9+R38Latt/8qLBV6i
+fJ5kFC9hAJ0cLXTqgm6xE4tVwY7qoHfAeYW5vYkCIAQQAQoACgUCUKFC3gMFATwA
+CgkQYtSPrRag3gFGgxAAl2oBwGgcIBRfi+zIEdLiQXUXnut3HTA/ImWbEulBJmlK
+ecZsEVhx4IYlxJZhJSrdiuvBNTToJWTTs/tvNLsVuXf8owJR7cV80RcVDmlWBKZB
+MbkIJ59hxUDJZWikOi1B+z1uVILWM5Gyv4j7Nbi4ZJ7zlzqIlXY+IcBA6JQXW9o2
+r5YuWkd3Pn/QwePCDEsYQ6dm5nef/Dh3O1Xvw99Wo6rsN2yHYlz67Mp0t0sg0jUY
+pBrczJa7s5dTeS081ciVSitefxDgpXKf+0Fw/C74pbR0CX9Q6QFzSRNlzWuE4ZNb
+4jUCA6EZWUbaVnS5Ef6Ep1E84ScpK7r33qbfGfrNKDP9lexu+cRp+xTsfyS0GeK7
+7Zku7KIVOImR+ro3Z16Yit2+6BfxGBimwSqR+/nmzxTdMMyhpoWxRy274BfX6xn6
+LjHG0UPRDaaXZZ2trZeFiYT6Wgf8HfbOckVk6HhUmdynYme7N3cw77h2UfBprDIu
+hXC1YmmDLiSR2iCDuv4t5VUiLt24HrvFosGdYbbAbtJ0U/t5KiAcFabYV6O1JN3x
+tsgLWZECfotS3Db+BWUKcsGAc24CLkPImXIy49k2HTRpV0jYRG24u/wP1SkwSkee
+kPZsXRz9IpOJU+2XF5mnAH6IQ+QQHNu4APTi8Znl+Pn0HyJyb3E6qk5tbXwvQRuJ
+AhwEEwECAAYFAlCiR+oACgkQSx2eCKCXya7NUw//TsCKNVLlzkqKgnrBLD9avscn
+t6MKk5BAvAlatpAzKuqwXKGYPgNoDiY26NK/0ccrdgjV9RskA16zqetdaSOX6XQc
+1CjSI+KnkYdkvLMjgzqvdtOQmoD2x7fcVhsoL9PuLuKgm9YndxBMV2FZlv8kBLyV
+S/0Wu1o7wN8cJODuc+UuOH1Idc96dW9gfZETyoc1wdruYAEp5drDEcL1nzbAYz+c
+5xop/23Lc7acVkZz0hEYlhCxBb4bxtku2cotkFP5aiCXyp2elogoqy+LSFzcOuSd
+qws7zdfvoFV8tHrzQ96FG09/s3E9OCm/PPOCVixh8PHhhfhr+KHqQgWbK5ncMpiV
+186pWbi84jRXdrgmBjFP9n5LgYEDS1Sq0eTYGUVGzMAVqeXrq7pKsXfl6Fu/u0Nr
+5JZtfRB0Mg+qU2zhgU7O2m6qNlGW7lCONuC5+MbGXTlGNk6MZVYOfRH6+1W+14kQ
+77AsxaEis5ooBlPSGAzBIpmkneVMnO9cA3uXWJ+yauPuP+Tmd/NNYF9UVtWQhpPR
+lzUc+dIiWrSTZs3hnQvCw2tS1TZnsvWN2z4pVRvbFIYyxJb3nzvvTABY1H3GCbBy
+hBICpGxbAoHuPe+U+uMzyfIXCN6CEej0LbOWEOqlKCeb0soBsPgEmE8Hu+LFVpQB
+Z2MvduQDfLeyt4luQeWJAhwEEAECAAYFAlCiZnMACgkQRmNAdwvZNvExrRAA3PpM
+4Iwml9B1t6zuFTzFRVvGlGdAVpF97/2ZJyn0ath/sLMLWDCW1/XxwjtC85F7oYde
+hL0K4Gu7h7+GnFZFwXQClc7cF5ZmXjiwm75zLWGYbFu+XoKY5ZjEF43TUDz10AXZ
+xrkxsStYC+U3UbCZ6H0F7jszYFO7RAY4hmtvjigZQnENwsvdFdWVEGQK61gHPkUI
+HRLZ+PCaQZLNHeeoN4wn6RcYshHlVuPFlcJ+ql4Zet8zHtbiEqjdF3xKLLamYkjy
+pcAxNIiCGjLaNsBUzt+gOitF526+sWppLpacsMjo8KVaeStla7nVAZnpXxgDwbIU
+GuSE3eAG2I62hZ90KgH7+DkLHVFOEx6n691Y7Lq7ak89BNssXcbrDQSR1qJZYvBW
+D0HOiAxN4lWWUjDSOkC7zVAjJHf7mYrT34e/p2wukqeA1nNDP+OtsU5GGUuTskeo
+vjMPQY7Pm6aqIi0WZrYF0CbvnXYYPbDHpiu9/ugL3uCLzBm+PZWn/knjrXXhzZN/
+fCrV0b3fl1EwGG/xXMwIlGyJCmLIa7+8FClvvVlEhN9MIbqDWvSq3n+cKT2op947
+B2OZ4r8n4EJFWKQqnIAzxliAcN3lZIhfxY/CweFK6Q0en3CvBYTFhgEV7UVpoLwO
+nA6ohMg6QjfefWjJQf1o/VkGiW3XJZyJGS23cFuJASIEEwECAAwFAlCpbfgFgweG
+H4AACgkQTYzEcEfkYQxEtAf7BP7gW9k0xJXIYnH3nZO95qBkYLxuYfenqAjv8WgG
+e+JveVX1TbYD13N5Au+HPRWOBeba/lhW1H5c/NSIK3S/fqOt1nz8Caj2cGYadPen
+tscszuguTSSKF8FCfabSj5y0yR8hTJB51eEToAGofgsZD1rc67F3ymoMLIiTdUT6
+yXHU1sVNRvD6q71fuf/yydSULsErPwgb7+sShUeHJxUTJ5qwMtWtsnNSMUKv2Ln8
+Ib2glWNvDgRygyvfEf8OJdyxfX3IFVJXWKEu08HPzZ36XhWXNf9ULxSg7qP1iKFr
+csV0wwCmdDWfUB29aZHbKLRNv5GgHArh21rMuWZMxReYbYkCHAQQAQIABgUCUMzi
+JgAKCRArEYpfoV8wuY/YD/0Um3Cy/eSyfbuLuFkxJKOokX8WGcPZsvsDnMhnyBs6
+ae7hk0jND4or3+R6vakYLpm3dN/Y8GO1+0wlL8VEpv1MKl+A7chGLYfoOa307ouO
+fSOzblU8qjht5xAeomUmwNhy4JX6Tx/NRjNLIJQqJi5NrfZ/YUXgp9vXgFhUdkh5
+LEg2TSDYWlCXHCCffMeClMGMD12Grvkg5xyIN5sBJG4E6VV2wi2X/JzT1NE1rYar
+/HRKFNVW9mRrVGUpFZW10K0f7DdFi6T3RZyIGSVxZZG2RJPs/8nlJtWeiqc99cnP
+PNdBEjHjjlzRRSLdpknpF0PEF8BJKjXmBsM+xG6mA213JfR/a2Jd7lm7cQ7qK0CR
+j42eLi7zBYeellxxaYLrnOrRKgYbcYHgaT5VzY2ccf8jb+Lelh4omGvH+yWDIPvF
+Uge00bgZpo+ghk1vNWfABVoAYdNHh1dpKR7N1tXPyAuimDmG3xEFyEfQHKuugaYC
+7TcHLi+fSanNNgtqMh8ONgGs3fQMpyt9pYfLLgkdRHFJiRPJwlIA6BUFeFVlyn6S
+CjV+XqXDawvZJtYY46SJKUKLEttLlljLE6vSvIOyc0WPIWVYClVWYWIzAmrJ7YL0
+B4lMX8lhwxzug5AzzM+e6QerjWjch+k4p6tf7lf/cJ4THXiSyvwD/bfJ8n2tA//Z
+/IhGBBARAgAGBQJQzOO3AAoJEJGTmI/nDSNX9LIAnidx7+uefZyZBXCDEO1OEVcG
+ojiJAJ9njLB/fvGu8Hgisz43vetBv+bSmokCHAQQAQIABgUCUMzkgAAKCRBMcPBo
+b+UPHANqD/9ik1M2LPAjoIDpx32n4f8BcJ7/xMpoIf8mzuLt/SUZ4Gj90dW04xes
+vN0jW9//m+wyS3UzTvHUFZ7MDyN+k8ysm+R/pF4daCY31yBHc/tMQco2AixGtI8l
+aicjuzuGRVnf7W+QDL3r+PP6NFd8IWO6/Vf7ZnWBpQfQ3z1PuEEIM0rlJsUQRXe2
+4vGMo9osSalGZN91Ivi5kFW/SUEDoz3D1li70+fjxXCBtns2j225XkClSk7m17Gv
+a6HFngAZuZvQ4K0gjl5ZTTkz4m7RN3+We41Q4zYO6DX3YNMxZ1hmJVgygXTiWbBQ
+HBPmi/bUBvnrDyr+jO7WRS8ZNpTDfH5/p6FD4qxDqx7BgpFYytqoHPpZnhrbMK2o
+KygAvv95zIfJIFc/TW85WYEafegd5ltP4mmAhzEwJ009y2QPcprD31zbpOW/1RbW
+pJ5do05CxHG7XAWq3kVVr5RcI9oVLW1st6AZjOewVcj+dh3k3r7kjthCwWZBYPYq
+zJhgcWfvuCpvaxXSVqoOz1D6c2bZYKBSpGqFdqmiJw2N8+yOEBw86I8KnJ/9GZa4
+OZt/ck9IVsgQNm1I4k+sUi/HwJpzwRoWsANYWr/FRiTinYBPXcKrUYyyrBjwCw6/
+kTuFRKN8zBc5Vr/KIghwFmZZgKLBQRBPMKOMcp7nywUELnNXy0suNoicBBABAgAG
+BQJQ2wfBAAoJEDGmPZbsFAuBz/MD/3J8i5H2nak6tQI+i3BaUhjb6cqF41dUg+xf
+1MeJCh6XVc0xe2AdmZNsav76fOlLRjVBB+65PX5wudZ9avByPaFahMQBaEmO75Ks
+AAORm9s0iJLQ6ZR3Hs9zKbDHcPYmud86KV+gPCVGWrqp+GnNoesZnv9+rO0rHGJY
++/yii/AAiEYEEBECAAYFAlDbB/4ACgkQ/W+IxiHQpxuxqgCcCKk1cz7Kxi92J3zU
+7eacb4kiPMUAoLsDxDSzQkkQV0FkGspSbnIie+3GiQIcBBABCgAGBQJUFgyHAAoJ
+EAEbRra2zTKAfuYQAKGypz0pQE2ptZdftw+K1reK0n0eE7LqysZiJYxm0HwvnEMW
+Mc0lzgsxW1otYQYaPpiVAc7VujTj2a1/vLn8kB+fzTt3R47nqdjjdKZvP2JsHFRB
+xFsERrx2Pa41lsxsTGNkslNYDhWRxXXibB2qcRzq9O2hHvEmuWE8wdeXIzp817Nb
+WV3Vc2eee5ziksmkLZuJEPKVESVFlaSf4/p14WpFAPXxz/tPjJ4QqC7iPSpT20CU
+6mCF+sXawIz7OzNZA3l0JCBPfE5ijqSvQWcZT2jMRknTax7Ji5fmmHwGLe1NvIGp
+8DhvZ7ojd+0qxuowtTNI2CqQ7O5hE8u7i12DO0Wvo7WdBpjTCuDlkuGyl3dLxUn5
+oyB1qDqjnK60YejwbfuNeKr0xbimbCT0/NtM4M2xg6+3n1cykYiMq3hgf9vk7x3K
+W/niqFeA+ZfxzAjFYBj4dht1RVIvVgLLKB8Ttkt7K/MSoiTt6/GZQc82h8KW2eWQ
+/tDaRffs0l/LN2ccyazpyvnrGQ0l7evD3fNtbLVrbt0NZHCL3pZsqUkvcg2Ws3lY
+s9MwbThcOCQ/OHtkhbq1oB7FNpR58loJ48pQS2BOIR2RIilcLzvNR58hZ5ro87XU
+EhgKrz1QwAhUz63v9n/1nAx8ys7ZAw83lwcpS5cnviOi3POeM/VSgM58N3FLiQIg
+BBABCgAKBQJXUISgAwUDeAAKCRBHscOtK/sPJZUND/4ixzhMtsxrLEFFwsz/5NNj
+qbzsNhSq7F/3nEU9fNZzuyedX/0GuThtB3HZg/306CDAhIImqwl7tPzlwqwzwLX3
+TBzJh5H007zDmSSotiI11fuN/g4xeeralMKe73SeLOj07/dbWL1/3isTcg5rAVid
+PMjuzw3Rr0r822IBZ3kF6xCIlqSxiLIfVX8B3Jdp0/9g8yzEi0dJTh7ArSlJsClU
+G2mYpJ4My6FbCfEenXDqrwIkL8+5dLhdk5MQ4ySgXWYKTGr+ju16BVvfdwCHWdql
+bGj81oMUIsvbMrXWbXthcwFgRrxtwCbt5Geg2xNOAC/DIMtEof8wH2nMmpKiD8eM
+7r7Xp08jbWAQpNa8iUOEYEzHtidlPQjLYrMTdlWJCpWVr6tnAQhvR/1Z4wQeG2G9
+wMXksNyfBtUt9Iyaz3PKL1mO+QkH3i9M4S2nrod+E70sH6Xor+eaDXM6R+GyTXaH
+/+pYXRHksnkLih6NONP9sFrNVXyOowmSSVJxSlsmff+lNH1MrP581C54XMO7+W8m
+3pz+xF/ER5w4D4Yljbh3spMNzaKc2b95lBH5qhBbwzT+oaWJwiiNR7H2/sa25LIJ
+TQgIHdT5TCBh4XvNPvUq2CDJlP3dpK+BBf5s0ZeKmS7ZNHVt4kw1MBhCgDxDPkjV
+B5LPU4BxNkgs9pcVR+ms6okCNwQQAQoAIRYhBF3wElwoWNbzmcSUpG1Igg0jTHJf
+BQJYT/07AwUDeAAKCRBtSIINI0xyXxQ+D/wLgK+Wg5X/oih7P2tlgP6Rj6DqP61K
+tTo85VxlEKR95kqQBRcgqI0zxCaSsEOZ80lqMbViILIiAPoJfnwfq5/HLqtXiaLu
+rdg2MGjMIjVu7zfPpPU6a7aODJ+5Ed0ob6gy9vCfLo9SbjQmPo5RWD1N22yjhu+5
+IWbT4X4CKlXcMOtUZ6PLAUwmwtbsShvBTRZ0RoGO18j/DOdp8KrP2ZwxD7LH+4ht
+WqmYae2SO9s9LN2vhiJIu+FS5ws2ORzYbhTPhcM2a9ocYxVYC7GnXVxEJs9ibcZh
+a2EJqzuhx/mAYgxsFuaMfGg5msTj92yKTeIRD7sJzNsVpSaqce2xWrkoImjsNgN3
+ySutgO23ZBg/heKlu8KAUw8K73FNulHXou90viOQJu1cmNSIVJ2U5YRkx1ZcIJcK
+Ywh7bomoFLLSlx/yVAME+QIJgXVPw1Az9Y4OmpqYjwon2IIhJidbVX74QK3FBXu7
+pa+7t4xDUzBD6/Kob0QIEFvqv07dXYascBz2S4sHFejs6u0LuKYbjZGzvWevLupU
+h3jiYIzjuCpAImrCPi5LHyyAiQaMcRm9/8ylvEXjlwMe1pxnuo77Gy0E4fAXrfUZ
+F8/0RY8i3VgeHj1oNHORlxo4zsqdtIhfAs7pimIWPv4B5KVlX0QVRVISl4jP3y8r
+hst9dOXCq/HHerQeTHVjIE1haXNvbm9iZSA8bHVjQG9yZWtpdC5vcmc+iQI3BBMB
+CgAhBQJMyCK+AhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEJrilv0C6fZb
+1PAP/2cpvblVWy8IWmXcMaG7Nh6/EhyCF8U6fB372YCkT6geNRtooS/sXWHnO1LH
+ggQ7NehS9AkA97LGd5wO/Q80EFJxWh0Ks+UfJCN6BueH4Ho6ttjS2gQse2EmZSbt
+wYU5xpVBPMJatSif9q1BVKMYD584hezzZwE7S1ip0pnFmwJoQS7OfI6tKwb2YEGr
+CRAuvgh4AVVvty59Mvw1CGyMnHlyC+SvuGo7dX3NVNZiuux/YOC1Po7P0Qw1JkJq
+2s9uOldF6QFdNDZ94fmNey+qTjiBQaF49HIfpFtRVyvL4pDxbkagtP+zfhSr+UsD
+nFDja+kHXBepw+F+MF3JpFwoDMmssfSNdi3KorI/yuoO7ND/k+6zlK6J+LgldjvK
+3o7R+zGuq4yKVJgK+wF4bsm1NjGq7URlB5emZOa+L3BV7X7CytgK5Zm1uX/kHF3e
+tJYPFvS0rqz4cY91BoTF2QAs4juM4ipkNL8IE5nvVpvw2z3ASUxslAlRcT2SfaXk
+3KqNokdqTwEVdOhAV7i3v02ulBvBNGyyhNey+B3riAX/BoiO2YJY8XQMWXMUN5fL
+BM3nCxe4APMlV8GKvi8Jsf+5OCCQFng4SBf5e1qQsIXcpsAfGqk49IGtDk1pUh+W
+Hv7rtcwauRBB6msb9Lipv4LOLj4XFLRHRUbKudkOyq3leyHwiEYEEBEKAAYFAkzI
+I3wACgkQc92MFgFTAjXWkACdE846Vv05YFZlbvvVz3reu+K6o08AmQHipeqPqAOu
+rwHYcqRBMwT7gm5BiQKTBBABAgB9BQJM0rQoNRxKaW0gSmFnaWVsc2tpIChSZWxl
+YXNlIFNpZ25pbmcgS2V5KSA8amltQGFwYWNoZS5vcmc+IBxKaW0gSmFnaWVsc2tp
+IDxqaW1AamFndU5FVC5jb20+HxxKaW0gSmFnaWVsc2tpIDxqaW1AamltamFnLmNv
+bT4ACgkQNOp25nkUhagzfA//fDOf2r4R4NmRxMBYksilQGdgtrenELGh0WXsPv9o
+gvau+7nuJ+nIVG9b7S4+rac7Lim9L+15SqWnwASdw+yVOUI7r6VHHaQY6mYh3Ptj
+dkuXkT04jCBKnEBXLLUariGTPIYEJEWH4FUzORg6nt/9vQBzXbG0DG4J7DzDmFGK
+S9nQDAqxKRbrRVth9IumhIh4kuwDJbN1PHdJAYHHRyD98f5uTxzMvU7r5VmG009o
+AG4JCVBMW9enj0/qu9mLmNm4IxyvmbuC3ZiJXwQUn2qDJobv9BtDnvJwYCNtFJYH
+tu8lpJZyk2wxP+tpcvny5D39iQRDG0WpymtDZcZt8I5S86PrhxVXEem37INHiXVs
+MDLGdfNpfdezAqvIkYVmVfK4gYhDLj1oka4ruiergwW3H8BakStpeGqdtp2tNMNG
+QXuoKbyjqK1WFNVUKN3s7efjrgoh2XR+yFjBop4pNLktbjo+LXGc0e4uT7exRrpS
+lHLUuEa2rZeeKtntnT4DKv3y4ebJ5f34VegBAb4e4Xsd6XgtDPYX/HUGseSjj72O
+PN7pAUtbBTO2q1NFG6vUPZBSLiV9ZzOu1lPYWEV0g+cIKPmLmIsRnETP346vWx2w
+q5EIWh7T3fTfYkYnsD3vyApPkG9MqMgotR+FIKlWFglPNEv0V2cZgcgVexvw5RD8
+TMuIygQQEQIAigUCTNK0Qx8cSmltIEphZ2llbHNraSA8amltQGFwYWNoZS5vcmc+
+IBxKaW0gSmFnaWVsc2tpIDxqaW1AamFndU5FVC5jb20+HxxKaW0gSmFnaWVsc2tp
+IDxqaW1AamltamFnLmNvbT4iHEppbSBKYWdpZWxza2kgPGppbWpAY292YWxlbnQu
+bmV0PgAKCRCLOmAfCMl15cvtAKCStVtCjnhZQeccqgGtwJqnGXba+ACdE1osWknK
+nvFCvurObyp6OPRoyuiJAhwEEAEKAAYFAkzStckACgkQgngd5G1ZVPpZPQ/8C7Lh
+kBwCaqjjKcFjEzGx9hefVhN134+5kxKJ9YcQUFFwOo61Il9ocdwz5UMWA6LZ1Qx4
+NjCEXiiA+2bndWjCAJaiDx+CI2NdpAv1Dz3GqswBPKN69kf4ZlVVI1WaCoHzCdHk
+9egfXPdK30vjuUkmfdacSdvPLIwh4N7GRKlnc5iobCZ5MzrzNAb2kjd0qOHRMCZ+
+I7WgECIu3ONxuol/dzWjSFlxJNh+3tejhU+D5qNYHMvP1DkzR/88M22uT/PHRBco
+vQs6KPG8Kj+FWe2xLZqGKNz0h3/NLbvlQ0w7kvfHJu5/K99fDEEQw0nVfCtxgbd7
+V3DYSXmGz6Hf2rFsFW84FH3rR12HBDGpRLirEA9aeRBAiwNXQRE/JwwnFCrgZc63
+WyIu2MEz5x873eJHtbpLMjaDcJebNNy7g7C4MysWx4pEgvCFY45RhJLBBWNAOZo1
+dJf628XAgsKagWOXlI0mWekRA0y3WbD18ZCEIO5d7qGxRtjuvsxohOLeLxxXEn7p
+sXy4O3yefzi5d2523+1b7ggNIt+hT86wTzBDiF5U9Z7Whoe+C6xExRRPVnZ7DhP9
+IrB0ql+q2g6lLjZNHITMAMhxalNy++leQhrKVIfy1HCECKV5serZFzamhlW6SJIN
+TutSxSdgWVYYNaKfDJ+MhdUpq0iQTsINJNUWtxGJAloEEAECAEQFAkzSuNg9HEFu
+dG9pbmUgTGV2eS1MYW1iZXJ0IChDT0RFIFNJR05JTkcgS0VZKSA8YW50b2luZUBh
+cGFjaGUub3JnPgAKCRBe+tn+gqf7zbtSD/0YhG14Hzc9q9p4Xdlrs0lFmA6Q4wjT
+nxNsExYDqrnOmmuQ8uAvFEtKu0wukrnjYYUjy8HL4wVZsNXmRRLK+KBN9hc/i7xe
+HLEqmg3eUP1PQokuATSIzvUMu16q4u6vJhUcQtknR6XwIbV5WbqPZgFazPFg731k
+e6/WVhkQdmzanIzMQAyXriyoToW5VfQfQ93QMz2Zv4SpR7wNpBYx70POl+Jq5FYl
+OygjIL75LJ5l4pzTNJU9dvfJmtRhIOKiAEhNSWUZxrgOjRL17g5mLHMWy9N1h/uX
+rq6jcioNPog6/u//COWfFQjn+oo9TLPhgfCs1TmmBjyUB9Q1ZH4B2ROUpfRRBAcU
+cvOjUt0hVHJR/SzztV3SFpF0A/Oy3hAnCCqBxi+X7oW7dx42B0VoPTsX0MWGtE2n
+FpjcRpjpSJaA//PGK3hbBct2sr+/4sR6J6SDt/QDCmSk3xeyFIDKGYX3ocyFh+4a
+v+QJ1vC5Wo5uHyCTdVSOVn8QViazjpe9eBI44VMv967ntRP3OCDDxl6IGuDESHzg
+G2kBXjtqTpZWqAjoo5pSjo0wMSSXgDqyhKDBG2Ic0ASg0+/Dg+/TucL1NDniR3ru
+UL41fjLQ/crN9l5q8m2vzRwJc8mhyajphnCXOx1aGn5VAxyleUbk0MeHg4kCoidD
+Dd0G9p8WkpJl94kCfQQQAQIAZwUCTNK+FSEcRGFuIFBvaXJpZXIgPHBvaXJpZXJA
+YXBhY2hlLm9yZz4gHERhbiBQb2lyaWVyIDxwb2lyaWVyQHBvYm94LmNvbT4dHERh
+biBQb2lyaWVyIDxkYW5AcG9pcmllci51cz4ACgkQnPorAWKylj829g//SPOoBL9z
+JNx1P9PFD8wBm6lCaGJWCs8XirlSCSZ968/LBC1iEnuhH0BPTkddnIkLhJhmWWms
+ki7fSdvDZGvIsW4IQvboeBDsVwNegk3+fviUcuz/SF+50um4JHCuo8vNXg4PCs3k
+SH5asBy00D0Bgyhe3BaeaIQ6FEsZfAxgAvrxM0DgZ7PQx9kQF6gqg59H88/brj2O
+qY1npHVLcVuBtzEVbEYuQas0LeAKgPQ/92tAC5ahTyqQr8XdPRfRGgAZmvbHwkMX
+khLFSPiLAgZjA1usrCB+ul7LweQmeS9K1irVDy+693E9iC+pY2NbRCxiBZOSPYks
+/xKnbh9967Cr0HyxXvVDxNRfq1YTjNzApm8d99zXhOzDqd13/SevCsEm1LpFMwV0
+9Qcke+13PaVz1ei5xmmmz/JZ02QRqsnjPCpCnqme59fH1FXsprF+jq6ziMu03F8t
+InrKVWA0NK1KwrvIxR4dt+o911oLt1p4q0/3iC+Pv0f4eDBoe2NV+AqoQefu+2cn
+ujyP7A8bNwMfmy/7oVhiO3f7RgYVAo73DoqEaA03HQny7JyOSqSvYiTJHM4P4Pxc
+YsoP+W/sbwUwg1E56GsXm9dihFeMLzmW8iIjrIEzELg0zExT1kqYte867kb1Wiwp
+9rBqaIL104QRiuvQA11eBaeOcVZBX9CKROyJAlIEEAECADwFAkzSzag1HFRob21h
+cyBEdWR6aWFrIChDT0RFIFNJR05JTkcgS0VZKSA8dG9tZHpAYXBhY2hlLm9yZz4A
+CgkQ6k3K3E3KqI/YHBAAkRDodI1TYpBFWv/o7kTgoIsB8X0ZYURbV0jZODGi8/FY
+i9qBipEYdW0S7UNlvUHOU6XyiA749DQ/XxjVwd5VIa2RXlYupWlnV1LjEGhsPmRn
+fZfs8V/9d1YrOUGoMQMedYI65e/3UX/agNan0IsYGbNwNajYZD1brj9qViT9Tvkx
+znIEnGnTN52uyuB+qe/5VuU0jGvXt/0LJRszkwP+hfBZWzRwDmeBQTZ4zcPbvyHT
+Yilz7f9LoXXxm0cMHaeTsagfMd3vHnEWgRx5bYXC9ikwQbDsQQjgFBT7y0yV3vFr
+ECo3WgXwmRGkojKr5vsrdrvnXL5DcYEOgfaoQCs+p1Kb1Ip/nd8yfxRlK/ZRM0Jq
+ut/vY3wWqNUUXcxooFhLqs7c26gXIkT82Rh1usJdXUIdmckV5xWDxmup0ZfvYxDo
+sLfKC9udEwMMKWQEEnEbSoU7UvL1Ga5EaIMhweflXuQCSua0TuNf9TiJP00osKqS
+yvyquGqj9LFAO4fRhPglQUV9Wmrtak4HQg8f+94BthSWwQz7jAJog5VAHHRmJI/W
+uXJDfs1yghLyoKTapM7euD1+3lnxsIHmq8N+CRqMopmrboulKOxtqjvHbHlBieOB
+F+e+3eN69aMMyBNWTYoX5qx3ufpGS8tn+2BI95Cb97McTpkX64MfalapZIx8fRKJ
+Al0EEAECAEcFAkzS0a5AHEpvc2VwaCBFZHdhcmQgQmVyZ21hcmsgKENPREUgU0lH
+TklORyBLRVkpIDxiZXJnbWFya0BhcGFjaGUub3JnPgAKCRCw2ZG0fECHNyOKD/wK
+XQL9UBrAjn0aySM8JXLRUWVOyAMGfObLVd2zxcKr+34ziW7KmcmzNEqI4vTCNURi
+QW0fMngRoC4YMQnb61c1j48lApsRSlOsJAA82PJNUFI5sqw3q3rgTy5PoJK/wVNl
+P00b3UaJ7zkuIxnttRvbFweFylBvblcJjmSdlhHkQVgWNI+pf65WMWokUs8JX24F
+Dx5LmKRBC09h3Ft5x40H6SGLkvcPtTtg09qbWD7gT61pX45T7ZsanD5TKivJ3+G6
+yP2+rrbtjGThBf2fDcGff++kYYSHVUstz/+XtywcPqHFJnnkLz6jgZDk13+6yhRT
+c/6YzfxNVKKIKj3KscXkcfuwpGhF7oh3qu4okNQRsyEkLdtxfgNMCVJOV9lWTi7u
+tHAaw9Zy7k0wmHH5WrYgc/+eJYdNfqL7AWDtUJD1Bf86hYaeqDljxNBKwT3/uLsp
+Ph8CTR7DkLU0twAr+POjct6QdzOlwv4bCh3EII4e9u5lqLlmlvvp/X7z8GzeDwxK
+2oDyUSIoN+FMPuS8ESVhMV+ST409io7p9On/MpgQ4F471UsYuzpLXfHBl4l32Ra3
+haNIfW477XgRHz3tfLUvFQWQ9PH7OFJV4rXDFyiLPQsJgW7TTgz0zJt4N2x/2awe
+nMOp3T3IXnIpO80ENO4VcPmVSjL9aXkMiS6YicZG1YkCXQQQAQIARwUCTNLXrEAc
+Sm9zZXBoIEVkd2FyZCBCZXJnbWFyayAoQ09ERSBTSUdOSU5HIEtFWSkgPGJlcmdt
+YXJrQGFwYWNoZS5vcmc+AAoJELDZkbR8QIc3jXIP/1R4765cKL59YnFpQuJNwrJm
+chg6oh4E+C60cWes955cYCitUPzRkHeHCSmC55TcFWwhGtzQH8Ql4XleUJk7AXh8
+yMv3KSxG7Ic5/w4MTXAaY53r1q/ImTHnlSmF0+iMq4LF2BfLGW1lWXz+5D1Fm2fq
+lyYJ7BfDRwhbQZiQAxeddf+Ydx8d8Zk1CVqQOkhTFWpN9O+oxiEyxfo5SNOZzcCK
+i+cR0E7AB9qet/OTjecnKy4vFqG4adc6RNPU7d+20GOiJAZMOChdgl6knrX3b11i
+lu41ri1Y2PqHyjBJnm4zSVw8x8HxbpHt5TB/TIy7YwVnZs13mitxRoa5CwuMeyDC
+z/8Ob5K3fTF7/0UAsG7c/dhZBA6ENzjHYSxhlYDDL0mTUG+b8C6ZypLHeS00rky8
+g8mNV901vsGxoJhu/w2diFRf/o4QAbj+9nttxxtYvBKTvuHSEGFErOLV35Chixhs
+Mdr+Sl4eBWF3HxRJUsyIT/kRZzL41usXP2KH8CbkerQVaMzOb3M9RfO67VLPfb+q
+2JW+BdHMlbVsQdfWhLJoMTLHGgxhXCA7Fpd6slI3HrUvlFxd/f9DHmZuE63mvtjg
+I9GRdbDOHdkhJ5yTd1uQe3p+QWXsgq/YOIQj+oIVI5R8ZMXHzsec4vPFxok1uajM
+V/fXeqdDYutbZrQgj56piQKpBBABAgCTBQJM0uOxIBxTYW5kZXIgVGVtbWUgPHNh
+bmRlckB0ZW1tZS5uZXQ+IhxTYW5kZXIgVGVtbWUgPHNjdGVtbWVAYXBhY2hlLm9y
+Zz4dHFNhbmRlciBUZW1tZSA8c2FuZGVyQG1lLmNvbT4qHFNhbmRlciBUZW1tZSA8
+c2N0ZW1tZUBrZXlzaW50aGVjbG91ZC5jb20+AAoJEJu4Y7D1G7iKmpUQAK9EKFrh
+M2xi5GeTFv/nhzwArK2MpncvQTBGousnyJYeqW3dptSl/u0gB2CpOZdtMwvdOciB
+39B/s0+uvXOV/ZCh8JozSqsr7u2dJ2tjFw1v18ZwKDk7meZ/M/VJqUkEOC49FjU3
+6Kx3EhPdN58u/V6kR4b6G08FSTjozwpCNK70GFNZz5pY4D3Pe70BFZhShVmHmdk3
+qA0ECbFs0bSk5cF5utQriqvWiAWsO/6dU9NiaB9aZoQsEc+nKwl0gCgzvNhmYGKz
+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
+aGUub3JnPiAcSmltIEphZ2llbHNraSA8amltQGphZ3VORVQuY29tPh8cSmltIEph
+Z2llbHNraSA8amltQGppbWphZy5jb20+AAoJEDTqduZ5FIWoM3wP/3wzn9q+EeDZ
+kcTAWJLIpUBnYLa3pxCxodFl7D7/aIL2rvu57ifpyFRvW+0uPq2nOy4pvS/teUql
+p8AEncPslTlCO6+lRx2kGOpmIdz7Y3ZLl5E9OIwgSpxAVyy1Gq4hkzyGBCRFh+BV
+MzkYOp7f/b0Ac12xtAxuCew8w5hRikvZ0AwKsSkW60VbYfSLpoSIeJLsAyWzdTx3
+SQGBx0cg/fH+bk8czL1O6+VZhtNPaABuCQlQTFvXp49P6rvZi5jZuCMcr5m7gt2Y
+iV8EFJ9qgyaG7/QbQ57ycGAjbRSWB7bvJaSWcpNsMT/raXL58uQ9/YkEQxtFqcpr
+Q2XGbfCOUvOj64cVVxHpt+yDR4l1bDAyxnXzaX3XswKryJGFZlXyuIGIQy49aJGu
+K7onq4MFtx/AWpEraXhqnbadrTTDRkF7qCm8o6itVhTVVCjd7O3n464KIdl0fshY
+waKeKTS5LW46Pi1xnNHuLk+3sUa6UpRy1LhGtq2XnirZ7Z0+Ayr98uHmyeX9+FXo
+AQG+HuF7Hel4LQz2F/x1BrHko4+9jjze6QFLWwUztqtTRRur1D2QUi4lfWczrtZT
+2FhFdIPnCCj5i5iLEZxEz9+Or1sdsKuRCFoe093032JGJ7A978gKT5BvTKjIKLUf
+hSCpVhYJTzRL9FdnGYHIFXsb8OUQ/EzLiMoEEBECAIoFAkzStEMfHEppbSBKYWdp
+ZWxza2kgPGppbUBhcGFjaGUub3JnPiAcSmltIEphZ2llbHNraSA8amltQGphZ3VO
+RVQuY29tPh8cSmltIEphZ2llbHNraSA8amltQGppbWphZy5jb20+IhxKaW0gSmFn
+aWVsc2tpIDxqaW1qQGNvdmFsZW50Lm5ldD4ACgkQizpgHwjJdeXL7QCgkrVbQo54
+WUHnHKoBrcCapxl22vgAnRNaLFpJyp7xQr7qzm8qejj0aMroiQIcBBABCgAGBQJM
+0rXJAAoJEIJ4HeRtWVT6q4kP/2bGNVdu/6oI+aERmV/m8oaQK6BCwJXsAN4dKXRx
+bevuFEyic/HZqztb+7Eq7DRAusx99uJVwWYEUP9lgP9G2ZctNXP1Y1YloIZY+Aoy
+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
+xCCOHvbuZai5Zpb76f1+8/Bs3g8MStqA8lEiKDfhTD7kvBElYTFfkk+NPYqO6fTp
+/zKYEOBeO9VLGLs6S13xwZeJd9kWt4WjSH1uO+14ER897Xy1LxUFkPTx+zhSVeK1
+wxcoiz0LCYFu004M9MybeDdsf9msHpzDqd09yF5yKTvNBDTuFXD5lUoy/Wl5DIku
+mInGRtWJAl0EEAECAEcFAkzS16xAHEpvc2VwaCBFZHdhcmQgQmVyZ21hcmsgKENP
+REUgU0lHTklORyBLRVkpIDxiZXJnbWFya0BhcGFjaGUub3JnPgAKCRCw2ZG0fECH
+N41yD/9UeO+uXCi+fWJxaULiTcKyZnIYOqIeBPgutHFnrPeeXGAorVD80ZB3hwkp
+gueU3BVsIRrc0B/EJeF5XlCZOwF4fMjL9yksRuyHOf8ODE1wGmOd69avyJkx55Up
+hdPojKuCxdgXyxltZVl8/uQ9RZtn6pcmCewXw0cIW0GYkAMXnXX/mHcfHfGZNQla
+kDpIUxVqTfTvqMYhMsX6OUjTmc3AiovnEdBOwAfanrfzk43nJysuLxahuGnXOkTT
+1O3fttBjoiQGTDgoXYJepJ61929dYpbuNa4tWNj6h8owSZ5uM0lcPMfB8W6R7eUw
+f0yMu2MFZ2bNd5orcUaGuQsLjHsgws//Dm+St30xe/9FALBu3P3YWQQOhDc4x2Es
+YZWAwy9Jk1Bvm/AumcqSx3ktNK5MvIPJjVfdNb7BsaCYbv8NnYhUX/6OEAG4/vZ7
+bccbWLwSk77h0hBhRKzi1d+QoYsYbDHa/kpeHgVhdx8USVLMiE/5EWcy+NbrFz9i
+h/Am5Hq0FWjMzm9zPUXzuu1Sz32/qtiVvgXRzJW1bEHX1oSyaDEyxxoMYVwgOxaX
+erJSNx61L5RcXf3/Qx5mbhOt5r7Y4CPRkXWwzh3ZISeck3dbkHt6fkFl7IKv2DiE
+I/qCFSOUfGTFx87HnOLzxcaJNbmozFf313qnQ2LrW2a0II+eqYkCqQQQAQIAkwUC
+TNLjsSAcU2FuZGVyIFRlbW1lIDxzYW5kZXJAdGVtbWUubmV0PiIcU2FuZGVyIFRl
+bW1lIDxzY3RlbW1lQGFwYWNoZS5vcmc+HRxTYW5kZXIgVGVtbWUgPHNhbmRlckBt
+ZS5jb20+KhxTYW5kZXIgVGVtbWUgPHNjdGVtbWVAa2V5c2ludGhlY2xvdWQuY29t
+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
+1Rd/N9uAngaAuoqGAThmyfc=
+=G1yK
+-----END PGP PUBLIC KEY BLOCK-----
+
+
pub 9B1FDA9F3C062231
sub 458AF764D812A037
-----BEGIN PGP PUBLIC KEY BLOCK-----
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 948919c..e28fe80 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -30,6 +30,7 @@
<trusted-keys>
<trusted-key id="00089ee8c3afa95a854d0f1df800dd0933ecf7f7" group="com.google.guava" name="guava"/>
<trusted-key id="012579464d01c06a" group="org.apache"/>
+ <trusted-key id="019082bc00e0324e2aef4cf00d3b328562a119a7" group="org.openjdk.jmh"/>
<trusted-key id="02216ed811210daa" group="io.github.detekt.sarif4k"/>
<trusted-key id="0315bfb7970a144f">
<trusting group="com.sun.istack"/>
@@ -65,6 +66,7 @@
<trusted-key id="1188b69f6d6259ca" group="com.google.accompanist"/>
<trusted-key id="11b581967f079a30a3e93140d57506cd188fd842" group="com.google.api.grpc"/>
<trusted-key id="120d6f34e627ed3a772ebbfe55c7e5e701832382" group="org.snakeyaml"/>
+ <trusted-key id="12d16069219c90212a974d119ae296fd02e9f65b" group="org.apache.commons" name="commons-math3"/>
<trusted-key id="147b691a19097624902f4ea9689cbe64f4bc997f" group="^org[.]mockito($|([.].*))" regex="true"/>
<trusted-key id="151ba00a46886a5f95441a0f5d67bffcba1f9a39" group="com.google.gradle" name="osdetector-gradle-plugin"/>
<trusted-key id="160a7a9cf46221a56b06ad64461a804f2609fd89" group="com.github.shyiko.klob" name="klob"/>
@@ -221,6 +223,7 @@
<trusted-key id="635ee627345f3c1dd422b2e207d3516820bcf6b1" group="com.github.ben-manes.caffeine"/>
<trusted-key id="6525fd70cc303655" group="org.codehaus.mojo"/>
<trusted-key id="666a4692ce11b7b3f4eb7b3410066a9707090cf9" group="org.javassist" name="javassist"/>
+ <trusted-key id="682f765eea718d250bbdb2f1685c46769dbb5e5d" group="com.squareup" name="kotlinpoet"/>
<trusted-key id="694621a7227d8d5289699830abe9f3126bb741c1">
<trusting group="com.google.guava"/>
<trusting group="com.google.jimfs"/>
@@ -705,6 +708,19 @@
<sha256 value="4df94aaeee8d900be431386e31ef44e82a66e57c3ae30866aec2875aff01fe70" origin="Generated by Gradle"/>
</artifact>
</component>
+ <component group="org.jetbrains.kotlinx" name="kotlinx-benchmark-plugin" version="0.4.4" androidx:reason="https://youtrack.jetbrains.com/issue/KT-53461">
+ <artifact name="kotlinx-benchmark-plugin-0.4.4.jar">
+ <sha256 value="b818d2b6c62cb6f46edbd098c4a6a71cec6f277f8fc3c51bca60d6f538693512" origin="Generated by Gradle"/>
+ </artifact>
+ <artifact name="kotlinx-benchmark-plugin-0.4.4.pom">
+ <sha256 value="57f0bbbf1d4d182e79f400d7cea1ebdd48bd1b4a092411756ccac054d1e07d5c" origin="Generated by Gradle"/>
+ </artifact>
+ </component>
+ <component group="org.jetbrains.kotlinx.benchmark" name="org.jetbrains.kotlinx.benchmark.gradle.plugin" version="0.4.4" androidx:reason="https://youtrack.jetbrains.com/issue/KT-53461">
+ <artifact name="org.jetbrains.kotlinx.benchmark.gradle.plugin-0.4.4.pom">
+ <sha256 value="72b338deead79e32f508da7ca3a8ac46a3ce3c76ce468e6b1b313ef4ddfb5cbd" origin="Generated by Gradle"/>
+ </artifact>
+ </component>
<component group="org.ow2" name="ow2" version="1.5" androidx:reason="https://gitlab.ow2.org/asm/asm/-/merge_requests/354">
<artifact name="ow2-1.5.pom">
<sha256 value="0f8a1b116e760b8fe6389c51b84e4b07a70fc11082d4f936e453b583dd50b43b" origin="Generated by Gradle"/>
diff --git a/javascriptengine/javascriptengine/api/current.txt b/javascriptengine/javascriptengine/api/current.txt
index 782c21f..e808cea 100644
--- a/javascriptengine/javascriptengine/api/current.txt
+++ b/javascriptengine/javascriptengine/api/current.txt
@@ -1,37 +1,46 @@
// Signature format: 4.0
package androidx.javascriptengine {
- public class EvaluationFailedException extends androidx.javascriptengine.JsException {
+ public final class EvaluationFailedException extends androidx.javascriptengine.JavaScriptException {
ctor public EvaluationFailedException(String);
}
- public class IsolateTerminatedException extends androidx.javascriptengine.JsException {
+ public final class IsolateStartupParameters {
+ ctor public IsolateStartupParameters();
+ method @IntRange(from=0) public long getMaxHeapSizeBytes();
+ method @RequiresFeature(name=androidx.javascriptengine.JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE, enforcement="androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported") public void setMaxHeapSizeBytes(@IntRange(from=0) long);
+ field public static final long DEFAULT_ISOLATE_HEAP_SIZE = 0L; // 0x0L
+ }
+
+ public final class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
ctor public IsolateTerminatedException();
}
- public class JsException extends java.lang.Exception {
- ctor public JsException(String);
- ctor public JsException();
+ public class JavaScriptException extends java.lang.Exception {
+ ctor public JavaScriptException(String);
+ ctor public JavaScriptException();
}
- public class JsIsolate implements java.lang.AutoCloseable {
+ public final class JavaScriptIsolate implements java.lang.AutoCloseable {
method public void close();
- method public com.google.common.util.concurrent.ListenableFuture<java.lang.String!> evaluateJavaScriptAsync(String);
- method @RequiresFeature(name=androidx.javascriptengine.JsSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER, enforcement="androidx.javascriptengine.JsSandbox#isFeatureSupported") public boolean provideNamedData(String, byte[]);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.String!> evaluateJavaScriptAsync(@org.intellij.lang.annotations.Language("javascript") String);
+ method @RequiresFeature(name=androidx.javascriptengine.JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER, enforcement="androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported") public boolean provideNamedData(String, byte[]);
}
- public class JsSandbox implements java.lang.AutoCloseable {
+ public final class JavaScriptSandbox implements java.lang.AutoCloseable {
method public void close();
- method public static com.google.common.util.concurrent.ListenableFuture<androidx.javascriptengine.JsSandbox!> createConnectedInstanceAsync(android.content.Context);
- method public androidx.javascriptengine.JsIsolate createIsolate();
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.javascriptengine.JavaScriptSandbox!> createConnectedInstanceAsync(android.content.Context);
+ method public androidx.javascriptengine.JavaScriptIsolate createIsolate();
+ method public androidx.javascriptengine.JavaScriptIsolate createIsolate(androidx.javascriptengine.IsolateStartupParameters);
method public boolean isFeatureSupported(String);
+ field public static final String JS_FEATURE_ISOLATE_MAX_HEAP_SIZE = "JS_FEATURE_ISOLATE_MAX_HEAP_SIZE";
field public static final String JS_FEATURE_ISOLATE_TERMINATION = "JS_FEATURE_ISOLATE_TERMINATION";
field public static final String JS_FEATURE_PROMISE_RETURN = "JS_FEATURE_PROMISE_RETURN";
field public static final String JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER = "JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER";
field public static final String JS_FEATURE_WASM_COMPILATION = "JS_FEATURE_WASM_COMPILATION";
}
- public class SandboxDeadException extends androidx.javascriptengine.JsException {
+ public final class SandboxDeadException extends androidx.javascriptengine.JavaScriptException {
ctor public SandboxDeadException();
}
diff --git a/javascriptengine/javascriptengine/api/public_plus_experimental_current.txt b/javascriptengine/javascriptengine/api/public_plus_experimental_current.txt
index 782c21f..e808cea 100644
--- a/javascriptengine/javascriptengine/api/public_plus_experimental_current.txt
+++ b/javascriptengine/javascriptengine/api/public_plus_experimental_current.txt
@@ -1,37 +1,46 @@
// Signature format: 4.0
package androidx.javascriptengine {
- public class EvaluationFailedException extends androidx.javascriptengine.JsException {
+ public final class EvaluationFailedException extends androidx.javascriptengine.JavaScriptException {
ctor public EvaluationFailedException(String);
}
- public class IsolateTerminatedException extends androidx.javascriptengine.JsException {
+ public final class IsolateStartupParameters {
+ ctor public IsolateStartupParameters();
+ method @IntRange(from=0) public long getMaxHeapSizeBytes();
+ method @RequiresFeature(name=androidx.javascriptengine.JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE, enforcement="androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported") public void setMaxHeapSizeBytes(@IntRange(from=0) long);
+ field public static final long DEFAULT_ISOLATE_HEAP_SIZE = 0L; // 0x0L
+ }
+
+ public final class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
ctor public IsolateTerminatedException();
}
- public class JsException extends java.lang.Exception {
- ctor public JsException(String);
- ctor public JsException();
+ public class JavaScriptException extends java.lang.Exception {
+ ctor public JavaScriptException(String);
+ ctor public JavaScriptException();
}
- public class JsIsolate implements java.lang.AutoCloseable {
+ public final class JavaScriptIsolate implements java.lang.AutoCloseable {
method public void close();
- method public com.google.common.util.concurrent.ListenableFuture<java.lang.String!> evaluateJavaScriptAsync(String);
- method @RequiresFeature(name=androidx.javascriptengine.JsSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER, enforcement="androidx.javascriptengine.JsSandbox#isFeatureSupported") public boolean provideNamedData(String, byte[]);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.String!> evaluateJavaScriptAsync(@org.intellij.lang.annotations.Language("javascript") String);
+ method @RequiresFeature(name=androidx.javascriptengine.JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER, enforcement="androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported") public boolean provideNamedData(String, byte[]);
}
- public class JsSandbox implements java.lang.AutoCloseable {
+ public final class JavaScriptSandbox implements java.lang.AutoCloseable {
method public void close();
- method public static com.google.common.util.concurrent.ListenableFuture<androidx.javascriptengine.JsSandbox!> createConnectedInstanceAsync(android.content.Context);
- method public androidx.javascriptengine.JsIsolate createIsolate();
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.javascriptengine.JavaScriptSandbox!> createConnectedInstanceAsync(android.content.Context);
+ method public androidx.javascriptengine.JavaScriptIsolate createIsolate();
+ method public androidx.javascriptengine.JavaScriptIsolate createIsolate(androidx.javascriptengine.IsolateStartupParameters);
method public boolean isFeatureSupported(String);
+ field public static final String JS_FEATURE_ISOLATE_MAX_HEAP_SIZE = "JS_FEATURE_ISOLATE_MAX_HEAP_SIZE";
field public static final String JS_FEATURE_ISOLATE_TERMINATION = "JS_FEATURE_ISOLATE_TERMINATION";
field public static final String JS_FEATURE_PROMISE_RETURN = "JS_FEATURE_PROMISE_RETURN";
field public static final String JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER = "JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER";
field public static final String JS_FEATURE_WASM_COMPILATION = "JS_FEATURE_WASM_COMPILATION";
}
- public class SandboxDeadException extends androidx.javascriptengine.JsException {
+ public final class SandboxDeadException extends androidx.javascriptengine.JavaScriptException {
ctor public SandboxDeadException();
}
diff --git a/javascriptengine/javascriptengine/api/restricted_current.txt b/javascriptengine/javascriptengine/api/restricted_current.txt
index 782c21f..e808cea 100644
--- a/javascriptengine/javascriptengine/api/restricted_current.txt
+++ b/javascriptengine/javascriptengine/api/restricted_current.txt
@@ -1,37 +1,46 @@
// Signature format: 4.0
package androidx.javascriptengine {
- public class EvaluationFailedException extends androidx.javascriptengine.JsException {
+ public final class EvaluationFailedException extends androidx.javascriptengine.JavaScriptException {
ctor public EvaluationFailedException(String);
}
- public class IsolateTerminatedException extends androidx.javascriptengine.JsException {
+ public final class IsolateStartupParameters {
+ ctor public IsolateStartupParameters();
+ method @IntRange(from=0) public long getMaxHeapSizeBytes();
+ method @RequiresFeature(name=androidx.javascriptengine.JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE, enforcement="androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported") public void setMaxHeapSizeBytes(@IntRange(from=0) long);
+ field public static final long DEFAULT_ISOLATE_HEAP_SIZE = 0L; // 0x0L
+ }
+
+ public final class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
ctor public IsolateTerminatedException();
}
- public class JsException extends java.lang.Exception {
- ctor public JsException(String);
- ctor public JsException();
+ public class JavaScriptException extends java.lang.Exception {
+ ctor public JavaScriptException(String);
+ ctor public JavaScriptException();
}
- public class JsIsolate implements java.lang.AutoCloseable {
+ public final class JavaScriptIsolate implements java.lang.AutoCloseable {
method public void close();
- method public com.google.common.util.concurrent.ListenableFuture<java.lang.String!> evaluateJavaScriptAsync(String);
- method @RequiresFeature(name=androidx.javascriptengine.JsSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER, enforcement="androidx.javascriptengine.JsSandbox#isFeatureSupported") public boolean provideNamedData(String, byte[]);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.String!> evaluateJavaScriptAsync(@org.intellij.lang.annotations.Language("javascript") String);
+ method @RequiresFeature(name=androidx.javascriptengine.JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER, enforcement="androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported") public boolean provideNamedData(String, byte[]);
}
- public class JsSandbox implements java.lang.AutoCloseable {
+ public final class JavaScriptSandbox implements java.lang.AutoCloseable {
method public void close();
- method public static com.google.common.util.concurrent.ListenableFuture<androidx.javascriptengine.JsSandbox!> createConnectedInstanceAsync(android.content.Context);
- method public androidx.javascriptengine.JsIsolate createIsolate();
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.javascriptengine.JavaScriptSandbox!> createConnectedInstanceAsync(android.content.Context);
+ method public androidx.javascriptengine.JavaScriptIsolate createIsolate();
+ method public androidx.javascriptengine.JavaScriptIsolate createIsolate(androidx.javascriptengine.IsolateStartupParameters);
method public boolean isFeatureSupported(String);
+ field public static final String JS_FEATURE_ISOLATE_MAX_HEAP_SIZE = "JS_FEATURE_ISOLATE_MAX_HEAP_SIZE";
field public static final String JS_FEATURE_ISOLATE_TERMINATION = "JS_FEATURE_ISOLATE_TERMINATION";
field public static final String JS_FEATURE_PROMISE_RETURN = "JS_FEATURE_PROMISE_RETURN";
field public static final String JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER = "JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER";
field public static final String JS_FEATURE_WASM_COMPILATION = "JS_FEATURE_WASM_COMPILATION";
}
- public class SandboxDeadException extends androidx.javascriptengine.JsException {
+ public final class SandboxDeadException extends androidx.javascriptengine.JavaScriptException {
ctor public SandboxDeadException();
}
diff --git a/javascriptengine/javascriptengine/build.gradle b/javascriptengine/javascriptengine/build.gradle
index ed63ae3..eae553f 100644
--- a/javascriptengine/javascriptengine/build.gradle
+++ b/javascriptengine/javascriptengine/build.gradle
@@ -25,6 +25,7 @@
api("androidx.annotation:annotation:1.3.0")
api("androidx.concurrent:concurrent-futures:1.0.0")
api("androidx.core:core:1.1.0")
+ implementation("org.jetbrains:annotations:13.0")
implementation(libs.guavaAndroid)
androidTestImplementation 'junit:junit:4.12'
annotationProcessor(libs.nullaway)
diff --git a/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJsSandboxTest.java b/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
similarity index 65%
rename from javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJsSandboxTest.java
rename to javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
index 195bd05..491edec 100644
--- a/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJsSandboxTest.java
+++ b/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
@@ -37,12 +37,12 @@
/** Instrumentation test for JsSandboxService. */
@RunWith(AndroidJUnit4.class)
-public class WebViewJsSandboxTest {
+public class WebViewJavaScriptSandboxTest {
private boolean canCreateJsSandbox() throws Throwable {
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox;
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox;
try {
jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
} catch (ExecutionException e) {
@@ -66,10 +66,10 @@
final String expected = "PASS";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox.createIsolate();
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);
jsIsolate.close();
@@ -85,11 +85,11 @@
final String expected = "PASS";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate1 = jsSandbox.createIsolate();
- JsIsolate jsIsolate2 = jsSandbox.createIsolate();
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate();
+ JavaScriptIsolate jsIsolate2 = jsSandbox.createIsolate();
jsIsolate1.close();
ListenableFuture<String> resultFuture = jsIsolate2.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);
@@ -109,13 +109,13 @@
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate1 = jsSandbox.createIsolate();
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate();
ListenableFuture<String> resultFuture1 = jsIsolate1.evaluateJavaScriptAsync(code1);
String result1 = resultFuture1.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate2 = jsSandbox.createIsolate();
+ JavaScriptIsolate jsIsolate2 = jsSandbox.createIsolate();
ListenableFuture<String> resultFuture2 = jsIsolate2.evaluateJavaScriptAsync(code2);
String result2 = resultFuture2.get(5, TimeUnit.SECONDS);
jsIsolate1.close();
@@ -135,13 +135,13 @@
final String expected2 = "undefined PASS";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate1 = jsSandbox.createIsolate();
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate();
ListenableFuture<String> resultFuture1 = jsIsolate1.evaluateJavaScriptAsync(code1);
String result1 = resultFuture1.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate2 = jsSandbox.createIsolate();
+ JavaScriptIsolate jsIsolate2 = jsSandbox.createIsolate();
ListenableFuture<String> resultFuture2 = jsIsolate2.evaluateJavaScriptAsync(code2);
String result2 = resultFuture2.get(5, TimeUnit.SECONDS);
jsIsolate1.close();
@@ -161,10 +161,10 @@
final String expected2 = "PASS PASS";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate1 = jsSandbox.createIsolate();
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate();
ListenableFuture<String> resultFuture1 = jsIsolate1.evaluateJavaScriptAsync(code1);
String result1 = resultFuture1.get(5, TimeUnit.SECONDS);
ListenableFuture<String> resultFuture2 = jsIsolate1.evaluateJavaScriptAsync(code2);
@@ -183,10 +183,10 @@
final String contains = "RandomLinkError";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox.createIsolate();
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
boolean isOfCorrectType = false;
String error = "";
@@ -209,12 +209,13 @@
final String code = "while(true){}";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.JS_FEATURE_ISOLATE_TERMINATION));
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_TERMINATION));
- JsIsolate jsIsolate = jsSandbox.createIsolate();
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
boolean isOfCorrectType = false;
try {
@@ -235,12 +236,13 @@
final int num_of_evaluations = 10;
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.JS_FEATURE_ISOLATE_TERMINATION));
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_TERMINATION));
- JsIsolate jsIsolate = jsSandbox.createIsolate();
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
Vector<ListenableFuture<String>> resultFutures = new Vector<ListenableFuture<String>>();
for (int i = 0; i < num_of_evaluations; i++) {
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
@@ -273,14 +275,15 @@
+ " return ab2str(value);"
+ "});";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox.createIsolate()) {
- Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.JS_FEATURE_PROMISE_RETURN));
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
Assume.assumeTrue(
jsSandbox.isFeatureSupported(
- JsSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
+ JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-1", bytes);
Assert.assertTrue(provideNamedDataReturn);
@@ -304,15 +307,17 @@
+ " });"
+ "});";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox.createIsolate()) {
- Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.JS_FEATURE_PROMISE_RETURN));
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
Assume.assumeTrue(
jsSandbox.isFeatureSupported(
- JsSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
- Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.JS_FEATURE_WASM_COMPILATION));
+ JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_WASM_COMPILATION));
boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-1", bytes);
Assert.assertTrue(provideNamedDataReturn);
@@ -329,11 +334,12 @@
final String code = "Promise.resolve(\"PASS\")";
final String expected = "PASS";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox.createIsolate()) {
- Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.JS_FEATURE_PROMISE_RETURN));
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);
@@ -354,11 +360,12 @@
final String expected = "PASS";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox.createIsolate()) {
- Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.JS_FEATURE_PROMISE_RETURN));
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavaScriptAsync(code1);
ListenableFuture<String> resultFuture2 = jsIsolate.evaluateJavaScriptAsync(code2);
@@ -389,13 +396,14 @@
+ " });"
+ "});";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox.createIsolate()) {
- Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.JS_FEATURE_PROMISE_RETURN));
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
Assume.assumeTrue(jsSandbox.isFeatureSupported(
- JsSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
+ JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
jsIsolate.provideNamedData("id-1", bytes);
jsIsolate.provideNamedData("id-2", bytes);
@@ -422,13 +430,14 @@
final String contains = "RandomLinkError";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox.createIsolate()) {
- Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.JS_FEATURE_PROMISE_RETURN));
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
Assume.assumeTrue(jsSandbox.isFeatureSupported(
- JsSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
+ JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
try {
@@ -449,10 +458,10 @@
final String code = "while(true){}";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox.createIsolate();
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
try {
jsSandbox.close();
@@ -470,13 +479,13 @@
public void testMultipleSandboxesCannotCoexist() throws Throwable {
Context context = ApplicationProvider.getApplicationContext();
final String contains = "already bound";
- ListenableFuture<JsSandbox> JsSandboxFuture1 =
- JsSandbox.createConnectedInstanceAsync(context);
- try (JsSandbox jsSandbox1 = JsSandboxFuture1.get(5, TimeUnit.SECONDS)) {
- ListenableFuture<JsSandbox> JsSandboxFuture2 =
- JsSandbox.createConnectedInstanceAsync(context);
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture1 =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox1 = JsSandboxFuture1.get(5, TimeUnit.SECONDS)) {
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture2 =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
try {
- JsSandbox jsSandbox2 = JsSandboxFuture2.get(5, TimeUnit.SECONDS);
+ JavaScriptSandbox jsSandbox2 = JsSandboxFuture2.get(5, TimeUnit.SECONDS);
Assert.fail("Should have thrown.");
} catch (ExecutionException e) {
if (!(e.getCause() instanceof RuntimeException)) {
@@ -494,18 +503,46 @@
final String expected = "PASS";
Context context = ApplicationProvider.getApplicationContext();
- ListenableFuture<JsSandbox> JsSandboxFuture1 =
- JsSandbox.createConnectedInstanceAsync(context);
- JsSandbox jsSandbox1 = JsSandboxFuture1.get(5, TimeUnit.SECONDS);
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture1 =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ JavaScriptSandbox jsSandbox1 = JsSandboxFuture1.get(5, TimeUnit.SECONDS);
jsSandbox1.close();
- ListenableFuture<JsSandbox> JsSandboxFuture2 =
- JsSandbox.createConnectedInstanceAsync(context);
- try (JsSandbox jsSandbox2 = JsSandboxFuture2.get(5, TimeUnit.SECONDS);
- JsIsolate jsIsolate = jsSandbox2.createIsolate()) {
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture2 =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox2 = JsSandboxFuture2.get(5, TimeUnit.SECONDS);
+ JavaScriptIsolate jsIsolate = jsSandbox2.createIsolate()) {
ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture1.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
}
+
+ @Test
+ @MediumTest
+ public void testHeapSize() throws Throwable {
+ final String code = "const buffer = new ArrayBuffer(500000000);"
+ + "\"PASS\"";
+ Context context = ApplicationProvider.getApplicationContext();
+ ListenableFuture<JavaScriptSandbox> JsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(
+ JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+ IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
+ isolateStartupParameters.setMaxHeapSizeBytes(10000);
+ try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+ ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
+ try {
+ resultFuture.get(5, TimeUnit.SECONDS);
+ Assert.fail("Should have thrown.");
+ } catch (ExecutionException e) {
+ if (!(e.getCause() instanceof SandboxDeadException)) {
+ throw e;
+ }
+ }
+ }
+ }
+ }
}
diff --git a/javascriptengine/javascriptengine/src/main/aidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl b/javascriptengine/javascriptengine/src/main/aidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
index 55f34a8..157b4cc 100644
--- a/javascriptengine/javascriptengine/src/main/aidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
+++ b/javascriptengine/javascriptengine/src/main/aidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
@@ -42,8 +42,12 @@
*/
const String WASM_FROM_ARRAY_BUFFER = "WASM_FROM_ARRAY_BUFFER";
+ const String ISOLATE_MAX_HEAP_SIZE_LIMIT = "ISOLATE_MAX_HEAP_SIZE_LIMIT";
+
/**
* @return A list of feature names supported by this implementation.
*/
List<String> getSupportedFeatures() = 1;
+
+ IJsSandboxIsolate createIsolateWithMaxHeapSizeBytes(long maxHeapSize) = 2;
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EvaluationFailedException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EvaluationFailedException.java
index 179cac9..b42738c 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EvaluationFailedException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EvaluationFailedException.java
@@ -19,7 +19,7 @@
import androidx.annotation.NonNull;
/** Wrapper for the exception thrown by the JS evaluation engine. */
-public class EvaluationFailedException extends JsException {
+public final class EvaluationFailedException extends JavaScriptException {
public EvaluationFailedException(@NonNull String error) {
super(error);
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java
new file mode 100644
index 0000000..5e13f74
--- /dev/null
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 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 androidx.javascriptengine;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.RequiresFeature;
+
+/**
+ * Class used to set startup parameters for {@link JavaScriptIsolate}.
+ */
+public final class IsolateStartupParameters {
+ private long mMaxHeapSizeBytes;
+ public static final long DEFAULT_ISOLATE_HEAP_SIZE = 0;
+ public IsolateStartupParameters(){};
+
+ /**
+ * Sets the max heap size used by the {@link JavaScriptIsolate}.
+ *
+ * A heap size of {@link IsolateStartupParameters#DEFAULT_ISOLATE_HEAP_SIZE} indicates no
+ * limit.
+ * <p>
+ * If a value higher than the device specific maximum heap size limit is supplied, this limit
+ * will be used as the maximum heap size.
+ *
+ * @param size heap size in bytes
+ */
+ @RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE,
+ enforcement =
+ "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
+ public void setMaxHeapSizeBytes(@IntRange(from = 0) long size) {
+ if (size < 0) {
+ throw new IllegalArgumentException("maxHeapSizeBytes should be >= 0");
+ }
+ mMaxHeapSizeBytes = size;
+ }
+
+ /**
+ * Gets the max heap size used by the {@link JavaScriptIsolate}.
+ *
+ * If not set using {@link IsolateStartupParameters#setMaxHeapSizeBytes(long)}, the default
+ * value is {@link IsolateStartupParameters#DEFAULT_ISOLATE_HEAP_SIZE} which indicates no heap
+ * size limit.
+ *
+ * @return heap size in bytes
+ */
+ public @IntRange(from = 0) long getMaxHeapSizeBytes() {
+ return mMaxHeapSizeBytes;
+ }
+}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java
index 8c4b514..56e6fb8 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java
@@ -17,9 +17,10 @@
package androidx.javascriptengine;
/**
- * Exception thrown when evaluation is terminated due the {@link JsIsolate} being terminated.
+ * Exception thrown when evaluation is terminated due to {@link JavaScriptIsolate} being
+ * closed. This can occur when the {@link JavaScriptIsolate#close()} is called.
*/
-public class IsolateTerminatedException extends JsException {
+public final class IsolateTerminatedException extends JavaScriptException {
public IsolateTerminatedException() {
super();
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JsException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptException.java
similarity index 74%
rename from javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JsException.java
rename to javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptException.java
index 0d3f38f..da93785 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JsException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptException.java
@@ -19,14 +19,15 @@
import androidx.annotation.NonNull;
/**
- * Super class for all exceptions resolved by {@link JsIsolate#evaluateJavaScriptAsync(String)}.
+ * Super class for all exceptions resolved by
+ * {@link JavaScriptIsolate#evaluateJavaScriptAsync(String)}.
*/
-public class JsException extends Exception {
- public JsException(@NonNull String error) {
+public class JavaScriptException extends Exception {
+ public JavaScriptException(@NonNull String error) {
super(error);
}
- public JsException() {
+ public JavaScriptException() {
super();
}
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JsIsolate.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
similarity index 81%
rename from javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JsIsolate.java
rename to javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
index 4e5fb80..3797ad2 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JsIsolate.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
@@ -30,6 +30,7 @@
import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateCallback;
+import org.intellij.lang.annotations.Language;
import java.io.Closeable;
import java.io.IOException;
@@ -43,22 +44,23 @@
import javax.annotation.concurrent.GuardedBy;
/**
- * Environment within a {@link JsSandbox} where Javascript is executed.
+ * Environment within a {@link JavaScriptSandbox} where Javascript is executed.
*
- * A single {@link JsSandbox} process can contain any number of {@link JsIsolate} instances where JS
- * can be evaluated independently and in parallel.
+ * A single {@link JavaScriptSandbox} process can contain any number of {@link JavaScriptIsolate}
+ * instances where JS can be evaluated independently and in parallel.
* <p>
* Each isolate has its own state and JS global object,
* and cannot interact with any other isolate through JS APIs. There is only a <em>moderate</em>
- * security boundary between isolates in a single {@link JsSandbox}. If the code in one {@link
- * JsIsolate} is able to compromise the security of the JS engine then it may be able to observe or
- * manipulate other isolates, since they run in the same process. For strong isolation multiple
- * {@link JsSandbox} processes should be used, but it is not supported at the moment.
+ * security boundary between isolates in a single {@link JavaScriptSandbox}. If the code in one
+ * {@link JavaScriptIsolate} is able to compromise the security of the JS engine then it may be
+ * able to observe or manipulate other isolates, since they run in the same process. For strong
+ * isolation multiple {@link JavaScriptSandbox} processes should be used, but it is not supported
+ * at the moment. Please find the feature request <a href="https://crbug.com/1349860">here</a>.
* <p>
* Each isolate object must only be used from one thread.
*/
-public class JsIsolate implements AutoCloseable {
- private static final String TAG = "JsIsolate";
+public final class JavaScriptIsolate implements AutoCloseable {
+ private static final String TAG = "JavaScriptIsolate";
private final Object mSetLock = new Object();
@Nullable
private IJsSandboxIsolate mJsIsolateStub;
@@ -69,10 +71,11 @@
@Override
public Thread newThread(Runnable r) {
- return new Thread(r, "JsIsolate Thread #" + mCount.getAndIncrement());
+ return new Thread(
+ r, "JavaScriptIsolate Thread #" + mCount.getAndIncrement());
}
});
- private final JsSandbox mJsSandbox;
+ private final JavaScriptSandbox mJsSandbox;
@Nullable
@GuardedBy("mSetLock")
@@ -100,7 +103,7 @@
}
}
- JsIsolate(IJsSandboxIsolate jsIsolateStub, JsSandbox sandbox) {
+ JavaScriptIsolate(IJsSandboxIsolate jsIsolateStub, JavaScriptSandbox sandbox) {
mJsSandbox = sandbox;
mJsIsolateStub = jsIsolateStub;
mGuard.open("close");
@@ -115,10 +118,10 @@
* <li><strong>If the JS expression returns a JS String</strong>, then the Java Future
* resolves to Java String.</li>
* <li><strong>If the JS expression returns a JS Promise</strong>,
- * and if {@link JsSandbox#isFeatureSupported(String)} for
- * {@link JsSandbox#JS_FEATURE_PROMISE_RETURN} returns {@code true}, Java Future resolves to
- * Java String once the promise resolves. If it returns {@code false}, then the Future
- * resolves to an empty string.</li>
+ * and if {@link JavaScriptSandbox#isFeatureSupported(String)} for
+ * {@link JavaScriptSandbox#JS_FEATURE_PROMISE_RETURN} returns {@code true}, Java Future
+ * resolves to Java String once the promise resolves. If it returns {@code false}, then the
+ * Future resolves to an empty string.</li>
* <li><strong>If the JS expression returns another data type</strong>, then Java Future
* resolves to empty Java String.</li>
* </ul>
@@ -134,14 +137,16 @@
* transaction limit. Refer {@link android.os.TransactionTooLargeException} for more details.
*
* @param code JavaScript code that is evaluated, it should return a JavaScript String or a
- * Promise of a String in case {@link JsSandbox#JS_FEATURE_PROMISE_RETURN} is supported
+ * Promise of a String in case {@link JavaScriptSandbox#JS_FEATURE_PROMISE_RETURN} is
+ * supported
*
* @return Future that evaluates to the result String of the evaluation or exceptions({@link
* IsolateTerminatedException}, {@link SandboxDeadException}) if there is an error
*/
@SuppressWarnings("NullAway")
@NonNull
- public ListenableFuture<String> evaluateJavaScriptAsync(@NonNull String code) {
+ public ListenableFuture<String> evaluateJavaScriptAsync(
+ @NonNull @Language("javascript") String code) {
if (mJsIsolateStub == null) {
throw new IllegalStateException(
"Calling evaluateJavascript() after closing the Isolate");
@@ -173,13 +178,13 @@
}
/**
- * Closes the {@link JsIsolate} object and renders it unusable.
+ * Closes the {@link JavaScriptIsolate} object and renders it unusable.
*
* Once closed, no more method calls should be made. Pending evaluations resolve with
* {@link IsolateTerminatedException} immediately.
* <p>
- * If {@link JsSandbox#isFeatureSupported(String)} is {@code true} for {@link
- * JsSandbox#JS_FEATURE_ISOLATE_TERMINATION}, then any pending evaluation is immediately
+ * If {@link JavaScriptSandbox#isFeatureSupported(String)} is {@code true} for {@link
+ * JavaScriptSandbox#JS_FEATURE_ISOLATE_TERMINATION}, then any pending evaluation is immediately
* terminated and memory is freed. If it is {@code false}, the isolate will not get cleaned
* up until the pending evaluations have run to completion and will consume resources until
* then.
@@ -212,13 +217,13 @@
* was used when calling this method. This is a one-time transfer and the calls should be
* paired.
* <p>
- * A single name can only be used once in a particular {@link JsIsolate}.
+ * A single name can only be used once in a particular {@link JavaScriptIsolate}.
* Clients can generate unique names for each call if they
* need to use this method multiple times. The same name should be included into the JS code.
* <p>
* This API can be used to pass a WASM module into the JS
- * environment for compilation if {@link JsSandbox#isFeatureSupported(String)} returns {@code
- * true} for {@link JsSandbox#JS_FEATURE_WASM_COMPILATION}.
+ * environment for compilation if {@link JavaScriptSandbox#isFeatureSupported(String)} returns
+ * {@code true} for {@link JavaScriptSandbox#JS_FEATURE_WASM_COMPILATION}.
* <br>
* In Java,
* <pre>
@@ -237,18 +242,19 @@
* #evaluateJavaScriptAsync(String)} and {@link #provideNamedData(String, byte[])} methods.
* <p>
* This method should only be called if
- * {@link JsSandbox#isFeatureSupported(String)}
- * returns true for {@link JsSandbox#JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER}.
+ * {@link JavaScriptSandbox#isFeatureSupported(String)}
+ * returns true for {@link JavaScriptSandbox#JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER}.
*
* @param name Identifier for the data that is passed, the same identifier should be used in the
* JavaScript environment to refer to the data
* @param inputBytes Bytes to be passed into the JavaScript environment
*
- * @return {@code true} on success, {@code false} otherwise
+ * @return {@code true} on success, {@code false} if the name has already been used before,
+ * in which case the client should use an unused name
*/
- @RequiresFeature(name = JsSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER,
+ @RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER,
enforcement =
- "androidx.javascriptengine.JsSandbox#isFeatureSupported")
+ "androidx.javascriptengine.JavaScriptSandbox#isFeatureSupported")
public boolean provideNamedData(@NonNull String name, @NonNull byte[] inputBytes) {
if (mJsIsolateStub == null) {
throw new IllegalStateException("Calling provideNamedData() after closing the Isolate");
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JsSandbox.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java
similarity index 69%
rename from javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JsSandbox.java
rename to javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java
index aa39a9d..a756777 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JsSandbox.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java
@@ -52,9 +52,9 @@
/**
* Sandbox that provides APIs for JavaScript evaluation in a restricted environment.
*
- * JsSandbox represents a connection to an isolated process. The isolated process is exclusive
- * to the calling app (i.e. it doesn't share anything with, and can't be compromised by another
- * app's isolated process).
+ * JavaScriptSandbox represents a connection to an isolated process. The isolated process is
+ * exclusive to the calling app (i.e. it doesn't share anything with, and can't be compromised by
+ * another app's isolated process).
* <p>
* Code that is run in a sandbox does not have any access to data
* belonging to the original app unless explicitly passed into it by using the methods of this
@@ -64,17 +64,18 @@
* The calling app can only have only one isolated process at a time, so only one
* instance of this object can exist at a time.
* <p>
- * It's safe to share a single {@link JsSandbox}
+ * It's safe to share a single {@link JavaScriptSandbox}
* object with multiple threads and use it from multiple threads at once.
- * For example, {@link JsSandbox} can be stored at a global location and multiple threads can create
- * their own {@link JsIsolate} objects from it but the {@link JsIsolate} object cannot be shared.
+ * For example, {@link JavaScriptSandbox} can be stored at a global location and multiple threads
+ * can create their own {@link JavaScriptIsolate} objects from it but the
+ * {@link JavaScriptIsolate} object cannot be shared.
*/
-public class JsSandbox implements AutoCloseable {
+public final class JavaScriptSandbox implements AutoCloseable {
// TODO(crbug.com/1297672): Add capability to this class to support spawning
// different processes as needed. This might require that we have a static
// variable in here that tracks the existing services we are connected to and
// connect to a different one when creating a new object.
- private static final String TAG = "JsSandbox";
+ private static final String TAG = "JavaScriptSandbox";
private static final String JS_SANDBOX_SERVICE_NAME =
"org.chromium.android_webview.js_sandbox.service.JsSandboxService0";
static AtomicBoolean sIsReadyToConnect = new AtomicBoolean(true);
@@ -89,7 +90,7 @@
@Nullable
@GuardedBy("mLock")
- private HashSet<JsIsolate> mActiveIsolateSet = new HashSet<JsIsolate>();
+ private HashSet<JavaScriptIsolate> mActiveIsolateSet = new HashSet<JavaScriptIsolate>();
/**
* @hide
@@ -110,14 +111,14 @@
* Feature for {@link #isFeatureSupported(String)}.
*
* When this
- * feature is present, {@link JsIsolate#close()} terminates the currently running JS
- * evaluation and close the isolate. If it is absent, {@link JsIsolate#close()} cannot terminate
- * any running or queued evaluations in the background,
- * so the isolate continues to consume resources until they complete.
+ * feature is present, {@link JavaScriptIsolate#close()} terminates the currently running JS
+ * evaluation and close the isolate. If it is absent, {@link JavaScriptIsolate#close()} cannot
+ * terminate any running or queued evaluations in the background, so the isolate continues to
+ * consume resources until they complete.
* <p>
- * Irrespective of this feature, calling {@link JsSandbox#close()} terminates all
- * {@link JsIsolate} objects (and the isolated process) immediately and all pending
- * {@link JsIsolate#evaluateJavaScriptAsync(String)} futures resolve with {@link
+ * Irrespective of this feature, calling {@link JavaScriptSandbox#close()} terminates all
+ * {@link JavaScriptIsolate} objects (and the isolated process) immediately and all pending
+ * {@link JavaScriptIsolate#evaluateJavaScriptAsync(String)} futures resolve with {@link
* IsolateTerminatedException}.
*/
public static final String JS_FEATURE_ISOLATE_TERMINATION = "JS_FEATURE_ISOLATE_TERMINATION";
@@ -126,14 +127,15 @@
* Feature for {@link #isFeatureSupported(String)}.
*
* When this feature is present, JS expressions may return promises. The Future returned by
- * {@link JsIsolate#evaluateJavaScriptAsync(String)} resolves to the promise's result,
+ * {@link JavaScriptIsolate#evaluateJavaScriptAsync(String)} resolves to the promise's result,
* once the promise resolves.
*/
public static final String JS_FEATURE_PROMISE_RETURN = "JS_FEATURE_PROMISE_RETURN";
/**
* Feature for {@link #isFeatureSupported(String)}.
- * When this feature is present, {@link JsIsolate#provideNamedData(String, byte[])} can be used.
+ * When this feature is present, {@link JavaScriptIsolate#provideNamedData(String, byte[])}
+ * can be used.
* <p>
* This also covers the JS API android.consumeNamedDataAsArrayBuffer(string).
*/
@@ -144,26 +146,35 @@
* Feature for {@link #isFeatureSupported(String)}.
*
* This features provides additional behavior to {@link
- * JsIsolate#evaluateJavaScriptAsync(String)} ()}. When this feature is present, the JS API
- * WebAssembly.compile(ArrayBuffer) can be used.
+ * JavaScriptIsolate#evaluateJavaScriptAsync(String)} ()}. When this feature is present, the JS
+ * API WebAssembly.compile(ArrayBuffer) can be used.
*/
public static final String JS_FEATURE_WASM_COMPILATION = "JS_FEATURE_WASM_COMPILATION";
+ /**
+ * Feature for {@link #isFeatureSupported(String)}.
+ *
+ * When this feature is present,
+ * {@link JavaScriptSandbox#createIsolate(IsolateStartupParameters)} can be used.
+ */
+ public static final String JS_FEATURE_ISOLATE_MAX_HEAP_SIZE =
+ "JS_FEATURE_ISOLATE_MAX_HEAP_SIZE";
+
@Nullable
private HashSet<String> mClientSideFeatureSet;
static class ConnectionSetup implements ServiceConnection {
@Nullable
- private CallbackToFutureAdapter.Completer<JsSandbox> mCompleter;
+ private CallbackToFutureAdapter.Completer<JavaScriptSandbox> mCompleter;
@Nullable
- private JsSandbox mJsSandbox;
+ private JavaScriptSandbox mJsSandbox;
Context mContext;
@Override
@SuppressWarnings("NullAway")
public void onServiceConnected(ComponentName name, IBinder service) {
IJsSandboxService jsSandboxService = IJsSandboxService.Stub.asInterface(service);
- mJsSandbox = new JsSandbox(this, jsSandboxService);
+ mJsSandbox = new JavaScriptSandbox(this, jsSandboxService);
mCompleter.set(mJsSandbox);
mCompleter = null;
}
@@ -174,17 +185,20 @@
@Override
public void onServiceDisconnected(ComponentName name) {
runShutdownTasks(
- new RuntimeException("JsSandbox internal error: onServiceDisconnected()"));
+ new RuntimeException(
+ "JavaScriptSandbox internal error: onServiceDisconnected()"));
}
@Override
public void onBindingDied(ComponentName name) {
- runShutdownTasks(new RuntimeException("JsSandbox internal error: onBindingDead()"));
+ runShutdownTasks(
+ new RuntimeException("JavaScriptSandbox internal error: onBindingDead()"));
}
@Override
public void onNullBinding(ComponentName name) {
- runShutdownTasks(new RuntimeException("JsSandbox internal error: onNullBinding()"));
+ runShutdownTasks(
+ new RuntimeException("JavaScriptSandbox internal error: onNullBinding()"));
}
private void runShutdownTasks(Exception e) {
@@ -201,7 +215,7 @@
}
ConnectionSetup(Context context,
- @NonNull CallbackToFutureAdapter.Completer<JsSandbox> completer) {
+ @NonNull CallbackToFutureAdapter.Completer<JavaScriptSandbox> completer) {
mContext = context;
mCompleter = completer;
}
@@ -217,11 +231,11 @@
* application
* context if the connection is expected to outlive a single activity or service.
*
- * @return Future that evaluates to a connected {@link JsSandbox} instance or an exception if
- * binding to service fails.
+ * @return Future that evaluates to a connected {@link JavaScriptSandbox} instance or an
+ * exception if binding to service fails.
*/
@NonNull
- public static ListenableFuture<JsSandbox> createConnectedInstanceAsync(
+ public static ListenableFuture<JavaScriptSandbox> createConnectedInstanceAsync(
@NonNull Context context) {
PackageInfo systemWebViewPackage = WebView.getCurrentWebViewPackage();
ComponentName compName =
@@ -240,22 +254,22 @@
* application
* context if the connection is expected to outlive a single activity/service.
*
- * @return Future that evaluates to a connected {@link JsSandbox} instance or an exception if
- * binding to service fails.
+ * @return Future that evaluates to a connected {@link JavaScriptSandbox} instance or an
+ * exception if binding to service fails.
*
* @hide
*/
@NonNull
@VisibleForTesting
@RestrictTo(RestrictTo.Scope.LIBRARY)
- public static ListenableFuture<JsSandbox> createConnectedInstanceForTestingAsync(
+ public static ListenableFuture<JavaScriptSandbox> createConnectedInstanceForTestingAsync(
@NonNull Context context) {
ComponentName compName = new ComponentName(context, JS_SANDBOX_SERVICE_NAME);
int flag = Context.BIND_AUTO_CREATE;
return bindToServiceWithCallback(context, compName, flag);
}
- @NonNull private static ListenableFuture<JsSandbox> bindToServiceWithCallback(
+ @NonNull private static ListenableFuture<JavaScriptSandbox> bindToServiceWithCallback(
Context context, ComponentName compName, int flag) {
Intent intent = new Intent();
intent.setComponent(compName);
@@ -288,12 +302,13 @@
}
// Debug string.
- return "JsSandbox Future";
+ return "JavaScriptSandbox Future";
});
}
- // We prevent direct initializations of this class. Use JsSandbox.createConnectedInstance().
- JsSandbox(ConnectionSetup connectionSetup, IJsSandboxService jsSandboxService) {
+ // We prevent direct initializations of this class.
+ // Use JavaScriptSandbox.createConnectedInstance().
+ JavaScriptSandbox(ConnectionSetup connectionSetup, IJsSandboxService jsSandboxService) {
mConnection = connectionSetup;
mJsSandboxService = jsSandboxService;
mGuard.open("close");
@@ -301,24 +316,56 @@
}
/**
- * Creates and returns an {@link JsIsolate} within which JS can be executed.
+ * Creates and returns an {@link JavaScriptIsolate} within which JS can be executed with default
+ * settings.
*/
@NonNull
- @SuppressWarnings("NullAway")
- public JsIsolate createIsolate() {
+ public JavaScriptIsolate createIsolate() {
synchronized (mLock) {
if (mJsSandboxService == null) {
throw new IllegalStateException(
"Attempting to createIsolate on a service that isn't connected");
}
+ IJsSandboxIsolate isolateStub;
try {
- IJsSandboxIsolate isolateStub = mJsSandboxService.createIsolate();
- JsIsolate isolate = new JsIsolate(isolateStub, this);
- mActiveIsolateSet.add(isolate);
- return isolate;
+ isolateStub = mJsSandboxService.createIsolate();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
+ return createJsIsolateLocked(isolateStub);
+ }
+ }
+
+ /**
+ * Creates and returns an {@link JavaScriptIsolate} within which JS can be executed with the
+ * specified settings.
+ *
+ * @param settings configuration used to set up the isolate
+ */
+ @NonNull
+ public JavaScriptIsolate createIsolate(@NonNull IsolateStartupParameters settings) {
+ synchronized (mLock) {
+ if (mJsSandboxService == null) {
+ throw new IllegalStateException(
+ "Attempting to createIsolate on a service that isn't connected");
+ }
+ IJsSandboxIsolate isolateStub;
+ try {
+ if (settings.getMaxHeapSizeBytes()
+ == IsolateStartupParameters.DEFAULT_ISOLATE_HEAP_SIZE) {
+ isolateStub = mJsSandboxService.createIsolate();
+ } else {
+ isolateStub = mJsSandboxService.createIsolateWithMaxHeapSizeBytes(
+ settings.getMaxHeapSizeBytes());
+ if (isolateStub == null) {
+ throw new RuntimeException(
+ "Service implementation doesn't support setting maximum heap size");
+ }
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ return createJsIsolateLocked(isolateStub);
}
}
@@ -340,6 +387,17 @@
mClientSideFeatureSet.add(JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER);
mClientSideFeatureSet.add(JS_FEATURE_WASM_COMPILATION);
}
+ if (features.contains(IJsSandboxService.ISOLATE_MAX_HEAP_SIZE_LIMIT)) {
+ mClientSideFeatureSet.add(JS_FEATURE_ISOLATE_MAX_HEAP_SIZE);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @SuppressWarnings("NullAway")
+ private JavaScriptIsolate createJsIsolateLocked(IJsSandboxIsolate isolateStub) {
+ JavaScriptIsolate isolate = new JavaScriptIsolate(isolateStub, this);
+ mActiveIsolateSet.add(isolate);
+ return isolate;
}
/**
@@ -369,7 +427,7 @@
}
}
- void removeFromIsolateSet(JsIsolate isolate) {
+ void removeFromIsolateSet(JavaScriptIsolate isolate) {
synchronized (mLock) {
if (mActiveIsolateSet != null) {
mActiveIsolateSet.remove(isolate);
@@ -378,14 +436,15 @@
}
/**
- * Closes the {@link JsSandbox} object and renders it unusable.
+ * Closes the {@link JavaScriptSandbox} object and renders it unusable.
*
* The client is expected to call this method explicitly to terminate the isolated process.
* <p>
- * Once closed, no more {@link JsSandbox} and {@link JsIsolate} method calls can be made.
- * Closing terminates the isolated process immediately. All pending evaluations are
- * immediately terminated. Once closed, the client may call {@link
- * JsSandbox#createConnectedInstanceAsync(Context)} to create another {@link JsSandbox}.
+ * Once closed, no more {@link JavaScriptSandbox} and {@link JavaScriptIsolate} method calls
+ * can be made. Closing terminates the isolated process immediately. All pending evaluations are
+ * immediately terminated. Once closed, the client may call
+ * {@link JavaScriptSandbox#createConnectedInstanceAsync(Context)} to create another
+ * {@link JavaScriptSandbox}.
*/
@Override
public void close() {
@@ -409,7 +468,7 @@
@GuardedBy("mLock")
private void cancelPendingEvaluationsLocked(Exception e) {
- for (JsIsolate ele : mActiveIsolateSet) {
+ for (JavaScriptIsolate ele : mActiveIsolateSet) {
ele.cancelAllPendingEvaluations(e);
}
mActiveIsolateSet = null;
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java
index 3bed154..735d8ef 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java
@@ -17,9 +17,14 @@
package androidx.javascriptengine;
/**
- * Exception thrown when evaluation is terminated due the {@link JsSandbox} being dead.
+ * Exception thrown when evaluation is terminated due the {@link JavaScriptSandbox} being dead.
+ * This can happen when {@link JavaScriptSandbox#close()} is called or when the sandbox process
+ * is killed by the framework.
+ * <p>
+ * This is different from {@link IsolateTerminatedException} which occurs when the
+ * {@link JavaScriptIsolate} within a {@link JavaScriptSandbox} is terminated.
*/
-public class SandboxDeadException extends JsException {
+public final class SandboxDeadException extends JavaScriptException {
public SandboxDeadException() {
super();
}
diff --git a/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundExtension.kt b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundExtension.kt
index 62c2cea..7a509bc 100644
--- a/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundExtension.kt
+++ b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundExtension.kt
@@ -91,6 +91,14 @@
it.mavenCentral()
it.gradlePluginPortal().content {
it.includeModule(
+ "org.jetbrains.kotlinx",
+ "kotlinx-benchmark-plugin"
+ )
+ it.includeModule(
+ "org.jetbrains.kotlinx.benchmark",
+ "org.jetbrains.kotlinx.benchmark.gradle.plugin"
+ )
+ it.includeModule(
"org.jetbrains.kotlin.plugin.serialization",
"org.jetbrains.kotlin.plugin.serialization.gradle.plugin"
)
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/InvalidationTrackerTrojan.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/InvalidationTrackerTrojan.java
index c57639a..0f2698df 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/InvalidationTrackerTrojan.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/InvalidationTrackerTrojan.java
@@ -20,8 +20,9 @@
* Trojan class to be able to assert internal state.
*/
public class InvalidationTrackerTrojan {
+ @SuppressWarnings("KotlinInternalInJava") // For testing
public static int countObservers(InvalidationTracker tracker) {
- return tracker.observerMap.size();
+ return tracker.getObserverMap$room_runtime_debug().size();
}
private InvalidationTrackerTrojan() {
diff --git a/room/room-paging/src/androidTest/kotlin/androidx/room/InvalidationTrackerExtRoomPaging.kt b/room/room-paging/src/androidTest/kotlin/androidx/room/InvalidationTrackerExtRoomPaging.kt
index 6d83c13..654a96e 100644
--- a/room/room-paging/src/androidTest/kotlin/androidx/room/InvalidationTrackerExtRoomPaging.kt
+++ b/room/room-paging/src/androidTest/kotlin/androidx/room/InvalidationTrackerExtRoomPaging.kt
@@ -20,13 +20,6 @@
import java.util.concurrent.TimeUnit
/**
- * Makes refresh runnable accessible in tests. Used for LimitOffsetPagingSource unit tests that
- * needs to block InvalidationTracker's invalidation notification
- */
-val InvalidationTracker.refreshRunnableForTest: Runnable
- get() = this.refreshRunnable
-
-/**
* True if invalidation tracker is pending a refresh event to get database changes.
*/
val InvalidationTracker.pendingRefreshForTest
diff --git a/room/room-runtime/api/current.ignore b/room/room-runtime/api/current.ignore
index 8a044d9..62d9a375 100644
--- a/room/room-runtime/api/current.ignore
+++ b/room/room-runtime/api/current.ignore
@@ -1,4 +1,8 @@
// Baseline format: 1.0
+AddedFinal: androidx.room.Room:
+ Class androidx.room.Room added 'final' qualifier
+
+
ChangedType: androidx.room.DatabaseConfiguration#autoMigrationSpecs:
Field androidx.room.DatabaseConfiguration.autoMigrationSpecs has changed type from java.util.List<androidx.room.migration.AutoMigrationSpec!> to java.util.List<androidx.room.migration.AutoMigrationSpec>
ChangedType: androidx.room.DatabaseConfiguration#callbacks:
@@ -55,3 +59,7 @@
Method androidx.room.RoomDatabase.MigrationContainer.findMigrationPath has changed return type from java.util.List<androidx.room.migration.Migration!> to java.util.List<androidx.room.migration.Migration>
ChangedType: androidx.room.RoomDatabase.MigrationContainer#getMigrations():
Method androidx.room.RoomDatabase.MigrationContainer.getMigrations has changed return type from java.util.Map<java.lang.Integer!,java.util.Map<java.lang.Integer!,androidx.room.migration.Migration!>!> to java.util.Map<java.lang.Integer,java.util.Map<java.lang.Integer,androidx.room.migration.Migration>>
+
+
+RemovedDeprecatedMethod: androidx.room.Room#Room():
+ Removed deprecated constructor androidx.room.Room()
diff --git a/room/room-runtime/api/current.txt b/room/room-runtime/api/current.txt
index de130d6..0c4e5aa 100644
--- a/room/room-runtime/api/current.txt
+++ b/room/room-runtime/api/current.txt
@@ -40,19 +40,13 @@
method public abstract void onInvalidated(java.util.Set<java.lang.String> tables);
}
- public class Room {
- ctor @Deprecated public Room();
- method public static final <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context context, Class<T> klass, String? name);
- method public static final <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context context, Class<T> klass);
- field public static final androidx.room.Room.Companion Companion;
+ public final class Room {
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context context, Class<T> klass, String? name);
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context context, Class<T> klass);
+ field public static final androidx.room.Room INSTANCE;
field public static final String MASTER_TABLE_NAME = "room_master_table";
}
- public static final class Room.Companion {
- method public <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context context, Class<T> klass, String? name);
- method public <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context context, Class<T> klass);
- }
-
public abstract class RoomDatabase {
ctor public RoomDatabase();
method @Deprecated public void beginTransaction();
diff --git a/room/room-runtime/api/public_plus_experimental_current.txt b/room/room-runtime/api/public_plus_experimental_current.txt
index 9af4092..408b98b 100644
--- a/room/room-runtime/api/public_plus_experimental_current.txt
+++ b/room/room-runtime/api/public_plus_experimental_current.txt
@@ -48,19 +48,13 @@
method public android.os.IBinder onBind(android.content.Intent intent);
}
- public class Room {
- ctor @Deprecated public Room();
- method public static final <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context context, Class<T> klass, String? name);
- method public static final <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context context, Class<T> klass);
- field public static final androidx.room.Room.Companion Companion;
+ public final class Room {
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context context, Class<T> klass, String? name);
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context context, Class<T> klass);
+ field public static final androidx.room.Room INSTANCE;
field public static final String MASTER_TABLE_NAME = "room_master_table";
}
- public static final class Room.Companion {
- method public <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context context, Class<T> klass, String? name);
- method public <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context context, Class<T> klass);
- }
-
public abstract class RoomDatabase {
ctor public RoomDatabase();
method @Deprecated public void beginTransaction();
diff --git a/room/room-runtime/api/restricted_current.ignore b/room/room-runtime/api/restricted_current.ignore
index 58e4397..d53b9c9 100644
--- a/room/room-runtime/api/restricted_current.ignore
+++ b/room/room-runtime/api/restricted_current.ignore
@@ -1,4 +1,8 @@
// Baseline format: 1.0
+AddedFinal: androidx.room.Room:
+ Class androidx.room.Room added 'final' qualifier
+
+
ChangedType: androidx.room.DatabaseConfiguration#autoMigrationSpecs:
Field androidx.room.DatabaseConfiguration.autoMigrationSpecs has changed type from java.util.List<androidx.room.migration.AutoMigrationSpec!> to java.util.List<androidx.room.migration.AutoMigrationSpec>
ChangedType: androidx.room.DatabaseConfiguration#callbacks:
@@ -117,6 +121,8 @@
Removed deprecated constructor androidx.room.DatabaseConfiguration(android.content.Context,String,androidx.sqlite.db.SupportSQLiteOpenHelper.Factory,androidx.room.RoomDatabase.MigrationContainer,java.util.List<androidx.room.RoomDatabase.Callback>,boolean,androidx.room.RoomDatabase.JournalMode,java.util.concurrent.Executor,java.util.concurrent.Executor,boolean,boolean,boolean,java.util.Set<java.lang.Integer>,String,java.io.File,java.util.concurrent.Callable<java.io.InputStream>,androidx.room.RoomDatabase.PrepackagedDatabaseCallback,java.util.List<java.lang.Object>)
RemovedDeprecatedMethod: androidx.room.DatabaseConfiguration#DatabaseConfiguration(android.content.Context, String, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer>, String, java.io.File, java.util.concurrent.Callable<java.io.InputStream>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback, java.util.List<java.lang.Object>, java.util.List<androidx.room.migration.AutoMigrationSpec>):
Removed deprecated constructor androidx.room.DatabaseConfiguration(android.content.Context,String,androidx.sqlite.db.SupportSQLiteOpenHelper.Factory,androidx.room.RoomDatabase.MigrationContainer,java.util.List<androidx.room.RoomDatabase.Callback>,boolean,androidx.room.RoomDatabase.JournalMode,java.util.concurrent.Executor,java.util.concurrent.Executor,boolean,boolean,boolean,java.util.Set<java.lang.Integer>,String,java.io.File,java.util.concurrent.Callable<java.io.InputStream>,androidx.room.RoomDatabase.PrepackagedDatabaseCallback,java.util.List<java.lang.Object>,java.util.List<androidx.room.migration.AutoMigrationSpec>)
+RemovedDeprecatedMethod: androidx.room.Room#Room():
+ Removed deprecated constructor androidx.room.Room()
RemovedMethod: androidx.room.DatabaseConfiguration#DatabaseConfiguration(android.content.Context, String, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, android.content.Intent, boolean, boolean, java.util.Set<java.lang.Integer>, String, java.io.File, java.util.concurrent.Callable<java.io.InputStream>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback, java.util.List<java.lang.Object>, java.util.List<androidx.room.migration.AutoMigrationSpec>):
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index ddee7ea..b066005 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -91,19 +91,13 @@
method public abstract void onInvalidated(java.util.Set<java.lang.String> tables);
}
- public class Room {
- ctor @Deprecated public Room();
- method public static final <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context context, Class<T> klass, String? name);
- method public static final <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context context, Class<T> klass);
- field public static final androidx.room.Room.Companion Companion;
+ public final class Room {
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context context, Class<T> klass, String? name);
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context context, Class<T> klass);
+ field public static final androidx.room.Room INSTANCE;
field public static final String MASTER_TABLE_NAME = "room_master_table";
}
- public static final class Room.Companion {
- method public <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context context, Class<T> klass, String? name);
- method public <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context context, Class<T> klass);
- }
-
public abstract class RoomDatabase {
ctor public RoomDatabase();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void assertNotMainThread();
diff --git a/room/room-runtime/src/main/java/androidx/room/AutoCloser.kt b/room/room-runtime/src/main/java/androidx/room/AutoCloser.kt
index a0a5ea1..f79f982 100644
--- a/room/room-runtime/src/main/java/androidx/room/AutoCloser.kt
+++ b/room/room-runtime/src/main/java/androidx/room/AutoCloser.kt
@@ -19,7 +19,6 @@
import android.os.Looper
import android.os.SystemClock
import androidx.annotation.GuardedBy
-import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
import androidx.room.util.SneakyThrow
import androidx.sqlite.db.SupportSQLiteDatabase
@@ -38,10 +37,7 @@
* @param autoCloseTimeoutAmount time for auto close timer
* @param autoCloseTimeUnit time unit for autoCloseTimeoutAmount
* @param autoCloseExecutor the executor on which the auto close operation will happen
- *
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY)
internal class AutoCloser(
autoCloseTimeoutAmount: Long,
autoCloseTimeUnit: TimeUnit,
diff --git a/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt b/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt
index 8b83f50..b766d6f 100644
--- a/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt
+++ b/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt
@@ -27,7 +27,7 @@
import androidx.annotation.WorkerThread
import androidx.arch.core.internal.SafeIterableMap
import androidx.lifecycle.LiveData
-import androidx.room.Room.Companion.LOG_TAG
+import androidx.room.Room.LOG_TAG
import androidx.room.util.useCursor
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteDatabase
@@ -53,37 +53,26 @@
// memory table table, flipping the invalidated flag ON.
// * When multi-instance invalidation is turned on, MultiInstanceInvalidationClient will be created.
// It works as an Observer, and notifies other instances of table invalidation.
-
open class InvalidationTracker @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) constructor(
- @get:RestrictTo(RestrictTo.Scope.LIBRARY)
- @field:RestrictTo(RestrictTo.Scope.LIBRARY)
- protected val database: RoomDatabase,
+ internal val database: RoomDatabase,
private val shadowTablesMap: Map<String, String>,
private val viewTables: Map<String, @JvmSuppressWildcards Set<String>>,
vararg tableNames: String
) {
- @JvmField
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- val tableIdLookup: Map<String, Int>
-
- @JvmField
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- val tablesNames: Array<String>
+ internal val tableIdLookup: Map<String, Int>
+ internal val tablesNames: Array<String>
private var autoCloser: AutoCloser? = null
- @JvmField
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+ @field:RestrictTo(RestrictTo.Scope.LIBRARY)
val pendingRefresh = AtomicBoolean(false)
@Volatile
private var initialized = false
@Volatile
- @get:RestrictTo(RestrictTo.Scope.LIBRARY)
- @set:RestrictTo(RestrictTo.Scope.LIBRARY)
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- var cleanupStatement: SupportSQLiteStatement? = null
+ internal var cleanupStatement: SupportSQLiteStatement? = null
private val observedTableTracker: ObservedTableTracker = ObservedTableTracker(tableNames.size)
@@ -91,17 +80,7 @@
InvalidationLiveDataContainer(database)
@GuardedBy("observerMap")
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- @JvmField
- @VisibleForTesting
- protected val observerMap = SafeIterableMap<Observer, ObserverWrapper>()
-
- @GuardedBy("observerMap")
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- @VisibleForTesting
- fun getObserverMap(): SafeIterableMap<Observer, ObserverWrapper> {
- return observerMap
- }
+ internal val observerMap = SafeIterableMap<Observer, ObserverWrapper>()
private var multiInstanceInvalidationClient: MultiInstanceInvalidationClient? = null
@@ -165,9 +144,7 @@
*
* You should never call this method, it is called by the generated code.
*/
- // TODO (b/218894771): Make internal when RoomDatabase is converted to Kotlin
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- fun internalInit(database: SupportSQLiteDatabase) {
+ internal fun internalInit(database: SupportSQLiteDatabase) {
synchronized(trackerLock) {
if (initialized) {
Log.e(LOG_TAG, "Invalidation tracker is initialized twice :/.")
@@ -193,7 +170,6 @@
}
}
- @RestrictTo(RestrictTo.Scope.LIBRARY)
internal fun startMultiInstanceInvalidation(
context: Context,
name: String,
@@ -208,7 +184,6 @@
)
}
- @RestrictTo(RestrictTo.Scope.LIBRARY)
internal fun stopMultiInstanceInvalidation() {
multiInstanceInvalidationClient?.stop()
multiInstanceInvalidationClient = null
@@ -353,8 +328,7 @@
}
}
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- protected fun ensureInitialization(): Boolean {
+ internal fun ensureInitialization(): Boolean {
if (!database.isOpen) {
return false
}
@@ -503,7 +477,6 @@
}
}
- @RestrictTo(RestrictTo.Scope.LIBRARY)
internal fun syncTriggers(database: SupportSQLiteDatabase) {
if (database.inTransaction()) {
// we won't run this inside another transaction.
@@ -552,8 +525,7 @@
*
* This api should eventually be public.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- fun syncTriggers() {
+ internal fun syncTriggers() {
if (!database.isOpen) {
return
}
@@ -613,15 +585,10 @@
*
* Internally table ids are used which may change from database to database so the table
* related information is kept here rather than in the Observer.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- class ObserverWrapper(
- @get:RestrictTo(RestrictTo.Scope.LIBRARY)
- val observer: Observer,
- @get:RestrictTo(RestrictTo.Scope.LIBRARY)
- val tableIds: IntArray,
+ internal class ObserverWrapper(
+ internal val observer: Observer,
+ internal val tableIds: IntArray,
private val tableNames: Array<String>
) {
private val singleTableSet = if (tableNames.isNotEmpty()) {
@@ -640,8 +607,7 @@
*
* @param invalidatedTablesIds The table ids of the tables that are invalidated.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- fun notifyByTableInvalidStatus(invalidatedTablesIds: Set<Int?>) {
+ internal fun notifyByTableInvalidStatus(invalidatedTablesIds: Set<Int?>) {
val invalidatedTables = when (tableIds.size) {
0 -> emptySet()
1 -> if (invalidatedTablesIds.contains(tableIds[0])) {
@@ -669,8 +635,7 @@
*
* @param tables The invalidated table names.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- fun notifyByTableNames(tables: Array<out String>) {
+ internal fun notifyByTableNames(tables: Array<out String>) {
val invalidatedTables = when (tableNames.size) {
0 -> emptySet()
1 -> if (tables.any { it.equals(tableNames[0], ignoreCase = true) }) {
@@ -698,7 +663,7 @@
/**
* An observer that can listen for changes in the database.
*/
- abstract class Observer(@get:RestrictTo(RestrictTo.Scope.LIBRARY) val tables: Array<String>) {
+ abstract class Observer(internal val tables: Array<String>) {
/**
* Observes the given list of tables and views.
*
@@ -721,8 +686,7 @@
*/
abstract fun onInvalidated(tables: Set<String>)
- @get:RestrictTo(RestrictTo.Scope.LIBRARY)
- open val isRemote: Boolean
+ internal open val isRemote: Boolean
get() = false
}
@@ -819,8 +783,7 @@
}
}
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- companion object {
+ internal companion object {
const val NO_OP = 0 // don't change trigger state for this table
const val ADD = 1 // add triggers for this table
const val REMOVE = 2 // remove triggers for this table
@@ -857,14 +820,12 @@
"$INVALIDATED_COLUMN_NAME INTEGER NOT NULL DEFAULT 0)"
@VisibleForTesting
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- const val RESET_UPDATED_TABLES_SQL =
+ internal const val RESET_UPDATED_TABLES_SQL =
"UPDATE $UPDATE_TABLE_NAME SET $INVALIDATED_COLUMN_NAME = 0 " +
"WHERE $INVALIDATED_COLUMN_NAME = 1"
@VisibleForTesting
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- const val SELECT_UPDATED_TABLES_SQL =
+ internal const val SELECT_UPDATED_TABLES_SQL =
"SELECT * FROM $UPDATE_TABLE_NAME WHERE $INVALIDATED_COLUMN_NAME = 1;"
internal fun getTriggerName(
diff --git a/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationClient.kt b/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationClient.kt
index bd52304..e4eb340 100644
--- a/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationClient.kt
+++ b/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationClient.kt
@@ -22,7 +22,7 @@
import android.os.IBinder
import android.os.RemoteException
import android.util.Log
-import androidx.room.Room.Companion.LOG_TAG
+import androidx.room.Room.LOG_TAG
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean
diff --git a/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationService.kt b/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationService.kt
index 9b8e836..9d72873 100644
--- a/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationService.kt
+++ b/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationService.kt
@@ -21,7 +21,7 @@
import android.os.IBinder
import android.os.RemoteException
import android.util.Log
-import androidx.room.Room.Companion.LOG_TAG
+import androidx.room.Room.LOG_TAG
/**
* A [Service] for remote invalidation among multiple [InvalidationTracker] instances.
diff --git a/room/room-runtime/src/main/java/androidx/room/Room.kt b/room/room-runtime/src/main/java/androidx/room/Room.kt
index dd72c02..9a68205 100644
--- a/room/room-runtime/src/main/java/androidx/room/Room.kt
+++ b/room/room-runtime/src/main/java/androidx/room/Room.kt
@@ -21,102 +21,96 @@
/**
* Utility functions for Room.
*/
-// TODO (b/225972678) remove class and make top-level functions in Room 3.0.
-open class Room {
- /** This type should not be instantiated as it contains only static methods. */
- @Deprecated("This type should not be instantiated as it contains only static methods. ")
- @SuppressWarnings("PrivateConstructorForUtilityClass")
- constructor()
+object Room {
- companion object {
- internal const val LOG_TAG = "ROOM"
+ internal const val LOG_TAG = "ROOM"
- /**
- * The master table where room keeps its metadata information.
- */
- const val MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME
- private const val CURSOR_CONV_SUFFIX = "_CursorConverter"
+ /**
+ * The master table where room keeps its metadata information.
+ */
+ const val MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME
- @Suppress("UNCHECKED_CAST")
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @JvmStatic
- fun <T, C> getGeneratedImplementation(
- klass: Class<C>,
- suffix: String
- ): T {
- val fullPackage = klass.getPackage()!!.name
- val name: String = klass.canonicalName!!
- val postPackageName =
- if (fullPackage.isEmpty()) name else name.substring(fullPackage.length + 1)
- val implName = postPackageName.replace('.', '_') + suffix
- return try {
- val fullClassName = if (fullPackage.isEmpty()) {
- implName
- } else {
- "$fullPackage.$implName"
- }
- val aClass = Class.forName(
- fullClassName, true, klass.classLoader
- ) as Class<T>
- aClass.newInstance()
- } catch (e: ClassNotFoundException) {
- throw RuntimeException(
- "Cannot find implementation for ${klass.canonicalName}. $implName does not " +
- "exist"
- )
- } catch (e: IllegalAccessException) {
- throw RuntimeException(
- "Cannot access the constructor $klass.canonicalName"
- )
- } catch (e: InstantiationException) {
- throw RuntimeException(
- "Failed to create an instance of $klass.canonicalName"
- )
+ private const val CURSOR_CONV_SUFFIX = "_CursorConverter"
+
+ @Suppress("UNCHECKED_CAST")
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @JvmStatic
+ fun <T, C> getGeneratedImplementation(
+ klass: Class<C>,
+ suffix: String
+ ): T {
+ val fullPackage = klass.getPackage()!!.name
+ val name: String = klass.canonicalName!!
+ val postPackageName =
+ if (fullPackage.isEmpty()) name else name.substring(fullPackage.length + 1)
+ val implName = postPackageName.replace('.', '_') + suffix
+ return try {
+ val fullClassName = if (fullPackage.isEmpty()) {
+ implName
+ } else {
+ "$fullPackage.$implName"
}
+ val aClass = Class.forName(
+ fullClassName, true, klass.classLoader
+ ) as Class<T>
+ aClass.newInstance()
+ } catch (e: ClassNotFoundException) {
+ throw RuntimeException(
+ "Cannot find implementation for ${klass.canonicalName}. $implName does not " +
+ "exist"
+ )
+ } catch (e: IllegalAccessException) {
+ throw RuntimeException(
+ "Cannot access the constructor $klass.canonicalName"
+ )
+ } catch (e: InstantiationException) {
+ throw RuntimeException(
+ "Failed to create an instance of $klass.canonicalName"
+ )
}
+ }
- /**
- * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
- * database disappears when the process is killed.
- * Once a database is built, you should keep a reference to it and re-use it.
- *
- * @param context The context for the database. This is usually the Application context.
- * @param klass The abstract class which is annotated with [Database] and extends
- * [RoomDatabase].
- * @param T The type of the database class.
- * @return A `RoomDatabaseBuilder<T>` which you can use to create the database.
- */
- @JvmStatic
- fun <T : RoomDatabase> inMemoryDatabaseBuilder(
- context: Context,
- klass: Class<T>
- ): RoomDatabase.Builder<T> {
- return RoomDatabase.Builder(context, klass, null)
- }
+ /**
+ * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
+ * database disappears when the process is killed.
+ * Once a database is built, you should keep a reference to it and re-use it.
+ *
+ * @param context The context for the database. This is usually the Application context.
+ * @param klass The abstract class which is annotated with [Database] and extends
+ * [RoomDatabase].
+ * @param T The type of the database class.
+ * @return A `RoomDatabaseBuilder<T>` which you can use to create the database.
+ */
+ @JvmStatic
+ fun <T : RoomDatabase> inMemoryDatabaseBuilder(
+ context: Context,
+ klass: Class<T>
+ ): RoomDatabase.Builder<T> {
+ return RoomDatabase.Builder(context, klass, null)
+ }
- /**
- * Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
- * should keep a reference to it and re-use it.
- *
- * @param context The context for the database. This is usually the Application context.
- * @param klass The abstract class which is annotated with [Database] and extends
- * [RoomDatabase].
- * @param name The name of the database file.
- * @param T The type of the database class.
- * @return A `RoomDatabaseBuilder<T>` which you can use to create the database.
- */
- @JvmStatic
- fun <T : RoomDatabase> databaseBuilder(
- context: Context,
- klass: Class<T>,
- name: String?
- ): RoomDatabase.Builder<T> {
- require(!name.isNullOrBlank()) {
- "Cannot build a database with null or empty name." +
- " If you are trying to create an in memory database, use Room" +
- ".inMemoryDatabaseBuilder"
- }
- return RoomDatabase.Builder(context, klass, name)
+ /**
+ * Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
+ * should keep a reference to it and re-use it.
+ *
+ * @param context The context for the database. This is usually the Application context.
+ * @param klass The abstract class which is annotated with [Database] and extends
+ * [RoomDatabase].
+ * @param name The name of the database file.
+ * @param T The type of the database class.
+ * @return A `RoomDatabaseBuilder<T>` which you can use to create the database.
+ */
+ @JvmStatic
+ fun <T : RoomDatabase> databaseBuilder(
+ context: Context,
+ klass: Class<T>,
+ name: String?
+ ): RoomDatabase.Builder<T> {
+ require(!name.isNullOrBlank()) {
+ "Cannot build a database with null or empty name." +
+ " If you are trying to create an in memory database, use Room" +
+ ".inMemoryDatabaseBuilder"
}
+ return RoomDatabase.Builder(context, klass, name)
}
}
\ No newline at end of file
diff --git a/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt b/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt
index b2885b2..1c5732e 100644
--- a/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt
+++ b/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt
@@ -29,6 +29,7 @@
import androidx.annotation.RestrictTo
import androidx.annotation.WorkerThread
import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.room.Room.LOG_TAG
import androidx.room.migration.AutoMigrationSpec
import androidx.room.migration.Migration
import androidx.sqlite.db.SimpleSQLiteQuery
@@ -137,8 +138,7 @@
*
* @return The lock for [close].
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- open fun getCloseLock(): Lock {
+ internal fun getCloseLock(): Lock {
return readWriteLock.readLock()
}
@@ -174,7 +174,7 @@
}
/**
- * Called by [Room] when it is initialized.
+ * Called by Room when it is initialized.
*
* @throws IllegalArgumentException if initialization fails.
*
@@ -657,8 +657,7 @@
/**
* Resolves [AUTOMATIC] to either [TRUNCATE] or [WRITE_AHEAD_LOGGING].
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- open fun resolve(context: Context): JournalMode {
+ internal fun resolve(context: Context): JournalMode {
if (this != AUTOMATIC) {
return this
}
@@ -1384,7 +1383,7 @@
val targetMap = migrations.getOrPut(start) { TreeMap<Int, Migration>() }
if (targetMap.contains(end)) {
- Log.w(Room.LOG_TAG, "Overriding migration ${targetMap[end]} with $migration")
+ Log.w(LOG_TAG, "Overriding migration ${targetMap[end]} with $migration")
}
targetMap[end] = migration
}
diff --git a/room/room-runtime/src/main/java/androidx/room/SQLiteCopyOpenHelper.kt b/room/room-runtime/src/main/java/androidx/room/SQLiteCopyOpenHelper.kt
index 0e34980..834ecec 100644
--- a/room/room-runtime/src/main/java/androidx/room/SQLiteCopyOpenHelper.kt
+++ b/room/room-runtime/src/main/java/androidx/room/SQLiteCopyOpenHelper.kt
@@ -19,6 +19,7 @@
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
+import androidx.room.Room.LOG_TAG
import androidx.room.util.readVersion
import androidx.room.util.copy
import androidx.sqlite.db.SupportSQLiteDatabase
@@ -119,7 +120,7 @@
val currentVersion = try {
readVersion(databaseFile)
} catch (e: IOException) {
- Log.w(Room.LOG_TAG, "Unable to read database version.", e)
+ Log.w(LOG_TAG, "Unable to read database version.", e)
return
}
if (currentVersion == databaseVersion) {
@@ -136,11 +137,11 @@
} catch (e: IOException) {
// We are more forgiving copying a database on a destructive migration since
// there is already a database file that can be opened.
- Log.w(Room.LOG_TAG, "Unable to copy database file.", e)
+ Log.w(LOG_TAG, "Unable to copy database file.", e)
}
} else {
Log.w(
- Room.LOG_TAG, "Failed to delete database file ($name) for " +
+ LOG_TAG, "Failed to delete database file ($name) for " +
"a copy destructive migration."
)
}
diff --git a/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt b/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt
index b897d80..6e3f170 100644
--- a/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt
@@ -152,11 +152,11 @@
fun addRemoveObserver() {
val observer: InvalidationTracker.Observer = LatchObserver(1, "a")
mTracker.addObserver(observer)
- assertThat(mTracker.getObserverMap().size()).isEqualTo(1)
+ assertThat(mTracker.observerMap.size()).isEqualTo(1)
mTracker.removeObserver(LatchObserver(1, "a"))
- assertThat(mTracker.getObserverMap().size()).isEqualTo(1)
+ assertThat(mTracker.observerMap.size()).isEqualTo(1)
mTracker.removeObserver(observer)
- assertThat(mTracker.getObserverMap().size()).isEqualTo(0)
+ assertThat(mTracker.observerMap.size()).isEqualTo(0)
}
private fun drainTasks() {
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiScrollable.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiScrollable.java
index 7a463ac..bc2e4f7 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiScrollable.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiScrollable.java
@@ -16,14 +16,11 @@
package androidx.test.uiautomator;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
import android.graphics.Rect;
import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
/**
* UiScrollable is a {@link UiCollection} and provides support for searching
@@ -306,7 +303,6 @@
* @throws UiObjectNotFoundException
* @hide
*/
- @RestrictTo(LIBRARY)
public boolean ensureFullyVisible(@NonNull UiObject childObject)
throws UiObjectNotFoundException {
Rect actual = childObject.getBounds();