Add lint rule for calling onBackPressed in BackHandler
You should not call onBackPressed on the Activity or
OnBackPressedDispatcher inside of the onBack lambda of a BackHandler or
PredictiveBackHandler. It is an anti-pattern and should be prevented.
This change adds a lint rule to ensure to call this case out at compile
time.
RelNote: "There is now a lint rule to detect calls to `onBackPressed()`
inside of `BackHandler` and `PredictiveBackHandler` callbacks."
Test: added tests
Bug: 287505200
Change-Id: I94d12850523b5b56aa34a5355fdb2228a8ee1236
diff --git a/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/BackHandlerOnBackPressedDetector.kt b/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/BackHandlerOnBackPressedDetector.kt
new file mode 100644
index 0000000..9822005
--- /dev/null
+++ b/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/BackHandlerOnBackPressedDetector.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2024 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.activity.compose.lint
+
+import androidx.compose.lint.Name
+import androidx.compose.lint.Package
+import androidx.compose.lint.findUnreferencedParameters
+import androidx.compose.lint.isInPackageName
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.computeKotlinArgumentMapping
+import com.intellij.psi.PsiMethod
+import java.util.EnumSet
+import org.jetbrains.kotlin.psi.KtLambdaExpression
+import org.jetbrains.kotlin.psi.KtSimpleNameExpression
+import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.ULambdaExpression
+
+class BackHandlerOnBackPressedDetector : Detector(), Detector.UastScanner, SourceCodeScanner {
+ override fun getApplicableMethodNames(): List<String> = listOf(
+ PredictiveBackHandler.shortName,
+ BackHandler.shortName
+ )
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (method.isInPackageName(ComposePackageName)) {
+ // Find the back lambda
+ val backLambda = computeKotlinArgumentMapping(node, method)
+ .orEmpty()
+ .filter { (_, parameter) ->
+ parameter.name == OnBack
+ }
+ .keys
+ .filterIsInstance<ULambdaExpression>()
+ .firstOrNull() ?: return
+
+ // If the parameter is not referenced, immediately trigger the warning
+ val unreferencedParameter = backLambda.findUnreferencedParameters().firstOrNull()
+ if (unreferencedParameter == null) {
+ // If the parameter is referenced, we need to make sure it doesn't call
+ // onBackPressed
+ val lambdaExpression = backLambda.sourcePsi as? KtLambdaExpression
+ // Find all of the reference inside of the lambda
+ val references =
+ lambdaExpression?.functionLiteral
+ ?.collectDescendantsOfType<KtSimpleNameExpression>()
+ // Check for references to OnBackPressed
+ val matchingReferences = references?.filter {
+ it.getReferencedName() == OnBackPressed.shortName
+ }.orEmpty()
+ // If references call onBackPressed(), trigger the warning
+ if (matchingReferences.isNotEmpty()) {
+ matchingReferences.forEach { reference ->
+ val location = reference.let { context.getLocation(it) }
+ context.report(
+ InvalidOnBackPressed,
+ node,
+ location,
+ "Should not call onBackPressed inside of BackHandler"
+ )
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ val InvalidOnBackPressed = Issue.create(
+ id = "OnBackPressedInsideOfBackHandler",
+ briefDescription = "Do not call onBackPressed() within" +
+ "BackHandler/PredictiveBackHandler",
+ explanation = """You should not used OnBackPressedCallback for non-UI cases. If you
+ |add a callback, you have to handle back completely in the callback.
+ """,
+ category = Category.CORRECTNESS,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ BackHandlerOnBackPressedDetector::class.java,
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+ )
+ ).addMoreInfo(
+ "https://developer.android.com/guide/navigation/custom-back/" +
+ "predictive-back-gesture#ui-logic"
+ )
+ }
+}
+
+private val ComposePackageName = Package("androidx.activity.compose")
+private val PredictiveBackHandler = Name(ComposePackageName, "PredictiveBackHandler")
+private val BackHandler = Name(ComposePackageName, "BackHandler")
+private val ActivityPackageName = Package("androidx.activity")
+private val OnBackPressed = Name(ActivityPackageName, "onBackPressed")
+private val OnBack = "onBack"
diff --git a/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/BackHandlerOnBackPressedDetectorTest.kt b/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/BackHandlerOnBackPressedDetectorTest.kt
new file mode 100644
index 0000000..d27ab06
--- /dev/null
+++ b/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/BackHandlerOnBackPressedDetectorTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2024 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.activity.compose.lint
+
+import androidx.compose.lint.test.Stubs
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class BackHandlerOnBackPressedDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = BackHandlerOnBackPressedDetector()
+
+ override fun getIssues(): MutableList<Issue> =
+ mutableListOf(BackHandlerOnBackPressedDetector.InvalidOnBackPressed)
+
+ @Test
+ fun expectPassOnBackPressed() {
+ lint().files(
+ kotlin(
+ """
+ package com.example
+
+ import androidx.compose.runtime.Composable
+ import androidx.activity.ComponentActivity
+ import androidx.activity.OnBackPressedDispatcher
+
+ @Composable
+ fun Test() {
+ val activity = ComponentActivity()
+ activity.onBackPressed()
+ val dispatcher = OnBackPressedDispatcher()
+ dispatcher.onBackPressed()
+ }
+ """
+ ),
+ Stubs.Composable,
+ COMPONENT_ACTIVITY,
+ ON_BACK_PRESSED_DISPATCHER,
+ )
+ .run().expectClean()
+ }
+ @Test
+ fun errors() {
+ lint().files(
+ kotlin(
+ """
+ package com.example
+
+ import androidx.compose.runtime.Composable
+ import androidx.activity.compose.BackHandler
+ import androidx.activity.compose.PredictiveBackHandler
+ import androidx.activity.ComponentActivity
+ import androidx.activity.OnBackPressedDispatcher
+
+ @Composable
+ fun Test() {
+ PredictiveBackHandler { progress ->
+ progress.collect()
+ val activity = ComponentActivity()
+ activity.onBackPressed()
+ val dispatcher = OnBackPressedDispatcher()
+ dispatcher.onBackPressed()
+ }
+
+ BackHandler {
+ val activity = ComponentActivity()
+ activity.onBackPressed()
+ val dispatcher = OnBackPressedDispatcher()
+ dispatcher.onBackPressed()
+ }
+ }
+ """
+ ),
+ Stubs.Composable,
+ BACK_HANDLER,
+ COMPONENT_ACTIVITY,
+ ON_BACK_PRESSED_DISPATCHER,
+ PREDICTIVE_BACK_HANDLER
+ )
+ .run()
+ .expect(
+ """
+src/com/example/test.kt:15: Warning: Should not call onBackPressed inside of BackHandler [OnBackPressedInsideOfBackHandler]
+ activity.onBackPressed()
+ ~~~~~~~~~~~~~
+src/com/example/test.kt:17: Warning: Should not call onBackPressed inside of BackHandler [OnBackPressedInsideOfBackHandler]
+ dispatcher.onBackPressed()
+ ~~~~~~~~~~~~~
+src/com/example/test.kt:22: Warning: Should not call onBackPressed inside of BackHandler [OnBackPressedInsideOfBackHandler]
+ activity.onBackPressed()
+ ~~~~~~~~~~~~~
+src/com/example/test.kt:24: Warning: Should not call onBackPressed inside of BackHandler [OnBackPressedInsideOfBackHandler]
+ dispatcher.onBackPressed()
+ ~~~~~~~~~~~~~
+0 errors, 4 warnings
+ """
+ )
+ }
+}
diff --git a/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/CollectProgressDetectorTest.kt b/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/CollectProgressDetectorTest.kt
index ce96fc6..e7e0f0f 100644
--- a/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/CollectProgressDetectorTest.kt
+++ b/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/CollectProgressDetectorTest.kt
@@ -31,46 +31,6 @@
override fun getIssues(): MutableList<Issue> =
mutableListOf(CollectProgressDetector.NoCollectCallFound)
- private val PREDICTIVE_BACK_HANDLER = bytecode(
- "libs/predictivebackhandler.jar",
- kotlin(
- """
- package androidx.activity.compose
-
- public fun PredictiveBackHandler(
- enabled: Boolean = true,
- onBack: (progress: String) -> Unit) { }
-
- """
- ).indented(),
- 0xd7427505,
- """
- META-INF/main.kotlin_module:
- H4sIAAAAAAAA/2NgYGBmYGBgBGJWKM3AZcIlmZiXUpSfmVKhl5hcklmWWVKp
- l5yfW5BfnCokHlCUmpIJEk11SkzO9gCqzEkt8i7hEuXiBqrRS61IzC3ISRVi
- C0ktLvEuUWLQYgAAnvRwIWUAAAA=
- """,
- """
- androidx/activity/compose/PredictiveBackHandlerKt.class:
- H4sIAAAAAAAA/4VSXU8TQRQ9s9222/JVFkGogCAoIMIWNOGhxkRNiI2lElEe
- 4GnYDnXodpbsTht8MfwNX/0HvhEfDPHRH2W8sy0fURLa5N479557zt078/vP
- j58AnmGNYY2rehTK+onHfS07Un/2/LB1HMbC245EXZqkeMX95hsCBiJ6q7Ng
- DIUj3uFewFXDe3dwJHzKphhGb2xhmF/cqzZDHUjlHXVa3mFbESZUsbfZi9bK
- S7sMm7fCnq9Ur4R3dCRVo3zR8lFJXX6REM1Vw6jhHQl9EHFJ/VypUPMuVy3U
- tXYQlBkyoTJTOsgxTF/TlUqLSPHAqyijEEs/zqKPvs7/JPxmr3+bR7wltPm6
- hcXqv9so/z/m0m4/BjCYRz+GGLJC8YNA1BnYHsPMbdthmLpxtfN1ccjbgWbY
- uH3Flf/HNEOlkcnDwhjD8AXDltC8zjUnXavVSdFbYcakadqmCSzKn0gTlSiq
- 0zPaPz+dzp+f5q2C1XV9iRu3KBzsescqPi2cnxatElt3HAJSlFqfLdjFKXfE
- HS5lfn3L9DtZ13Ec13acxZxru4QtpY3EOqMZ4F4MeH0zYxfJyyupkaOCrcgz
- OMdR2IhEHDOM37jE1Sbtz34d1gk8VJVK1NqtAxF9MBdkNEOfB7s8kubcS+Z2
- ZENx3Y4onn/fVlq2REV1ZCypfDnHy6tnxzCwo0lyix/3KPI7YTvyxaY0h4ke
- x26X4Voj1uhubJifhQlzWUhhiU5lOlvks8tu/gyF7wngMdkMLSpD/2WKx7oQ
- DMNNKLLIYYTqTxJ0FivkcwZCawIKOdzBKMWGf6OnOzhpf/mKtF0uLp/hbldm
- lWwKzEn0BmEegk2KaSKxqewloEWUyFeIbpwqE/tIVVCs4B5ZTFYwhekK7mNm
- HyzGLB7sIx8jHWMuxnBiczHmk+BhjEcxFv4C1+LeOLYEAAA=
- """
- )
-
@Test
fun errors() {
lint().files(
diff --git a/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/Stubs.kt b/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/Stubs.kt
new file mode 100644
index 0000000..b4a9de0
--- /dev/null
+++ b/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/Stubs.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2024 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.activity.compose.lint
+
+import androidx.compose.lint.test.bytecodeStub
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+
+val BACK_HANDLER = bytecodeStub(
+ filename = "BackHandler.kt",
+ filepath = "androidx/activity/compose",
+ checksum = 0x40a94587,
+ """
+ package androidx.activity.compose
+
+ import androidx.compose.runtime.Composable
+
+ @Composable
+ fun BackHandler(
+ enabled: Boolean = true,
+ onBack: () -> Unit) { }
+ """,
+ """
+ META-INF/main.kotlin_module:
+ H4sIAAAAAAAA/2NgYGBmYGBgBGJWKM3AZcQlmZiXUpSfmVKhl5hcklmWWVKp
+ l5yfW5BfnCrE65SYnO0BlM9JLfIuEWILSS0u8S7hEuXiBqrQS61IzC3ISYUJ
+ KzFoMQAA3mTzZGMAAAA=
+ """,
+ """
+ androidx/activity/compose/BackHandlerKt.class:
+ H4sIAAAAAAAA/4VSXU8TQRQ9s/1eoJQiCgURBQRE2YImPFRNlITQWNBY5QGe
+ ptuhTrudJbvTBl8Mf8Of4RvxwfDsjzLe2bZY4YEmnft17r3n3r2///z8BeAF
+ igwrXNUDX9bPHO5q2ZX6q+P67VM/FM5b7rb2KOyJ4J1OgTHkmrzLHY+rhvO+
+ 1hQueWMMI0NAhqXVo0rL155UTrPbdk46iur6KnR2+1qxtHbI8PxW2MtB/LOS
+ uvQ6ylquXNEdsAw6Ssu2cHYim9c8UWJYrPhBw2kKXQu4pKJcKV/zXoMDXx90
+ PI9QSV8Z6mnYDPNDZKTSIlDcc8pKB5Qu3TCFUYYp94twW/38DzzgbaHNyCur
+ leuLKQ15qqZIg/iPIotxG2PIMaSEMlzrDOyIYeG2lTFMDm15qS5OeMfTDNu3
+ b7t8k5yhkkDShoV7DBODCvtC8zrXnLpZ7W6MToSZJ0EcW0axyH8mjUaHY9U3
+ GUqX53n78ty2clZPjERi2ios5C7PC1aRbaXTFCQttjWaixfS+XievMXEXsqU
+ 2GLUA/kBgeF5s0PzbrRo1PiOXxcM4xWpxEGnXRPBJ7NBk+673DvkgTR235mp
+ yobiuhOQPvuxdyNl1ZWhpPCbf+dAB3s9evVl/4ONVTUR2uen/QZ21e8ErtiV
+ xpjp1zi8UR+btOQ4zM/CjNk6WStklci2SKbW8yMXmPgRAVbpTdJGkshgjfS7
+ PQjymIxKpGDjDsWfROgU1vv4NMmn9M8YOA0P5DKYonQW9druc8jOxb99RyJe
+ KqxfYLrX8hm9MbB01DuLWNQlSRXTJC1sRKDHcEi+onJmhMIxYmXMljFHL+6X
+ MY8HZSzg4TFYiEdYPEYyRCLEUoh89Nohlv8C003RZnYEAAA=
+ """
+)
+
+val COMPONENT_ACTIVITY = bytecodeStub(
+ "ComponentActivity.kt",
+ "androidx/activity",
+ 0xd291c9ac,
+ """
+ package androidx.activity
+
+ class ComponentActivity {
+ fun onBackPressed() { }
+ }
+ """,
+ """
+ META-INF/main.kotlin_module:
+ H4sIAAAAAAAA/2NgYGBmYGBgBGJWKM3AZcQlmZiXUpSfmVKhl5hcklmWWVKp
+ l5yfW5BfnCrE65SYnO0BlM9JLfIuEWILSS0u8S7hEuXiBqrQS61IzC3ISYUJ
+ KzFoMQAA3mTzZGMAAAA=
+ """,
+ """
+ androidx/activity/ComponentActivity.class:
+ H4sIAAAAAAAA/41RTW8TMRB9481uyralm7ZAWuCEkGgrsWnFCVClthJSqrQg
+ QLnk5Oxa4Cax0dqJ2lt+C/+AExIHFHHsj0KMQ4QQXGrJb+a98fPH+Prnt+8A
+ nuEh4ZE0ZWV1eZnLwuuJ9lf5iR19skYZf7RQ6iBCdiEnMh9K8yF/3b9Qha8j
+ IiQvtdH+kBA92emuIEaSooY6oeY/akd43LnB/i8Iq9Ycy2LwplLOqZLQ6Ays
+ H2qTnykvS+klrxGjScTXpgAxgQYsXerAWpyV+4S92TRLRVOkIptNU7EUEtGc
+ TQ9Ei47jH58TpqdJFm2LVi1YDoi3w+Z/F3o68PyCE1sqwlpHG3U+HvVV9V72
+ h6ysd2whh11Z6cAXYvrOjqtCvdKBbL0dG69Hqqud5uqRMdZLr61x2IfgBoUh
+ +GjuF2OTWc6RwsN2v2Lpy7y8xZjMxRq2GVd+L8AtpBwbWP5j3gttCfNfY/yX
+ kRZGgftzvIcHHJ+zHv5ttYeojdttrDEiC9BoYx0bPZDDJu70EDukDncdEodl
+ Tn4B9lImlUcCAAA=
+ """
+)
+
+val ON_BACK_PRESSED_DISPATCHER = bytecodeStub(
+ "OnBackPressedDispatcher.kt",
+ "androidx/activity",
+ 0x38be529,
+ """
+ package androidx.activity
+
+ class OnBackPressedDispatcher {
+ fun onBackPressed() { }
+ }
+ """,
+ """
+ META-INF/main.kotlin_module:
+ H4sIAAAAAAAA/2NgYGBmYGBgBGJWKM3AZcQlmZiXUpSfmVKhl5hcklmWWVKp
+ l5yfW5BfnCrE65SYnO0BlM9JLfIuEWILSS0u8S7hEuXiBqrQS61IzC3ISYUJ
+ KzFoMQAA3mTzZGMAAAA=
+ """,
+ """
+ androidx/activity/OnBackPressedDispatcher.class:
+ H4sIAAAAAAAA/41R0UobQRQ9dza7savWjbY1xva9VnCj+GSLoC2FSFqLLXnJ
+ 02R3qGOSWdmZBH3Lt/QP+lTogwQf+1Gld6KICEIH5tx7zp2zM/fun7+/rwDs
+ 4hVhQ5q8LHR+kcrM6bF2l+mxOZRZ/0uprFX5B23PpctOVVkFEZIzOZbpQJrv
+ 6XHvTGWuioAQvdNGu31C8Hqjs4AQUYwKqoSKO9WWsNn+71veEhaL+yVCrd0v
+ 3ECb9JNyMpdO8hkxHAfcAnkICdRn6UJ71uQs3+Y7p5MkFnURi2Q6icWcT0R9
+ OtkRTToMr39ETI+iJGiIZsVbdog/h8Yjz9rqO+7mfZErwlJbG/V5NOyp8pvs
+ DVhZbheZHHRkqT2/FeOvxajM1EftydrJyDg9VB1tNVcPjCmcdLowFtsQPCy/
+ BD+AZ8dYZ5ZyJN/em1+Y+zkrrzFGM7GCBuPCzQE8Qcyxhvk786Yfjt8PjeE9
+ I90aBdZnuIqXHPdY9/9wsYughactLDEi8VBrYRkrXZDFMzzvIrSILV5YRBbz
+ nPwDYUKQQFkCAAA=
+ """
+)
+
+val PREDICTIVE_BACK_HANDLER = LintDetectorTest.bytecode(
+ "libs/predictivebackhandler.jar",
+ LintDetectorTest.kotlin(
+ """
+ package androidx.activity.compose
+
+ import androidx.compose.runtime.Composable
+
+ @Composable
+ fun PredictiveBackHandler(
+ enabled: Boolean = true,
+ onBack: (progress: String) -> Unit) { }
+ """
+ ).indented(),
+ 0x7806fd68,
+ """
+ META-INF/main.kotlin_module:
+ H4sIAAAAAAAA/2NgYGBmYGBgBGJWKM3ApcwlmZiXUpSfmVKhl5hcklmWWVKp
+ l5yfW5BfnCrEFpJaXOJdwiXKxQ0U0kutSMwtyIELKzFoMQAAfOuo51QAAAA=
+ """,
+ """
+ androidx/activity/compose/TestKt.class:
+ H4sIAAAAAAAA/4VTW08TQRT+ZnvblltZRaFcFEEtImxBDQ81JkpCbKyVCPIg
+ 8WHYDnXodpbsTht9MfwNX/0HvhEfDPHRH2U8s20BwYQmPZc535zznXNmf//5
+ 8RPAYzxiuM1VPQxk/ZPLPS07Un92vaB1GETC3RaRfqUzYAz5A97hrs9Vw32z
+ dyA8Ok0wjG2Goi7NNfGCe82XlMoXIcN88X21GWhfKveg03L324owgYrcjZ61
+ Ul7YYdi4EvZ0qXpWeEuHUjXK/SvvlNTlZ3Giu9XTJvrcw7bSsiXc9djne74o
+ M8xVg7DhHgi9F3JJdbhSgebdmrVA19q+T6h0oEw3NnIMM+f4SaVFqLjvVpRh
+ EkkvymCQpuB9FF6zd3+Th7wltJnC/WL14tTKl9tZ2BnEMEZyGEKeISOU4Vpn
+ YO9pN1dNkWH6vyuYr4t93vY1w9rVq6hcpmlIpZDOwcJNhtF+htdC8zrXnOpa
+ rU6CnhAzIkVsm8aw6PyTNFaJrPoKw4eTo5ncyVHOyltdNRCrcYvM4a62rcKT
+ /MlRwSqxVdsmIFmJ1dl8sjDtXHNGS+lf39KDdsaxbdtJ2nYx6yQdwpZSLzOm
+ yCojFnD6FM/P5kb/8HQpNVIUSCrSDPZhGDRCEUU0d01vfblJA0uuB3WKjVSl
+ ErV2a0+E22YjpkTgcX+Hh9L4vcPslmwortsh2ZNvu2+uojoykhR+fva86Ju4
+ GD0l9Q9saEvTGl/zw16B3FbQDj2xIY0z0cuxcyk/VmhVSZifhQmzO/IekFcm
+ 3yKdWXQGjjH6PQYskkzT1NKw8ZDsG10IHFyLU2SQw3WKL8XoDJZ7eJu0S/+s
+ gVPzQD6LMbrO4lprPQ7DU8kvX5FKlguLxxjvliyRTIDZce1hJOKMacptk7SI
+ vgEtYJV0hdKZFgq7SFQwWcEUSUxXMINbFdzG7C5YhDuY20UuQirCfAQnluTe
+ jY17Ee5HKP4FMROs2egEAAA=
+ """
+)