Merge changes I1eb2d81e,I3f39e521 into androidx-main
* changes:
Add @JvmStatic to companion and object class functions.
Move util functions in XProcessingStep to be extension functions.
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
index da39dab..2bd8dca 100644
--- a/.github/workflows/presubmit.yml
+++ b/.github/workflows/presubmit.yml
@@ -84,12 +84,6 @@
id: changed-files
uses: androidx/changed-files-action@main
- - name: Check invalid suppressions
- run: |
- set -x
- echo ${{ steps.changed-files.outputs.files }}
- ./development/checkInvalidSuppress.py -f ${{ steps.changed-files.outputs.files }}
-
- name: "Warn on missing updateApi"
run: |
set -x
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 89fe9cb..2b320a9 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,7 +1,6 @@
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} -c ${REPO_ROOT}/frameworks/support/development/checkstyle/config/support-lib.xml -p development/checkstyle/prebuilt/com.android.support.checkstyle.jar
ktlint_hook = ${REPO_ROOT}/frameworks/support/gradlew -q -p ${REPO_ROOT}/frameworks/support --continue :ktlintCheckFile --file=${PREUPLOAD_FILES_PREFIXED}
-invalid_suppress_hook = ${REPO_ROOT}/frameworks/support/development/checkInvalidSuppress.py ${PREUPLOAD_FILES}
warn_check_api = ${REPO_ROOT}/frameworks/support/development/apilint.py -f ${PREUPLOAD_FILES}
[Builtin Hooks]
diff --git a/activity/activity-compose/src/main/java/androidx/activity/compose/ActivityResultRegistry.kt b/activity/activity-compose/src/main/java/androidx/activity/compose/ActivityResultRegistry.kt
index b53189a..6055636 100644
--- a/activity/activity-compose/src/main/java/androidx/activity/compose/ActivityResultRegistry.kt
+++ b/activity/activity-compose/src/main/java/androidx/activity/compose/ActivityResultRegistry.kt
@@ -150,7 +150,7 @@
* This launcher does not support the [unregister] function. Attempting to use [unregister] will
* result in an [IllegalStateException].
*
- * @param <I> type of the input required to launch
+ * @param I type of the input required to launch
*/
public class ManagedActivityResultLauncher<I, O> internal constructor(
private val launcher: ActivityResultLauncherHolder<I>,
diff --git a/activity/activity/api/current.txt b/activity/activity/api/current.txt
index 9e49deb..4d4be33 100644
--- a/activity/activity/api/current.txt
+++ b/activity/activity/api/current.txt
@@ -160,7 +160,7 @@
method public final Boolean parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+ @RequiresApi(19) public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
ctor public ActivityResultContracts.CreateDocument();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String);
@@ -174,28 +174,28 @@
method public final android.net.Uri? parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
+ @RequiresApi(18) public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
ctor public ActivityResultContracts.GetMultipleContents();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String);
method public final java.util.List<android.net.Uri!> parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+ @RequiresApi(19) public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
ctor public ActivityResultContracts.OpenDocument();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String![]);
method public final android.net.Uri? parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+ @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
ctor public ActivityResultContracts.OpenDocumentTree();
method @CallSuper public android.content.Intent createIntent(android.content.Context, android.net.Uri?);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, android.net.Uri?);
method public final android.net.Uri? parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
+ @RequiresApi(19) public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
ctor public ActivityResultContracts.OpenMultipleDocuments();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String![]);
diff --git a/activity/activity/api/public_plus_experimental_current.txt b/activity/activity/api/public_plus_experimental_current.txt
index 40c43ad..5ae7716 100644
--- a/activity/activity/api/public_plus_experimental_current.txt
+++ b/activity/activity/api/public_plus_experimental_current.txt
@@ -159,7 +159,7 @@
method public final Boolean parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+ @RequiresApi(19) public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
ctor public ActivityResultContracts.CreateDocument();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String);
@@ -173,28 +173,28 @@
method public final android.net.Uri? parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
+ @RequiresApi(18) public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
ctor public ActivityResultContracts.GetMultipleContents();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String);
method public final java.util.List<android.net.Uri!> parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+ @RequiresApi(19) public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
ctor public ActivityResultContracts.OpenDocument();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String![]);
method public final android.net.Uri? parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+ @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
ctor public ActivityResultContracts.OpenDocumentTree();
method @CallSuper public android.content.Intent createIntent(android.content.Context, android.net.Uri?);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, android.net.Uri?);
method public final android.net.Uri? parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
+ @RequiresApi(19) public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
ctor public ActivityResultContracts.OpenMultipleDocuments();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String![]);
diff --git a/activity/activity/api/restricted_current.txt b/activity/activity/api/restricted_current.txt
index 40c43ad..5ae7716 100644
--- a/activity/activity/api/restricted_current.txt
+++ b/activity/activity/api/restricted_current.txt
@@ -159,7 +159,7 @@
method public final Boolean parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+ @RequiresApi(19) public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
ctor public ActivityResultContracts.CreateDocument();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String);
@@ -173,28 +173,28 @@
method public final android.net.Uri? parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
+ @RequiresApi(18) public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
ctor public ActivityResultContracts.GetMultipleContents();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String);
method public final java.util.List<android.net.Uri!> parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+ @RequiresApi(19) public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
ctor public ActivityResultContracts.OpenDocument();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String![]);
method public final android.net.Uri? parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+ @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
ctor public ActivityResultContracts.OpenDocumentTree();
method @CallSuper public android.content.Intent createIntent(android.content.Context, android.net.Uri?);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, android.net.Uri?);
method public final android.net.Uri? parseResult(int, android.content.Intent?);
}
- public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
+ @RequiresApi(19) public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
ctor public ActivityResultContracts.OpenMultipleDocuments();
method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String![]);
diff --git a/activity/activity/lint-baseline.xml b/activity/activity/lint-baseline.xml
deleted file mode 100644
index 420b07e..0000000
--- a/activity/activity/lint-baseline.xml
+++ /dev/null
@@ -1,81 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
- <issue
- id="BanTargetApiAnnotation"
- message="Uses @TargetApi annotation"
- errorLine1=" @TargetApi(18)"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
- line="459"
- column="5"/>
- </issue>
-
- <issue
- id="BanTargetApiAnnotation"
- message="Uses @TargetApi annotation"
- errorLine1=" @TargetApi(19)"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
- line="523"
- column="5"/>
- </issue>
-
- <issue
- id="BanTargetApiAnnotation"
- message="Uses @TargetApi annotation"
- errorLine1=" @TargetApi(19)"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
- line="561"
- column="5"/>
- </issue>
-
- <issue
- id="BanTargetApiAnnotation"
- message="Uses @TargetApi annotation"
- errorLine1=" @TargetApi(21)"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
- line="603"
- column="5"/>
- </issue>
-
- <issue
- id="BanTargetApiAnnotation"
- message="Uses @TargetApi annotation"
- errorLine1=" @TargetApi(19)"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
- line="642"
- column="5"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 16; however, the containing class androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" ClipData clipData = intent.getClipData();"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
- line="497"
- column="40"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 19; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" decor.cancelPendingInputEvents();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/activity/ComponentActivity.java"
- line="236"
- column="35"/>
- </issue>
-
-</issues>
diff --git a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
index 3e81c8c..a0a6e13 100644
--- a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
+++ b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
@@ -61,6 +61,7 @@
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
@@ -233,7 +234,7 @@
Window window = getWindow();
final View decor = window != null ? window.peekDecorView() : null;
if (decor != null) {
- decor.cancelPendingInputEvents();
+ Api19Impl.cancelPendingInputEvents(decor);
}
}
}
@@ -703,4 +704,14 @@
Trace.endSection();
}
}
+
+ @RequiresApi(19)
+ static class Api19Impl {
+ private Api19Impl() { }
+
+ static void cancelPendingInputEvents(View view) {
+ view.cancelPendingInputEvents();
+ }
+
+ }
}
diff --git a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.java b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.java
index 27d34cf..c488640 100644
--- a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.java
+++ b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.java
@@ -20,7 +20,6 @@
import static java.util.Collections.emptyMap;
-import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
@@ -40,6 +39,7 @@
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.collection.ArrayMap;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
@@ -494,7 +494,7 @@
* This can be extended to override {@link #createIntent} if you wish to pass additional
* extras to the Intent created by {@code super.createIntent()}.
*/
- @TargetApi(18)
+ @RequiresApi(18)
public static class GetMultipleContents extends ActivityResultContract<String, List<Uri>> {
@CallSuper
@@ -558,7 +558,7 @@
*
* @see DocumentsContract
*/
- @TargetApi(19)
+ @RequiresApi(19)
public static class OpenDocument extends ActivityResultContract<String[], Uri> {
@CallSuper
@@ -596,7 +596,7 @@
*
* @see DocumentsContract
*/
- @TargetApi(19)
+ @RequiresApi(19)
public static class OpenMultipleDocuments extends ActivityResultContract<String[], List<Uri>> {
@CallSuper
@@ -638,7 +638,7 @@
* @see DocumentsContract#buildDocumentUriUsingTree
* @see DocumentsContract#buildChildDocumentsUriUsingTree
*/
- @TargetApi(21)
+ @RequiresApi(21)
public static class OpenDocumentTree extends ActivityResultContract<Uri, Uri> {
@CallSuper
@@ -677,7 +677,7 @@
* This can be extended to override {@link #createIntent} if you wish to pass additional
* extras to the Intent created by {@code super.createIntent()}.
*/
- @TargetApi(19)
+ @RequiresApi(19)
public static class CreateDocument extends ActivityResultContract<String, Uri> {
@CallSuper
diff --git a/activity/integration-tests/testapp/lint-baseline.xml b/activity/integration-tests/testapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/activity/integration-tests/testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt b/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt
index 09bc3e9..29a4375 100644
--- a/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt
+++ b/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt
@@ -33,6 +33,7 @@
import android.widget.LinearLayout.VERTICAL
import android.widget.Toast
import androidx.activity.ComponentActivity
+import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.CaptureVideo
@@ -70,13 +71,7 @@
toast("Got image: $uri")
}
- val openDocuments = registerForActivityResult(OpenMultipleDocuments()) { uris ->
- var docs = ""
- uris.forEach {
- docs += "uri: $it \n"
- }
- toast("Got documents: $docs")
- }
+ lateinit var openDocuments: ActivityResultLauncher<Array<String>>
val intentSender = registerForActivityResult(
ActivityResultContracts
@@ -88,6 +83,16 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ if (android.os.Build.VERSION.SDK_INT >= 19) {
+ openDocuments = registerForActivityResult(OpenMultipleDocuments()) { uris ->
+ var docs = ""
+ uris.forEach {
+ docs += "uri: $it \n"
+ }
+ toast("Got documents: $docs")
+ }
+ }
+
setContentView {
add(::LinearLayout) {
orientation = VERTICAL
@@ -111,8 +116,10 @@
button("Pick an image") {
getContent.launch("image/*")
}
- button("Open documents") {
- openDocuments.launch(arrayOf("*/*"))
+ if (android.os.Build.VERSION.SDK_INT >= 19) {
+ button("Open documents") {
+ openDocuments.launch(arrayOf("*/*"))
+ }
}
button("Start IntentSender") {
val request = IntentSenderRequest.Builder(
diff --git a/ads/ads-identifier-benchmark/lint-baseline.xml b/ads/ads-identifier-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/ads/ads-identifier-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/ads/ads-identifier-provider/lint-baseline.xml b/ads/ads-identifier-provider/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/ads/ads-identifier-provider/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/ads/ads-identifier-testing/lint-baseline.xml b/ads/ads-identifier-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/ads/ads-identifier-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/ads/ads-identifier/lint-baseline.xml b/ads/ads-identifier/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/ads/ads-identifier/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/appcompat/appcompat-benchmark/lint-baseline.xml b/appcompat/appcompat-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/appcompat/appcompat-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/appcompat/appcompat-lint/lint-baseline.xml b/appcompat/appcompat-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/appcompat/appcompat-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 4cfa8d5..546496f 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -18,7 +18,7 @@
implementation("androidx.collection:collection:1.0.0")
api("androidx.cursoradapter:cursoradapter:1.0.0")
api("androidx.activity:activity:1.2.3")
- api("androidx.fragment:fragment:1.3.3")
+ api("androidx.fragment:fragment:1.3.4")
api(project(":appcompat:appcompat-resources"))
api("androidx.drawerlayout:drawerlayout:1.0.0")
implementation("androidx.lifecycle:lifecycle-runtime:2.3.1")
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index cfe1931..08f06a8 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -592,16 +592,17 @@
if (ab != null) {
ab.onDestroy();
}
+ mActionBar = null;
if (toolbar != null) {
final ToolbarActionBar tbab = new ToolbarActionBar(toolbar, getTitle(),
- mWindow.getCallback());
+ mAppCompatWindowCallback);
mActionBar = tbab;
- mWindow.setCallback(tbab.getWrappedWindowCallback());
+ // Set the nested action bar window callback so that it receive menu events
+ mAppCompatWindowCallback.setActionBarCallback(tbab.mMenuCallback);
} else {
- mActionBar = null;
- // Re-set the original window callback since we may have already set a Toolbar wrapper
- mWindow.setCallback(mAppCompatWindowCallback);
+ // Clear the nested action bar window callback
+ mAppCompatWindowCallback.setActionBarCallback(null);
}
invalidateOptionsMenu();
@@ -3069,12 +3070,28 @@
}
}
+ /**
+ * Interface which allows ToolbarActionBar to receive menu events without
+ * needing to wrap the Window.Callback
+ */
+ interface ActionBarMenuCallback {
+ boolean onPreparePanel(int featureId);
+
+ @Nullable
+ View onCreatePanelView(int featureId);
+ }
class AppCompatWindowCallback extends WindowCallbackWrapper {
+ private ActionBarMenuCallback mActionBarCallback;
+
AppCompatWindowCallback(Window.Callback callback) {
super(callback);
}
+ void setActionBarCallback(@Nullable ActionBarMenuCallback callback) {
+ mActionBarCallback = callback;
+ }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return AppCompatDelegateImpl.this.dispatchKeyEvent(event)
@@ -3098,6 +3115,17 @@
}
@Override
+ public View onCreatePanelView(int featureId) {
+ if (mActionBarCallback != null) {
+ View created = mActionBarCallback.onCreatePanelView(featureId);
+ if (created != null) {
+ return created;
+ }
+ }
+ return super.onCreatePanelView(featureId);
+ }
+
+ @Override
public void onContentChanged() {
// We purposely do not propagate this call as this is called when we install
// our sub-decor rather than the user's content
@@ -3121,7 +3149,13 @@
mb.setOverrideVisibleItems(true);
}
- final boolean handled = super.onPreparePanel(featureId, view, menu);
+ boolean handled = false;
+ if (mActionBarCallback != null && mActionBarCallback.onPreparePanel(featureId)) {
+ handled = true;
+ }
+ if (!handled) {
+ handled = super.onPreparePanel(featureId, view, menu);
+ }
if (mb != null) {
mb.setOverrideVisibleItems(false);
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/ToolbarActionBar.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/ToolbarActionBar.java
index f898efc..d2565a5 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/ToolbarActionBar.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/ToolbarActionBar.java
@@ -32,20 +32,21 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.appcompat.view.WindowCallbackWrapper;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.view.menu.MenuPresenter;
import androidx.appcompat.widget.DecorToolbar;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.widget.ToolbarWidgetWrapper;
+import androidx.core.util.Preconditions;
import androidx.core.view.ViewCompat;
import java.util.ArrayList;
class ToolbarActionBar extends ActionBar {
- DecorToolbar mDecorToolbar;
+ final DecorToolbar mDecorToolbar;
+ final Window.Callback mWindowCallback;
+ final AppCompatDelegateImpl.ActionBarMenuCallback mMenuCallback;
boolean mToolbarMenuPrepared;
- Window.Callback mWindowCallback;
private boolean mMenuCallbackSet;
private boolean mLastMenuVisibility;
@@ -66,16 +67,17 @@
}
};
- ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
+ ToolbarActionBar(@NonNull Toolbar toolbar, @Nullable CharSequence title,
+ @NonNull Window.Callback windowCallback) {
+ Preconditions.checkNotNull(toolbar);
mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
- mWindowCallback = new ToolbarCallbackWrapper(windowCallback);
- mDecorToolbar.setWindowCallback(mWindowCallback);
+
+ mWindowCallback = Preconditions.checkNotNull(windowCallback);
+ mDecorToolbar.setWindowCallback(windowCallback);
toolbar.setOnMenuItemClickListener(mMenuClicker);
mDecorToolbar.setWindowTitle(title);
- }
- public Window.Callback getWrappedWindowCallback() {
- return mWindowCallback;
+ mMenuCallback = new ToolbarMenuCallback();
}
@Override
@@ -513,19 +515,18 @@
}
}
- private class ToolbarCallbackWrapper extends WindowCallbackWrapper {
- public ToolbarCallbackWrapper(Window.Callback wrapped) {
- super(wrapped);
- }
+ private class ToolbarMenuCallback implements AppCompatDelegateImpl.ActionBarMenuCallback {
+ ToolbarMenuCallback() {}
@Override
- public boolean onPreparePanel(int featureId, View view, Menu menu) {
- final boolean result = super.onPreparePanel(featureId, view, menu);
- if (result && !mToolbarMenuPrepared) {
+ public boolean onPreparePanel(int featureId) {
+ if (featureId == Window.FEATURE_OPTIONS_PANEL && !mToolbarMenuPrepared) {
mDecorToolbar.setMenuPrepared();
mToolbarMenuPrepared = true;
+ // We don't want the stop the AppCompat/Window receiving the event, so don't
+ // return true
}
- return result;
+ return false;
}
@Override
@@ -536,7 +537,7 @@
// preparing a default one.
return new View(mDecorToolbar.getContext());
}
- return super.onCreatePanelView(featureId);
+ return null;
}
}
@@ -557,11 +558,8 @@
@Override
public boolean onOpenSubMenu(@NonNull MenuBuilder subMenu) {
- if (mWindowCallback != null) {
- mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, subMenu);
- return true;
- }
- return false;
+ mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, subMenu);
+ return true;
}
@Override
@@ -572,9 +570,7 @@
mClosingActionMenu = true;
mDecorToolbar.dismissPopupMenus();
- if (mWindowCallback != null) {
- mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
- }
+ mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
mClosingActionMenu = false;
}
}
@@ -591,13 +587,10 @@
@Override
public void onMenuModeChange(@NonNull MenuBuilder menu) {
- if (mWindowCallback != null) {
- if (mDecorToolbar.isOverflowMenuShowing()) {
- mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
- } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL,
- null, menu)) {
- mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
- }
+ if (mDecorToolbar.isOverflowMenuShowing()) {
+ mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
+ } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
+ mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
}
}
}
diff --git a/appcompat/integration-tests/receive-content-testapp/lint-baseline.xml b/appcompat/integration-tests/receive-content-testapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/appcompat/integration-tests/receive-content-testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/appsearch/appsearch/lint-baseline.xml b/appsearch/appsearch/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/appsearch/appsearch/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GenericDocumentCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GenericDocumentCtsTest.java
index 164adad..4fa033a 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GenericDocumentCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GenericDocumentCtsTest.java
@@ -22,6 +22,7 @@
import androidx.appsearch.app.GenericDocument;
+import org.junit.Ignore;
import org.junit.Test;
public class GenericDocumentCtsTest {
@@ -174,6 +175,7 @@
.containsExactly(sDocumentProperties1, sDocumentProperties2);
}
+ @Ignore
@Test
public void testDocument_toString() {
GenericDocument document = new GenericDocument.Builder<>("uri1", "schemaType1")
diff --git a/appsearch/compiler/build.gradle b/appsearch/compiler/build.gradle
index 7d67114..6a5f3d9 100644
--- a/appsearch/compiler/build.gradle
+++ b/appsearch/compiler/build.gradle
@@ -36,7 +36,7 @@
testImplementation(GOOGLE_COMPILE_TESTING)
}
-tasks.findByName('compileJava').dependsOn(":appsearch:appsearch:jarDebug")
+tasks.findByName('compileJava').dependsOn(":appsearch:appsearch:jarRelease")
androidx {
name = 'AndroidX AppSearch Compiler'
diff --git a/appsearch/local-storage/lint-baseline.xml b/appsearch/local-storage/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/appsearch/local-storage/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/asynclayoutinflater/asynclayoutinflater/lint-baseline.xml b/asynclayoutinflater/asynclayoutinflater/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/asynclayoutinflater/asynclayoutinflater/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/gradle-plugin/lint-baseline.xml b/benchmark/gradle-plugin/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/benchmark/gradle-plugin/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/integration-tests/crystalball-experiment/lint-baseline.xml b/benchmark/integration-tests/crystalball-experiment/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/benchmark/integration-tests/crystalball-experiment/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/integration-tests/dry-run-benchmark/lint-baseline.xml b/benchmark/integration-tests/dry-run-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/benchmark/integration-tests/dry-run-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/integration-tests/macrobenchmark/build.gradle b/benchmark/integration-tests/macrobenchmark/build.gradle
index 7ce9cba..3067b8a 100644
--- a/benchmark/integration-tests/macrobenchmark/build.gradle
+++ b/benchmark/integration-tests/macrobenchmark/build.gradle
@@ -37,6 +37,7 @@
dependencies {
androidTestImplementation(project(":benchmark:benchmark-junit4"))
androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+ androidTestImplementation(project(":internal-testutils-macrobenchmark"))
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/MacrobenchUtils.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/MacrobenchUtils.kt
deleted file mode 100644
index 8a3b2aa..0000000
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/MacrobenchUtils.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2020 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.benchmark.integration.macrobenchmark
-
-import android.content.Intent
-import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.StartupMode
-import androidx.benchmark.macro.StartupTimingMetric
-import androidx.benchmark.macro.isSupportedWithVmSettings
-import androidx.benchmark.macro.junit4.MacrobenchmarkRule
-
-const val TARGET_PACKAGE = "androidx.benchmark.integration.macrobenchmark.target"
-
-fun MacrobenchmarkRule.measureStartup(
- compilationMode: CompilationMode,
- startupMode: StartupMode,
- iterations: Int = 3,
- setupIntent: Intent.() -> Unit = {}
-) = measureRepeated(
- packageName = TARGET_PACKAGE,
- metrics = listOf(StartupTimingMetric()),
- compilationMode = compilationMode,
- iterations = iterations,
- startupMode = startupMode
-) {
- pressHome()
- val intent = Intent()
- intent.setPackage(TARGET_PACKAGE)
- setupIntent(intent)
- startActivityAndWait(intent)
-}
-
-fun createStartupCompilationParams(
- startupModes: List<StartupMode> = listOf(StartupMode.HOT, StartupMode.WARM, StartupMode.COLD),
- compilationModes: List<CompilationMode> = listOf(
- CompilationMode.None,
- CompilationMode.Interpreted,
- CompilationMode.SpeedProfile()
- )
-): List<Array<Any>> = mutableListOf<Array<Any>>().apply {
- for (startupMode in startupModes) {
- for (compilationMode in compilationModes) {
- // Skip configs that can't run, so they don't clutter Studio benchmark
- // output with AssumptionViolatedException dumps
- if (compilationMode.isSupportedWithVmSettings()) {
- add(arrayOf(startupMode, compilationMode))
- }
- }
- }
-}
-
-fun createCompilationParams(
- compilationModes: List<CompilationMode> = listOf(
- CompilationMode.None,
- CompilationMode.Interpreted,
- CompilationMode.SpeedProfile()
- )
-): List<Array<Any>> = mutableListOf<Array<Any>>().apply {
- for (compilationMode in compilationModes) {
- // Skip configs that can't run, so they don't clutter Studio benchmark
- // output with AssumptionViolatedException dumps
- if (compilationMode.isSupportedWithVmSettings()) {
- add(arrayOf(compilationMode))
- }
- }
-}
\ No newline at end of file
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/SmallListStartupBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/SmallListStartupBenchmark.kt
index 553c577..98b8cfb20 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/SmallListStartupBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/SmallListStartupBenchmark.kt
@@ -20,6 +20,8 @@
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,7 +39,8 @@
@Test
fun startup() = benchmarkRule.measureStartup(
compilationMode = compilationMode,
- startupMode = startupMode
+ startupMode = startupMode,
+ packageName = "androidx.benchmark.integration.macrobenchmark.target"
) {
action = "androidx.benchmark.integration.macrobenchmark.target.RECYCLER_VIEW"
putExtra("ITEM_COUNT", 5)
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBenchmark.kt
index 62ede92..39efe29 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBenchmark.kt
@@ -26,6 +26,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
+import androidx.testutils.createCompilationParams
import org.junit.Before
import org.junit.Rule
import org.junit.Test
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBenchmark.kt
index 29d9be3..856badf 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBenchmark.kt
@@ -20,6 +20,8 @@
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,7 +39,8 @@
@Test
fun startup() = benchmarkRule.measureStartup(
compilationMode = compilationMode,
- startupMode = startupMode
+ startupMode = startupMode,
+ packageName = "androidx.benchmark.integration.macrobenchmark.target"
) {
action = "androidx.benchmark.integration.macrobenchmark.target.TRIVIAL_STARTUP_ACTIVITY"
}
diff --git a/benchmark/integration-tests/startup-benchmark/lint-baseline.xml b/benchmark/integration-tests/startup-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/benchmark/integration-tests/startup-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/macro/src/main/java/androidx/benchmark/macro/perfetto/UiState.kt b/benchmark/macro/src/main/java/androidx/benchmark/macro/perfetto/UiState.kt
index 9aa1837..9c583ae 100644
--- a/benchmark/macro/src/main/java/androidx/benchmark/macro/perfetto/UiState.kt
+++ b/benchmark/macro/src/main/java/androidx/benchmark/macro/perfetto/UiState.kt
@@ -16,11 +16,9 @@
package androidx.benchmark.macro.perfetto
-import android.util.Log
-import androidx.benchmark.macro.TAG
-import perfetto.protos.UiState
import perfetto.protos.Trace
import perfetto.protos.TracePacket
+import perfetto.protos.UiState
import java.io.File
/**
@@ -40,10 +38,7 @@
)
internal fun File.appendUiState(state: UiState) {
- val origBytes = readBytes()
- val trace = Trace.ADAPTER.decode(origBytes)
+ val trace = Trace.ADAPTER.decode(inputStream())
val appendedTrace = Trace(packet = trace.packet + listOf(TracePacket(ui_state = state)))
- val modifiedBytes = Trace.ADAPTER.encode(appendedTrace)
- writeBytes(modifiedBytes)
- Log.d(TAG, "Appended UiState $this, from ${origBytes.size} -> ${modifiedBytes.size} bytes")
+ Trace.ADAPTER.encode(outputStream(), appendedTrace)
}
diff --git a/biometric/biometric-ktx/lint-baseline.xml b/biometric/biometric-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/biometric/biometric-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/biometric/biometric/lint-baseline.xml b/biometric/biometric/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/biometric/biometric/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/biometric/integration-tests/testapp/lint-baseline.xml b/biometric/integration-tests/testapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/biometric/integration-tests/testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/buildSrc-tests/lint-baseline.xml b/buildSrc-tests/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/buildSrc-tests/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
index d7144494..243bf52 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
@@ -53,7 +53,6 @@
tasks.register("listAndroidXProperties", ListAndroidXPropertiesTask::class.java)
setDependencyVersions()
configureKtlintCheckFile()
- configureCheckInvalidSuppress()
tasks.register(CheckExternalDependencyLicensesTask.TASK_NAME)
val buildOnServerTask = tasks.create(
@@ -112,7 +111,7 @@
)
)
project.plugins.withType(AndroidBasePlugin::class.java) {
- buildOnServerTask.dependsOn("${project.path}:assembleDebug")
+ buildOnServerTask.dependsOn("${project.path}:assembleRelease")
if (!project.usingMaxDepVersions()) {
project.afterEvaluate {
project.agpVariants.all { variant ->
@@ -159,7 +158,7 @@
buildOnServerTask.dependsOn(Jacoco.createUberJarTask(this))
}
- val zipTestConfigsWithApks = project.tasks.register(
+ project.tasks.register(
ZIP_TEST_CONFIGS_WITH_APKS_TASK, Zip::class.java
) {
it.destinationDirectory.set(project.getDistributionDirectory())
@@ -168,7 +167,7 @@
// We're mostly zipping a bunch of .apk files that are already compressed
it.entryCompression = ZipEntryCompression.STORED
}
- val zipConstrainedTestConfigsWithApks = project.tasks.register(
+ project.tasks.register(
ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK, Zip::class.java
) {
it.destinationDirectory.set(project.getDistributionDirectory())
@@ -177,8 +176,6 @@
// We're mostly zipping a bunch of .apk files that are already compressed
it.entryCompression = ZipEntryCompression.STORED
}
- buildOnServerTask.dependsOn(zipTestConfigsWithApks)
- buildOnServerTask.dependsOn(zipConstrainedTestConfigsWithApks)
AffectedModuleDetector.configure(gradle, this)
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
index 06ba8d8..f0f382f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
@@ -113,9 +113,16 @@
return properties.get(COMPOSE_MPP_ENABLED)?.toString()?.toBoolean() ?: false
}
+ /**
+ * @param isMultiplatformEnabled whether this module has a corresponding
+ * multiplatform configuration, or whether it is Android only
+ */
@JvmStatic
- fun Project.applyAndConfigureKotlinPlugin() {
- if (isMultiplatformEnabled()) {
+ @JvmOverloads
+ fun Project.applyAndConfigureKotlinPlugin(
+ isMultiplatformEnabled: Boolean = isMultiplatformEnabled()
+ ) {
+ if (isMultiplatformEnabled) {
apply(plugin = "kotlin-multiplatform")
} else {
apply(plugin = "org.jetbrains.kotlin.android")
@@ -123,7 +130,7 @@
configureManifests()
configureForKotlinMultiplatformSourceStructure()
- if (isMultiplatformEnabled()) {
+ if (isMultiplatformEnabled) {
configureForMultiplatform()
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index 4e99f61..c42f8b5 100644
--- a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -65,7 +65,7 @@
val errorProneConfiguration = createErrorProneConfiguration()
variants.all { variant ->
// Using getName() instead of name due to b/150427408
- if (variant.buildType.getName() == BuilderConstants.DEBUG) {
+ if (variant.buildType.getName() == BuilderConstants.RELEASE) {
val task = variant.javaCompileProvider
(variant as BaseVariant).annotationProcessorConfiguration.extendsFrom(
errorProneConfiguration
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
index 9f63528..4ce2920 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
@@ -53,6 +53,7 @@
val EXIFINTERFACE = LibraryGroup("androidx.exifinterface", LibraryVersions.EXIFINTERFACE)
val FRAGMENT = LibraryGroup("androidx.fragment", LibraryVersions.FRAGMENT)
val GRIDLAYOUT = LibraryGroup("androidx.gridlayout", LibraryVersions.GRIDLAYOUT)
+ val HEALTH = LibraryGroup("androidx.health", null)
val HEIFWRITER = LibraryGroup("androidx.heifwriter", LibraryVersions.HEIFWRITER)
val HILT = LibraryGroup("androidx.hilt", null)
val INSPECTION = LibraryGroup("androidx.inspection", LibraryVersions.INSPECTION)
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 0b8fffe..b238e0c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -52,7 +52,7 @@
val CORE_ANIMATION = Version("1.0.0-alpha03")
val CORE_ANIMATION_TESTING = Version("1.0.0-alpha03")
val CORE_APPDIGEST = Version("1.0.0-alpha01")
- val CORE_GOOGLE_SHORTCUTS = Version("1.0.0-alpha04")
+ val CORE_GOOGLE_SHORTCUTS = Version("1.0.0-beta01")
val CORE_ROLE = Version("1.1.0-alpha02")
val CURSORADAPTER = Version("1.1.0-alpha01")
val CUSTOMVIEW = Version("1.2.0-alpha01")
@@ -68,8 +68,9 @@
val FRAGMENT = Version("1.4.0-alpha01")
val FUTURES = Version("1.2.0-alpha01")
val GRIDLAYOUT = Version("1.1.0-alpha01")
+ val HEALTH_SERVICES_CLIENT = Version("1.0.0-alpha01")
val HEIFWRITER = Version("1.1.0-alpha02")
- val HILT = Version("1.0.0-beta01")
+ val HILT = Version("1.1.0-alpha01")
val HILT_NAVIGATION_COMPOSE = Version("1.0.0-alpha02")
val INSPECTION = Version("1.0.0")
val INTERPOLATOR = Version("1.1.0-alpha01")
@@ -81,7 +82,7 @@
val LEGACY = Version("1.1.0-alpha01")
val LOCALBROADCASTMANAGER = Version("1.1.0-alpha02")
val LIFECYCLE = Version("2.4.0-alpha01")
- val LIFECYCLE_VIEWMODEL_COMPOSE = Version("1.0.0-alpha04")
+ val LIFECYCLE_VIEWMODEL_COMPOSE = Version("1.0.0-alpha05")
val LIFECYCLE_EXTENSIONS = Version("2.2.0")
val LOADER = Version("1.2.0-alpha01")
val MEDIA = Version("1.4.0-alpha02")
diff --git a/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
index bf9cff9..08e2e98 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -195,22 +195,41 @@
// be able to burn down existing violations. That's hard to enforce, though, so we'll
// generally allow teams to update their baseline files with a publicly-known flag.
if (updateLintBaseline) {
- // Continue generating baselines regardless of errors
+ // Continue generating baselines regardless of errors.
isAbortOnError = false
- // Avoid printing every single lint error to the terminal
+ // Avoid printing every single lint error to the terminal.
textReport = false
- val lintDebugTask = tasks.named("lintDebug")
- lintDebugTask.configure {
- it.doFirst {
- lintBaseline.delete()
+
+ listOf(
+ tasks.named("lintDebug"),
+ tasks.named("lint"),
+ ).forEach { task ->
+ task.configure {
+ // Delete any existing baseline so that we clear old obsolete entries.
+ it.doFirst {
+ lintBaseline.delete()
+ }
+
+ // Delete empty generated baselines because they are annoying.
+ it.doLast {
+ if (lintBaseline.exists()) {
+ val hasAnyIssues = lintBaseline.reader().useLines { lines ->
+ lines.any { line ->
+ line.endsWith("<issue")
+ }
+ }
+ if (!hasAnyIssues) {
+ // Using println is consistent with lint's own output.
+ println(
+ "Removed empty baseline file ${lintBaseline.absolutePath}"
+ )
+ lintBaseline.delete()
+ }
+ }
+ }
}
}
- val lintTask = tasks.named("lint")
- lintTask.configure {
- it.doFirst {
- lintBaseline.delete()
- }
- }
+
// Continue running after errors or after creating a new, blank baseline file.
System.setProperty(LINT_BASELINE_CONTINUE, "true")
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/checkInvalidSuppress.kt b/buildSrc/src/main/kotlin/androidx/build/checkInvalidSuppress.kt
deleted file mode 100644
index bb46d82..0000000
--- a/buildSrc/src/main/kotlin/androidx/build/checkInvalidSuppress.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2019 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
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.GradleException
-import org.gradle.api.Project
-import org.gradle.api.tasks.Internal
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.options.Option
-import java.io.File
-
-// first level line filter - we ignore lines that don't contain the following for speed
-const val ROOT_MATCH = "noinspection"
-// Map of invalid pattern to correct replacement
-// These could be regex for fancy whitespace matching, but don't seem to need in practice
-val MATCHERS = mapOf(
- "//noinspection deprecation" to "@SuppressWarnings(\"deprecation\")",
- "//noinspection unchecked" to "@SuppressWarnings(\"unchecked\")"
-)
-
-open class CheckInvalidSuppressTask : DefaultTask() {
-
- @Option(option = "files", description = "List of files to check")
- @Internal
- lateinit var filenamesOption: String
-
- @Option(
- option = "format",
- description = "Use --format to auto-correct invalid suppressions"
- )
- @Internal
- var autocorrectOption = false
-
- // Check a line for any invalid suppressions, and return it if found
- fun getInvalidSuppression(line: String): String {
- MATCHERS.keys.forEach { bad ->
- if (line.trimStart().startsWith(bad)) {
- return bad
- }
- }
- return ""
- }
-
- // Get report for line with invalid suppression
- fun getReportForLine(filename: String, i: Int, lines: List<String>, good: String): String {
- var context = ""
- for (index in 0..2) {
- if (index + i >= lines.size) break
- context += lines[index + i] + "\n"
- }
- return "\n%s:%d:\nError: unsupported comment suppression\n%sInstead, use: %s\n"
- .format(
- filename, i, context, good
- )
- }
-
- @TaskAction
- fun checkForInvalidSuppression() {
- val filenames = filenamesOption.split(" ")
- var report = ""
-
- for (filename in filenames) {
- // suppress comments ignored in kotlin, but may as well block there too
- if (!filename.endsWith(".java") && !filename.endsWith(".kt"))
- continue
-
- // check file exists
- val file = File(filename)
- if (!file.exists())
- continue
-
- val lines = file.readLines(Charsets.UTF_8).toMutableList()
- for ((i, line) in lines.withIndex()) {
- if (line.contains(ROOT_MATCH)) {
- val bad = getInvalidSuppression(line)
- if (bad != "") {
- if (autocorrectOption) {
- if (line.trimStart() == bad) {
- lines[i] = line.replaceFirst(bad, MATCHERS[bad]!!)
- } else {
- lines[i] = line.replaceFirst(bad, MATCHERS[bad]!! + " // ")
- }
- file.writeText(lines.joinToString(System.lineSeparator()))
- } else {
- report += getReportForLine(filename, i, lines, MATCHERS[bad]!!)
- }
- }
- }
- }
- }
- if (report != "") {
- throw GradleException(
- "Invalid, IDEA-specific warning suppression found. These cause " +
- "warnings during compilation." + "\n" + report
- )
- }
- }
-}
-
-fun Project.configureCheckInvalidSuppress() {
- tasks.register("checkInvalidSuppress", CheckInvalidSuppressTask::class.java) { task ->
- task.description = "Task used to find IDEA-specific warning suppressions that do not " +
- "work for command line builds."
- task.group = "Verification"
- }
-}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 1173c37..273ff17 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -477,7 +477,15 @@
setOf(
":compose:integration-tests:macrobenchmark",
":compose:integration-tests:macrobenchmark-target"
- ), // link compose's macrobenchmark and its target
+ ),
+ setOf(
+ ":emoji2:integration-tests:init-disabled-macrobenchmark",
+ ":emoji2:integration-tests:init-disabled-macrobenchmark-target",
+ ),
+ setOf(
+ ":emoji2:integration-tests:init-enabled-macrobenchmark",
+ ":emoji2:integration-tests:init-enabled-macrobenchmark-target",
+ ),
)
}
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
index 9d33f6f..9b6e95d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
@@ -562,7 +562,7 @@
}
}
-private const val DACKKA_DEPENDENCY = "com.google.devsite:dackka:0.0.3"
+private const val DACKKA_DEPENDENCY = "com.google.devsite:dackka:0.0.4"
private const val DOCLAVA_DEPENDENCY = "com.android:doclava:1.0.6"
// Allowlist for directories that should be processed by Dackka
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
index bc5cd67..17c29c3 100644
--- a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
@@ -128,8 +128,6 @@
configBuilder.isBenchmark(true)
if (configBuilder.isPostsubmit) {
configBuilder.tag("microbenchmarks")
- } else {
- configBuilder.tag("microbenchmarks_presubmit")
}
} else if (testProjectPath.get().endsWith("macrobenchmark")) {
configBuilder.tag("macrobenchmarks")
diff --git a/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt b/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
index 53863ec..79c3b66 100644
--- a/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
@@ -37,8 +37,8 @@
const val RECORD_FLAG_NAME = VERIFY_UP_TO_DATE
// Temporary set of exempt tasks that are known to still be out-of-date after running once
-// Entries in this set may be task names (like assembleDebug) or task paths
-// (like :core:core:assembleDebug)
+// Entries in this set may be task names (like assembleRelease) or task paths
+// (like :core:core:assembleRelease)
// Entries in this set do still get rerun because they might produce files that are needed by
// subsequent tasks
val ALLOW_RERUNNING_TASKS = setOf(
diff --git a/busytown/androidx_max_dep_versions.sh b/busytown/androidx_max_dep_versions.sh
index 65717ed..72c6665 100755
--- a/busytown/androidx_max_dep_versions.sh
+++ b/busytown/androidx_max_dep_versions.sh
@@ -5,7 +5,7 @@
cd "$(dirname $0)"
-impl/build.sh assembleDebug assembleAndroidTest \
+impl/build.sh assembleRelease assembleAndroidTest \
-Pandroidx.useMaxDepVersions \
"$@"
diff --git a/busytown/impl/build.sh b/busytown/impl/build.sh
index 5da0420..34d8330 100755
--- a/busytown/impl/build.sh
+++ b/busytown/impl/build.sh
@@ -3,6 +3,12 @@
# This script runs frameworks/support/gradlew
+function showDiskStats() {
+ echo "df -h"
+ df -h
+}
+showDiskStats
+
# find script
SCRIPT_DIR="$(cd $(dirname $0) && pwd)"
@@ -38,6 +44,7 @@
# Put each argument on its own line because some arguments may be long.
# Also put "\" at the end of non-final lines so the command can be copy-pasted
echo "$*" | sed 's/ / \\\n/g' | sed 's/^/ /' >&2
+ showDiskStats
return 1
fi
}
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index 2c57aeb..e14d471 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -38,6 +38,7 @@
)
dependencies {
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.concurrent:concurrent-listenablefuture-callback:1.0.0-beta01")
bundleInside(project(path: ":camera:camera-camera2-pipe", configuration: "exportRelease"))
diff --git a/camera/camera-camera2-pipe-integration/lint-baseline.xml b/camera/camera-camera2-pipe-integration/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/camera/camera-camera2-pipe-integration/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt
new file mode 100644
index 0000000..d038a85
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.integration.impl
+
+import android.graphics.ImageFormat
+import android.graphics.SurfaceTexture
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice
+import android.os.Build
+import android.util.Size
+import android.view.Surface
+import androidx.camera.camera2.pipe.core.Log.error
+import androidx.camera.camera2.pipe.integration.adapter.CameraUseCaseAdapter
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.Config
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.ImageInputConfig
+import androidx.camera.core.impl.ImmediateSurface
+import androidx.camera.core.impl.MutableOptionsBundle
+import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.UseCaseConfig.OPTION_SESSION_CONFIG_UNPACKER
+import androidx.camera.core.impl.UseCaseConfigFactory
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.core.util.Consumer
+
+private val DEFAULT_PREVIEW_SIZE = Size(0, 0)
+
+/**
+ * A [UseCase] used to issue repeating requests when only [androidx.camera.core.ImageCapture] is
+ * enabled, since taking a picture may require a repeating surface to perform pre-capture checks,
+ * mainly around 3A.
+ */
+class MeteringRepeating(
+ private val cameraProperties: CameraProperties,
+ config: MeteringRepeatingConfig,
+) : UseCase(config) {
+
+ private val meteringSurfaceSize = cameraProperties.getMinimumPreviewSize()
+ private var deferrableSurface: DeferrableSurface? = null
+
+ override fun getDefaultConfig(applyDefaultConfig: Boolean, factory: UseCaseConfigFactory) =
+ Builder(cameraProperties).useCaseConfig
+
+ override fun getUseCaseConfigBuilder(config: Config) = Builder(cameraProperties)
+
+ override fun onSuggestedResolutionUpdated(suggestedResolution: Size): Size {
+ updateSessionConfig(createPipeline().build())
+ notifyActive()
+ return meteringSurfaceSize
+ }
+
+ override fun onDetached() {
+ deferrableSurface?.close()
+ deferrableSurface = null
+ }
+
+ private fun createPipeline(): SessionConfig.Builder {
+ val surfaceTexture = SurfaceTexture(0).apply {
+ setDefaultBufferSize(meteringSurfaceSize.width, meteringSurfaceSize.height)
+ }
+ val surface = Surface(surfaceTexture)
+
+ deferrableSurface?.close()
+ deferrableSurface = ImmediateSurface(surface)
+ deferrableSurface!!.terminationFuture
+ .addListener(
+ {
+ surface.release()
+ surfaceTexture.release()
+ },
+ CameraXExecutors.directExecutor()
+ )
+
+ return SessionConfig.Builder
+ .createFrom(MeteringRepeatingConfig())
+ .apply {
+ setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
+ addSurface(deferrableSurface!!)
+ addErrorListener { _, _ ->
+ updateSessionConfig(createPipeline().build())
+ notifyReset()
+ }
+ }
+ }
+
+ private fun CameraProperties.getMinimumPreviewSize(): Size {
+ val map = metadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
+ if (map == null) {
+ error { "Can not retrieve SCALER_STREAM_CONFIGURATION_MAP." }
+ return DEFAULT_PREVIEW_SIZE
+ }
+
+ val outputSizes = if (Build.VERSION.SDK_INT < 23) {
+ // ImageFormat.PRIVATE is only public after Android level 23. Therefore, use
+ // SurfaceTexture.class to get the supported output sizes before Android level 23.
+ map.getOutputSizes(SurfaceTexture::class.java)
+ } else {
+ map.getOutputSizes(ImageFormat.PRIVATE)
+ }
+
+ if (outputSizes == null) {
+ error { "Can not get output size list." }
+ return DEFAULT_PREVIEW_SIZE
+ }
+
+ check(outputSizes.isNotEmpty()) { "Output sizes empty" }
+ return outputSizes.minWithOrNull { size1, size2 -> size1.area().compareTo(size2.area()) }!!
+ }
+
+ class MeteringRepeatingConfig : UseCaseConfig<MeteringRepeating>, ImageInputConfig {
+ private val config = MutableOptionsBundle.create().apply {
+ insertOption(
+ OPTION_SESSION_CONFIG_UNPACKER,
+ CameraUseCaseAdapter.DefaultSessionOptionsUnpacker
+ )
+ }
+
+ override fun getConfig() = config
+
+ override fun getInputFormat() = ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+ }
+
+ class Builder(private val cameraProperties: CameraProperties) :
+ UseCaseConfig.Builder<MeteringRepeating, MeteringRepeatingConfig, Builder> {
+
+ override fun getMutableConfig() = MutableOptionsBundle.create()
+
+ override fun getUseCaseConfig() = MeteringRepeatingConfig()
+
+ override fun setTargetClass(targetClass: Class<MeteringRepeating>) = this
+
+ override fun setTargetName(targetName: String) = this
+
+ override fun setUseCaseEventCallback(eventCallback: EventCallback) = this
+
+ override fun setDefaultSessionConfig(sessionConfig: SessionConfig) = this
+
+ override fun setDefaultCaptureConfig(captureConfig: CaptureConfig) = this
+
+ override fun setSessionOptionUnpacker(optionUnpacker: SessionConfig.OptionUnpacker) = this
+
+ override fun setCaptureOptionUnpacker(optionUnpacker: CaptureConfig.OptionUnpacker) = this
+
+ override fun setSurfaceOccupancyPriority(priority: Int) = this
+
+ override fun setCameraSelector(cameraSelector: CameraSelector) = this
+
+ override fun setAttachedUseCasesUpdateListener(
+ attachedUseCasesUpdateListener: Consumer<MutableCollection<UseCase>>
+ ): Builder = this
+
+ override fun build(): MeteringRepeating {
+ return MeteringRepeating(cameraProperties, useCaseConfig)
+ }
+ }
+}
diff --git a/camera/camera-camera2-pipe-testing/lint-baseline.xml b/camera/camera-camera2-pipe-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/camera/camera-camera2-pipe-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraFilter.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraFilter.java
index 960447f..c2c7f15 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraFilter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraFilter.java
@@ -17,6 +17,11 @@
package androidx.camera.core;
import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.impl.CameraConfig;
+import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
+
+import com.google.auto.value.AutoValue;
import java.util.List;
@@ -43,4 +48,47 @@
*/
@NonNull
List<CameraInfo> filter(@NonNull List<CameraInfo> cameraInfos);
+
+ /**
+ * Returns the id of this camera filter.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @NonNull
+ default Id getId() {
+ return Id.DEFAULT;
+ }
+
+ /**
+ * An identifier for a {@link CameraFilter}.
+ *
+ * <p>A camera filter can be associated with a set of camera configuration options. This
+ * means a camera filter's {@link Id} can be mapped to a unique {@link CameraConfig}. An
+ * example of this is extension modes, where a camera filter can represent an extension mode,
+ * and each extension mode adds a set of camera configurations to the camera that supports it
+ * . {@link ExtendedCameraConfigProviderStore#getConfig(Object)} allows retrieving the
+ * {@link CameraConfig} of an extension mode given the {@link CameraFilter.Id} of its camera
+ * filter.
+ *
+ * <p>The {@link Id} class allows anything to be wrapped as an {@link Id} instance. The
+ * caller should make the input value object unique when calling the {@link #create(Object)}
+ * function. So that the {@link Id} can be recognized and used for specific purposes.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @AutoValue
+ abstract class Id {
+
+ public static final Id DEFAULT = create(new Object());
+
+ @NonNull
+ public static Id create(@NonNull Object value) {
+ return new AutoValue_CameraFilter_Id(value);
+ }
+
+ @NonNull
+ public abstract Object getValue();
+ }
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index 41a8fb4..77cbe6f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -40,6 +40,7 @@
import androidx.camera.core.impl.CameraThreadConfig;
import androidx.camera.core.impl.CameraValidator;
import androidx.camera.core.impl.UseCaseConfigFactory;
+import androidx.camera.core.impl.utils.ContextUtil;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.FutureChain;
@@ -395,7 +396,7 @@
} else {
// Try to retrieve the CameraXConfig.Provider through the application's resources
try {
- Resources resources = getApplicationContext(context).getResources();
+ Resources resources = ContextUtil.getApplicationContext(context).getResources();
String defaultProviderClassName =
resources.getString(
R.string.androidx_camera_default_config_provider);
@@ -430,7 +431,7 @@
@Nullable
private static Application getApplicationFromContext(@NonNull Context context) {
Application application = null;
- Context appContext = getApplicationContext(context);
+ Context appContext = ContextUtil.getApplicationContext(context);
while (appContext instanceof ContextWrapper) {
if (appContext instanceof Application) {
application = (Application) appContext;
@@ -443,18 +444,6 @@
}
/**
- * Gets the application context and preserves the attribution tag.
- */
- private static Context getApplicationContext(@NonNull Context context) {
- Context applicationContext = context.getApplicationContext();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- return applicationContext.createAttributionContext(context.getAttributionTag());
- } else {
- return applicationContext;
- }
- }
-
- /**
* Gets the base context and preserves the attribution tag.
*/
private static Context getBaseContext(ContextWrapper context) {
@@ -574,7 +563,7 @@
// the context within the called method.
mAppContext = getApplicationFromContext(context);
if (mAppContext == null) {
- mAppContext = getApplicationContext(context);
+ mAppContext = ContextUtil.getApplicationContext(context);
}
CameraFactory.Provider cameraFactoryProvider =
mCameraXConfig.getCameraFactoryProvider(null);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigProvider.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigProvider.java
new file mode 100644
index 0000000..2007b49
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigProvider.java
@@ -0,0 +1,36 @@
+/*
+ * 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.core.impl;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.CameraInfo;
+
+/**
+ * Provides the ability to create a {@link CameraConfig} given a {@link CameraInfo}.
+ */
+public interface CameraConfigProvider {
+
+ CameraConfigProvider EMPTY = (cameraInfo, context) -> CameraConfigs.emptyConfig();
+
+ /**
+ * Returns a camera config according to the input camera info.
+ */
+ @NonNull
+ CameraConfig getConfig(@NonNull CameraInfo cameraInfo, @NonNull Context context);
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java
new file mode 100644
index 0000000..81d48a3
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java
@@ -0,0 +1,67 @@
+/*
+ * 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.core.impl;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Stores {@link CameraConfigProvider} instances which allow building {@link CameraConfig} using
+ * keys (extensions modes for example). The provided {@link CameraConfig}s are unique to the device.
+ */
+public final class ExtendedCameraConfigProviderStore {
+
+ private static final Object LOCK = new Object();
+
+ @GuardedBy("LOCK")
+ private static final Map<Object, CameraConfigProvider> CAMERA_CONFIG_PROVIDERS =
+ new HashMap<>();
+
+ private ExtendedCameraConfigProviderStore() {
+ }
+
+ /**
+ * Associates the specified {@link CameraConfigProvider} with the specified key and stores them.
+ */
+ public static void addConfig(@NonNull Object key, @NonNull CameraConfigProvider provider) {
+ synchronized (LOCK) {
+ CAMERA_CONFIG_PROVIDERS.put(key, provider);
+ }
+ }
+
+ /**
+ * Retrieves the {@link CameraConfigProvider} associated with the specified key.
+ *
+ * <p>A default {@link CameraConfigProvider#EMPTY} will be returned if there isn't a
+ * {@link CameraConfigProvider} associated with the key.
+ */
+ @NonNull
+ public static CameraConfigProvider getConfig(@NonNull Object key) {
+ final CameraConfigProvider provider;
+ synchronized (LOCK) {
+ provider = CAMERA_CONFIG_PROVIDERS.get(key);
+ }
+
+ if (provider == null) {
+ return CameraConfigProvider.EMPTY;
+ }
+ return provider;
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ContextUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ContextUtil.java
new file mode 100644
index 0000000..6964e31
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ContextUtil.java
@@ -0,0 +1,43 @@
+/*
+ * 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.core.impl.utils;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Utility class for {@link Context} related operations.
+ */
+public final class ContextUtil {
+ /**
+ * Gets the application context and preserves the attribution tag.
+ */
+ @NonNull
+ public static Context getApplicationContext(@NonNull Context context) {
+ Context applicationContext = context.getApplicationContext();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ return applicationContext.createAttributionContext(context.getAttributionTag());
+ } else {
+ return applicationContext;
+ }
+ }
+
+ private ContextUtil() {
+ }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt
new file mode 100644
index 0000000..571126b
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.core.impl
+
+import android.os.Build
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public class ExtendedCameraConfigProviderStoreTest {
+
+ @Test
+ public fun canRetrieveStoredCameraConfigProvider() {
+ val id = Object()
+ val cameraConfigProvider =
+ CameraConfigProvider { _, _ -> CameraConfigs.emptyConfig() }
+
+ ExtendedCameraConfigProviderStore.addConfig(id, cameraConfigProvider)
+
+ assertThat(ExtendedCameraConfigProviderStore.getConfig(id)).isEqualTo(cameraConfigProvider)
+ }
+
+ @Test
+ public fun returnDefaultEmptyCameraConfigProvider_whenNoDataStored() {
+ assertThat(ExtendedCameraConfigProviderStore.getConfig(Object())).isEqualTo(
+ CameraConfigProvider.EMPTY
+ )
+ }
+}
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
index 112eb3e..a364d4b 100755
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
public List<Pair<Integer, Size[]>> getSupportedResolutions() {
throw new RuntimeException("Stub, replace with implementation.");
}
+
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
}
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
index 79dbff6..d68d8eb 100755
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
public List<Pair<Integer, Size[]>> getSupportedResolutions() {
throw new RuntimeException("Stub, replace with implementation.");
}
+
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
}
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
index dc823c1..b943e0a 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
public List<Pair<Integer, Size[]>> getSupportedResolutions() {
throw new RuntimeException("Stub, replace with implementation.");
}
+
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
}
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index a277fba..66f4a50 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
public List<Pair<Integer, Size[]>> getSupportedResolutions() {
throw new RuntimeException("Stub, replace with implementation.");
}
+
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
}
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index b95c047..571c2e3 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -19,6 +19,7 @@
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import androidx.annotation.Nullable;
@@ -80,4 +81,21 @@
*/
@Nullable
List<Pair<Integer, Size[]>> getSupportedResolutions();
+
+ /**
+ * Returns the estimated capture latency range in milliseconds for the target capture
+ * resolution.
+ *
+ * <p>This includes the time spent processing the multi-frame capture request along with any
+ * additional time for encoding of the processed buffer in the framework if necessary.
+ *
+ * @param captureOutputSize size of the capture output surface. If it is null or not in the
+ * supported output sizes, maximum capture output size is used for
+ * the estimation.
+ * @return the range of estimated minimal and maximal capture latency in milliseconds, or
+ * null if no capture latency info can be provided.
+ * @since 1.2
+ */
+ @Nullable
+ Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize);
}
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
index 8570fd3..3b39cf1 100755
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
public List<Pair<Integer, Size[]>> getSupportedResolutions() {
throw new RuntimeException("Stub, replace with implementation.");
}
+
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+ throw new RuntimeException("Stub, replace with implementation.");
+ }
}
diff --git a/camera/camera-extensions/build.gradle b/camera/camera-extensions/build.gradle
index 64d147b..9740be7 100644
--- a/camera/camera-extensions/build.gradle
+++ b/camera/camera-extensions/build.gradle
@@ -53,6 +53,7 @@
androidTestImplementation(KOTLIN_STDLIB)
androidTestImplementation(MOCKITO_CORE, excludes.bytebuddy) // DexMaker has its own MockMaker
androidTestImplementation(TRUTH)
+ androidTestImplementation(project(":camera:camera-lifecycle"))
androidTestImplementation(project(":camera:camera-testing"))
androidTestImplementation(project(":internal-testutils-truth"))
androidTestImplementation(project(":camera:integration-tests:camera-testlib-extensions"))
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
index 1ebe218..e2ddb55 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
@@ -38,13 +38,10 @@
import android.util.Size;
import androidx.annotation.NonNull;
-import androidx.camera.camera2.Camera2Config;
import androidx.camera.camera2.impl.Camera2ImplConfig;
import androidx.camera.camera2.impl.CameraEventCallback;
import androidx.camera.camera2.impl.CameraEventCallbacks;
import androidx.camera.core.CameraSelector;
-import androidx.camera.core.CameraX;
-import androidx.camera.core.CameraXConfig;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.ImageProxy;
@@ -55,8 +52,10 @@
import androidx.camera.core.internal.CameraUseCaseAdapter;
import androidx.camera.extensions.ExtensionsManager.EffectMode;
import androidx.camera.extensions.util.ExtensionsTestUtil;
+import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.testing.CameraUtil;
import androidx.camera.testing.SurfaceTextureProvider;
+import androidx.camera.testing.fakes.FakeLifecycleOwner;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
@@ -99,7 +98,10 @@
@CameraSelector.LensFacing
private final int mLensFacing;
- private CameraUseCaseAdapter mCamera;
+ private ProcessCameraProvider mProcessCameraProvider = null;
+ private FakeLifecycleOwner mFakeLifecycleOwner;
+ private CameraSelector mBaseCameraSelector;
+ private CameraSelector mExtensionsCameraSelector;
public ExtensionTest(EffectMode effectMode, @CameraSelector.LensFacing int lensFacing) {
mEffectMode = effectMode;
@@ -110,31 +112,40 @@
@Before
public void setUp() throws Exception {
assumeTrue(CameraUtil.deviceHasCamera());
-
- CameraXConfig cameraXConfig = Camera2Config.defaultConfig();
- CameraX.initialize(mContext, cameraXConfig).get();
-
assumeTrue(CameraUtil.hasCameraWithLensFacing(mLensFacing));
+
+ mProcessCameraProvider = ProcessCameraProvider.getInstance(mContext).get(10000,
+ TimeUnit.MILLISECONDS);
assumeTrue(ExtensionsTestUtil.initExtensions(mContext));
assumeTrue(isTargetDeviceAvailableForExtensions(mLensFacing));
- CameraSelector cameraSelector =
- new CameraSelector.Builder().requireLensFacing(mLensFacing).build();
- mCamera = CameraUtil.createCameraUseCaseAdapter(mContext, cameraSelector);
+ mBaseCameraSelector = new CameraSelector.Builder().requireLensFacing(mLensFacing).build();
Extensions extensions = ExtensionsManager.getExtensions(mContext);
- assumeTrue(extensions.isExtensionAvailable(mCamera, mExtensionMode));
+ assumeTrue(extensions.isExtensionAvailable(mProcessCameraProvider, mBaseCameraSelector,
+ mExtensionMode));
+
+ mExtensionsCameraSelector = extensions.getExtensionCameraSelector(mBaseCameraSelector,
+ mExtensionMode);
+
+ mFakeLifecycleOwner = new FakeLifecycleOwner();
+ mFakeLifecycleOwner.startAndResume();
}
@After
public void cleanUp() throws InterruptedException, ExecutionException, TimeoutException {
- CameraX.shutdown().get(10000, TimeUnit.MILLISECONDS);
- ExtensionsManager.deinit().get();
+ if (mProcessCameraProvider != null) {
+ mProcessCameraProvider.shutdown().get(10000, TimeUnit.MILLISECONDS);
+ ExtensionsManager.deinit().get(10000, TimeUnit.MILLISECONDS);
+ }
}
@Test
- public void testCanBindToLifeCycleAndTakePicture() {
+ public void testCanBindToLifeCycleAndTakePicture_ByExtenderAPI() {
ImageCapture.OnImageCapturedCallback mockOnImageCapturedCallback = mock(
ImageCapture.OnImageCapturedCallback.class);
+ CameraUseCaseAdapter camera = CameraUtil.createCameraUseCaseAdapter(mContext,
+ mBaseCameraSelector);
+
// To test bind/unbind and take picture.
ImageCapture imageCapture = ExtensionsTestUtil.createImageCaptureWithEffect(mEffectMode,
mLensFacing);
@@ -161,7 +172,7 @@
}));
try {
- mCamera.addUseCases(Arrays.asList(preview, imageCapture));
+ camera.addUseCases(Arrays.asList(preview, imageCapture));
} catch (CameraUseCaseAdapter.CameraException e) {
throw new IllegalArgumentException("Unable to bind preview and image "
+ "capture");
@@ -183,7 +194,7 @@
}
@Test
- public void testEventCallbackInConfig() {
+ public void testEventCallbackInConfig_ByExtenderAPI() {
// Verify Preview config should have related callback.
PreviewConfig previewConfig = ExtensionsTestUtil.createPreviewConfigWithEffect(mEffectMode,
mLensFacing);
@@ -207,6 +218,83 @@
assertThat(callback2.getAllItems().get(0)).isInstanceOf(CameraEventCallback.class);
}
+ @Test
+ public void testCanBindToLifeCycleAndTakePicture() {
+ ImageCapture.OnImageCapturedCallback mockOnImageCapturedCallback = mock(
+ ImageCapture.OnImageCapturedCallback.class);
+
+ // To test bind/unbind and take picture.
+ ImageCapture imageCapture = new ImageCapture.Builder().build();
+ Preview preview = new Preview.Builder().build();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ // To set the update listener and Preview will change to active state.
+ preview.setSurfaceProvider(createSurfaceTextureProvider(
+ new SurfaceTextureProvider.SurfaceTextureCallback() {
+ @Override
+ public void onSurfaceTextureReady(
+ @NonNull SurfaceTexture surfaceTexture,
+ @NonNull Size resolution) {
+ // No-op.
+ }
+
+ @Override
+ public void onSafeToRelease(
+ @NonNull SurfaceTexture surfaceTexture) {
+ // No-op.
+ }
+ }));
+
+ mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+ mExtensionsCameraSelector, preview, imageCapture);
+
+ imageCapture.takePicture(CameraXExecutors.mainThreadExecutor(),
+ mockOnImageCapturedCallback);
+ });
+
+ // Verify the image captured.
+ ArgumentCaptor<ImageProxy> imageProxy = ArgumentCaptor.forClass(ImageProxy.class);
+ verify(mockOnImageCapturedCallback, timeout(10000)).onCaptureSuccess(
+ imageProxy.capture());
+ assertNotNull(imageProxy.getValue());
+ imageProxy.getValue().close(); // Close the image after verification.
+
+ // Verify the take picture should not have any error happen.
+ verify(mockOnImageCapturedCallback, never()).onError(any(ImageCaptureException.class));
+ }
+
+ @Test
+ public void testEventCallbackInConfig() {
+ Preview preview = new Preview.Builder().build();
+ ImageCapture imageCapture = new ImageCapture.Builder().build();
+
+ mInstrumentation.runOnMainSync(
+ () -> mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+ mExtensionsCameraSelector, preview, imageCapture));
+
+ // Verify Preview config should have related callback.
+ PreviewConfig previewConfig = (PreviewConfig) preview.getCurrentConfig();
+ assertNotNull(previewConfig.getUseCaseEventCallback());
+ CameraEventCallbacks callback1 = new Camera2ImplConfig(
+ previewConfig).getCameraEventCallback(
+ null);
+ assertNotNull(callback1);
+ assertEquals(callback1.getAllItems().size(), 1);
+ assertThat(callback1.getAllItems().get(0)).isInstanceOf(CameraEventCallback.class);
+
+ // Verify ImageCapture config should have related callback.
+ ImageCaptureConfig imageCaptureConfig =
+ (ImageCaptureConfig) imageCapture.getCurrentConfig();
+ assertNotNull(imageCaptureConfig.getUseCaseEventCallback());
+ assertNotNull(imageCaptureConfig.getCaptureBundle());
+ CameraEventCallbacks callback2 = new Camera2ImplConfig(
+ imageCaptureConfig).getCameraEventCallback(null);
+ assertNotNull(callback2);
+ assertEquals(callback2.getAllItems().size(), 1);
+ assertThat(callback2.getAllItems().get(0)).isInstanceOf(CameraEventCallback.class);
+ }
+
/**
* Returns whether the target camera device can support the test cases.
*
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java
index 0cd4b84..55ffa9f 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java
@@ -30,11 +30,8 @@
import android.os.Build;
import androidx.annotation.NonNull;
-import androidx.camera.camera2.Camera2Config;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraSelector;
-import androidx.camera.core.CameraX;
-import androidx.camera.core.CameraXConfig;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.Preview;
import androidx.camera.core.UseCase;
@@ -45,7 +42,9 @@
import androidx.camera.extensions.impl.ImageCaptureExtenderImpl;
import androidx.camera.extensions.impl.PreviewExtenderImpl;
import androidx.camera.extensions.util.ExtensionsTestUtil;
+import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.testing.CameraUtil;
+import androidx.camera.testing.fakes.FakeLifecycleOwner;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
@@ -78,6 +77,8 @@
@Rule
public TestRule mUseCamera = CameraUtil.grantCameraPermissionAndPreTest();
+ private static final int TIMEOUT_MILLISECONDS = 10000;
+
private final Context mContext = ApplicationProvider.getApplicationContext();
Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -95,6 +96,9 @@
@CameraSelector.LensFacing
private int mLensFacing;
private CountDownLatch mLatch;
+ private ProcessCameraProvider mProcessCameraProvider = null;
+ private FakeLifecycleOwner mFakeLifecycleOwner;
+ private CameraSelector mExtensionsCameraSelector;
private CameraUseCaseAdapter mCamera;
final AtomicReference<ExtensionsErrorCode> mErrorCode = new AtomicReference<>();
@@ -120,8 +124,8 @@
public void setUp() throws InterruptedException, ExecutionException, TimeoutException {
assumeTrue(CameraUtil.deviceHasCamera());
- CameraXConfig cameraXConfig = Camera2Config.defaultConfig();
- CameraX.initialize(mContext, cameraXConfig).get();
+ mProcessCameraProvider = ProcessCameraProvider.getInstance(mContext).get(
+ TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
assumeTrue(CameraUtil.hasCameraWithLensFacing(mLensFacing));
assumeTrue(ExtensionsTestUtil.initExtensions(mContext));
@@ -129,6 +133,11 @@
mExtensions = ExtensionsManager.getExtensions(mContext);
mLatch = new CountDownLatch(1);
+
+ mFakeLifecycleOwner = new FakeLifecycleOwner();
+ mFakeLifecycleOwner.startAndResume();
+ mExtensionsCameraSelector =
+ mExtensions.getExtensionCameraSelector(mCameraSelector, mExtensionMode);
}
@After
@@ -141,8 +150,10 @@
);
}
- CameraX.shutdown().get(10000, TimeUnit.MILLISECONDS);
- ExtensionsManager.deinit().get();
+ if (mProcessCameraProvider != null) {
+ mProcessCameraProvider.shutdown().get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
+ ExtensionsManager.deinit().get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
+ }
}
@Test
@@ -280,10 +291,9 @@
mErrorCode.set(null);
- mCamera = CameraUtil.createCameraAndAttachUseCase(mContext, mCameraSelector,
- imageCapture);
-
- mInstrumentation.runOnMainSync(() -> mExtensions.setExtension(mCamera, mExtensionMode));
+ mInstrumentation.runOnMainSync(
+ () -> mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+ mExtensionsCameraSelector, imageCapture));
// Waits for one second to get error code.
mLatch.await(1, TimeUnit.SECONDS);
@@ -298,9 +308,9 @@
mErrorCode.set(null);
- mCamera = CameraUtil.createCameraAndAttachUseCase(mContext, mCameraSelector, preview);
-
- mInstrumentation.runOnMainSync(() -> mExtensions.setExtension(mCamera, mExtensionMode));
+ mInstrumentation.runOnMainSync(
+ () -> mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+ mExtensionsCameraSelector, preview));
// Waits for one second to get error code.
mLatch.await(1, TimeUnit.SECONDS);
@@ -318,10 +328,9 @@
ImageCapture imageCapture = new ImageCapture.Builder().build();
Preview preview = new Preview.Builder().build();
- mCamera = CameraUtil.createCameraAndAttachUseCase(mContext, mCameraSelector, imageCapture,
- preview);
-
- mInstrumentation.runOnMainSync(() -> mExtensions.setExtension(mCamera, mExtensionMode));
+ mInstrumentation.runOnMainSync(
+ () -> mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+ mExtensionsCameraSelector, preview, imageCapture));
// Waits for one second to get error code.
Thread.sleep(1000);
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsTest.kt
index 1b888f7..980c37c 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsTest.kt
@@ -17,17 +17,19 @@
import android.content.Context
import android.os.Build
-import androidx.camera.camera2.Camera2Config
+import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraSelector.LensFacing
-import androidx.camera.core.CameraX
import androidx.camera.extensions.Extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager.EffectMode
import androidx.camera.extensions.util.ExtensionsTestUtil
+import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.fakes.FakeLifecycleOwner
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
import org.junit.After
@@ -40,36 +42,39 @@
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
-import kotlin.jvm.Throws
@MediumTest
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
class ExtensionsTest(
- @field:ExtensionMode @param:ExtensionMode private val mExtensionMode: Int,
- @field:LensFacing @param:LensFacing private val mLensFacing: Int
+ @field:ExtensionMode @param:ExtensionMode private val extensionMode: Int,
+ @field:LensFacing @param:LensFacing private val lensFacing: Int
) {
- private val mContext =
- ApplicationProvider.getApplicationContext<Context>()
+ private val context = ApplicationProvider.getApplicationContext<Context>()
- private val mEffectMode: EffectMode =
- ExtensionsTestUtil.extensionModeToEffectMode(mExtensionMode)
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
- private lateinit var mExtensions: Extensions
+ private val effectMode: EffectMode =
+ ExtensionsTestUtil.extensionModeToEffectMode(extensionMode)
+
+ private lateinit var extensions: Extensions
+ private lateinit var cameraProvider: ProcessCameraProvider
@Before
@Throws(Exception::class)
fun setUp() {
assumeTrue(CameraUtil.deviceHasCamera())
- val cameraXConfig = Camera2Config.defaultConfig()
- CameraX.initialize(mContext, cameraXConfig).get()
+
+ cameraProvider =
+ ProcessCameraProvider.getInstance(context).get(10000, TimeUnit.MILLISECONDS)
+
assumeTrue(
CameraUtil.hasCameraWithLensFacing(
- mLensFacing
+ lensFacing
)
)
- assumeTrue(ExtensionsTestUtil.initExtensions(mContext))
- mExtensions = ExtensionsManager.getExtensions(mContext)
+ assumeTrue(ExtensionsTestUtil.initExtensions(context))
+ extensions = ExtensionsManager.getExtensions(context)
}
@After
@@ -79,8 +84,8 @@
TimeoutException::class
)
fun cleanUp() {
- CameraX.shutdown()[10000, TimeUnit.MILLISECONDS]
- ExtensionsManager.deinit().get()
+ cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ ExtensionsManager.deinit().get(10000, TimeUnit.MILLISECONDS)
}
companion object {
@@ -93,37 +98,55 @@
// TODO: Can be removed after the Extensions class is fully implemented.
@Test
fun isExtensionAvailable() {
- val cameraSelector = CameraSelector.Builder().requireLensFacing(mLensFacing).build()
- val camera = CameraUtil.createCameraUseCaseAdapter(mContext, cameraSelector)
+ val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
- assertThat(
- ExtensionsManager.isExtensionAvailable(mEffectMode, mLensFacing) ==
- mExtensions.isExtensionAvailable(camera, mExtensionMode)
- ).isTrue()
+ assertThat(ExtensionsManager.isExtensionAvailable(effectMode, lensFacing)).isEqualTo(
+ extensions.isExtensionAvailable(cameraProvider, cameraSelector, extensionMode)
+ )
}
@Test
fun setExtensionSucceedsIfAvailable() {
- val cameraSelector = CameraSelector.Builder().requireLensFacing(mLensFacing).build()
- val camera = CameraUtil.createCameraUseCaseAdapter(mContext, cameraSelector)
+ val baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
- assumeTrue(mExtensions.isExtensionAvailable(camera, mExtensionMode))
+ assumeTrue(
+ extensions.isExtensionAvailable(
+ cameraProvider,
+ baseCameraSelector,
+ extensionMode
+ )
+ )
- mExtensions.setExtension(camera, mExtensionMode)
- assertThat(mExtensions.getExtension(camera)).isEqualTo(mExtensionMode)
+ val extensionCameraSelector =
+ extensions.getExtensionCameraSelector(baseCameraSelector, extensionMode)
+
+ lateinit var camera: Camera
+ instrumentation.runOnMainSync {
+ camera = cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+ }
+
+ assertThat(extensions.getExtension(camera)).isEqualTo(extensionMode)
}
@Test
fun setExtensionFailsIfNotAvailable() {
- val cameraSelector = CameraSelector.Builder().requireLensFacing(mLensFacing).build()
- val camera = CameraUtil.createCameraUseCaseAdapter(mContext, cameraSelector)
+ val baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
- assumeFalse(mExtensions.isExtensionAvailable(camera, mExtensionMode))
+ assumeFalse(
+ extensions.isExtensionAvailable(
+ cameraProvider,
+ baseCameraSelector,
+ extensionMode
+ )
+ )
- assertThrows<IllegalArgumentException> {
- mExtensions.setExtension(camera, mExtensionMode)
+ val extensionCameraSelector =
+ extensions.getExtensionCameraSelector(baseCameraSelector, extensionMode)
+
+ instrumentation.runOnMainSync {
+ assertThrows<IllegalArgumentException> {
+ cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+ }
}
-
- assertThat(mExtensions.getExtension(camera)).isEqualTo(Extensions.EXTENSION_MODE_NONE)
}
-}
\ No newline at end of file
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
index 06237ee..811b450 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
@@ -40,25 +40,59 @@
*/
@OptIn(markerClass = ExperimentalCameraFilter.class)
public final class ExtensionCameraFilter implements CameraFilter {
+ private final Id mId;
private final PreviewExtenderImpl mPreviewExtenderImpl;
private final ImageCaptureExtenderImpl mImageCaptureExtenderImpl;
+ // TODO(b/183075483): These three constructors will be removed after new Extensions APIs are
+ // public and the old extender APIs are removed.
ExtensionCameraFilter(@Nullable PreviewExtenderImpl previewExtenderImpl) {
+ mId = Id.DEFAULT;
mPreviewExtenderImpl = previewExtenderImpl;
mImageCaptureExtenderImpl = null;
}
ExtensionCameraFilter(@Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+ mId = Id.DEFAULT;
mPreviewExtenderImpl = null;
mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
}
ExtensionCameraFilter(@Nullable PreviewExtenderImpl previewExtenderImpl,
@Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+ mId = Id.DEFAULT;
mPreviewExtenderImpl = previewExtenderImpl;
mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
}
+ ExtensionCameraFilter(@NonNull String filterId,
+ @Nullable PreviewExtenderImpl previewExtenderImpl) {
+ mId = Id.create(filterId);
+ mPreviewExtenderImpl = previewExtenderImpl;
+ mImageCaptureExtenderImpl = null;
+ }
+
+ ExtensionCameraFilter(@NonNull String filterId,
+ @Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+ mId = Id.create(filterId);
+ mPreviewExtenderImpl = null;
+ mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
+ }
+
+ ExtensionCameraFilter(@NonNull String filterId,
+ @Nullable PreviewExtenderImpl previewExtenderImpl,
+ @Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+ mId = Id.create(filterId);
+ mPreviewExtenderImpl = previewExtenderImpl;
+ mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
+ }
+
+ @NonNull
+ @Override
+ public Id getId() {
+ return mId;
+ }
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
@NonNull
@Override
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/Extensions.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/Extensions.java
index f70b0b3..e13bc80 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/Extensions.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/Extensions.java
@@ -24,12 +24,12 @@
import androidx.annotation.RestrictTo;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraFilter;
-import androidx.camera.core.CameraInfo;
+import androidx.camera.core.CameraProvider;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ExperimentalCameraFilter;
-import androidx.camera.core.UseCase;
+import androidx.camera.core.impl.CameraConfigProvider;
import androidx.camera.core.impl.CameraFilters;
-import androidx.camera.core.internal.CameraUseCaseAdapter;
+import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
@@ -44,28 +44,25 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.LinkedHashSet;
-import java.util.List;
/**
- * A class for querying and controlling the extensions that are enable for individual
- * {@link Camera} instances.
+ * A class for querying extensions related information.
*
- * <p> The typical usages is to check whether or not a Camera and/or the {@link UseCase}
- * combinations support the extension by using {@link #isExtensionAvailable(Camera, int)} and
- * {@link #checkUseCases(Camera, List, int)}. Then after it has been determined that the
- * extension can be enable then a {@link #setExtension(Camera, int)} call can be used to set the
- * specified extension on the camera.
+ * <p>The typical usages include checking whether or not a camera exists that supports an extension
+ * by using {@link #isExtensionAvailable(CameraProvider, CameraSelector, int)}. Then after it has
+ * been determined that the extension can be enabled, a
+ * {@link #getExtensionCameraSelector(CameraSelector, int)} call can be used to get the
+ * specified {@link CameraSelector} to bind use cases and enable the extension mode on the camera.
*
- * <p> When the Camera has been set to a particular extension it might require the camera to
- * restart which can cause the preview to temporarily stop. Once the extension has been enable
+ * <p>When the Camera has been set to a particular extension it might require the camera to
+ * restart which can cause the preview to momentarily stop. Once the extension has been enabled
* for a Camera instance then it will stay in that extension mode until the extension has been
- * disabled. Setting extension modes is separate for each camera instance.
+ * disabled.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Extensions {
+public final class Extensions {
private static final String TAG = "Extensions";
/** Normal mode without any specific effect applied. */
@@ -93,14 +90,14 @@
*/
public static final int EXTENSION_MODE_AUTO = 5;
- private final Context mContext;
-
+ private static final String EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX = ":camera:camera"
+ + "-extensions-";
/**
* The different extension modes that a {@link Camera} can be configured for.
*
- * <p>Not all devices and camera support the different extension modes. To query whether or
+ * <p>Not all devices and cameras support the different extension modes. To query whether or
* not a specific Camera supports an extension mode use
- * {@link Extensions#isExtensionAvailable(Camera, int)}.
+ * {@link Extensions#isExtensionAvailable(CameraProvider, CameraSelector, int)}.
*
* @hide
*/
@@ -112,63 +109,52 @@
}
Extensions(@NonNull Context context) {
- mContext = context;
}
/**
- * Sets the specified extension mode for the Camera.
+ * Returns a {@link CameraSelector} for the specific extension mode.
*
- * To return to a non-extensions mode Camera set the extension mode as NONE.
- * For full list of extensions see Extension Modes
- *
- * @param camera The camera that the UseCases are attached to
- * @param mode The extension mode to set. Setting this to EXTENSION_NONE will
- * remove the current extension and will always succeed.
- * @throws IllegalArgumentException if the specified extension mode can not be set on the
- * Camera. This can happen if the extension is not supported
- * on this camera. This might also
- * because the combination of UseCases attached to the
- * Camera. Use {@link
- * #checkUseCases(Camera, List, int)} to verify that the
- * Camera can support the list of
- * UseCases for the extension.
+ * @param baseCameraSelector The base {@link CameraSelector} to be applied the extension
+ * related configuration on.
+ * {@link #isExtensionAvailable(CameraProvider, CameraSelector, int)}
+ * can be used to check whether any camera can support the specified
+ * extension mode for the base camera selector.
+ * @param mode The target extension mode.
+ * @return a {@link CameraSelector} for the specific Extensions mode.
+ * @throws IllegalArgumentException if the base {@link CameraSelector} has contained
+ * extension related configuration in it.
*/
@OptIn(markerClass = ExperimentalCameraFilter.class)
- public void setExtension(@NonNull Camera camera, @ExtensionMode int mode) {
- if (!isExtensionAvailable(camera, mode)) {
- throw new IllegalArgumentException("Extension mode not supported on camera: " + mode);
+ @NonNull
+ public CameraSelector getExtensionCameraSelector(@NonNull CameraSelector baseCameraSelector,
+ @ExtensionMode int mode) {
+ // Checks whether there has been Extensions related CameraConfig set in the base
+ // CameraSelector.
+ for (CameraFilter cameraFilter : baseCameraSelector.getCameraFilterSet()) {
+ if (cameraFilter instanceof ExtensionCameraFilter) {
+ throw new IllegalArgumentException(
+ "An extension is already applied to the base CameraSelector.");
+ }
}
- CameraSelector cameraSelector =
- new CameraSelector.Builder().addCameraFilter(getFilter(mode)).build();
- Camera extensionCamera =
- cameraSelector.select(new LinkedHashSet<>(camera.getCameraInternals()));
- CameraInfo extensionsCameraInfo = extensionCamera.getCameraInfo();
+ // Injects CameraConfigProvider for the extension mode to the
+ // ExtendedCameraConfigProviderStore.
+ injectExtensionCameraConfig(mode);
- ExtensionsUseCaseConfigFactory factory = new ExtensionsUseCaseConfigFactory(mode,
- extensionsCameraInfo, mContext);
+ CameraSelector.Builder builder = CameraSelector.Builder.fromSelector(baseCameraSelector);
- // Set the Camera
- ExtensionsConfig extensionsConfig =
- new ExtensionsConfig.Builder()
- .setExtensionMode(mode)
- .setCameraFilter(getFilter(mode))
- .setUseCaseConfigFactory(factory)
- .build();
+ // Adds the CameraFilter that determines which cameras can support the Extensions mode
+ // to the CameraSelector.
+ builder.addCameraFilter(getFilter(mode));
- // Set the config on the camera
- try {
- camera.setExtendedConfig(extensionsConfig);
- } catch (CameraUseCaseAdapter.CameraException e) {
- throw new IllegalArgumentException("Camera unable to support the extension with the "
- + "attached UseCases. " + e);
- }
+ return builder.build();
}
/**
* Returns the extension mode that is currently set on the camera.
*/
- public @ExtensionMode int getExtension(@NonNull Camera camera) {
+ @ExtensionMode
+ public int getExtension(@NonNull Camera camera) {
Object extensionsConfigObject = camera.getExtendedConfig();
if (extensionsConfigObject instanceof ExtensionsConfig) {
@@ -180,53 +166,55 @@
/**
* Returns true if the particular extension mode is available for the specified
- * Camera.
+ * {@link CameraSelector}.
*
- * <p> This check is independent of the {@link UseCase} which are currently attached to the
- * {@link Camera}. To check whether the Camera can support the attached UseCases use {@link
- * #checkUseCases(Camera, List, int)}.
- *
- * @param camera The Camera to check if it supports the extension.
- * @param mode The extension mode to check
+ * @param cameraProvider The {@link CameraProvider} which will be used to bind use cases.
+ * @param baseCameraSelector The base {@link CameraSelector} to find a camera to use.
+ * @param mode The target extension mode to support.
*/
@OptIn(markerClass = ExperimentalCameraFilter.class)
- public boolean isExtensionAvailable(@NonNull Camera camera, @ExtensionMode int mode) {
- CameraSelector cameraSelector =
- new CameraSelector.Builder().addCameraFilter(getFilter(mode)).build();
-
- // Extension is available for the camera if it contains a CameraInternal which supports
- // the extension
+ public boolean isExtensionAvailable(
+ @NonNull CameraProvider cameraProvider,
+ @NonNull CameraSelector baseCameraSelector,
+ @ExtensionMode int mode) {
try {
- cameraSelector.select(new LinkedHashSet<>(camera.getCameraInternals()));
+ CameraSelector.Builder builder = CameraSelector.Builder.fromSelector(
+ baseCameraSelector);
+ builder.addCameraFilter(getFilter(mode));
+
+ builder.build().filter(cameraProvider.getAvailableCameraInfos());
} catch (IllegalArgumentException e) {
return false;
}
+
return true;
}
@OptIn(markerClass = ExperimentalCameraFilter.class)
private CameraFilter getFilter(@ExtensionMode int mode) {
CameraFilter filter;
+ String id = getExtendedCameraConfigProviderId(mode);
+
try {
switch (mode) {
case EXTENSION_MODE_BOKEH:
- filter = new ExtensionCameraFilter(new BokehPreviewExtenderImpl(),
+ filter = new ExtensionCameraFilter(id, new BokehPreviewExtenderImpl(),
new BokehImageCaptureExtenderImpl());
break;
case EXTENSION_MODE_HDR:
- filter = new ExtensionCameraFilter(new HdrPreviewExtenderImpl(),
+ filter = new ExtensionCameraFilter(id, new HdrPreviewExtenderImpl(),
new HdrImageCaptureExtenderImpl());
break;
case EXTENSION_MODE_NIGHT:
- filter = new ExtensionCameraFilter(new NightPreviewExtenderImpl(),
+ filter = new ExtensionCameraFilter(id, new NightPreviewExtenderImpl(),
new NightImageCaptureExtenderImpl());
break;
case EXTENSION_MODE_BEAUTY:
- filter = new ExtensionCameraFilter(new BeautyPreviewExtenderImpl(),
+ filter = new ExtensionCameraFilter(id, new BeautyPreviewExtenderImpl(),
new BeautyImageCaptureExtenderImpl());
break;
case EXTENSION_MODE_AUTO:
- filter = new ExtensionCameraFilter(new AutoPreviewExtenderImpl(),
+ filter = new ExtensionCameraFilter(id, new AutoPreviewExtenderImpl(),
new AutoImageCaptureExtenderImpl());
break;
case EXTENSION_MODE_NONE:
@@ -241,20 +229,49 @@
}
/**
- * Checks if the list of UseCases attached to the Camera can support the
- * extension.
- *
- * If the list of UseCases exceeds the capacity of Surfaces for the Camera then
- * it returns a list of UseCase lists that can be removed in order to allow for
- * the extension to be enabled. Any of the individual lists can be removed.
- *
- * @return null if the Camera supports the extension using the list of UseCases, otherwise a
- * list of UseCase list to remove.
+ * Injects {@link CameraConfigProvider} for specific extension mode to the
+ * {@link ExtendedCameraConfigProviderStore}.
*/
- @NonNull
- public List<List<UseCase>> checkUseCases(@NonNull Camera camera,
- @NonNull List<UseCase> useCases,
- @ExtensionMode int mode) {
- throw new UnsupportedOperationException("not yet implemented");
+ private void injectExtensionCameraConfig(@ExtensionMode int mode) {
+ CameraFilter.Id id = CameraFilter.Id.create(getExtendedCameraConfigProviderId(mode));
+
+ if (ExtendedCameraConfigProviderStore.getConfig(id) == CameraConfigProvider.EMPTY) {
+ ExtendedCameraConfigProviderStore.addConfig(id, (cameraInfo, context) -> {
+ ExtensionsUseCaseConfigFactory factory = new
+ ExtensionsUseCaseConfigFactory(mode, cameraInfo, context);
+ return new ExtensionsConfig.Builder()
+ .setExtensionMode(mode)
+ .setUseCaseConfigFactory(factory)
+ .build();
+ });
+ }
+ }
+
+ private String getExtendedCameraConfigProviderId(@ExtensionMode int mode) {
+ String id;
+
+ switch (mode) {
+ case EXTENSION_MODE_BOKEH:
+ id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_BOKEH";
+ break;
+ case EXTENSION_MODE_HDR:
+ id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_HDR";
+ break;
+ case EXTENSION_MODE_NIGHT:
+ id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_NIGHT";
+ break;
+ case EXTENSION_MODE_BEAUTY:
+ id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_BEAUTY";
+ break;
+ case EXTENSION_MODE_AUTO:
+ id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_AUTO";
+ break;
+ case EXTENSION_MODE_NONE:
+ id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_NONE";
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid extension mode!");
+ }
+ return id;
}
}
diff --git a/camera/camera-lifecycle/lint-baseline.xml b/camera/camera-lifecycle/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/camera/camera-lifecycle/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index ddebddb..5233c98 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -42,7 +42,11 @@
import androidx.camera.core.UseCase;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.core.ViewPort;
+import androidx.camera.core.impl.CameraConfig;
+import androidx.camera.core.impl.CameraConfigs;
import androidx.camera.core.impl.CameraInternal;
+import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
+import androidx.camera.core.impl.utils.ContextUtil;
import androidx.camera.core.impl.utils.Threads;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.Futures;
@@ -83,6 +87,7 @@
private final LifecycleCameraRepository
mLifecycleCameraRepository = new LifecycleCameraRepository();
private CameraX mCameraX;
+ private Context mContext;
/**
* Retrieves the {@link ProcessCameraProvider} associated with the current process.
@@ -152,6 +157,7 @@
Preconditions.checkNotNull(context);
return Futures.transform(CameraX.getOrCreateInstance(context), cameraX -> {
sAppInstance.setCameraX(cameraX);
+ sAppInstance.setContext(ContextUtil.getApplicationContext(context));
return sAppInstance;
}, CameraXExecutors.directExecutor());
}
@@ -213,6 +219,10 @@
mCameraX = cameraX;
}
+ private void setContext(Context context) {
+ mContext = context;
+ }
+
/**
* Binds the collection of {@link UseCase} to a {@link LifecycleOwner}.
*
@@ -421,6 +431,33 @@
mCameraX.getDefaultConfigFactory()));
}
+ CameraConfig cameraConfig = CameraConfigs.emptyConfig();
+
+ // Retrieves extended camera configs from ExtendedCameraConfigProviderStore
+ for (CameraFilter cameraFilter : cameraSelector.getCameraFilterSet()) {
+ if (cameraFilter.getId() != CameraFilter.Id.DEFAULT) {
+ CameraConfig extendedCameraConfig = ExtendedCameraConfigProviderStore.getConfig(
+ cameraFilter.getId()).getConfig(lifecycleCameraToBind.getCameraInfo(),
+ mContext);
+
+ // Only allows one camera config now.
+ if (extendedCameraConfig != CameraConfigs.emptyConfig()
+ && cameraConfig != CameraConfigs.emptyConfig()) {
+ throw new IllegalArgumentException(
+ "Cannot apply multiple extended camera configs at the same time.");
+ } else {
+ cameraConfig = extendedCameraConfig;
+ }
+ }
+ }
+
+ // Applies extended camera configs to the camera
+ try {
+ lifecycleCameraToBind.setExtendedConfig(cameraConfig);
+ } catch (CameraUseCaseAdapter.CameraException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+
if (useCases.length == 0) {
return lifecycleCameraToBind;
}
diff --git a/camera/camera-view/api/current.txt b/camera/camera-view/api/current.txt
index 645b302..160857f 100644
--- a/camera/camera-view/api/current.txt
+++ b/camera/camera-view/api/current.txt
@@ -33,40 +33,6 @@
field public static final int IMAGE_CAPTURE = 1; // 0x1
}
- @Deprecated public final class CameraView extends android.widget.FrameLayout {
- ctor @Deprecated public CameraView(android.content.Context);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?, int);
- ctor @Deprecated @RequiresApi(21) public CameraView(android.content.Context, android.util.AttributeSet?, int, int);
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
- method @Deprecated public void enableTorch(boolean);
- method @Deprecated public Integer? getCameraLensFacing();
- method @Deprecated public androidx.camera.view.CameraView.CaptureMode getCaptureMode();
- method @Deprecated public int getFlash();
- method @Deprecated public float getMaxZoomRatio();
- method @Deprecated public float getMinZoomRatio();
- method @Deprecated public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
- method @Deprecated public androidx.camera.view.PreviewView.ScaleType getScaleType();
- method @Deprecated public float getZoomRatio();
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public boolean hasCameraWithLensFacing(int);
- method @Deprecated public boolean isPinchToZoomEnabled();
- method @Deprecated public boolean isTorchOn();
- method @Deprecated public boolean isZoomSupported();
- method @Deprecated public void setCameraLensFacing(Integer?);
- method @Deprecated public void setCaptureMode(androidx.camera.view.CameraView.CaptureMode);
- method @Deprecated public void setFlash(int);
- method @Deprecated public void setPinchToZoomEnabled(boolean);
- method @Deprecated public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
- method @Deprecated public void setZoomRatio(float);
- method @Deprecated public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
- method @Deprecated public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
- method @Deprecated public void toggleCamera();
- }
-
- @Deprecated public enum CameraView.CaptureMode {
- enum_constant @Deprecated public static final androidx.camera.view.CameraView.CaptureMode IMAGE;
- }
-
public final class LifecycleCameraController extends androidx.camera.view.CameraController {
ctor public LifecycleCameraController(android.content.Context);
method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
diff --git a/camera/camera-view/api/public_plus_experimental_current.txt b/camera/camera-view/api/public_plus_experimental_current.txt
index 6dbb420..4190144 100644
--- a/camera/camera-view/api/public_plus_experimental_current.txt
+++ b/camera/camera-view/api/public_plus_experimental_current.txt
@@ -38,47 +38,6 @@
field @androidx.camera.view.video.ExperimentalVideo public static final int VIDEO_CAPTURE = 4; // 0x4
}
- @Deprecated public final class CameraView extends android.widget.FrameLayout {
- ctor @Deprecated public CameraView(android.content.Context);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?, int);
- ctor @Deprecated @RequiresApi(21) public CameraView(android.content.Context, android.util.AttributeSet?, int, int);
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
- method @Deprecated public void enableTorch(boolean);
- method @Deprecated public Integer? getCameraLensFacing();
- method @Deprecated public androidx.camera.view.CameraView.CaptureMode getCaptureMode();
- method @Deprecated @androidx.camera.core.ImageCapture.FlashMode public int getFlash();
- method @Deprecated public float getMaxZoomRatio();
- method @Deprecated public float getMinZoomRatio();
- method @Deprecated public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
- method @Deprecated public androidx.camera.view.PreviewView.ScaleType getScaleType();
- method @Deprecated public float getZoomRatio();
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public boolean hasCameraWithLensFacing(@androidx.camera.core.CameraSelector.LensFacing int);
- method @Deprecated public boolean isPinchToZoomEnabled();
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public boolean isRecording();
- method @Deprecated public boolean isTorchOn();
- method @Deprecated public boolean isZoomSupported();
- method @Deprecated public void setCameraLensFacing(Integer?);
- method @Deprecated public void setCaptureMode(androidx.camera.view.CameraView.CaptureMode);
- method @Deprecated public void setFlash(@androidx.camera.core.ImageCapture.FlashMode int);
- method @Deprecated public void setPinchToZoomEnabled(boolean);
- method @Deprecated public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
- method @Deprecated public void setZoomRatio(float);
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void startRecording(java.io.File, java.util.concurrent.Executor, androidx.camera.view.video.OnVideoSavedCallback);
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void startRecording(android.os.ParcelFileDescriptor, java.util.concurrent.Executor, androidx.camera.view.video.OnVideoSavedCallback);
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void startRecording(androidx.camera.view.video.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.view.video.OnVideoSavedCallback);
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void stopRecording();
- method @Deprecated public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
- method @Deprecated public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
- method @Deprecated public void toggleCamera();
- }
-
- @Deprecated public enum CameraView.CaptureMode {
- enum_constant @Deprecated public static final androidx.camera.view.CameraView.CaptureMode IMAGE;
- enum_constant @Deprecated @androidx.camera.view.video.ExperimentalVideo public static final androidx.camera.view.CameraView.CaptureMode MIXED;
- enum_constant @Deprecated @androidx.camera.view.video.ExperimentalVideo public static final androidx.camera.view.CameraView.CaptureMode VIDEO;
- }
-
public final class LifecycleCameraController extends androidx.camera.view.CameraController {
ctor public LifecycleCameraController(android.content.Context);
method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
diff --git a/camera/camera-view/api/restricted_current.txt b/camera/camera-view/api/restricted_current.txt
index d0ba609..56cdeaa 100644
--- a/camera/camera-view/api/restricted_current.txt
+++ b/camera/camera-view/api/restricted_current.txt
@@ -33,40 +33,6 @@
field public static final int IMAGE_CAPTURE = 1; // 0x1
}
- @Deprecated public final class CameraView extends android.widget.FrameLayout {
- ctor @Deprecated public CameraView(android.content.Context);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?, int);
- ctor @Deprecated @RequiresApi(21) public CameraView(android.content.Context, android.util.AttributeSet?, int, int);
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
- method @Deprecated public void enableTorch(boolean);
- method @Deprecated public Integer? getCameraLensFacing();
- method @Deprecated public androidx.camera.view.CameraView.CaptureMode getCaptureMode();
- method @Deprecated @androidx.camera.core.ImageCapture.FlashMode public int getFlash();
- method @Deprecated public float getMaxZoomRatio();
- method @Deprecated public float getMinZoomRatio();
- method @Deprecated public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
- method @Deprecated public androidx.camera.view.PreviewView.ScaleType getScaleType();
- method @Deprecated public float getZoomRatio();
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public boolean hasCameraWithLensFacing(@androidx.camera.core.CameraSelector.LensFacing int);
- method @Deprecated public boolean isPinchToZoomEnabled();
- method @Deprecated public boolean isTorchOn();
- method @Deprecated public boolean isZoomSupported();
- method @Deprecated public void setCameraLensFacing(Integer?);
- method @Deprecated public void setCaptureMode(androidx.camera.view.CameraView.CaptureMode);
- method @Deprecated public void setFlash(@androidx.camera.core.ImageCapture.FlashMode int);
- method @Deprecated public void setPinchToZoomEnabled(boolean);
- method @Deprecated public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
- method @Deprecated public void setZoomRatio(float);
- method @Deprecated public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
- method @Deprecated public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
- method @Deprecated public void toggleCamera();
- }
-
- @Deprecated public enum CameraView.CaptureMode {
- enum_constant @Deprecated public static final androidx.camera.view.CameraView.CaptureMode IMAGE;
- }
-
public final class LifecycleCameraController extends androidx.camera.view.CameraController {
ctor public LifecycleCameraController(android.content.Context);
method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
diff --git a/camera/camera-view/build.gradle b/camera/camera-view/build.gradle
index 06c00c8..2b2420f 100644
--- a/camera/camera-view/build.gradle
+++ b/camera/camera-view/build.gradle
@@ -36,7 +36,7 @@
implementation("androidx.camera:camera-lifecycle:${VIEW_ATOMIC_GROUP_PINNED_VER}")
implementation("androidx.annotation:annotation-experimental:1.1.0-rc01")
implementation(GUAVA_LISTENABLE_FUTURE)
- implementation("androidx.core:core:1.1.0")
+ implementation("androidx.core:core:1.3.2")
implementation("androidx.concurrent:concurrent-futures:1.0.0")
implementation(AUTO_VALUE_ANNOTATIONS)
implementation("androidx.appcompat:appcompat:1.1.0")
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java
deleted file mode 100644
index b6106fc..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java
+++ /dev/null
@@ -1,873 +0,0 @@
-/*
- * Copyright (C) 2019 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.view;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RequiresPermission;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.RestrictTo.Scope;
-import androidx.camera.core.Camera;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.core.FocusMeteringAction;
-import androidx.camera.core.FocusMeteringResult;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.Logger;
-import androidx.camera.core.MeteringPoint;
-import androidx.camera.core.MeteringPointFactory;
-import androidx.camera.core.VideoCapture;
-import androidx.camera.core.impl.LensFacingConverter;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.camera.view.video.OnVideoSavedCallback;
-import androidx.camera.view.video.OutputFileOptions;
-import androidx.camera.view.video.OutputFileResults;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LiveData;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.File;
-import java.util.concurrent.Executor;
-
-/**
- * A {@link View} that displays a preview of the camera with methods {@link
- * #takePicture(Executor, OnImageCapturedCallback)},
- * {@link #takePicture(ImageCapture.OutputFileOptions, Executor, OnImageSavedCallback)},
- * {@link #startRecording(File, Executor, OnVideoSavedCallback callback)}
- * and {@link #stopRecording()}.
- *
- * <p>Because the Camera is a limited resource and consumes a high amount of power, CameraView must
- * be opened/closed. CameraView will handle opening/closing automatically through use of a {@link
- * LifecycleOwner}. Use {@link #bindToLifecycle(LifecycleOwner)} to start the camera.
- *
- * @deprecated Use {@link LifecycleCameraController}. See
- * <a href="https://medium.com/androiddevelopers/camerax-learn-how-to-use-cameracontroller
- * -e3ed10fffecf">migration guide</a>.
- */
-@Deprecated
-public final class CameraView extends FrameLayout {
- static final String TAG = CameraView.class.getSimpleName();
-
- static final int INDEFINITE_VIDEO_DURATION = -1;
- static final int INDEFINITE_VIDEO_SIZE = -1;
-
- private static final String EXTRA_SUPER = "super";
- private static final String EXTRA_ZOOM_RATIO = "zoom_ratio";
- private static final String EXTRA_PINCH_TO_ZOOM_ENABLED = "pinch_to_zoom_enabled";
- private static final String EXTRA_FLASH = "flash";
- private static final String EXTRA_MAX_VIDEO_DURATION = "max_video_duration";
- private static final String EXTRA_MAX_VIDEO_SIZE = "max_video_size";
- private static final String EXTRA_SCALE_TYPE = "scale_type";
- private static final String EXTRA_CAMERA_DIRECTION = "camera_direction";
- private static final String EXTRA_CAPTURE_MODE = "captureMode";
-
- private static final int LENS_FACING_NONE = 0;
- private static final int LENS_FACING_FRONT = 1;
- private static final int LENS_FACING_BACK = 2;
- private static final int FLASH_MODE_AUTO = 1;
- private static final int FLASH_MODE_ON = 2;
- private static final int FLASH_MODE_OFF = 4;
- // For tap-to-focus
- private long mDownEventTimestamp;
- // For pinch-to-zoom
- private PinchToZoomGestureDetector mPinchToZoomGestureDetector;
- private boolean mIsPinchToZoomEnabled = true;
- CameraXModule mCameraModule;
- private final DisplayManager.DisplayListener mDisplayListener =
- new DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- mCameraModule.invalidateView();
- }
- };
- private PreviewView mPreviewView;
- // For accessibility event
- private MotionEvent mUpEvent;
-
- public CameraView(@NonNull Context context) {
- this(context, null);
- }
-
- public CameraView(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public CameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs);
- }
-
- @RequiresApi(21)
- public CameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- init(context, attrs);
- }
-
- /**
- * Binds control of the camera used by this view to the given lifecycle.
- *
- * <p>This links opening/closing the camera to the given lifecycle. The camera will not operate
- * unless this method is called with a valid {@link LifecycleOwner} that is not in the {@link
- * androidx.lifecycle.Lifecycle.State#DESTROYED} state. Call this method only once camera
- * permissions have been obtained.
- *
- * <p>Once the provided lifecycle has transitioned to a {@link
- * androidx.lifecycle.Lifecycle.State#DESTROYED} state, CameraView must be bound to a new
- * lifecycle through this method in order to operate the camera.
- *
- * @param lifecycleOwner The lifecycle that will control this view's camera
- * @throws IllegalArgumentException if provided lifecycle is in a {@link
- * androidx.lifecycle.Lifecycle.State#DESTROYED} state.
- * @throws IllegalStateException if camera permissions are not granted.
- */
- @RequiresPermission(permission.CAMERA)
- public void bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner) {
- mCameraModule.bindToLifecycle(lifecycleOwner);
- }
-
- private void init(Context context, @Nullable AttributeSet attrs) {
- addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
- mCameraModule = new CameraXModule(this);
-
- if (attrs != null) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
- setScaleType(
- PreviewView.ScaleType.fromId(
- a.getInteger(R.styleable.CameraView_scaleType,
- getScaleType().getId())));
- setPinchToZoomEnabled(
- a.getBoolean(
- R.styleable.CameraView_pinchToZoomEnabled, isPinchToZoomEnabled()));
- setCaptureMode(
- CaptureMode.fromId(
- a.getInteger(R.styleable.CameraView_captureMode,
- getCaptureMode().getId())));
-
- int lensFacing = a.getInt(R.styleable.CameraView_lensFacing, LENS_FACING_BACK);
- switch (lensFacing) {
- case LENS_FACING_NONE:
- setCameraLensFacing(null);
- break;
- case LENS_FACING_FRONT:
- setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
- break;
- case LENS_FACING_BACK:
- setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
- break;
- default:
- // Unhandled event.
- }
-
- int flashMode = a.getInt(R.styleable.CameraView_flash, 0);
- switch (flashMode) {
- case FLASH_MODE_AUTO:
- setFlash(ImageCapture.FLASH_MODE_AUTO);
- break;
- case FLASH_MODE_ON:
- setFlash(ImageCapture.FLASH_MODE_ON);
- break;
- case FLASH_MODE_OFF:
- setFlash(ImageCapture.FLASH_MODE_OFF);
- break;
- default:
- // Unhandled event.
- }
-
- a.recycle();
- }
-
- if (getBackground() == null) {
- setBackgroundColor(0xFF111111);
- }
-
- mPinchToZoomGestureDetector = new PinchToZoomGestureDetector(context);
- }
-
- @Override
- @NonNull
- protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- }
-
- @Override
- @NonNull
- protected Parcelable onSaveInstanceState() {
- // TODO(b/113884082): Decide what belongs here or what should be invalidated on
- // configuration
- // change
- Bundle state = new Bundle();
- state.putParcelable(EXTRA_SUPER, super.onSaveInstanceState());
- state.putInt(EXTRA_SCALE_TYPE, getScaleType().getId());
- state.putFloat(EXTRA_ZOOM_RATIO, getZoomRatio());
- state.putBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED, isPinchToZoomEnabled());
- state.putString(EXTRA_FLASH, FlashModeConverter.nameOf(getFlash()));
- state.putLong(EXTRA_MAX_VIDEO_DURATION, getMaxVideoDuration());
- state.putLong(EXTRA_MAX_VIDEO_SIZE, getMaxVideoSize());
- if (getCameraLensFacing() != null) {
- state.putString(EXTRA_CAMERA_DIRECTION,
- LensFacingConverter.nameOf(getCameraLensFacing()));
- }
- state.putInt(EXTRA_CAPTURE_MODE, getCaptureMode().getId());
- return state;
- }
-
- @Override
- protected void onRestoreInstanceState(@Nullable Parcelable savedState) {
- // TODO(b/113884082): Decide what belongs here or what should be invalidated on
- // configuration
- // change
- if (savedState instanceof Bundle) {
- Bundle state = (Bundle) savedState;
- super.onRestoreInstanceState(state.getParcelable(EXTRA_SUPER));
- setScaleType(PreviewView.ScaleType.fromId(state.getInt(EXTRA_SCALE_TYPE)));
- setZoomRatio(state.getFloat(EXTRA_ZOOM_RATIO));
- setPinchToZoomEnabled(state.getBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED));
- setFlash(FlashModeConverter.valueOf(state.getString(EXTRA_FLASH)));
- setMaxVideoDuration(state.getLong(EXTRA_MAX_VIDEO_DURATION));
- setMaxVideoSize(state.getLong(EXTRA_MAX_VIDEO_SIZE));
- String lensFacingString = state.getString(EXTRA_CAMERA_DIRECTION);
- setCameraLensFacing(
- TextUtils.isEmpty(lensFacingString)
- ? null
- : LensFacingConverter.valueOf(lensFacingString));
- setCaptureMode(CaptureMode.fromId(state.getInt(EXTRA_CAPTURE_MODE)));
- } else {
- super.onRestoreInstanceState(savedState);
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- DisplayManager dpyMgr =
- (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
- dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- DisplayManager dpyMgr =
- (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
- dpyMgr.unregisterDisplayListener(mDisplayListener);
- }
-
- /**
- * Gets the {@link LiveData} of the underlying {@link PreviewView}'s
- * {@link PreviewView.StreamState}.
- *
- * @return A {@link LiveData} containing the {@link PreviewView.StreamState}. Apps can either
- * get current value by {@link LiveData#getValue()} or register a observer by
- * {@link LiveData#observe}.
- * @see PreviewView#getPreviewStreamState()
- */
- @NonNull
- public LiveData<PreviewView.StreamState> getPreviewStreamState() {
- return mPreviewView.getPreviewStreamState();
- }
-
- @NonNull
- PreviewView getPreviewView() {
- return mPreviewView;
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Since bindToLifecycle will depend on the measured dimension, only call it when measured
- // dimension is not 0x0
- if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
- mCameraModule.bindToLifecycleAfterViewMeasured();
- }
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // In case that the CameraView size is always set as 0x0, we still need to trigger to force
- // binding to lifecycle
- mCameraModule.bindToLifecycleAfterViewMeasured();
-
- mCameraModule.invalidateView();
- super.onLayout(changed, left, top, right, bottom);
- }
-
- /**
- * @return One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, {@link
- * Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
- */
- int getDisplaySurfaceRotation() {
- Display display = getDisplay();
-
- // Null when the View is detached. If we were in the middle of a background operation,
- // better to not NPE. When the background operation finishes, it'll realize that the camera
- // was closed.
- if (display == null) {
- return 0;
- }
-
- return display.getRotation();
- }
-
- /**
- * Returns the scale type used to scale the preview.
- *
- * @return The current {@link PreviewView.ScaleType}.
- */
- @NonNull
- public PreviewView.ScaleType getScaleType() {
- return mPreviewView.getScaleType();
- }
-
- /**
- * Sets the view finder scale type.
- *
- * <p>This controls how the view finder should be scaled and positioned within the view.
- *
- * @param scaleType The desired {@link PreviewView.ScaleType}.
- */
- public void setScaleType(@NonNull PreviewView.ScaleType scaleType) {
- mPreviewView.setScaleType(scaleType);
- }
-
- /**
- * Returns the scale type used to scale the preview.
- *
- * @return The current {@link CaptureMode}.
- */
- @NonNull
- public CaptureMode getCaptureMode() {
- return mCameraModule.getCaptureMode();
- }
-
- /**
- * Sets the CameraView capture mode
- *
- * <p>This controls only image or video capture function is enabled or both are enabled.
- *
- * @param captureMode The desired {@link CaptureMode}.
- */
- public void setCaptureMode(@NonNull CaptureMode captureMode) {
- mCameraModule.setCaptureMode(captureMode);
- }
-
- /**
- * Returns the maximum duration of videos, or {@link #INDEFINITE_VIDEO_DURATION} if there is no
- * timeout.
- *
- * @hide Not currently implemented.
- */
- @RestrictTo(Scope.LIBRARY_GROUP)
- public long getMaxVideoDuration() {
- return mCameraModule.getMaxVideoDuration();
- }
-
- /**
- * Sets the maximum video duration before
- * {@link OnVideoSavedCallback#onVideoSaved(OutputFileResults)} is called
- * automatically.
- * Use {@link #INDEFINITE_VIDEO_DURATION} to disable the timeout.
- */
- private void setMaxVideoDuration(long duration) {
- mCameraModule.setMaxVideoDuration(duration);
- }
-
- /**
- * Returns the maximum size of videos in bytes, or {@link #INDEFINITE_VIDEO_SIZE} if there is no
- * timeout.
- */
- private long getMaxVideoSize() {
- return mCameraModule.getMaxVideoSize();
- }
-
- /**
- * Sets the maximum video size in bytes before
- * {@link OnVideoSavedCallback#onVideoSaved(OutputFileResults)}
- * is called automatically. Use {@link #INDEFINITE_VIDEO_SIZE} to disable the size restriction.
- */
- private void setMaxVideoSize(long size) {
- mCameraModule.setMaxVideoSize(size);
- }
-
- /**
- * Takes a picture, and calls {@link OnImageCapturedCallback#onCaptureSuccess(ImageProxy)}
- * once when done.
- *
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure callbacks.
- */
- public void takePicture(@NonNull Executor executor, @NonNull OnImageCapturedCallback callback) {
- mCameraModule.takePicture(executor, callback);
- }
-
- /**
- * Takes a picture and calls
- * {@link OnImageSavedCallback#onImageSaved(ImageCapture.OutputFileResults)} when done.
- *
- * <p> The value of {@link ImageCapture.Metadata#isReversedHorizontal()} in the
- * {@link ImageCapture.OutputFileOptions} will be overwritten based on camera direction. For
- * front camera, it will be set to true; for back camera, it will be set to false.
- *
- * @param outputFileOptions Options to store the newly captured image.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
- @NonNull Executor executor,
- @NonNull OnImageSavedCallback callback) {
- mCameraModule.takePicture(outputFileOptions, executor, callback);
- }
-
- /**
- * Takes a video and calls the OnVideoSavedCallback when done.
- *
- * @param file The destination.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- @ExperimentalVideo
- public void startRecording(@NonNull File file, @NonNull Executor executor,
- @NonNull OnVideoSavedCallback callback) {
- OutputFileOptions options = OutputFileOptions.builder(file).build();
- startRecording(options, executor, callback);
- }
-
- /**
- * Takes a video and calls the OnVideoSavedCallback when done.
- *
- * @param fd The destination {@link ParcelFileDescriptor}.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- @ExperimentalVideo
- public void startRecording(@NonNull ParcelFileDescriptor fd, @NonNull Executor executor,
- @NonNull OnVideoSavedCallback callback) {
- OutputFileOptions options = OutputFileOptions.builder(fd).build();
- startRecording(options, executor, callback);
- }
-
- /**
- * Takes a video and calls the OnVideoSavedCallback when done.
- *
- * @param outputFileOptions Options to store the newly captured video.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- @ExperimentalVideo
- public void startRecording(@NonNull OutputFileOptions outputFileOptions,
- @NonNull Executor executor,
- @NonNull OnVideoSavedCallback callback) {
- VideoCapture.OnVideoSavedCallback callbackWrapper =
- new VideoCapture.OnVideoSavedCallback() {
- @Override
- public void onVideoSaved(
- @NonNull VideoCapture.OutputFileResults outputFileResults) {
- callback.onVideoSaved(
- OutputFileResults.create(outputFileResults.getSavedUri()));
- }
-
- @Override
- public void onError(int videoCaptureError, @NonNull String message,
- @Nullable Throwable cause) {
- callback.onError(videoCaptureError, message, cause);
- }
- };
-
- mCameraModule.startRecording(outputFileOptions.toVideoCaptureOutputFileOptions(), executor,
- callbackWrapper);
- }
-
- /** Stops an in progress video. */
- @ExperimentalVideo
- public void stopRecording() {
- mCameraModule.stopRecording();
- }
-
- /** @return True if currently recording. */
- @ExperimentalVideo
- public boolean isRecording() {
- return mCameraModule.isRecording();
- }
-
- /**
- * Queries whether the current device has a camera with the specified direction.
- *
- * @return True if the device supports the direction.
- * @throws IllegalStateException if the CAMERA permission is not currently granted.
- */
- @RequiresPermission(permission.CAMERA)
- public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
- return mCameraModule.hasCameraWithLensFacing(lensFacing);
- }
-
- /**
- * Toggles between the primary front facing camera and the primary back facing camera.
- *
- * <p>This will have no effect if not already bound to a lifecycle via {@link
- * #bindToLifecycle(LifecycleOwner)}.
- */
- public void toggleCamera() {
- mCameraModule.toggleCamera();
- }
-
- /**
- * Sets the desired camera by specifying desired lensFacing.
- *
- * <p>This will choose the primary camera with the specified camera lensFacing.
- *
- * <p>If called before {@link #bindToLifecycle(LifecycleOwner)}, this will set the camera to be
- * used when first bound to the lifecycle. If the specified lensFacing is not supported by the
- * device, as determined by {@link #hasCameraWithLensFacing(int)}, the first supported
- * lensFacing will be chosen when {@link #bindToLifecycle(LifecycleOwner)} is called.
- *
- * <p>If called with {@code null} AFTER binding to the lifecycle, the behavior would be
- * equivalent to unbind the use cases without the lifecycle having to be destroyed.
- *
- * @param lensFacing The desired camera lensFacing.
- */
- public void setCameraLensFacing(@Nullable Integer lensFacing) {
- mCameraModule.setCameraLensFacing(lensFacing);
- }
-
- /** Returns the currently selected lensFacing. */
- @Nullable
- public Integer getCameraLensFacing() {
- return mCameraModule.getLensFacing();
- }
-
- /** Gets the active flash strategy. */
- @ImageCapture.FlashMode
- public int getFlash() {
- return mCameraModule.getFlash();
- }
-
- /** Sets the active flash strategy. */
- public void setFlash(@ImageCapture.FlashMode int flashMode) {
- mCameraModule.setFlash(flashMode);
- }
-
- private long delta() {
- return System.currentTimeMillis() - mDownEventTimestamp;
- }
-
- @Override
- public boolean onTouchEvent(@NonNull MotionEvent event) {
- // Disable pinch-to-zoom and tap-to-focus while the camera module is paused.
- if (mCameraModule.isPaused()) {
- return false;
- }
- // Only forward the event to the pinch-to-zoom gesture detector when pinch-to-zoom is
- // enabled.
- if (isPinchToZoomEnabled()) {
- mPinchToZoomGestureDetector.onTouchEvent(event);
- }
- if (event.getPointerCount() == 2 && isPinchToZoomEnabled() && isZoomSupported()) {
- return true;
- }
-
- // Camera focus
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mDownEventTimestamp = System.currentTimeMillis();
- break;
- case MotionEvent.ACTION_UP:
- if (delta() < ViewConfiguration.getLongPressTimeout()
- && mCameraModule.isBoundToLifecycle()) {
- mUpEvent = event;
- performClick();
- }
- break;
- default:
- // Unhandled event.
- return false;
- }
- return true;
- }
-
- /**
- * Focus the position of the touch event, or focus the center of the preview for
- * accessibility events
- */
- @Override
- public boolean performClick() {
- super.performClick();
-
- final float x = (mUpEvent != null) ? mUpEvent.getX() : getX() + getWidth() / 2f;
- final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
- mUpEvent = null;
-
- Camera camera = mCameraModule.getCamera();
- if (camera != null) {
- MeteringPointFactory pointFactory = mPreviewView.getMeteringPointFactory();
- float afPointWidth = 1.0f / 6.0f; // 1/6 total area
- float aePointWidth = afPointWidth * 1.5f;
- MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
- MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
-
- ListenableFuture<FocusMeteringResult> future =
- camera.getCameraControl().startFocusAndMetering(
- new FocusMeteringAction.Builder(afPoint,
- FocusMeteringAction.FLAG_AF).addPoint(aePoint,
- FocusMeteringAction.FLAG_AE).build());
- Futures.addCallback(future, new FutureCallback<FocusMeteringResult>() {
- @Override
- public void onSuccess(@Nullable FocusMeteringResult result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Throw the unexpected error.
- throw new RuntimeException(t);
- }
- }, CameraXExecutors.directExecutor());
-
- } else {
- Logger.d(TAG, "cannot access camera");
- }
-
- return true;
- }
-
- float rangeLimit(float val, float max, float min) {
- return Math.min(Math.max(val, min), max);
- }
-
- /**
- * Returns whether the view allows pinch-to-zoom.
- *
- * @return True if pinch to zoom is enabled.
- */
- public boolean isPinchToZoomEnabled() {
- return mIsPinchToZoomEnabled;
- }
-
- /**
- * Sets whether the view should allow pinch-to-zoom.
- *
- * <p>When enabled, the user can pinch the camera to zoom in/out. This only has an effect if the
- * bound camera supports zoom.
- *
- * @param enabled True to enable pinch-to-zoom.
- */
- public void setPinchToZoomEnabled(boolean enabled) {
- mIsPinchToZoomEnabled = enabled;
- }
-
- /**
- * Returns the current zoom ratio.
- *
- * @return The current zoom ratio.
- */
- public float getZoomRatio() {
- return mCameraModule.getZoomRatio();
- }
-
- /**
- * Sets the current zoom ratio.
- *
- * <p>Valid zoom values range from {@link #getMinZoomRatio()} to {@link #getMaxZoomRatio()}.
- *
- * @param zoomRatio The requested zoom ratio.
- */
- public void setZoomRatio(float zoomRatio) {
- mCameraModule.setZoomRatio(zoomRatio);
- }
-
- /**
- * Returns the minimum zoom ratio.
- *
- * <p>For most cameras this should return a zoom ratio of 1. A zoom ratio of 1 corresponds to a
- * non-zoomed image.
- *
- * @return The minimum zoom ratio.
- */
- public float getMinZoomRatio() {
- return mCameraModule.getMinZoomRatio();
- }
-
- /**
- * Returns the maximum zoom ratio.
- *
- * <p>The zoom ratio corresponds to the ratio between both the widths and heights of a
- * non-zoomed image and a maximally zoomed image for the selected camera.
- *
- * @return The maximum zoom ratio.
- */
- public float getMaxZoomRatio() {
- return mCameraModule.getMaxZoomRatio();
- }
-
- /**
- * Returns whether the bound camera supports zooming.
- *
- * @return True if the camera supports zooming.
- */
- public boolean isZoomSupported() {
- return mCameraModule.isZoomSupported();
- }
-
- /**
- * Turns on/off torch.
- *
- * @param torch True to turn on torch, false to turn off torch.
- */
- public void enableTorch(boolean torch) {
- mCameraModule.enableTorch(torch);
- }
-
- /**
- * Returns current torch status.
- *
- * @return true if torch is on , otherwise false
- */
- public boolean isTorchOn() {
- return mCameraModule.isTorchOn();
- }
-
- /**
- * The capture mode used by CameraView.
- *
- * <p>This enum can be used to determine which capture mode will be enabled for {@link
- * CameraView}.
- */
- public enum CaptureMode {
- /** A mode where image capture is enabled. */
- IMAGE(0),
- /** A mode where video capture is enabled. */
- @ExperimentalVideo
- VIDEO(1),
- /**
- * A mode where both image capture and video capture are simultaneously enabled. Note that
- * this mode may not be available on every device.
- */
- @ExperimentalVideo
- MIXED(2);
-
- private final int mId;
-
- int getId() {
- return mId;
- }
-
- CaptureMode(int id) {
- mId = id;
- }
-
- static CaptureMode fromId(int id) {
- for (CaptureMode f : values()) {
- if (f.mId == id) {
- return f;
- }
- }
- throw new IllegalArgumentException();
- }
- }
-
- static class S extends ScaleGestureDetector.SimpleOnScaleGestureListener {
- private ScaleGestureDetector.OnScaleGestureListener mListener;
-
- void setRealGestureDetector(ScaleGestureDetector.OnScaleGestureListener l) {
- mListener = l;
- }
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- return mListener.onScale(detector);
- }
- }
-
- private class PinchToZoomGestureDetector extends ScaleGestureDetector
- implements ScaleGestureDetector.OnScaleGestureListener {
- PinchToZoomGestureDetector(Context context) {
- this(context, new S());
- }
-
- PinchToZoomGestureDetector(Context context, S s) {
- super(context, s);
- s.setRealGestureDetector(this);
- }
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- float scale = detector.getScaleFactor();
-
- // Speeding up the zoom by 2X.
- if (scale > 1f) {
- scale = 1.0f + (scale - 1.0f) * 2;
- } else {
- scale = 1.0f - (1.0f - scale) * 2;
- }
-
- float newRatio = getZoomRatio() * scale;
- newRatio = rangeLimit(newRatio, getMaxZoomRatio(), getMinZoomRatio());
- setZoomRatio(newRatio);
- return true;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- }
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java
deleted file mode 100644
index c1ad936..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java
+++ /dev/null
@@ -1,659 +0,0 @@
-/*
- * Copyright (C) 2019 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.view;
-
-import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.util.Rational;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.annotation.RequiresPermission;
-import androidx.camera.core.AspectRatio;
-import androidx.camera.core.Camera;
-import androidx.camera.core.CameraInfoUnavailableException;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.Logger;
-import androidx.camera.core.Preview;
-import androidx.camera.core.TorchState;
-import androidx.camera.core.UseCase;
-import androidx.camera.core.VideoCapture;
-import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
-import androidx.camera.core.impl.LensFacingConverter;
-import androidx.camera.core.impl.utils.CameraOrientationUtil;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.camera.lifecycle.ProcessCameraProvider;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.core.util.Preconditions;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.OnLifecycleEvent;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * CameraX use case operation built on @{link androidx.camera.core}.
- *
- * @deprecated Use {@link LifecycleCameraController}. See
- * <a href="https://medium.com/androiddevelopers/camerax-learn-how-to-use-cameracontroller
- * -e3ed10fffecf">migration guide</a>.
- */
-@Deprecated
-final class CameraXModule {
- public static final String TAG = "CameraXModule";
-
- private static final float UNITY_ZOOM_SCALE = 1f;
- private static final float ZOOM_NOT_SUPPORTED = UNITY_ZOOM_SCALE;
- private static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9);
- private static final Rational ASPECT_RATIO_4_3 = new Rational(4, 3);
- private static final Rational ASPECT_RATIO_9_16 = new Rational(9, 16);
- private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
-
- private final Preview.Builder mPreviewBuilder;
- private final VideoCapture.Builder mVideoCaptureBuilder;
- private final ImageCapture.Builder mImageCaptureBuilder;
- private final CameraView mCameraView;
- final AtomicBoolean mVideoIsRecording = new AtomicBoolean(false);
- private CameraView.CaptureMode mCaptureMode = CameraView.CaptureMode.IMAGE;
- private long mMaxVideoDuration = CameraView.INDEFINITE_VIDEO_DURATION;
- private long mMaxVideoSize = CameraView.INDEFINITE_VIDEO_SIZE;
- @ImageCapture.FlashMode
- private int mFlash = FLASH_MODE_OFF;
- @Nullable
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- Camera mCamera;
- @Nullable
- private ImageCapture mImageCapture;
- @Nullable
- private VideoCapture mVideoCapture;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- Preview mPreview;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- LifecycleOwner mCurrentLifecycle;
- private final LifecycleObserver mCurrentLifecycleObserver =
- new LifecycleObserver() {
- @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
- public void onDestroy(LifecycleOwner owner) {
- if (owner == mCurrentLifecycle) {
- clearCurrentLifecycle();
- }
- }
- };
- @Nullable
- private LifecycleOwner mNewLifecycle;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- Integer mCameraLensFacing = CameraSelector.LENS_FACING_BACK;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- ProcessCameraProvider mCameraProvider;
-
- CameraXModule(CameraView view) {
- mCameraView = view;
-
- Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
- new FutureCallback<ProcessCameraProvider>() {
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- @Override
- public void onSuccess(@Nullable ProcessCameraProvider provider) {
- Preconditions.checkNotNull(provider);
- mCameraProvider = provider;
- if (mCurrentLifecycle != null) {
- bindToLifecycle(mCurrentLifecycle);
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- throw new RuntimeException("CameraX failed to initialize.", t);
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- mPreviewBuilder = new Preview.Builder().setTargetName("Preview");
-
- mImageCaptureBuilder = new ImageCapture.Builder().setTargetName("ImageCapture");
-
- mVideoCaptureBuilder = new VideoCapture.Builder().setTargetName("VideoCapture");
- }
-
- @RequiresPermission(permission.CAMERA)
- void bindToLifecycle(LifecycleOwner lifecycleOwner) {
- mNewLifecycle = lifecycleOwner;
-
- if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
- bindToLifecycleAfterViewMeasured();
- }
- }
-
- @OptIn(markerClass = ExperimentalVideo.class)
- @RequiresPermission(permission.CAMERA)
- void bindToLifecycleAfterViewMeasured() {
- if (mNewLifecycle == null) {
- return;
- }
-
- clearCurrentLifecycle();
- if (mNewLifecycle.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
- // Lifecycle is already in a destroyed state. Since it may have been a valid
- // lifecycle when bound, but became destroyed while waiting for layout, treat this as
- // a no-op now that we have cleared the previous lifecycle.
- mNewLifecycle = null;
- return;
- }
- mCurrentLifecycle = mNewLifecycle;
- mNewLifecycle = null;
-
- if (mCameraProvider == null) {
- // try again once the camera provider is no longer null
- return;
- }
-
- Set<Integer> available = getAvailableCameraLensFacing();
-
- if (available.isEmpty()) {
- Logger.w(TAG, "Unable to bindToLifeCycle since no cameras available");
- mCameraLensFacing = null;
- }
-
- // Ensure the current camera exists, or default to another camera
- if (mCameraLensFacing != null && !available.contains(mCameraLensFacing)) {
- Logger.w(TAG, "Camera does not exist with direction " + mCameraLensFacing);
-
- // Default to the first available camera direction
- mCameraLensFacing = available.iterator().next();
-
- Logger.w(TAG, "Defaulting to primary camera with direction " + mCameraLensFacing);
- }
-
- // Do not attempt to create use cases for a null cameraLensFacing. This could occur if
- // the user explicitly sets the LensFacing to null, or if we determined there
- // were no available cameras, which should be logged in the logic above.
- if (mCameraLensFacing == null) {
- return;
- }
-
- // Set the preferred aspect ratio as 4:3 if it is IMAGE only mode. Set the preferred aspect
- // ratio as 16:9 if it is VIDEO or MIXED mode. Then, it will be WYSIWYG when the view finder
- // is in CENTER_INSIDE mode.
-
- boolean isDisplayPortrait = getDisplayRotationDegrees() == 0
- || getDisplayRotationDegrees() == 180;
-
- Rational targetAspectRatio;
- if (getCaptureMode() == CameraView.CaptureMode.IMAGE) {
- targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_3_4 : ASPECT_RATIO_4_3;
- } else {
- mImageCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_16_9);
- mVideoCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_16_9);
- targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_9_16 : ASPECT_RATIO_16_9;
- }
-
- mImageCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
- mImageCapture = mImageCaptureBuilder.build();
-
- mVideoCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
- mVideoCapture = mVideoCaptureBuilder.build();
-
- // Adjusts the preview resolution according to the view size and the target aspect ratio.
- int height = (int) (getMeasuredWidth() / targetAspectRatio.floatValue());
- mPreviewBuilder.setTargetResolution(new Size(getMeasuredWidth(), height));
-
- mPreview = mPreviewBuilder.build();
- mPreview.setSurfaceProvider(mCameraView.getPreviewView().getSurfaceProvider());
-
- CameraSelector cameraSelector =
- new CameraSelector.Builder().requireLensFacing(mCameraLensFacing).build();
- if (getCaptureMode() == CameraView.CaptureMode.IMAGE) {
- mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
- mImageCapture,
- mPreview);
- } else if (getCaptureMode() == CameraView.CaptureMode.VIDEO) {
- mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
- mVideoCapture,
- mPreview);
- } else {
- mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
- mImageCapture,
- mVideoCapture, mPreview);
- }
-
- setZoomRatio(UNITY_ZOOM_SCALE);
- mCurrentLifecycle.getLifecycle().addObserver(mCurrentLifecycleObserver);
- // Enable flash setting in ImageCapture after use cases are created and binded.
- setFlash(getFlash());
- }
-
- public void open() {
- throw new UnsupportedOperationException(
- "Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
- }
-
- public void close() {
- throw new UnsupportedOperationException(
- "Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
- }
-
- @OptIn(markerClass = ExperimentalVideo.class)
- public void takePicture(Executor executor, OnImageCapturedCallback callback) {
- if (mImageCapture == null) {
- return;
- }
-
- if (getCaptureMode() == CameraView.CaptureMode.VIDEO) {
- throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
- }
-
- if (callback == null) {
- throw new IllegalArgumentException("OnImageCapturedCallback should not be empty");
- }
-
- mImageCapture.takePicture(executor, callback);
- }
-
- @OptIn(markerClass = ExperimentalVideo.class)
- public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
- @NonNull Executor executor, OnImageSavedCallback callback) {
- if (mImageCapture == null) {
- return;
- }
-
- if (getCaptureMode() == CameraView.CaptureMode.VIDEO) {
- throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
- }
-
- if (callback == null) {
- throw new IllegalArgumentException("OnImageSavedCallback should not be empty");
- }
-
- outputFileOptions.getMetadata().setReversedHorizontal(mCameraLensFacing != null
- && mCameraLensFacing == CameraSelector.LENS_FACING_FRONT);
- mImageCapture.takePicture(outputFileOptions, executor, callback);
- }
-
- public void startRecording(VideoCapture.OutputFileOptions outputFileOptions,
- Executor executor, final OnVideoSavedCallback callback) {
- if (mVideoCapture == null) {
- return;
- }
-
- if (getCaptureMode() == CameraView.CaptureMode.IMAGE) {
- throw new IllegalStateException("Can not record video under IMAGE capture mode.");
- }
-
- if (callback == null) {
- throw new IllegalArgumentException("OnVideoSavedCallback should not be empty");
- }
-
- mVideoIsRecording.set(true);
- mVideoCapture.startRecording(
- outputFileOptions,
- executor,
- new VideoCapture.OnVideoSavedCallback() {
- @Override
- public void onVideoSaved(
- @NonNull VideoCapture.OutputFileResults outputFileResults) {
- mVideoIsRecording.set(false);
- callback.onVideoSaved(outputFileResults);
- }
-
- @Override
- public void onError(
- @VideoCapture.VideoCaptureError int videoCaptureError,
- @NonNull String message,
- @Nullable Throwable cause) {
- mVideoIsRecording.set(false);
- Logger.e(TAG, message, cause);
- callback.onError(videoCaptureError, message, cause);
- }
- });
- }
-
- public void stopRecording() {
- if (mVideoCapture == null) {
- return;
- }
-
- mVideoCapture.stopRecording();
- }
-
- public boolean isRecording() {
- return mVideoIsRecording.get();
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- public void setCameraLensFacing(@Nullable Integer lensFacing) {
- // Setting same lens facing is a no-op, so check for that first
- if (!Objects.equals(mCameraLensFacing, lensFacing)) {
- // If we're not bound to a lifecycle, just update the camera that will be opened when we
- // attach to a lifecycle.
- mCameraLensFacing = lensFacing;
-
- if (mCurrentLifecycle != null) {
- // Re-bind to lifecycle with new camera
- bindToLifecycle(mCurrentLifecycle);
- }
- }
- }
-
- @RequiresPermission(permission.CAMERA)
- public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
- if (mCameraProvider == null) {
- return false;
- }
- try {
- return mCameraProvider.hasCamera(
- new CameraSelector.Builder().requireLensFacing(lensFacing).build());
- } catch (CameraInfoUnavailableException e) {
- return false;
- }
- }
-
- @Nullable
- public Integer getLensFacing() {
- return mCameraLensFacing;
- }
-
- public void toggleCamera() {
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- Set<Integer> availableCameraLensFacing = getAvailableCameraLensFacing();
-
- if (availableCameraLensFacing.isEmpty()) {
- return;
- }
-
- if (mCameraLensFacing == null) {
- setCameraLensFacing(availableCameraLensFacing.iterator().next());
- return;
- }
-
- if (mCameraLensFacing == CameraSelector.LENS_FACING_BACK
- && availableCameraLensFacing.contains(CameraSelector.LENS_FACING_FRONT)) {
- setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
- return;
- }
-
- if (mCameraLensFacing == CameraSelector.LENS_FACING_FRONT
- && availableCameraLensFacing.contains(CameraSelector.LENS_FACING_BACK)) {
- setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
- return;
- }
- }
-
- public float getZoomRatio() {
- if (mCamera != null) {
- return mCamera.getCameraInfo().getZoomState().getValue().getZoomRatio();
- } else {
- return UNITY_ZOOM_SCALE;
- }
- }
-
- public void setZoomRatio(float zoomRatio) {
- if (mCamera != null) {
- ListenableFuture<Void> future = mCamera.getCameraControl().setZoomRatio(
- zoomRatio);
- Futures.addCallback(future, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Throw the unexpected error.
- throw new RuntimeException(t);
- }
- }, CameraXExecutors.directExecutor());
- } else {
- Logger.e(TAG, "Failed to set zoom ratio");
- }
- }
-
- public float getMinZoomRatio() {
- if (mCamera != null) {
- return mCamera.getCameraInfo().getZoomState().getValue().getMinZoomRatio();
- } else {
- return UNITY_ZOOM_SCALE;
- }
- }
-
- public float getMaxZoomRatio() {
- if (mCamera != null) {
- return mCamera.getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
- } else {
- return ZOOM_NOT_SUPPORTED;
- }
- }
-
- public boolean isZoomSupported() {
- return getMaxZoomRatio() != ZOOM_NOT_SUPPORTED;
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- private void rebindToLifecycle() {
- if (mCurrentLifecycle != null) {
- bindToLifecycle(mCurrentLifecycle);
- }
- }
-
- boolean isBoundToLifecycle() {
- return mCamera != null;
- }
-
- int getRelativeCameraOrientation(boolean compensateForMirroring) {
- int rotationDegrees = 0;
- if (mCamera != null) {
- rotationDegrees =
- mCamera.getCameraInfo().getSensorRotationDegrees(getDisplaySurfaceRotation());
- if (compensateForMirroring) {
- rotationDegrees = (360 - rotationDegrees) % 360;
- }
- }
-
- return rotationDegrees;
- }
-
- public void invalidateView() {
- updateViewInfo();
- }
-
- void clearCurrentLifecycle() {
- if (mCurrentLifecycle != null && mCameraProvider != null) {
- // Remove previous use cases
- List<UseCase> toUnbind = new ArrayList<>();
- if (mImageCapture != null && mCameraProvider.isBound(mImageCapture)) {
- toUnbind.add(mImageCapture);
- }
- if (mVideoCapture != null && mCameraProvider.isBound(mVideoCapture)) {
- toUnbind.add(mVideoCapture);
- }
- if (mPreview != null && mCameraProvider.isBound(mPreview)) {
- toUnbind.add(mPreview);
- }
-
- if (!toUnbind.isEmpty()) {
- mCameraProvider.unbind(toUnbind.toArray((new UseCase[0])));
- }
-
- // Remove surface provider once unbound.
- if (mPreview != null) {
- mPreview.setSurfaceProvider(null);
- }
- }
- mCamera = null;
- mCurrentLifecycle = null;
- }
-
- // Update view related information used in use cases
- private void updateViewInfo() {
- if (mImageCapture != null) {
- mImageCapture.setCropAspectRatio(new Rational(getWidth(), getHeight()));
- mImageCapture.setTargetRotation(getDisplaySurfaceRotation());
- }
-
- if (mVideoCapture != null) {
- mVideoCapture.setTargetRotation(getDisplaySurfaceRotation());
- }
- }
-
- @RequiresPermission(permission.CAMERA)
- private Set<Integer> getAvailableCameraLensFacing() {
- // Start with all camera directions
- Set<Integer> available = new LinkedHashSet<>(Arrays.asList(LensFacingConverter.values()));
-
- // If we're bound to a lifecycle, remove unavailable cameras
- if (mCurrentLifecycle != null) {
- if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)) {
- available.remove(CameraSelector.LENS_FACING_BACK);
- }
-
- if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT)) {
- available.remove(CameraSelector.LENS_FACING_FRONT);
- }
- }
-
- return available;
- }
-
- @ImageCapture.FlashMode
- public int getFlash() {
- return mFlash;
- }
-
- public void setFlash(@ImageCapture.FlashMode int flash) {
- this.mFlash = flash;
-
- if (mImageCapture == null) {
- // Do nothing if there is no imageCapture
- return;
- }
-
- mImageCapture.setFlashMode(flash);
- }
-
- public void enableTorch(boolean torch) {
- if (mCamera == null) {
- return;
- }
- ListenableFuture<Void> future = mCamera.getCameraControl().enableTorch(torch);
- Futures.addCallback(future, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Throw the unexpected error.
- throw new RuntimeException(t);
- }
- }, CameraXExecutors.directExecutor());
- }
-
- public boolean isTorchOn() {
- if (mCamera == null) {
- return false;
- }
- return mCamera.getCameraInfo().getTorchState().getValue() == TorchState.ON;
- }
-
- public Context getContext() {
- return mCameraView.getContext();
- }
-
- public int getWidth() {
- return mCameraView.getWidth();
- }
-
- public int getHeight() {
- return mCameraView.getHeight();
- }
-
- public int getDisplayRotationDegrees() {
- return CameraOrientationUtil.surfaceRotationToDegrees(getDisplaySurfaceRotation());
- }
-
- protected int getDisplaySurfaceRotation() {
- return mCameraView.getDisplaySurfaceRotation();
- }
-
- private int getMeasuredWidth() {
- return mCameraView.getMeasuredWidth();
- }
-
- private int getMeasuredHeight() {
- return mCameraView.getMeasuredHeight();
- }
-
- @Nullable
- public Camera getCamera() {
- return mCamera;
- }
-
- @NonNull
- public CameraView.CaptureMode getCaptureMode() {
- return mCaptureMode;
- }
-
- public void setCaptureMode(@NonNull CameraView.CaptureMode captureMode) {
- this.mCaptureMode = captureMode;
- rebindToLifecycle();
- }
-
- public long getMaxVideoDuration() {
- return mMaxVideoDuration;
- }
-
- public void setMaxVideoDuration(long duration) {
- mMaxVideoDuration = duration;
- }
-
- public long getMaxVideoSize() {
- return mMaxVideoSize;
- }
-
- public void setMaxVideoSize(long size) {
- mMaxVideoSize = size;
- }
-
- public boolean isPaused() {
- return false;
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
index 9459be0..89775f6 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
@@ -69,6 +69,7 @@
import androidx.camera.view.transform.CoordinateTransform;
import androidx.camera.view.transform.OutputTransform;
import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -156,6 +157,7 @@
@SuppressWarnings("WeakerAccess")
final Preview.SurfaceProvider mSurfaceProvider = new Preview.SurfaceProvider() {
+ @SuppressLint("NewApi")
@OptIn(markerClass = ExperimentalUseCaseGroup.class)
@Override
@AnyThread
@@ -230,10 +232,8 @@
Threads.checkMainThread();
final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.PreviewView, defStyleAttr, defStyleRes);
- if (Build.VERSION.SDK_INT >= 29) {
- saveAttributeDataForStyleable(context, R.styleable.PreviewView, attrs, attributes,
- defStyleAttr, defStyleRes);
- }
+ ViewCompat.saveAttributeDataForStyleable(this, context, R.styleable.PreviewView, attrs,
+ attributes, defStyleAttr, defStyleRes);
try {
final int scaleTypeId = attributes.getInteger(
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java b/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
index 4110046..0d9a473 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
@@ -16,7 +16,6 @@
package androidx.camera.view;
-import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.util.Size;
import android.view.PixelCopy;
@@ -28,6 +27,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.annotation.UiThread;
import androidx.camera.core.Logger;
import androidx.camera.core.SurfaceRequest;
@@ -114,7 +114,7 @@
* would introduced in API level 24. PreviewView doesn't currently use a SurfaceView on API
* levels below 24.
*/
- @TargetApi(24)
+ @RequiresApi(24)
@Nullable
@Override
Bitmap getPreviewBitmap() {
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
index a121fac..d84d77d 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
@@ -184,15 +184,19 @@
// Check that extension can be enabled and if so enable it
@Extensions.ExtensionMode
int extensionMode = extensionModeFrom(imageCaptureType);
- boolean extensionAvailable = mExtensions.isExtensionAvailable(mCamera, extensionMode);
- if (extensionAvailable) {
- Log.d(TAG, "Enabling extension mode: " + imageCaptureType.name());
- mExtensions.setExtension(mCamera, extensionMode);
- } else {
- Log.d(TAG, "Unable to enable extension mode, skipping: " + imageCaptureType.name());
+
+ if (!mExtensions.isExtensionAvailable(mCameraProvider, mCurrentCameraSelector,
+ extensionMode)) {
return false;
}
+ CameraSelector cameraSelector = mExtensions.getExtensionCameraSelector(
+ mCurrentCameraSelector, extensionMode);
+
+ mCameraProvider.unbindAll();
+
+ mCameraProvider.bindToLifecycle(this, cameraSelector, mImageCapture, mPreview);
+
// Update the UI and save location for ImageCapture
Button toggleButton = findViewById(R.id.PhotoToggle);
toggleButton.setText(mCurrentImageCaptureType.toString());
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
index 7c9077f..6154482 100755
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.util.Log;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import android.view.Surface;
@@ -194,4 +195,10 @@
public List<Pair<Integer, Size[]>> getSupportedResolutions() {
return null;
}
+
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+ return new Range<>(300L, 1000L);
+ }
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
index e23aeb9..8380a7f 100755
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -26,6 +26,7 @@
import android.os.Build;
import android.util.Log;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import android.view.Surface;
@@ -220,4 +221,10 @@
return formatResolutionsPairList;
}
+
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+ return new Range<>(300L, 1000L);
+ }
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
index eff6232..f0f7c59 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.util.Log;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import android.view.Surface;
@@ -196,4 +197,9 @@
return null;
}
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+ return new Range<>(300L, 1000L);
+ }
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index 2ac4548..d2ea323 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.util.Log;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import android.view.Surface;
@@ -261,4 +262,9 @@
return null;
}
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+ return new Range<>(300L, 1000L);
+ }
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index a488f14..884c0dd 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -19,6 +19,7 @@
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import androidx.annotation.Nullable;
@@ -80,5 +81,22 @@
*/
@Nullable
List<Pair<Integer, Size[]>> getSupportedResolutions();
+
+ /**
+ * Returns the estimated capture latency range in milliseconds for the target capture
+ * resolution.
+ *
+ * <p> This includes the time spent processing the multi-frame capture request along with any
+ * additional time for encoding of the processed buffer in the framework if necessary.
+ *
+ * @param captureOutputSize size of the capture output surface. If it is null or not in the
+ * supported output sizes, maximum capture output size is used for
+ * the estimation.
+ * @return the range of estimated minimal and maximal capture latency in milliseconds.
+ * Returns null if no capture latency info can be provided.
+ * @since 1.2
+ */
+ @Nullable
+ Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize);
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
index 1434871..405da8d 100755
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.util.Log;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import android.view.Surface;
@@ -194,4 +195,10 @@
public List<Pair<Integer, Size[]>> getSupportedResolutions() {
return null;
}
+
+ @Nullable
+ @Override
+ public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+ return new Range<>(300L, 1000L);
+ }
}
diff --git a/camera/integration-tests/uiwidgetstestapp/build.gradle b/camera/integration-tests/uiwidgetstestapp/build.gradle
index 7d8e90f..e52e977 100644
--- a/camera/integration-tests/uiwidgetstestapp/build.gradle
+++ b/camera/integration-tests/uiwidgetstestapp/build.gradle
@@ -39,10 +39,6 @@
}
buildTypes {
- debug {
- testCoverageEnabled true
- }
-
release {
minifyEnabled true
shrinkResources true
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt
deleted file mode 100644
index 65a5116..0000000
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2020 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.integration.view
-
-import androidx.camera.testing.CameraUtil
-import androidx.camera.testing.CoreAppTestUtil
-import androidx.camera.view.PreviewView
-import androidx.core.os.bundleOf
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.testing.FragmentScenario
-import androidx.fragment.app.testing.launchFragmentInContainer
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.asFlow
-import androidx.lifecycle.testing.TestLifecycleOwner
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.GrantPermissionRule
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.runBlocking
-import org.junit.Assume
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TestRule
-import org.junit.runner.RunWith
-
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class CameraViewFragmentTest {
-
- @get:Rule
- val useCamera: TestRule = CameraUtil.grantCameraPermissionAndPreTest()
-
- @get:Rule
- val permissionRule: GrantPermissionRule =
- GrantPermissionRule.grant(
- android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
- android.Manifest.permission.RECORD_AUDIO
- )
-
- @Before
- fun setup() {
- Assume.assumeTrue(CameraUtil.deviceHasCamera())
- CoreAppTestUtil.assumeCompatibleDevice()
- // Clear the device UI and check if there is no dialog or lock screen on the top of the
- // window before start the test.
- CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation())
- }
-
- @Test
- fun cameraView_canStream_defaultLifecycle() = runBlocking {
- with(launchFragmentInContainer<CameraViewFragment>()) { assertStreaming() }
- }
-
- @Test
- fun cameraView_canStream_withActivityLifecycle() = runBlocking {
- with(
- launchFragmentInContainer<CameraViewFragment>(
- fragmentArgs = bundleOf(
- CameraViewFragment.ARG_LIFECYCLE_TYPE to
- CameraViewFragment.LIFECYCLE_TYPE_ACTIVITY
- )
- )
- ) { assertStreaming() }
- }
-
- @Test
- fun cameraView_canStream_withFragmentLifecycle() = runBlocking {
- with(
- launchFragmentInContainer<CameraViewFragment>(
- fragmentArgs = bundleOf(
- CameraViewFragment.ARG_LIFECYCLE_TYPE to
- CameraViewFragment.LIFECYCLE_TYPE_FRAGMENT
- )
- )
- ) { assertStreaming() }
- }
-
- @Test
- fun cameraView_canStream_withFragmentViewLifecycle() = runBlocking {
- with(
- launchFragmentInContainer<CameraViewFragment>(
- fragmentArgs = bundleOf(
- CameraViewFragment.ARG_LIFECYCLE_TYPE to
- CameraViewFragment.LIFECYCLE_TYPE_FRAGMENT_VIEW
- )
- )
- ) { assertStreaming() }
- }
-
- @Test
- fun cameraView_ignoresLifecycleInDestroyedState() {
- // Since launchFragmentInContainer waits for onResume() to complete, CameraView should
- // have measured its view and bound to the lifecycle by this time. This would crash with
- // an IllegalArgumentException prior to applying fix for b/157949175
- launchFragmentInContainer(
- fragmentArgs = bundleOf(
- CameraViewFragment.ARG_LIFECYCLE_TYPE to CameraViewFragment.LIFECYCLE_TYPE_DEBUG
- ),
- instantiate = {
- CameraViewFragment().apply {
- setDebugLifecycleOwner(TestLifecycleOwner(Lifecycle.State.DESTROYED))
- }
- }
- )
- }
-}
-
-@Suppress("DEPRECATION")
-private suspend inline fun FragmentScenario<CameraViewFragment>.assertStreaming() {
- val streamState = withFragment {
- (view?.findViewById<androidx.camera.view.CameraView>(R.id.camera)?.previewStreamState)!!
- }.asFlow().first {
- it == PreviewView.StreamState.STREAMING
- }
-
- assertThat(streamState).isEqualTo(PreviewView.StreamState.STREAMING)
-}
-
-// Adapted from ActivityScenario.withActivity extension function
-private inline fun <reified F : Fragment, T : Any> FragmentScenario<F>.withFragment(
- crossinline block: F.() -> T
-): T {
- lateinit var value: T
- var err: Throwable? = null
- onFragment { fragment ->
- try {
- value = block(fragment)
- } catch (t: Throwable) {
- err = t
- }
- }
- err?.let { throw it }
- return value
-}
\ No newline at end of file
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraViewFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraViewFragment.java
deleted file mode 100644
index 6a5e574..0000000
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraViewFragment.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2019 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.integration.view;
-
-import android.app.Activity;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.CompoundButton;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.view.CameraView;
-import androidx.camera.view.CameraView.CaptureMode;
-import androidx.camera.view.PreviewView;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
-import androidx.lifecycle.LifecycleOwner;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.Locale;
-import java.util.Objects;
-
-/** The main camera fragment. */
-public class CameraViewFragment extends Fragment {
- private static final String TAG = "CameraViewFragment";
-
- // Possible values for this intent key are the name values of LensFacing encoded as
- // strings (case-insensitive): "back", "front".
- private static final String INTENT_EXTRA_CAMERA_DIRECTION = "camera_direction";
-
- // Possible values for this intent key are the name values of CameraView.CaptureMode encoded as
- // strings (case-insensitive): "image", "video", "mixed"
- private static final String INTENT_EXTRA_CAPTURE_MODE = "captureMode";
-
- // The time-out to wait for the ready of CameraProver in the CameraXModule of CameraView.
- private static final int CAMERA_PROVIDER_READY_TIMEOUT = 2000;
-
- // Argument key which determines the lifecycle used to control the camera of CameraView.
- // Possible values for this argument key are LIFECYCLE_TYPE_ACTIVITY, LIFECYCLE_TYPE_FRAGMENT,
- // LIFECYCLE_TYPE_FRAGMENT_VIEW, LIFECYCLE_TYPE_CUSTOM. If using LIFECYCLE_TYPE_DEBUG, then
- // a lifecycle must be provided via setDebugLifecycleOwner().
- static final String ARG_LIFECYCLE_TYPE = "lifecycle_type";
-
- // Fragment's Activity lifecycle
- static final String LIFECYCLE_TYPE_ACTIVITY = "activity";
- // Fragment lifecycle (this). This is the default lifecycle used by this fragment
- static final String LIFECYCLE_TYPE_FRAGMENT = "fragment";
- // Fragment's View lifecycle (getViewLifecycleOwner())
- static final String LIFECYCLE_TYPE_FRAGMENT_VIEW = "fragment_view";
- // Lifecycle provided by setDebugLifecycleOwner
- static final String LIFECYCLE_TYPE_DEBUG = "debug";
-
-
- private View mCameraHolder;
- CameraView mCameraView;
- private View mCaptureView;
- private CompoundButton mModeButton;
- @Nullable
- private CompoundButton mToggleCameraButton;
- private CompoundButton mToggleCropButton;
- @Nullable
- private LifecycleOwner mDebugLifecycleOwner;
-
- /**
- * Sets the debug lifecycle used by this fragment IF the fragment has argument
- * {@link #ARG_LIFECYCLE_TYPE} set to {@link #LIFECYCLE_TYPE_DEBUG}.
- *
- * <p>This lifecycle must be set before the fragment lifecycle reaches
- * {@link #onViewStateRestored(Bundle)}, or it will be ignored.
- *
- * <p>This value set here is not retained across fragment recreation, so it is only safe to
- * use for debugging/testing purposes.
- */
- public void setDebugLifecycleOwner(@NonNull LifecycleOwner owner) {
- mDebugLifecycleOwner = owner;
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- mCameraHolder = view.findViewById(R.id.layout_camera);
- mCameraView = view.findViewById(R.id.camera);
- mToggleCameraButton = view.findViewById(R.id.toggle);
- mToggleCropButton = view.findViewById(R.id.toggle_crop);
- mCaptureView = mCameraHolder.findViewById(R.id.capture);
- if (mCameraHolder == null) {
- throw new IllegalStateException("No View found with id R.id.layout_camera");
- }
- if (mCameraView == null) {
- throw new IllegalStateException("No CameraView found with id R.id.camera");
- }
- if (mCaptureView == null) {
- throw new IllegalStateException("No CameraView found with id R.id.capture");
- }
-
- mModeButton = mCameraHolder.findViewById(R.id.mode);
-
- if (mModeButton == null) {
- throw new IllegalStateException("No View found with id R.id.mode");
- }
-
- // Log the location of some views, so their locations can be used to perform some automated
- // clicks in tests.
- logCenterCoordinates(mCameraView, "camera_view");
- logCenterCoordinates(mCaptureView, "capture");
- logCenterCoordinates(mToggleCameraButton, "toggle_camera");
- logCenterCoordinates(mToggleCropButton, "toggle_crop");
- logCenterCoordinates(mModeButton, "mode");
-
- // Get extra option for setting initial camera direction
- Bundle bundle = getActivity().getIntent().getExtras();
- if (bundle != null) {
- final String cameraDirectionString = bundle.getString(INTENT_EXTRA_CAMERA_DIRECTION);
- final boolean isCameraDirectionValid =
- cameraDirectionString != null && (cameraDirectionString.equalsIgnoreCase("BACK")
- || cameraDirectionString.equalsIgnoreCase("FRONT"));
- if (isCameraDirectionValid) {
- @CameraSelector.LensFacing int lensFacing = cameraDirectionString.equalsIgnoreCase(
- "BACK")
- ? CameraSelector.LENS_FACING_BACK : CameraSelector.LENS_FACING_FRONT;
- mCameraView.setCameraLensFacing(lensFacing);
- }
-
- String captureModeString = bundle.getString(INTENT_EXTRA_CAPTURE_MODE);
- if (captureModeString != null) {
- CaptureMode captureMode = CaptureMode.valueOf(captureModeString.toUpperCase());
- mCameraView.setCaptureMode(captureMode);
- }
- }
- }
-
- @Override
- @OptIn(markerClass = ExperimentalVideo.class)
- public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
- super.onViewStateRestored(savedInstanceState);
-
- if (ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.CAMERA)
- != PackageManager.PERMISSION_GRANTED) {
- throw new IllegalStateException("App has not been granted CAMERA permission");
- }
-
- // Set the lifecycle that will be used to control the camera
- Bundle args = getArguments();
- String lifecycleType = args == null ? LIFECYCLE_TYPE_FRAGMENT :
- args.getString(ARG_LIFECYCLE_TYPE, LIFECYCLE_TYPE_FRAGMENT);
- Log.d(TAG, "Attempting bindToLifecycle with lifecycle type: " + lifecycleType);
- switch (lifecycleType) {
- case LIFECYCLE_TYPE_ACTIVITY:
- mCameraView.bindToLifecycle(requireActivity());
- break;
- case LIFECYCLE_TYPE_FRAGMENT:
- mCameraView.bindToLifecycle(CameraViewFragment.this);
- break;
- case LIFECYCLE_TYPE_FRAGMENT_VIEW:
- mCameraView.bindToLifecycle(Objects.requireNonNull(getViewLifecycleOwner()));
- break;
- case LIFECYCLE_TYPE_DEBUG:
- if (mDebugLifecycleOwner == null) {
- throw new IllegalStateException("Lifecycle type set to DEBUG, but no debug "
- + "lifecycle exists. setDebugLifecycleOwner() must be called before "
- + "onViewStateRestored()");
- }
- mCameraView.bindToLifecycle(mDebugLifecycleOwner);
- break;
- default:
- throw new IllegalArgumentException(String.format(Locale.US, "Invalid lifecycle "
- + "type: %s. Valid options are %s, %s, %s, and %s.", lifecycleType,
- LIFECYCLE_TYPE_ACTIVITY, LIFECYCLE_TYPE_FRAGMENT,
- LIFECYCLE_TYPE_FRAGMENT_VIEW, LIFECYCLE_TYPE_DEBUG));
- }
-
- mCameraView.setPinchToZoomEnabled(true);
- mCaptureView.setOnTouchListener(new CaptureViewOnTouchListener(mCameraView));
-
- // Set clickable, Let the cameraView can be interacted by Voice Access
- mCameraView.setClickable(true);
-
- // CameraView.hasCameraWithLensFacing need to wait for the ready of CameraProvider. Check
- // the b/183916771 for the workaround.
- Handler handler = new Handler(Looper.getMainLooper());
- handler.postDelayed(() -> {
- if (mToggleCameraButton != null) {
- if (mToggleCameraButton != null) {
- mToggleCameraButton.setVisibility(
- (mCameraView.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)
- && mCameraView.hasCameraWithLensFacing(
- CameraSelector.LENS_FACING_FRONT))
- ? View.VISIBLE
- : View.INVISIBLE);
- mToggleCameraButton.setChecked(
- mCameraView.getCameraLensFacing() == CameraSelector.LENS_FACING_FRONT);
- }
- }
- }, CAMERA_PROVIDER_READY_TIMEOUT);
-
- // Set listeners here, or else restoring state will trigger them.
- if (mToggleCameraButton != null) {
- mToggleCameraButton.setOnCheckedChangeListener(
- new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton b, boolean checked) {
- mCameraView.setCameraLensFacing(
- checked ? CameraSelector.LENS_FACING_FRONT
- : CameraSelector.LENS_FACING_BACK);
- }
- });
- }
-
- mToggleCropButton.setChecked(
- mCameraView.getScaleType() == PreviewView.ScaleType.FILL_CENTER);
- mToggleCropButton.setOnCheckedChangeListener((b, checked) -> {
- if (checked) {
- mCameraView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
- } else {
- mCameraView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
- }
- });
-
- if (mModeButton != null) {
- updateModeButtonIcon();
-
- mModeButton.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (mCameraView.isRecording()) {
- Toast.makeText(
- CameraViewFragment.this.getContext(),
- "Can not switch mode during video recording.",
- Toast.LENGTH_SHORT)
- .show();
- return;
- }
-
- if (mCameraView.getCaptureMode() == CaptureMode.MIXED) {
- mCameraView.setCaptureMode(CaptureMode.IMAGE);
- } else if (mCameraView.getCaptureMode() == CaptureMode.IMAGE) {
- mCameraView.setCaptureMode(CaptureMode.VIDEO);
- } else {
- mCameraView.setCaptureMode(CaptureMode.MIXED);
- }
-
- CameraViewFragment.this.updateModeButtonIcon();
- }
- });
- }
- }
-
- @OptIn(markerClass = ExperimentalVideo.class)
- void updateModeButtonIcon() {
- if (mCameraView.getCaptureMode() == CaptureMode.MIXED) {
- mModeButton.setButtonDrawable(R.drawable.ic_photo_camera);
- } else if (mCameraView.getCaptureMode() == CaptureMode.IMAGE) {
- mModeButton.setButtonDrawable(R.drawable.ic_camera);
- } else {
- mModeButton.setButtonDrawable(R.drawable.ic_videocam);
- }
- }
-
- @Override
- @NonNull
- public View onCreateView(
- @NonNull LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment_main, container, false);
- }
-
- private void logCenterCoordinates(View view, String name) {
- view.getViewTreeObserver()
- .addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- Activity activity = getActivity();
- if (activity == null) {
- // The fragment has been detached from the parent Activity.
- return;
- }
- Rect rect = new Rect();
- view.getGlobalVisibleRect(rect);
- Log.d(
- TAG,
- "View "
- + name
- + " Center "
- + rect.centerX()
- + " "
- + rect.centerY());
- File externalDir = activity.getExternalFilesDir(null);
- File logFile =
- new File(externalDir, name + "_button_coordinates.txt");
- try (PrintStream stream = new PrintStream(logFile)) {
- stream.print(rect.centerX() + " " + rect.centerY());
- } catch (IOException e) {
- Log.e(TAG, "Could not save to " + logFile, e);
- }
- }
- });
- }
-}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java
deleted file mode 100644
index 0f08848..0000000
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2019 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.integration.view;
-
-import android.content.ContentValues;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.ImageCaptureException;
-import androidx.camera.view.CameraView;
-import androidx.camera.view.CameraView.CaptureMode;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.camera.view.video.OnVideoSavedCallback;
-import androidx.camera.view.video.OutputFileResults;
-import androidx.core.content.ContextCompat;
-
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.Locale;
-
-/**
- * A {@link View.OnTouchListener} which converts a view's touches into camera actions.
- *
- * <p>The listener converts touches on a {@link View}, such as a button, into appropriate photo
- * taking or video recording actions through a {@link CameraView}. A click is interpreted as a
- * take-photo signal, while a long-press is interpreted as a record-video signal.
- */
-class CaptureViewOnTouchListener implements View.OnTouchListener {
- private static final String TAG = "ViewOnTouchListener";
-
- private static final String FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS";
- private static final String PHOTO_EXTENSION = ".jpg";
- private static final String VIDEO_EXTENSION = ".mp4";
-
- private static final int TAP = 1;
- private static final int HOLD = 2;
- private static final int RELEASE = 3;
-
- private final long mLongPress = ViewConfiguration.getLongPressTimeout();
- private final CameraView mCameraView;
-
- // TODO: Use a Handler for a background thread, rather than running on the current (main)
- // thread.
- private final Handler mHandler =
- new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case TAP:
- onTap();
- break;
- case HOLD:
- onHold();
- if (mCameraView.getMaxVideoDuration() > 0) {
- sendEmptyMessageDelayed(RELEASE, mCameraView.getMaxVideoDuration());
- }
- break;
- case RELEASE:
- onRelease();
- break;
- default:
- // No op
- }
- }
- };
-
- private long mDownEventTimestamp;
- private Rect mViewBoundsRect;
-
- /** Creates a new listener which links to the given {@link CameraView}. */
- CaptureViewOnTouchListener(CameraView cameraView) {
- mCameraView = cameraView;
- }
-
- /** Called when the user taps. */
- @OptIn(markerClass = ExperimentalVideo.class)
- private void onTap() {
- if (mCameraView.getCaptureMode() == CaptureMode.IMAGE
- || mCameraView.getCaptureMode() == CaptureMode.MIXED) {
-
- File saveFile = createNewFile(PHOTO_EXTENSION);
- ImageCapture.OutputFileOptions outputFileOptions;
- ContentValues contentValues = new ContentValues();
- contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, saveFile.getName());
- contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,
- Environment.DIRECTORY_PICTURES);
- } else {
- contentValues.put(MediaStore.MediaColumns.DATA, saveFile.getAbsolutePath());
- }
- outputFileOptions = new ImageCapture.OutputFileOptions.Builder(
- mCameraView.getContext().getContentResolver(),
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- contentValues).build();
-
- mCameraView.takePicture(outputFileOptions,
- ContextCompat.getMainExecutor(mCameraView.getContext()),
- new OnImageSavedCallback() {
- @Override
- public void onImageSaved(
- @NonNull ImageCapture.OutputFileResults outputFileResults) {
- report("Picture saved to " + saveFile.getAbsolutePath());
- // Print out metadata about the picture
- // TODO: Print out metadata to log once metadata is implemented
- }
-
- @Override
- public void onError(@NonNull ImageCaptureException exception) {
- report("Failure: " + exception.getMessage(), exception.getCause());
- }
- });
- }
- }
-
- /** Called when the user holds (long presses). */
- @OptIn(markerClass = ExperimentalVideo.class)
- private void onHold() {
- if (mCameraView.getCaptureMode() == CaptureMode.VIDEO
- || mCameraView.getCaptureMode() == CaptureMode.MIXED) {
-
- final File saveFile = createNewFile(VIDEO_EXTENSION);
- mCameraView.startRecording(saveFile,
- ContextCompat.getMainExecutor(mCameraView.getContext()),
- new OnVideoSavedCallback() {
- @Override
- public void onVideoSaved(
- @NonNull OutputFileResults outputFileResults) {
- report("Video saved to " + outputFileResults.getSavedUri());
- }
-
- @Override
- public void onError(int videoCaptureError, @NonNull String message,
- @Nullable Throwable cause) {
- report("Failure: " + message, cause);
- }
- });
- }
- }
-
- /** Called when the user releases. */
- @OptIn(markerClass = ExperimentalVideo.class)
- private void onRelease() {
- if (mCameraView.getCaptureMode() == CaptureMode.VIDEO
- || mCameraView.getCaptureMode() == CaptureMode.MIXED) {
- mCameraView.stopRecording();
- }
- }
-
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mDownEventTimestamp = System.currentTimeMillis();
- mViewBoundsRect =
- new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
- mHandler.sendEmptyMessageDelayed(HOLD, mLongPress);
- view.setPressed(true);
- break;
- case MotionEvent.ACTION_MOVE:
- // If the user moves their finger off the button, trigger RELEASE
- if (mViewBoundsRect.contains(
- view.getLeft() + (int) event.getX(), view.getTop() + (int) event.getY())) {
- break;
- }
- // Fall-through
- case MotionEvent.ACTION_CANCEL:
- clearHandler();
- if (deltaSinceDownEvent() > mLongPress
- && (mCameraView.getMaxVideoDuration() <= 0
- || deltaSinceDownEvent() < mCameraView.getMaxVideoDuration())) {
- mHandler.sendEmptyMessage(RELEASE);
- }
- view.setPressed(false);
- break;
- case MotionEvent.ACTION_UP:
- clearHandler();
- if (deltaSinceDownEvent() < mLongPress) {
- mHandler.sendEmptyMessage(TAP);
- } else if ((mCameraView.getMaxVideoDuration() <= 0
- || deltaSinceDownEvent() < mCameraView.getMaxVideoDuration())) {
- mHandler.sendEmptyMessage(RELEASE);
- }
- view.setPressed(false);
- break;
- default:
- // No op
- }
- return true;
- }
-
- private long deltaSinceDownEvent() {
- return System.currentTimeMillis() - mDownEventTimestamp;
- }
-
- private void clearHandler() {
- mHandler.removeMessages(TAP);
- mHandler.removeMessages(HOLD);
- mHandler.removeMessages(RELEASE);
- }
-
- private File createNewFile(String extension) {
- File dirFile =
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
- if (dirFile != null && !dirFile.exists()) {
- dirFile.mkdirs();
- }
- // Use Locale.US to ensure we get ASCII digits
- return new File(dirFile,
- new SimpleDateFormat(FILENAME, Locale.US).format(System.currentTimeMillis())
- + extension);
- }
-
- @SuppressWarnings("WeakerAccess")
- void report(@NonNull String msg) {
- report(msg, null);
- }
-
- @SuppressWarnings("WeakerAccess")
- void report(@NonNull String msg, @Nullable Throwable cause) {
- Log.d(TAG, msg, cause);
- Toast.makeText(mCameraView.getContext(), msg, Toast.LENGTH_SHORT).show();
- }
-}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java
index 504277c..411ec1f 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java
@@ -54,7 +54,7 @@
private static final int REQUEST_CODE_PERMISSIONS = 10;
private boolean mCheckedPermissions = false;
- private Mode mMode = Mode.CAMERA_VIEW;
+ private Mode mMode = Mode.CAMERA_CONTROLLER;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -114,9 +114,6 @@
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
- case R.id.camera_view:
- mMode = Mode.CAMERA_VIEW;
- break;
case R.id.preview_view:
mMode = Mode.PREVIEW_VIEW;
break;
@@ -143,9 +140,6 @@
private void startFragment() {
switch (mMode) {
- case CAMERA_VIEW:
- startFragment(R.string.camera_view, new CameraViewFragment());
- break;
case PREVIEW_VIEW:
startFragment(R.string.preview_view, new PreviewViewFragment());
break;
@@ -175,6 +169,6 @@
}
private enum Mode {
- CAMERA_VIEW, PREVIEW_VIEW, CAMERA_CONTROLLER, TRANSFORM
+ PREVIEW_VIEW, CAMERA_CONTROLLER, TRANSFORM
}
}
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/fullscreen_selector.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/fullscreen_selector.xml
deleted file mode 100644
index ff39715..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/fullscreen_selector.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<selector
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:constantSize="true">
- <item android:drawable="@drawable/ic_fullscreen_exit" android:state_checked="true" android:state_enabled="true" />
- <item android:drawable="@drawable/ic_fullscreen" android:state_checked="false" android:state_enabled="true" />
-</selector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_camera.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_camera.xml
deleted file mode 100644
index a35682e..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_camera.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
-
- android:fillColor="?android:attr/colorControlNormal"
- android:pathData="M9.4,10.5l4.77,-8.26C13.47,2.09 12.75,2 12,2c-2.4,0 -4.6,0.85 -6.32,2.25l3.66,6.35 0.06,-0.1zM21.54,9c-0.92,-2.92 -3.15,-5.26 -6,-6.34L11.88,9h9.66zM21.8,10h-7.49l0.29,0.5 4.76,8.25C21,16.97 22,14.61 22,12c0,-0.69 -0.07,-1.35 -0.2,-2zM8.54,12l-3.9,-6.75C3.01,7.03 2,9.39 2,12c0,0.69 0.07,1.35 0.2,2h7.49l-1.15,-2zM2.46,15c0.92,2.92 3.15,5.26 6,6.34L12.12,15L2.46,15zM13.73,15l-3.9,6.76c0.7,0.15 1.42,0.24 2.17,0.24 2.4,0 4.6,-0.85 6.32,-2.25l-3.66,-6.35 -0.93,1.6z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen.xml
deleted file mode 100644
index 467ce90..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
- android:fillColor="?android:attr/colorControlNormal"
- android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen_exit.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen_exit.xml
deleted file mode 100644
index 8ee9dfc..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
- android:fillColor="?android:attr/colorControlNormal"
- android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_photo_camera.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_photo_camera.xml
deleted file mode 100644
index 625e155..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_photo_camera.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:tint="?attr/colorControlNormal"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_videocam.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_videocam.xml
deleted file mode 100644
index f009891..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_videocam.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:tint="?attr/colorControlNormal"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M18,10.48V6c0,-1.1 -0.9,-2 -2,-2H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11L18,10.48zM16,9.69V18H4V6h12V9.69zM11.67,11l-2.5,3.72L7.5,12L5,16h10L11.67,11z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/layout-land/fragment_main.xml b/camera/integration-tests/viewtestapp/src/main/res/layout-land/fragment_main.xml
deleted file mode 100644
index 7938a58..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/layout-land/fragment_main.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/layout_camera"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
-
- <androidx.camera.view.CameraView
- android:id="@+id/camera"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:layout_weight="1" />
-
- <LinearLayout
- android:layout_width="125dp"
- android:layout_height="match_parent"
- android:layout_marginBottom="10dp"
- android:layout_marginTop="10dp"
- android:gravity="center"
- android:orientation="vertical">
-
- <CheckBox
- android:id="@+id/toggle_crop"
- android:layout_width="wrap_content"
- android:layout_height="50dp"
- android:button="@drawable/fullscreen_selector" />
-
- <CheckBox
- android:id="@+id/mode"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:button="@drawable/ic_photo_camera" />
-
- <Button
- android:id="@+id/capture"
- style="?android:buttonBarButtonStyle"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_marginBottom="10dp"
- android:layout_marginTop="10dp"
- android:layout_weight="1"
- android:text="@string/btn_capture" />
-
- <CheckBox
- android:id="@+id/toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- </LinearLayout>
-
- </LinearLayout>
-
-</FrameLayout>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/layout/fragment_main.xml b/camera/integration-tests/viewtestapp/src/main/res/layout/fragment_main.xml
deleted file mode 100644
index 72f8ecb..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/layout/fragment_main.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/layout_camera"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <androidx.camera.view.CameraView
- android:id="@+id/camera"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_gravity="center_horizontal"
- android:layout_weight="1" />
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="58dp"
- android:layout_marginLeft="10dp"
- android:layout_marginRight="10dp"
- android:gravity="center">
-
- <CheckBox
- android:id="@+id/toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <Button
- android:id="@+id/capture"
- style="?android:buttonBarButtonStyle"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_marginLeft="50dp"
- android:layout_marginRight="50dp"
- android:layout_weight="1"
- android:text="@string/btn_capture" />
-
- <CheckBox
- android:id="@+id/mode"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="15dp"
- android:button="@drawable/ic_photo_camera" />
-
- <CheckBox
- android:id="@+id/toggle_crop"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:button="@drawable/fullscreen_selector" />
-
- </LinearLayout>
-
- </LinearLayout>
-
-</FrameLayout>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml b/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml
index 8babd1e..4868f69 100644
--- a/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml
+++ b/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml
@@ -17,10 +17,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
- android:id="@+id/camera_view"
- android:title="@string/camera_view"
- app:showAsAction="never" />
- <item
android:id="@+id/preview_view"
android:title="@string/preview_view"
app:showAsAction="never" />
diff --git a/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml b/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml
index 0eb3bd9..e257662 100644
--- a/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml
+++ b/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml
@@ -41,7 +41,6 @@
<string name="btn_switch">Switch</string>
<string name="btn_confirm">Confirm</string>
<string name="btn_cancel">Cancel</string>
- <string name="camera_view">Camera View</string>
<string name="preview_view">Preview View</string>
<string name="camera_controller">Camera Controller</string>
<string name="transform">Transform</string>
diff --git a/car/app/app-activity/lint-baseline.xml b/car/app/app-activity/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-activity/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/ActivityLifecycleDelegate.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/ActivityLifecycleDelegate.java
index 66bad88..0df1174 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/ActivityLifecycleDelegate.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/ActivityLifecycleDelegate.java
@@ -32,7 +32,6 @@
* IRendererCallback}.
*/
final class ActivityLifecycleDelegate implements ActivityLifecycleCallbacks {
- public static final String TAG = "ActivityLifecycleListener";
@NonNull
private ServiceDispatcher mServiceDispatcher;
@Nullable
@@ -88,8 +87,7 @@
@Override
public void onActivityPreDestroyed(@NonNull Activity activity) {
- requireNonNull(activity);
- notifyEvent(Event.ON_DESTROY);
+ // No-op as the view model will call terminate on the service when needed.
}
@Override
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
index f87ebec..1130a27 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
@@ -22,15 +22,10 @@
import android.annotation.SuppressLint;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
import android.util.Log;
import android.view.View;
@@ -49,8 +44,7 @@
import androidx.car.app.serialization.BundlerException;
import androidx.car.app.utils.ThreadUtils;
import androidx.fragment.app.FragmentActivity;
-
-import java.util.List;
+import androidx.lifecycle.ViewModelProvider;
/**
* The class representing a car app activity.
@@ -96,20 +90,16 @@
public final class CarAppActivity extends FragmentActivity {
@VisibleForTesting
static final String SERVICE_METADATA_KEY = "androidx.car.app.CAR_APP_SERVICE";
- private static final String TAG = "CarAppActivity";
@SuppressLint({"ActionValue"})
@VisibleForTesting
static final String ACTION_RENDER = "android.car.template.host.RendererService";
- @Nullable
- private ComponentName mServiceComponentName;
TemplateSurfaceView mSurfaceView;
- SurfaceHolderListener mSurfaceHolderListener;
- ActivityLifecycleDelegate mActivityLifecycleDelegate;
- @Nullable
- OnBackPressedListener mOnBackPressedListener;
- ServiceDispatcher mServiceDispatcher;
+ @Nullable SurfaceHolderListener mSurfaceHolderListener;
+ @Nullable ActivityLifecycleDelegate mActivityLifecycleDelegate;
+ @Nullable OnBackPressedListener mOnBackPressedListener;
+ @Nullable CarAppViewModel mViewModel;
private int mDisplayId;
/**
@@ -121,7 +111,7 @@
Log.e(LogTags.TAG, "Service error: " + errorType, exception);
- unbindService();
+ requireNonNull(mViewModel).unbind();
ThreadUtils.runOnMain(() -> {
Log.d(LogTags.TAG, "Showing error fragment");
@@ -162,14 +152,16 @@
ThreadUtils.runOnMain(
() -> {
mSurfaceView.setOnCreateInputConnectionListener(editorInfo ->
- mServiceDispatcher.fetch(null, () ->
+ getServiceDispatcher().fetch(null, () ->
callback.onCreateInputConnection(
editorInfo)));
mOnBackPressedListener = () ->
- mServiceDispatcher.dispatch(callback::onBackPressed);
+ getServiceDispatcher().dispatch(callback::onBackPressed);
- mActivityLifecycleDelegate.registerRendererCallback(callback);
+ requireNonNull(mActivityLifecycleDelegate)
+ .registerRendererCallback(callback);
+ requireNonNull(mViewModel).setRendererCallback(callback);
});
}
@@ -177,7 +169,8 @@
public void setSurfaceListener(@NonNull ISurfaceListener listener) {
requireNonNull(listener);
ThreadUtils.runOnMain(
- () -> mSurfaceHolderListener.setSurfaceListener(listener));
+ () -> requireNonNull(mSurfaceHolderListener)
+ .setSurfaceListener(listener));
}
@Override
@@ -201,87 +194,41 @@
}
};
- /** The service connection for the renderer service. */
- private ServiceConnection mServiceConnectionImpl =
- new ServiceConnection() {
- @Override
- public void onServiceConnected(
- @NonNull ComponentName name, @NonNull IBinder service) {
- requireNonNull(name);
- requireNonNull(service);
- Log.i(LogTags.TAG, String.format("Host service %s is connected",
- name.flattenToShortString()));
- IRendererService rendererService = IRendererService.Stub.asInterface(service);
- if (rendererService == null) {
- mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
- new Exception("Failed to get IRenderService binder from host: "
- + name));
- return;
- }
-
- mServiceDispatcher.setRendererService(rendererService);
- verifyServiceVersion(rendererService);
- initializeService(rendererService);
- updateIntent(getIntent());
- }
-
- @Override
- public void onServiceDisconnected(@NonNull ComponentName name) {
- requireNonNull(name);
-
- // Connection lost, but it might reconnect.
- Log.w(LogTags.TAG, String.format("Host service %s is disconnected",
- name.flattenToShortString()));
- }
-
- @Override
- public void onBindingDied(@NonNull ComponentName name) {
- requireNonNull(name);
-
- // Connection permanently lost
- mErrorHandler.onError(ErrorHandler.ErrorType.HOST_CONNECTION_LOST,
- new Exception("Host service " + name + " is permanently disconnected"));
- }
-
- @Override
- public void onNullBinding(@NonNull ComponentName name) {
- requireNonNull(name);
-
- // Host rejected the binding.
- mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
- new Exception("Host service " + name + " rejected the binding "
- + "request"));
- }
- };
-
@SuppressWarnings("deprecation")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- mServiceDispatcher = new ServiceDispatcher(mErrorHandler);
setContentView(R.layout.activity_template);
mSurfaceView = requireViewById(R.id.template_view_surface);
- mActivityLifecycleDelegate = new ActivityLifecycleDelegate(mServiceDispatcher);
- mSurfaceHolderListener = new SurfaceHolderListener(mServiceDispatcher,
- new SurfaceWrapperProvider(mSurfaceView));
- mServiceComponentName = retrieveServiceComponentName();
- if (mServiceComponentName == null) {
- Log.e(TAG, "Unspecified service class name");
+ ComponentName serviceComponentName = retrieveServiceComponentName();
+ if (serviceComponentName == null) {
+ Log.e(LogTags.TAG, "Unspecified service class name");
finish();
return;
}
+ mDisplayId = getWindowManager().getDefaultDisplay().getDisplayId();
- registerActivityLifecycleCallbacks(mActivityLifecycleDelegate);
+ CarAppViewModelFactory factory = CarAppViewModelFactory.getInstance(getApplication(),
+ serviceComponentName);
+ mViewModel = new ViewModelProvider(this, factory).get(CarAppViewModel.class);
+ mViewModel.getErrorEvent().observe(this,
+ errorEvent -> mErrorHandler.onError(errorEvent.getErrorType(),
+ errorEvent.getException()));
+
+ mActivityLifecycleDelegate = new ActivityLifecycleDelegate(getServiceDispatcher());
+ mSurfaceHolderListener = new SurfaceHolderListener(getServiceDispatcher(),
+ new SurfaceWrapperProvider(mSurfaceView));
+
+ registerActivityLifecycleCallbacks(requireNonNull(mActivityLifecycleDelegate));
// Set the z-order to receive the UI events on the surface.
mSurfaceView.setZOrderOnTop(true);
- mSurfaceView.setServiceDispatcher(mServiceDispatcher);
+ mSurfaceView.setServiceDispatcher(getServiceDispatcher());
mSurfaceView.setErrorHandler(mErrorHandler);
mSurfaceView.getHolder().addCallback(mSurfaceHolderListener);
- mDisplayId = getWindowManager().getDefaultDisplay().getDisplayId();
- bindService();
+
+ mViewModel.bind(getIntent(), mCarActivity, mDisplayId);
}
@Override
@@ -300,13 +247,6 @@
}
}
- @Override
- protected void onDestroy() {
- if (isFinishing()) {
- unbindService();
- }
- super.onDestroy();
- }
@Override
public void onBackPressed() {
@@ -318,21 +258,7 @@
@Override
protected void onNewIntent(@NonNull Intent intent) {
super.onNewIntent(intent);
- if (!mServiceDispatcher.isBound()) {
- bindService();
- } else {
- updateIntent(intent);
- }
- }
-
- @VisibleForTesting
- ServiceConnection getServiceConnection() {
- return mServiceConnectionImpl;
- }
-
- @VisibleForTesting
- void setServiceConnection(ServiceConnection serviceConnection) {
- mServiceConnectionImpl = serviceConnection;
+ requireNonNull(mViewModel).bind(intent, mCarActivity, mDisplayId);
}
@VisibleForTesting
@@ -340,6 +266,11 @@
return mDisplayId;
}
+ @VisibleForTesting
+ ServiceDispatcher getServiceDispatcher() {
+ return requireNonNull(mViewModel).getServiceDispatcher();
+ }
+
@Nullable
private ComponentName retrieveServiceComponentName() {
ActivityInfo activityInfo = null;
@@ -348,7 +279,7 @@
getPackageManager()
.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
- Log.e(TAG, "Unable to find component: " + getComponentName(), e);
+ Log.e(LogTags.TAG, "Unable to find component: " + getComponentName(), e);
}
if (activityInfo == null) {
@@ -358,7 +289,7 @@
String serviceName = activityInfo.metaData.getString(SERVICE_METADATA_KEY);
if (serviceName == null) {
Log.e(
- TAG,
+ LogTags.TAG,
"Unable to find required metadata tag with name "
+ SERVICE_METADATA_KEY
+ ". App manifest must include metadata tag with name "
@@ -369,113 +300,4 @@
return new ComponentName(this, serviceName);
}
-
- /** Binds to the renderer service. */
- private void bindService() {
- Intent rendererIntent = new Intent(ACTION_RENDER);
- List<ResolveInfo> resolveInfoList =
- getPackageManager()
- .queryIntentServices(rendererIntent, PackageManager.GET_META_DATA);
- if (resolveInfoList.size() == 1) {
- rendererIntent.setPackage(resolveInfoList.get(0).serviceInfo.packageName);
- if (!bindService(
- rendererIntent,
- mServiceConnectionImpl,
- Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
- mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
- new Exception("Cannot bind to the renderer host with intent: "
- + rendererIntent));
- }
- } else if (resolveInfoList.isEmpty()) {
- mErrorHandler.onError(ErrorHandler.ErrorType.HOST_NOT_FOUND, new Exception("No "
- + "handlers found for intent: " + rendererIntent));
- } else {
- StringBuilder logMessage =
- new StringBuilder("Multiple hosts found, only one is allowed");
- for (ResolveInfo resolveInfo : resolveInfoList) {
- logMessage.append(
- String.format("\nFound host %s", resolveInfo.serviceInfo.packageName));
- }
- mErrorHandler.onError(ErrorHandler.ErrorType.MULTIPLE_HOSTS,
- new Exception(logMessage.toString()));
- }
- }
-
- /**
- * Verifies that the renderer service supports the current version.
- *
- * @param rendererService the renderer service which should verify the version
- */
- void verifyServiceVersion(IRendererService rendererService) {
- // TODO(169604451) Add version support logic
- boolean isCompatible = true;
-
- if (!isCompatible) {
- mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
- new Exception("Renderer service unsupported"));
- }
- }
-
- /**
- * Initializes the {@code rendererService} for the current activity with {@code carActivity},
- * {@code serviceComponentName} and {@code displayId}.
- *
- * @param rendererService the renderer service that needs to be initialized
- */
- void initializeService(@NonNull IRendererService rendererService) {
- requireNonNull(rendererService);
- requireNonNull(mServiceComponentName);
- ComponentName serviceComponentName = mServiceComponentName;
- Boolean success = mServiceDispatcher.fetch(false,
- () -> rendererService.initialize(mCarActivity,
- serviceComponentName, mDisplayId));
- if (success == null || !success) {
- mErrorHandler.onError(ErrorHandler.ErrorType.HOST_ERROR,
- new Exception("Cannot create renderer for" + mServiceComponentName));
- }
- }
-
- /** Closes the connection to the connected {@code rendererService} if any. */
- void unbindService() {
- // Remove the renderer callback since there is no need to communicate the state with
- // the host.
- mActivityLifecycleDelegate.registerRendererCallback(null);
- // Stop sending SurfaceView updates
- mSurfaceView.getHolder().removeCallback(mSurfaceHolderListener);
- // If host has already disconnected, there is no need for an unbind.
- IRendererService rendererService = mServiceDispatcher.getRendererService();
- if (rendererService == null) {
- return;
- }
- try {
- rendererService.terminate(requireNonNull(mServiceComponentName));
- } catch (RemoteException e) {
- // We are already unbinding (maybe because the host has already cut the connection)
- // Let's not log more errors unnecessarily.
- }
-
- Log.i(LogTags.TAG, "Unbinding from " + mServiceComponentName);
- unbindService(mServiceConnectionImpl);
- mServiceDispatcher.setRendererService(null);
- }
-
- /**
- * Updates the activity intent for the {@code rendererService}.
- */
- void updateIntent(Intent intent) {
- requireNonNull(mServiceComponentName);
- IRendererService service = mServiceDispatcher.getRendererService();
- if (service == null) {
- mErrorHandler.onError(ErrorHandler.ErrorType.CLIENT_SIDE_ERROR,
- new Exception("Service dispatcher is not connected"));
- return;
- }
- ComponentName serviceComponentName = mServiceComponentName;
- Boolean success = mServiceDispatcher.fetch(false, () ->
- service.onNewIntent(intent, serviceComponentName, mDisplayId));
- if (success == null || !success) {
- mErrorHandler.onError(ErrorHandler.ErrorType.HOST_ERROR, new Exception("Renderer "
- + "cannot handle the intent: " + intent));
- }
- }
}
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModel.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModel.java
new file mode 100644
index 0000000..aa48ca9
--- /dev/null
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModel.java
@@ -0,0 +1,130 @@
+/*
+ * 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.car.app.activity;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.car.app.activity.renderer.ICarAppActivity;
+import androidx.car.app.activity.renderer.IRendererCallback;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.MutableLiveData;
+
+/**
+ * The view model to keep track of the CarAppActivity data.
+ *
+ * This main role of this class is to extent the life of a service connection beyond the regular
+ * lifecycle of an activity. This is done by making sure the unbind happens when the view model
+ * clears instead of when the activity calls onDestroy.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class CarAppViewModel extends AndroidViewModel implements ErrorHandler {
+ /** Holds the information about an error event. */
+ public static class ErrorEvent {
+ private final ErrorType mErrorType;
+ private final Throwable mException;
+
+ public ErrorEvent(@NonNull ErrorType errorType, @NonNull Throwable exception) {
+ mErrorType = errorType;
+ mException = exception;
+ }
+
+ /** Returns the type of error. */
+ @NonNull ErrorType getErrorType() {
+ return mErrorType;
+ }
+
+ /** Returns the exception associated with this error event. */
+ @NonNull Throwable getException() {
+ return mException;
+ }
+ }
+
+ private final MutableLiveData<ErrorEvent> mErrorEvent = new MutableLiveData<>();
+ private ServiceConnectionManager mServiceConnectionManager;
+ @Nullable private IRendererCallback mIRendererCallback;
+
+ public CarAppViewModel(@NonNull Application application, @NonNull ComponentName componentName) {
+ super(application);
+
+ mServiceConnectionManager = new ServiceConnectionManager(application, componentName, this);
+ }
+
+ @VisibleForTesting
+ @NonNull ServiceConnectionManager getServiceConnectionManager() {
+ return mServiceConnectionManager;
+ }
+
+ @VisibleForTesting
+ void setServiceConnectionManager(ServiceConnectionManager serviceConnectionManager) {
+ mServiceConnectionManager = serviceConnectionManager;
+ }
+
+ @NonNull ServiceDispatcher getServiceDispatcher() {
+ return mServiceConnectionManager.getServiceDispatcher();
+ }
+
+ /** Updates the rendeer callback. */
+ void setRendererCallback(@NonNull IRendererCallback rendererCallback) {
+ mIRendererCallback = rendererCallback;
+ }
+
+ /**
+ * Binds to the renderer service and initializes the service if not bound already.
+ *
+ * Initializes the renderer service with given properties if already bound to the renderer
+ * service.
+ */
+ void bind(@NonNull Intent intent, @NonNull ICarAppActivity iCarAppActivity,
+ int displayId) {
+ mServiceConnectionManager.bind(intent, iCarAppActivity, displayId);
+ }
+
+ /** Closes the connection to the renderer service if any. */
+ void unbind() {
+ mServiceConnectionManager.unbind();
+ }
+
+ @NonNull
+ MutableLiveData<ErrorEvent> getErrorEvent() {
+ return mErrorEvent;
+ }
+
+ @Override
+ protected void onCleared() {
+ super.onCleared();
+ if (mIRendererCallback != null) {
+ mServiceConnectionManager.getServiceDispatcher()
+ .dispatch(mIRendererCallback::onDestroyed);
+ }
+ mServiceConnectionManager.unbind();
+ }
+
+ @Override
+ public void onError(@NonNull ErrorHandler.ErrorType errorType, @NonNull Throwable exception) {
+ mErrorEvent.setValue(new ErrorEvent(errorType, exception));
+ }
+}
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java
new file mode 100644
index 0000000..3c9627d
--- /dev/null
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java
@@ -0,0 +1,72 @@
+/*
+ * 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.car.app.activity;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.Application;
+import android.content.ComponentName;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A factory to provide a unique {@link CarAppViewModel} for each given {@link ComponentName}.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+class CarAppViewModelFactory implements ViewModelProvider.Factory {
+ private static final Map<ComponentName, CarAppViewModelFactory> sInstances = new HashMap<>();
+
+ Application mApplication;
+ ComponentName mComponentName;
+
+ private CarAppViewModelFactory(@NonNull ComponentName componentName,
+ @NonNull Application application) {
+ mComponentName = componentName;
+ mApplication = application;
+ }
+
+ /**
+ * Retrieve a singleton instance of CarAppViewModelFactory for the given key.
+ *
+ * @return A valid {@link CarAppViewModelFactory}
+ */
+ @NonNull
+ static CarAppViewModelFactory getInstance(Application application,
+ ComponentName componentName) {
+ CarAppViewModelFactory instance = sInstances.get(componentName);
+ if (instance == null) {
+ instance = new CarAppViewModelFactory(componentName, application);
+ sInstances.put(componentName, instance);
+ }
+ return instance;
+ }
+
+ @SuppressWarnings("unchecked")
+ @NonNull
+ @Override
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+ return (T) new CarAppViewModel(mApplication, mComponentName);
+ }
+}
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java
new file mode 100644
index 0000000..93f9b5d
--- /dev/null
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java
@@ -0,0 +1,267 @@
+/*
+ * 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.car.app.activity;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.car.app.activity.renderer.ICarAppActivity;
+import androidx.car.app.activity.renderer.IRendererService;
+
+import java.util.List;
+
+/**
+ * Manages the renderer service connection state.
+ *
+ * This class handles binding and unbinding to the renderer service and make sure the renderer
+ * service gets initialized and terminated properly.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ServiceConnectionManager {
+ @SuppressLint({"ActionValue"})
+ @VisibleForTesting
+ static final String ACTION_RENDER = "android.car.template.host.RendererService";
+
+ final ErrorHandler mErrorHandler;
+ private final ComponentName mServiceComponentName;
+ private final Context mContext;
+ private final ServiceDispatcher mServiceDispatcher;
+ private int mDisplayId;
+ @Nullable private Intent mIntent;
+ @Nullable private ICarAppActivity mICarAppActivity;
+
+ @Nullable IRendererService mRendererService;
+
+ public ServiceConnectionManager(@NonNull Context context,
+ @NonNull ComponentName serviceComponentName, @NonNull ErrorHandler errorHandler) {
+ mContext = context;
+ mErrorHandler = errorHandler;
+ mServiceComponentName = serviceComponentName;
+ mServiceDispatcher = new ServiceDispatcher(mErrorHandler, this::isBound);
+ }
+
+ /**
+ * Returns a {@link ServiceDispatcher} that can be used to communicate with the renderer
+ * service.
+ */
+ @NonNull ServiceDispatcher getServiceDispatcher() {
+ return mServiceDispatcher;
+ }
+
+ @VisibleForTesting
+ ComponentName getServiceComponentName() {
+ return mServiceComponentName;
+ }
+
+ @VisibleForTesting
+ ErrorHandler getErrorHandler() {
+ return mErrorHandler;
+ }
+
+ @VisibleForTesting
+ ServiceConnection getServiceConnection() {
+ return mServiceConnectionImpl;
+ }
+
+ @VisibleForTesting
+ void setServiceConnection(ServiceConnection serviceConnection) {
+ mServiceConnectionImpl = serviceConnection;
+ }
+
+ @VisibleForTesting
+ void setRendererService(@Nullable IRendererService rendererService) {
+ mRendererService = rendererService;
+ }
+
+ /** Returns true if the service is currently bound and able to receive messages */
+ boolean isBound() {
+ return mRendererService != null;
+ }
+
+ /** The service connection for the renderer service. */
+ private ServiceConnection mServiceConnectionImpl =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(
+ @NonNull ComponentName name, @NonNull IBinder service) {
+ requireNonNull(name);
+ requireNonNull(service);
+ Log.i(LogTags.TAG, String.format("Host service %s is connected",
+ name.flattenToShortString()));
+ IRendererService rendererService = IRendererService.Stub.asInterface(service);
+ if (rendererService == null) {
+ mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
+ new Exception("Failed to get IRenderService binder from host: "
+ + name));
+ return;
+ }
+
+ mRendererService = rendererService;
+ initializeService();
+ }
+
+ @Override
+ public void onServiceDisconnected(@NonNull ComponentName name) {
+ requireNonNull(name);
+
+ // Connection lost, but it might reconnect.
+ Log.w(LogTags.TAG, String.format("Host service %s is disconnected",
+ name.flattenToShortString()));
+ }
+
+ @Override
+ public void onBindingDied(@NonNull ComponentName name) {
+ requireNonNull(name);
+
+ // Connection permanently lost
+ mErrorHandler.onError(ErrorHandler.ErrorType.HOST_CONNECTION_LOST,
+ new Exception("Host service " + name + " is permanently disconnected"));
+ }
+
+ @Override
+ public void onNullBinding(@NonNull ComponentName name) {
+ requireNonNull(name);
+
+ // Host rejected the binding.
+ mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
+ new Exception("Host service " + name + " rejected the binding "
+ + "request"));
+ }
+ };
+
+ /**
+ * Binds to the renderer service and initializes the service if not bound already.
+ *
+ * Initializes the renderer service with given properties if already bound to the renderer
+ * service.
+ */
+ void bind(@NonNull Intent intent, @NonNull ICarAppActivity iCarAppActivity, int displayId) {
+ mIntent = requireNonNull(intent);
+ mICarAppActivity = requireNonNull(iCarAppActivity);
+ mDisplayId = displayId;
+
+ if (isBound()) {
+ initializeService();
+ return;
+ }
+
+ Intent rendererIntent = new Intent(ACTION_RENDER);
+ List<ResolveInfo> resolveInfoList =
+ mContext.getPackageManager()
+ .queryIntentServices(rendererIntent, PackageManager.GET_META_DATA);
+ if (resolveInfoList.size() == 1) {
+ rendererIntent.setPackage(resolveInfoList.get(0).serviceInfo.packageName);
+ if (!mContext.bindService(
+ rendererIntent,
+ mServiceConnectionImpl,
+ Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
+ mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
+ new Exception("Cannot bind to the renderer host with intent: "
+ + rendererIntent));
+ }
+ } else if (resolveInfoList.isEmpty()) {
+ mErrorHandler.onError(ErrorHandler.ErrorType.HOST_NOT_FOUND, new Exception("No "
+ + "handlers found for intent: " + rendererIntent));
+ } else {
+ StringBuilder logMessage =
+ new StringBuilder("Multiple hosts found, only one is allowed");
+ for (ResolveInfo resolveInfo : resolveInfoList) {
+ logMessage.append(
+ String.format("\nFound host %s", resolveInfo.serviceInfo.packageName));
+ }
+ mErrorHandler.onError(ErrorHandler.ErrorType.MULTIPLE_HOSTS,
+ new Exception(logMessage.toString()));
+ }
+ }
+
+ /** Closes the connection to the connected {@code rendererService} if any. */
+ void unbind() {
+ if (mRendererService == null) {
+ return;
+ }
+ try {
+ mRendererService.terminate(requireNonNull(mServiceComponentName));
+ } catch (RemoteException e) {
+ // We are already unbinding (maybe because the host has already cut the connection)
+ // Let's not log more errors unnecessarily.
+ }
+
+ Log.i(LogTags.TAG, "Unbinding from " + mServiceComponentName);
+ mContext.unbindService(mServiceConnectionImpl);
+ mRendererService = null;
+ }
+
+ /**
+ * Initializes the {@code rendererService} for the current {@code carIAppActivity},
+ * {@code serviceComponentName} and {@code displayId}.
+ */
+ void initializeService() {
+ ICarAppActivity carAppActivity = requireNonNull(mICarAppActivity);
+ IRendererService rendererService = requireNonNull(mRendererService);
+ ComponentName serviceComponentName = requireNonNull(mServiceComponentName);
+
+ Boolean success = mServiceDispatcher.fetch(false,
+ () -> rendererService.initialize(carAppActivity,
+ serviceComponentName, mDisplayId));
+ if (success == null || !success) {
+ mErrorHandler.onError(ErrorHandler.ErrorType.HOST_ERROR,
+ new Exception("Cannot create renderer for" + serviceComponentName));
+ return;
+ }
+ updateIntent();
+ }
+
+ /**
+ * Updates the activity intent for the connected {@code rendererService}.
+ */
+ private void updateIntent() {
+ ComponentName serviceComponentName = requireNonNull(mServiceComponentName);
+ Intent intent = requireNonNull(mIntent);
+
+ IRendererService service = mRendererService;
+ if (service == null) {
+ mErrorHandler.onError(ErrorHandler.ErrorType.CLIENT_SIDE_ERROR,
+ new Exception("Service dispatcher is not connected"));
+ return;
+ }
+
+ Boolean success = mServiceDispatcher.fetch(false, () ->
+ service.onNewIntent(intent, serviceComponentName, mDisplayId));
+ if (success == null || !success) {
+ mErrorHandler.onError(ErrorHandler.ErrorType.HOST_ERROR, new Exception("Renderer "
+ + "cannot handle the intent: " + intent));
+ }
+ }
+}
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceDispatcher.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceDispatcher.java
index a77a3e5..83c575f 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceDispatcher.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceDispatcher.java
@@ -23,6 +23,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.car.app.activity.renderer.IRendererService;
import androidx.car.app.serialization.BundlerException;
@@ -33,9 +34,15 @@
*/
@RestrictTo(LIBRARY)
public class ServiceDispatcher {
+
+ /** An interface for monitoring the binding state of a service connection. */
+ public interface OnBindingListener {
+ /** Returns true if the service connection is bound. */
+ boolean isBound();
+ }
+
private final ErrorHandler mErrorHandler;
- @Nullable
- private IRendererService mRendererService;
+ private OnBindingListener mOnBindingListener;
/** A one way call to the service */
public interface OneWayCall {
@@ -56,28 +63,15 @@
T invoke() throws RemoteException, BundlerException;
}
- public ServiceDispatcher(@NonNull ErrorHandler errorHandler) {
+ public ServiceDispatcher(@NonNull ErrorHandler errorHandler,
+ @NonNull OnBindingListener onBindingListener) {
mErrorHandler = errorHandler;
+ mOnBindingListener = onBindingListener;
}
- /**
- * Updates the bound service reference
- *
- * @param rendererService bound service or {@code null} if the service is not bound.
- */
- public void setRendererService(@Nullable IRendererService rendererService) {
- mRendererService = rendererService;
- }
-
- /** Returns the bound service, or null if the service is not bound */
- @Nullable
- public IRendererService getRendererService() {
- return mRendererService;
- }
-
- /** Returns true if the service is currently bound and able to receive messages */
- public boolean isBound() {
- return mRendererService != null;
+ @VisibleForTesting
+ public void setOnBindingListener(@NonNull OnBindingListener onBindingListener) {
+ mOnBindingListener = onBindingListener;
}
/** Dispatches the given {@link OneWayCall}. This is a non-blocking call. */
@@ -99,7 +93,7 @@
// TODO(b/184697399): Remove two-way calls as these are blocking.
@Nullable
public <T> T fetch(@Nullable T fallbackValue, @NonNull ReturnCall<T> call) {
- if (mRendererService == null) {
+ if (!mOnBindingListener.isBound()) {
// Avoid dispatching messages if we are not bound to the service
return fallbackValue;
}
diff --git a/car/app/app-activity/src/main/res/layout/activity_template.xml b/car/app/app-activity/src/main/res/layout/activity_template.xml
index 09c432f..7054403 100644
--- a/car/app/app-activity/src/main/res/layout/activity_template.xml
+++ b/car/app/app-activity/src/main/res/layout/activity_template.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/fragment_container">
<androidx.car.app.activity.renderer.surface.TemplateSurfaceView
@@ -11,6 +10,7 @@
android:focusableInTouchMode="true" />
<LinearLayout
+ android:background="#000000"
android:id="@+id/error_message_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
index 8cb2e32..7ffef3b 100644
--- a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
@@ -239,8 +239,11 @@
CarAppActivity.class)) {
scenario.onActivity(activity -> {
try {
- ServiceConnection serviceConnection = spy(activity.getServiceConnection());
- activity.setServiceConnection(serviceConnection);
+ ServiceConnectionManager serviceConnectionManager =
+ activity.mViewModel.getServiceConnectionManager();
+ ServiceConnection serviceConnection =
+ spy(serviceConnectionManager.getServiceConnection());
+ serviceConnectionManager.setServiceConnection(serviceConnection);
// Destroy activity to force unbind.
scenario.moveToState(Lifecycle.State.DESTROYED);
@@ -251,7 +254,7 @@
// Verify service connection is closed.
verify(serviceConnection, times(1)).onServiceDisconnected(
mRendererComponent);
- assertThat(activity.mServiceDispatcher.isBound()).isFalse();
+ assertThat(serviceConnectionManager.isBound()).isFalse();
} catch (RemoteException e) {
fail(Log.getStackTraceString(e));
}
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java
new file mode 100644
index 0000000..7650a2d
--- /dev/null
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.car.app.activity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.content.ComponentName;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link CarAppViewModelFactory} */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarAppViewModelFactoryTest {
+ private static final ComponentName TEST_COMPONENT_NAME_1 = new ComponentName(
+ ApplicationProvider.getApplicationContext(), "Class1");
+ private static final ComponentName TEST_COMPONENT_NAME_2 = new ComponentName(
+ ApplicationProvider.getApplicationContext(), "Class2");
+
+ private final Application mApplication = ApplicationProvider.getApplicationContext();
+
+ @Test
+ public void getInstance_sameKey_returnsSame() {
+ CarAppViewModelFactory factory1 = CarAppViewModelFactory.getInstance(mApplication,
+ TEST_COMPONENT_NAME_1);
+
+ CarAppViewModelFactory factory2 = CarAppViewModelFactory.getInstance(mApplication,
+ TEST_COMPONENT_NAME_1);
+
+ assertThat(factory1).isEqualTo(factory2);
+ }
+
+ @Test
+ public void getInstance_differentKeys_returnsDifferent() {
+ CarAppViewModelFactory factory1 = CarAppViewModelFactory.getInstance(mApplication,
+ TEST_COMPONENT_NAME_1);
+
+ CarAppViewModelFactory factory2 = CarAppViewModelFactory.getInstance(mApplication,
+ TEST_COMPONENT_NAME_2);
+
+ assertThat(factory1).isNotEqualTo(factory2);
+ }
+
+ @Test
+ public void create_correctComponentName() {
+ CarAppViewModelFactory factory = CarAppViewModelFactory.getInstance(mApplication,
+ TEST_COMPONENT_NAME_1);
+
+ CarAppViewModel viewModel = factory.create(CarAppViewModel.class);
+
+ assertThat(viewModel).isNotNull();
+ assertThat(viewModel.getServiceConnectionManager().getServiceComponentName())
+ .isEqualTo(TEST_COMPONENT_NAME_1);
+ }
+}
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
new file mode 100644
index 0000000..9eda009
--- /dev/null
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.car.app.activity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Intent;
+
+import androidx.car.app.activity.renderer.ICarAppActivity;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link CarAppViewModel} */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarAppViewModelTest {
+ private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
+ ApplicationProvider.getApplicationContext(), "Class1");
+ private static final int TEST_DISPLAY_ID = 123;
+ private static final Intent TEST_INTENT = new Intent("TestAction");
+
+ private final Application mApplication = ApplicationProvider.getApplicationContext();
+ private CarAppViewModel mCarAppViewModel;
+ private ICarAppActivity mICarAppActivity;
+
+ @Before
+ public void setUp() {
+ mCarAppViewModel = new CarAppViewModel(mApplication, TEST_COMPONENT_NAME);
+ mICarAppActivity = mock(ICarAppActivity.class);
+ }
+
+ @Test
+ public void testSetup() {
+ assertThat(mCarAppViewModel.getServiceConnectionManager()).isNotNull();
+ assertThat(mCarAppViewModel.getServiceConnectionManager().getServiceComponentName())
+ .isEqualTo(TEST_COMPONENT_NAME);
+ assertThat(mCarAppViewModel.getServiceDispatcher()).isNotNull();
+ }
+
+ @Test
+ public void testBind_serviceConnectionManager_invoke() {
+ ServiceConnectionManager serviceConnectionManager = mock(ServiceConnectionManager.class);
+ mCarAppViewModel.setServiceConnectionManager(serviceConnectionManager);
+ mCarAppViewModel.bind(TEST_INTENT, mICarAppActivity, TEST_DISPLAY_ID);
+
+ verify(serviceConnectionManager).bind(TEST_INTENT, mICarAppActivity, TEST_DISPLAY_ID);
+ }
+
+ @Test
+ public void testUnbind_serviceConnectionManager_invoke() {
+ ServiceConnectionManager serviceConnectionManager = mock(ServiceConnectionManager.class);
+ mCarAppViewModel.setServiceConnectionManager(serviceConnectionManager);
+ mCarAppViewModel.unbind();
+
+ verify(serviceConnectionManager).unbind();
+ }
+
+ @Test
+ public void testErrorHandler_capturesErrorEvent() {
+ ErrorHandler.ErrorType errorType = ErrorHandler.ErrorType.HOST_ERROR;
+ Throwable exception = new IllegalStateException();
+
+ ErrorHandler errorHandler =
+ mCarAppViewModel.getServiceConnectionManager().getErrorHandler();
+ errorHandler.onError(errorType, exception);
+
+ CarAppViewModel.ErrorEvent errorEvent = mCarAppViewModel.getErrorEvent().getValue();
+ assertThat(errorEvent).isNotNull();
+ assertThat(errorEvent.getErrorType()).isEqualTo(errorType);
+ assertThat(errorEvent.getException()).isEqualTo(exception);
+ }
+}
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java
new file mode 100644
index 0000000..82bc8c1
--- /dev/null
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java
@@ -0,0 +1,289 @@
+/*
+ * 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.car.app.activity;
+
+import static android.os.Looper.getMainLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.car.app.activity.renderer.ICarAppActivity;
+import androidx.car.app.activity.renderer.IRendererService;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowPackageManager;
+
+/** Tests for {@link ServiceConnectionManager}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class ServiceConnectionManagerTest {
+
+ private static final int TEST_DISPLAY_ID = 123;
+ private static final Intent TEST_INTENT = new Intent("Test");
+
+ private final ComponentName mRendererComponent = new ComponentName(
+ ApplicationProvider.getApplicationContext(), getClass().getName());
+
+ private final String mFakeCarAppServiceClass = "com.fake.FakeCarAppService";
+ private final ComponentName mFakeCarAppServiceComponent = new ComponentName(
+ ApplicationProvider.getApplicationContext(), mFakeCarAppServiceClass);
+ private final IRendererService mRenderService = mock(IRendererService.class);
+ private final RenderServiceDelegate mRenderServiceDelegate =
+ new RenderServiceDelegate(mRenderService);
+
+ private final ErrorHandler mErrorHandler = mock(ErrorHandler.class);
+ private final ServiceConnectionManager mServiceConnectionManager =
+ new ServiceConnectionManager(ApplicationProvider.getApplicationContext(),
+ mFakeCarAppServiceComponent, mErrorHandler);
+
+ private void setupCarAppActivityForTesting() {
+ try {
+ Application app = ApplicationProvider.getApplicationContext();
+
+ PackageManager packageManager = app.getPackageManager();
+ ShadowPackageManager spm = shadowOf(packageManager);
+
+ // Register fake renderer service which will be simulated by {@code mRenderService}.
+ spm.addServiceIfNotPresent(mRendererComponent);
+ spm.addIntentFilterForService(mRendererComponent,
+ new IntentFilter(CarAppActivity.ACTION_RENDER));
+
+ when(mRenderService.initialize(any(ICarAppActivity.class),
+ any(ComponentName.class),
+ anyInt())).thenReturn(true);
+ when(mRenderService.onNewIntent(any(Intent.class), any(ComponentName.class),
+ anyInt())).thenReturn(true);
+
+ ShadowApplication sa = shadowOf(app);
+ sa.setComponentNameAndServiceForBindService(mRendererComponent, mRenderServiceDelegate);
+ } catch (Exception e) {
+ fail(Log.getStackTraceString(e));
+ }
+ }
+
+ @Test
+ public void testIsBound_serviceNotNull_returnsTrue() {
+ mServiceConnectionManager.setRendererService(mock(IRendererService.class));
+ assertThat(mServiceConnectionManager.isBound()).isTrue();
+ }
+
+ @Test
+ public void testIsBound_serviceIsNull_returnsFalse() {
+ mServiceConnectionManager.setRendererService(null);
+ assertThat(mServiceConnectionManager.isBound()).isFalse();
+ }
+
+ @Test
+ public void testBind_unbound_bindsToRenderer() {
+ setupCarAppActivityForTesting();
+ ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+
+ mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+ shadowOf(getMainLooper()).idle();
+ try {
+ verify(mErrorHandler, never()).onError(any(), any());
+ verify(mRenderService).initialize(iCarAppActivity, mFakeCarAppServiceComponent,
+ TEST_DISPLAY_ID);
+ verify(mRenderService).onNewIntent(TEST_INTENT, mFakeCarAppServiceComponent,
+ TEST_DISPLAY_ID);
+ } catch (RemoteException e) {
+ fail(Log.getStackTraceString(e));
+ }
+ assertThat(mServiceConnectionManager.isBound()).isTrue();
+ }
+
+ @Test
+ public void testBind_bound_doesNotRebound() {
+ setupCarAppActivityForTesting();
+ ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+
+ IRendererService renderService = createMockRendererService();
+ mServiceConnectionManager.setRendererService(renderService);
+ mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+ shadowOf(getMainLooper()).idle();
+ try {
+ verify(mErrorHandler, never()).onError(any(), any());
+ verify(mRenderService, never()).initialize(iCarAppActivity, mFakeCarAppServiceComponent,
+ TEST_DISPLAY_ID);
+ verify(mRenderService, never()).onNewIntent(TEST_INTENT, mFakeCarAppServiceComponent,
+ TEST_DISPLAY_ID);
+ } catch (RemoteException e) {
+ fail(Log.getStackTraceString(e));
+ }
+ assertThat(mServiceConnectionManager.isBound()).isTrue();
+ }
+
+ @Test
+ public void testBind_bound_initializes() {
+ setupCarAppActivityForTesting();
+ ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+
+ IRendererService renderService = createMockRendererService();
+
+ mServiceConnectionManager.setRendererService(renderService);
+ mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+ shadowOf(getMainLooper()).idle();
+
+ try {
+ verify(mErrorHandler, never()).onError(any(), any());
+ verify(renderService).initialize(iCarAppActivity, mFakeCarAppServiceComponent,
+ TEST_DISPLAY_ID);
+ verify(renderService).onNewIntent(TEST_INTENT, mFakeCarAppServiceComponent,
+ TEST_DISPLAY_ID);
+ } catch (RemoteException e) {
+ fail(Log.getStackTraceString(e));
+ }
+ assertThat(mServiceConnectionManager.isBound()).isTrue();
+ }
+
+ @Test
+ public void testBind_bound_noRebound() {
+ setupCarAppActivityForTesting();
+ ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+ IRendererService renderService = createMockRendererService();
+
+ mServiceConnectionManager.setRendererService(renderService);
+ mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+ shadowOf(getMainLooper()).idle();
+ try {
+ verify(mErrorHandler, never()).onError(any(), any());
+ verify(mRenderService, never()).initialize(iCarAppActivity, mFakeCarAppServiceComponent,
+ TEST_DISPLAY_ID);
+ verify(mRenderService, never()).onNewIntent(TEST_INTENT, mFakeCarAppServiceComponent,
+ TEST_DISPLAY_ID);
+ } catch (RemoteException e) {
+ fail(Log.getStackTraceString(e));
+ }
+ assertThat(mServiceConnectionManager.isBound()).isTrue();
+ }
+
+ @Test
+ public void testBind_unbound_failure() {
+ setupCarAppActivityForTesting();
+ ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+
+ try {
+ when(mRenderService.initialize(any(ICarAppActivity.class),
+ any(ComponentName.class),
+ anyInt())).thenReturn(false);
+ } catch (RemoteException e) {
+ fail(Log.getStackTraceString(e));
+ }
+
+ mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+ shadowOf(getMainLooper()).idle();
+
+ verify(mErrorHandler).onError(eq(ErrorHandler.ErrorType.HOST_ERROR), any());
+ }
+
+ @Test
+ public void testUnBind_bound_terminate() {
+ setupCarAppActivityForTesting();
+
+ IRendererService renderService = mock(IRendererService.class);
+ mServiceConnectionManager.setRendererService(renderService);
+ mServiceConnectionManager.unbind();
+ shadowOf(getMainLooper()).idle();
+
+ try {
+ verify(renderService).terminate(mFakeCarAppServiceComponent);
+ } catch (RemoteException e) {
+ fail(Log.getStackTraceString(e));
+ }
+
+ assertThat(mServiceConnectionManager.isBound()).isFalse();
+ }
+
+ @Test
+ public void testUnBind_unbound_doNothing() {
+ setupCarAppActivityForTesting();
+
+ mServiceConnectionManager.unbind();
+ shadowOf(getMainLooper()).idle();
+
+ try {
+ verify(mRenderService, never()).terminate(mFakeCarAppServiceComponent);
+ } catch (RemoteException e) {
+ fail(Log.getStackTraceString(e));
+ }
+
+ assertThat(mServiceConnectionManager.isBound()).isFalse();
+ }
+
+ // Use delegate to forward events to a mock. Mockito interceptor is not maintained on
+ // top-level IBinder after call to IRenderService.Stub.asInterface() in CarAppActivity.
+ private static class RenderServiceDelegate extends IRendererService.Stub {
+ private final IRendererService mService;
+
+ RenderServiceDelegate(IRendererService service) {
+ mService = service;
+ }
+
+ @Override
+ public boolean initialize(ICarAppActivity carActivity, ComponentName serviceName,
+ int displayId) throws RemoteException {
+ return mService.initialize(carActivity, serviceName, displayId);
+ }
+
+ @Override
+ public boolean onNewIntent(Intent intent, ComponentName serviceName, int displayId)
+ throws RemoteException {
+ return mService.onNewIntent(intent, serviceName, displayId);
+ }
+
+ @Override
+ public void terminate(ComponentName serviceName) throws RemoteException {
+ mService.terminate(serviceName);
+ }
+ }
+
+ private IRendererService createMockRendererService() {
+ IRendererService renderService = mock(IRendererService.class);
+ try {
+ when(renderService.initialize(any(ICarAppActivity.class),
+ any(ComponentName.class),
+ anyInt())).thenReturn(true);
+ when(renderService.onNewIntent(any(Intent.class), any(ComponentName.class),
+ anyInt())).thenReturn(true);
+ } catch (RemoteException e) {
+ fail(Log.getStackTraceString(e));
+ }
+ return renderService;
+ }
+}
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceDispatcherTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceDispatcherTest.java
index bbb8137..ea22617 100644
--- a/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceDispatcherTest.java
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceDispatcherTest.java
@@ -48,19 +48,7 @@
@Before
public void setup() {
mErrorHandler = mock(ErrorHandler.class);
- mServiceDispatcher = new ServiceDispatcher(mErrorHandler);
- }
-
- @Test
- public void isBound_serviceNotNull_returnsTrue() {
- mServiceDispatcher.setRendererService(mock(IRendererService.class));
- assertThat(mServiceDispatcher.isBound()).isTrue();
- }
-
- @Test
- public void isBound_serviceIsNull_returnsFalse() {
- mServiceDispatcher.setRendererService(null);
- assertThat(mServiceDispatcher.isBound()).isFalse();
+ mServiceDispatcher = new ServiceDispatcher(mErrorHandler, () -> false);
}
@Test
@@ -73,7 +61,7 @@
ServiceDispatcher.OneWayCall call = () -> rendererService.onNewIntent(intent,
componentName, 0);
- mServiceDispatcher.setRendererService(rendererService);
+ mServiceDispatcher.setOnBindingListener(() -> true);
mServiceDispatcher.dispatch(call);
verify(rendererService, times(1)).onNewIntent(intent, componentName, 0);
@@ -83,7 +71,7 @@
public void dispatch_serviceNotBound_notInvoked() throws BundlerException, RemoteException {
ServiceDispatcher.OneWayCall call = mock(ServiceDispatcher.OneWayCall.class);
- mServiceDispatcher.setRendererService(null);
+ mServiceDispatcher.setOnBindingListener(() -> false);
mServiceDispatcher.dispatch(call);
verify(call, never()).invoke();
@@ -95,7 +83,7 @@
throw new RemoteException();
};
- mServiceDispatcher.setRendererService(mock(IRendererService.class));
+ mServiceDispatcher.setOnBindingListener(() -> true);
mServiceDispatcher.dispatch(call);
verify(mErrorHandler, times(1))
@@ -104,10 +92,9 @@
@Test
public void fetch_serviceBound_valueReturned() {
- IRendererService rendererService = mock(IRendererService.class);
ServiceDispatcher.ReturnCall<Integer> call = () -> 123;
- mServiceDispatcher.setRendererService(rendererService);
+ mServiceDispatcher.setOnBindingListener(() -> true);
Integer result = mServiceDispatcher.fetch(234, call);
assertThat(result).isEqualTo(123);
@@ -119,7 +106,7 @@
throws BundlerException, RemoteException {
ServiceDispatcher.ReturnCall<Integer> call = mock(ServiceDispatcher.ReturnCall.class);
- mServiceDispatcher.setRendererService(null);
+ mServiceDispatcher.setOnBindingListener(() -> false);
Integer result = mServiceDispatcher.fetch(234, call);
verify(call, never()).invoke();
@@ -132,7 +119,7 @@
throw new RemoteException();
};
- mServiceDispatcher.setRendererService(mock(IRendererService.class));
+ mServiceDispatcher.setOnBindingListener(() -> true);
Integer result = mServiceDispatcher.fetch(234, call);
verify(mErrorHandler, times(1))
diff --git a/car/app/app-samples/helloworld/automotive/lint-baseline.xml b/car/app/app-samples/helloworld/automotive/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-samples/helloworld/automotive/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml
index 023f647..431920d 100644
--- a/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml
@@ -24,7 +24,8 @@
<application
android:label="@string/app_name"
- android:icon="@drawable/ic_launcher">
+ android:icon="@drawable/ic_launcher"
+ android:extractNativeLibs="false">
<meta-data
android:name="com.android.automotive"
diff --git a/car/app/app-samples/helloworld/mobile/lint-baseline.xml b/car/app/app-samples/helloworld/mobile/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-samples/helloworld/mobile/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app-samples/helloworld/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/helloworld/mobile/src/main/AndroidManifest.xml
index 4ad83c7..7a36a70 100644
--- a/car/app/app-samples/helloworld/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/helloworld/mobile/src/main/AndroidManifest.xml
@@ -21,7 +21,8 @@
android:versionName="1.0">
<application
android:label="@string/app_name"
- android:icon="@drawable/ic_launcher">
+ android:icon="@drawable/ic_launcher"
+ android:extractNativeLibs="false">
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"
tools:ignore="MetadataTagInsideApplicationTag" />
diff --git a/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
index 97f5250..be7c611 100644
--- a/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
@@ -32,7 +32,8 @@
<application
android:label="@string/app_name"
- android:icon="@drawable/ic_launcher">
+ android:icon="@drawable/ic_launcher"
+ android:extractNativeLibs="false">
<meta-data
android:name="com.android.automotive"
diff --git a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/NavigationScreen.java b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/NavigationScreen.java
index 49b6deb..00def13 100644
--- a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/NavigationScreen.java
+++ b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/NavigationScreen.java
@@ -19,6 +19,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
import androidx.car.app.model.ActionStrip;
@@ -42,6 +43,14 @@
/** Simple demo of how to present a trip on the routing screen. */
public final class NavigationScreen extends Screen {
+ /** Invalid zoom focal point value, used for the zoom buttons. */
+ private static final float INVALID_FOCAL_POINT_VAL = -1f;
+
+ /** Zoom-in scale factor, used for the zoom-in button. */
+ private static final float ZOOM_IN_BUTTON_SCALE_FACTOR = 1.1f;
+
+ /** Zoom-out scale factor, used for the zoom-out button. */
+ private static final float ZOOM_OUT_BUTTON_SCALE_FACTOR = 0.9f;
/** A listener for navigation start and stop signals. */
public interface Listener {
@@ -80,6 +89,8 @@
@Nullable
CarIcon mJunctionImage;
+ private boolean mIsInPanMode;
+
public NavigationScreen(
@NonNull CarContext carContext,
@NonNull Action settingsAction,
@@ -125,6 +136,7 @@
NavigationTemplate.Builder builder = new NavigationTemplate.Builder();
builder.setBackgroundColor(CarColor.SECONDARY);
+ // Set the action strip.
ActionStrip.Builder actionStripBuilder = new ActionStrip.Builder();
actionStripBuilder.addAction(mSettingsAction);
if (mIsNavigating) {
@@ -152,6 +164,59 @@
}
builder.setActionStrip(actionStripBuilder.build());
+ // Set the map action strip with the pan and zoom buttons.
+ CarIcon.Builder panIconBuilder = new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ R.drawable.ic_pan_24));
+ if (mIsInPanMode) {
+ panIconBuilder.setTint(CarColor.BLUE);
+ }
+
+ builder.setMapActionStrip(new ActionStrip.Builder()
+ .addAction(new Action.Builder(Action.PAN)
+ .setIcon(panIconBuilder.build())
+ .build())
+ .addAction(
+ new Action.Builder()
+ .setIcon(
+ new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ R.drawable.ic_zoom_out_24))
+ .build())
+ .setOnClickListener(
+ () -> mSurfaceRenderer.handleScale(INVALID_FOCAL_POINT_VAL,
+ INVALID_FOCAL_POINT_VAL,
+ ZOOM_OUT_BUTTON_SCALE_FACTOR))
+ .build())
+ .addAction(
+ new Action.Builder()
+ .setIcon(
+ new CarIcon.Builder(
+ IconCompat.createWithResource(
+ getCarContext(),
+ R.drawable.ic_zoom_in_24))
+ .build())
+ .setOnClickListener(
+ () -> mSurfaceRenderer.handleScale(INVALID_FOCAL_POINT_VAL,
+ INVALID_FOCAL_POINT_VAL,
+ ZOOM_IN_BUTTON_SCALE_FACTOR))
+ .build())
+ .build());
+
+ // When the user enters the pan mode, remind the user that they can exit the pan mode by
+ // pressing the select button again.
+ builder.setPanModeListener(isInPanMode -> {
+ if (isInPanMode) {
+ CarToast.makeText(getCarContext(),
+ "Press Select to exit the pan mode",
+ CarToast.LENGTH_LONG).show();
+ }
+ mIsInPanMode = isInPanMode;
+ invalidate();
+ });
+
if (mIsNavigating) {
if (mDestinationTravelEstimate != null) {
builder.setDestinationTravelEstimate(mDestinationTravelEstimate);
diff --git a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SurfaceRenderer.java b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SurfaceRenderer.java
index bc9503b..dd92668 100644
--- a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SurfaceRenderer.java
+++ b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SurfaceRenderer.java
@@ -16,12 +16,16 @@
package androidx.car.app.sample.navigation.common.car;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.util.Log;
import android.view.Surface;
@@ -31,6 +35,7 @@
import androidx.car.app.CarContext;
import androidx.car.app.SurfaceCallback;
import androidx.car.app.SurfaceContainer;
+import androidx.car.app.sample.navigation.common.R;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
@@ -39,6 +44,15 @@
public final class SurfaceRenderer implements DefaultLifecycleObserver {
private static final String TAG = "SurfaceRenderer";
+ /** The maximum scale factor of the background map. */
+ private static final float MAX_SCALE_FACTOR = 5f;
+
+ /** The minimum scale factor of the background map. */
+ private static final float MIN_SCALE_FACTOR = 0.5f;
+
+ /** The scale factor to apply when initializing the background map. */
+ private static final float MAP_ENLARGE_FACTOR = 5f;
+
@Nullable
Surface mSurface;
@Nullable
@@ -57,7 +71,37 @@
private int mActiveMarker;
private String mLocationString = "unknown";
- private final SurfaceCallback mSurfaceCallback =
+ /** The bitmap that contains the background map image. */
+ private final Bitmap mBackgroundMap;
+
+ /**
+ * The transformation matrix for the background map image, to reflect the result of the user's
+ * pan and zoom actions.
+ */
+ final Matrix mBackgroundMapMatrix = new Matrix();
+
+ /** The cumulative scale factor for the background map image. */
+ float mCumulativeScaleFactor = 1f;
+
+ /**
+ * The current horizontal center point of the background map, used to prevent the map from
+ * disappearing from pan actions.
+ */
+ float mBackgroundMapCenterX = 0f;
+
+ /**
+ * The current vertical center point of the background map, used to prevent the map from
+ * disappearing from pan actions.
+ */
+ float mBackgroundMapCenterY = 0f;
+
+ /**
+ * The original clip bounds of the background map, used to prevent the map from disappearing
+ * from pan actions.
+ */
+ Rect mBackgroundMapClipBounds = new Rect();
+
+ public final SurfaceCallback mSurfaceCallback =
new SurfaceCallback() {
@Override
public void onSurfaceAvailable(@NonNull SurfaceContainer surfaceContainer) {
@@ -101,6 +145,28 @@
mSurface = null;
}
}
+
+ @Override
+ public void onScroll(float distanceX, float distanceY) {
+ synchronized (SurfaceRenderer.this) {
+ float newBackgroundCenterX = mBackgroundMapCenterX - distanceX;
+ float newBackgroundCenterY = mBackgroundMapCenterY - distanceY;
+
+ // If the map stays within the clip bounds, pan the map.
+ if (mBackgroundMapClipBounds.contains((int) newBackgroundCenterX,
+ (int) newBackgroundCenterY)) {
+ mBackgroundMapCenterX = newBackgroundCenterX;
+ mBackgroundMapCenterY = newBackgroundCenterY;
+ mBackgroundMapMatrix.postTranslate(-distanceX, -distanceY);
+ renderFrame();
+ }
+ }
+ }
+
+ @Override
+ public void onScale(float focusX, float focusY, float scaleFactor) {
+ handleScale(focusX, focusY, scaleFactor);
+ }
};
public SurfaceRenderer(@NonNull CarContext carContext, @NonNull Lifecycle lifecycle) {
@@ -124,6 +190,8 @@
mMarkerPaint.setStyle(Style.STROKE);
mMarkerPaint.setStrokeWidth(3);
+ mBackgroundMap = BitmapFactory.decodeResource(carContext.getResources(),
+ R.drawable.map);
lifecycle.addObserver(this);
}
@@ -138,6 +206,34 @@
renderFrame();
}
+ /** Handles a map zoom-in and zoom-out events. */
+ public void handleScale(float focusX, float focusY, float scaleFactor) {
+ synchronized (this) {
+ float x = focusX;
+ float y = focusY;
+
+ Rect visibleArea = mVisibleArea;
+ if (visibleArea != null) {
+ // If a focal point value is negative, use the center point of the visible area.
+ if (x < 0) {
+ x = visibleArea.centerX();
+ }
+ if (y < 0) {
+ y = visibleArea.centerY();
+ }
+ }
+
+ // If the map stays between the maximum and minimum scale factors, scale the map.
+ float newCumulativeScaleFactor = mCumulativeScaleFactor * scaleFactor;
+ if (newCumulativeScaleFactor > MIN_SCALE_FACTOR
+ && newCumulativeScaleFactor < MAX_SCALE_FACTOR) {
+ mCumulativeScaleFactor = newCumulativeScaleFactor;
+ mBackgroundMapMatrix.postScale(scaleFactor, scaleFactor, x, y);
+ renderFrame();
+ }
+ }
+ }
+
/** Updates the markers drawn on the surface. */
public void updateMarkerVisibility(boolean showMarkers, int numMarkers, int activeMarker) {
mShowMarkers = showMarkers;
@@ -162,6 +258,31 @@
// Clear the background.
canvas.drawColor(mCarContext.isDarkMode() ? Color.DKGRAY : Color.LTGRAY);
+ // Initialize the background map.
+ if (mBackgroundMapMatrix.isIdentity()) {
+ // Enlarge the original image.
+ RectF backgroundRect = new RectF(0, 0, mBackgroundMap.getWidth(),
+ mBackgroundMap.getHeight());
+ RectF scaledBackgroundRect = new RectF(0, 0,
+ backgroundRect.width() * MAP_ENLARGE_FACTOR,
+ backgroundRect.height() * MAP_ENLARGE_FACTOR);
+
+ // Initialize the cumulative scale factor and map center points.
+ mCumulativeScaleFactor = 1f;
+ mBackgroundMapCenterX = scaledBackgroundRect.centerX();
+ mBackgroundMapCenterY = scaledBackgroundRect.centerY();
+
+ // Move to the center of the enlarged map.
+ mBackgroundMapMatrix.setRectToRect(backgroundRect, scaledBackgroundRect,
+ Matrix.ScaleToFit.FILL);
+ mBackgroundMapMatrix.postTranslate(
+ -mBackgroundMapCenterX + canvas.getClipBounds().centerX(),
+ -mBackgroundMapCenterY + canvas.getClipBounds().centerY());
+ scaledBackgroundRect.round(mBackgroundMapClipBounds);
+ }
+ canvas.drawBitmap(mBackgroundMap, mBackgroundMapMatrix, null);
+
+
final int horizontalTextMargin = 10;
final int verticalTextMarginFromTop = 20;
final int verticalTextMarginFromBottom = 10;
diff --git a/car/app/app-samples/navigation/common/src/main/res/drawable/ic_pan_24.xml b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_pan_24.xml
new file mode 100644
index 0000000..1dfeed1
--- /dev/null
+++ b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_pan_24.xml
@@ -0,0 +1,28 @@
+<!--
+ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M15.54,5.54L13.77,7.3 12,5.54 10.23,7.3 8.46,5.54 12,2zM18.46,15.54l-1.76,-1.77L18.46,12l-1.76,-1.77 1.76,-1.77L22,12zM8.46,18.46l1.77,-1.76L12,18.46l1.77,-1.76 1.77,1.76L12,22zM5.54,8.46l1.76,1.77L5.54,12l1.76,1.77 -1.76,1.77L2,12z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"/>
+</vector>
diff --git a/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_in_24.xml b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_in_24.xml
new file mode 100644
index 0000000..36d3282
--- /dev/null
+++ b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_in_24.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_out_24.xml b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_out_24.xml
new file mode 100644
index 0000000..089d265
--- /dev/null
+++ b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_out_24.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,13H5v-2h14v2z"/>
+</vector>
diff --git a/car/app/app-samples/navigation/common/src/main/res/drawable/map.png b/car/app/app-samples/navigation/common/src/main/res/drawable/map.png
new file mode 100644
index 0000000..3cfca26
--- /dev/null
+++ b/car/app/app-samples/navigation/common/src/main/res/drawable/map.png
Binary files differ
diff --git a/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml
index d0e6d5b..08b4f1f 100644
--- a/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml
@@ -30,7 +30,8 @@
<application
android:label="@string/app_name"
- android:icon="@drawable/ic_launcher">
+ android:icon="@drawable/ic_launcher"
+ android:extractNativeLibs="false">
<activity
android:name="androidx.car.app.sample.navigation.common.app.MainActivity"
diff --git a/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml
index 5a2330d..679d8e5 100644
--- a/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml
@@ -31,7 +31,8 @@
<application
android:label="@string/app_name"
- android:icon="@drawable/ic_launcher">
+ android:icon="@drawable/ic_launcher"
+ android:extractNativeLibs="false">
<meta-data
android:name="com.android.automotive"
diff --git a/car/app/app-samples/places/common/lint-baseline.xml b/car/app/app-samples/places/common/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-samples/places/common/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app-samples/places/common/src/main/java/androidx/car/app/sample/places/common/PlaceDetailsScreen.java b/car/app/app-samples/places/common/src/main/java/androidx/car/app/sample/places/common/PlaceDetailsScreen.java
index 082558e..493552f 100644
--- a/car/app/app-samples/places/common/src/main/java/androidx/car/app/sample/places/common/PlaceDetailsScreen.java
+++ b/car/app/app-samples/places/common/src/main/java/androidx/car/app/sample/places/common/PlaceDetailsScreen.java
@@ -16,6 +16,7 @@
package androidx.car.app.sample.places.common;
+import static androidx.car.app.CarToast.LENGTH_LONG;
import static androidx.car.app.sample.places.common.Executors.BACKGROUND_EXECUTOR;
import static androidx.car.app.sample.places.common.Executors.UI_EXECUTOR;
@@ -31,6 +32,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.HostException;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
import androidx.car.app.model.CarColor;
@@ -152,7 +155,16 @@
private void onClickNavigate() {
Uri uri = Uri.parse("geo:0,0?q=" + mPlace.getAddress(mGeocoder).getAddressLine(0));
Intent intent = new Intent(CarContext.ACTION_NAVIGATE, uri);
- getCarContext().startCarApp(intent);
+
+ try {
+ getCarContext().startCarApp(intent);
+ } catch (HostException e) {
+ CarToast.makeText(
+ getCarContext(),
+ "Failure starting navigation",
+ LENGTH_LONG)
+ .show();
+ }
}
private static List<CharSequence> getAddressLines(Address address) {
diff --git a/car/app/app-samples/places/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/places/mobile/src/main/AndroidManifest.xml
index 4a95aff..62d9faf 100644
--- a/car/app/app-samples/places/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/places/mobile/src/main/AndroidManifest.xml
@@ -29,7 +29,8 @@
<application
android:label="@string/app_name"
- android:icon="@drawable/ic_launcher">
+ android:icon="@drawable/ic_launcher"
+ android:extractNativeLibs="false">
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"
diff --git a/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
index a7770fa..45423e3 100644
--- a/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
@@ -37,7 +37,8 @@
<application
android:label="@string/app_name"
- android:icon="@drawable/ic_launcher">
+ android:icon="@drawable/ic_launcher"
+ android:extractNativeLibs="false">
<meta-data
android:name="com.android.automotive"
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/PlaceDetailsScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/PlaceDetailsScreen.java
index f972036..6125f04 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/PlaceDetailsScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/PlaceDetailsScreen.java
@@ -16,6 +16,7 @@
package androidx.car.app.sample.showcase.common.common;
+import static androidx.car.app.CarToast.LENGTH_LONG;
import static androidx.car.app.model.Action.BACK;
import android.content.Intent;
@@ -23,6 +24,8 @@
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.HostException;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
import androidx.car.app.model.CarColor;
@@ -78,13 +81,31 @@
private void onClickNavigate() {
Uri uri = Uri.parse("geo:0,0?q=" + mPlace.address);
Intent intent = new Intent(CarContext.ACTION_NAVIGATE, uri);
- getCarContext().startCarApp(intent);
+
+ try {
+ getCarContext().startCarApp(intent);
+ } catch (HostException e) {
+ CarToast.makeText(
+ getCarContext(),
+ "Failure starting navigation",
+ LENGTH_LONG)
+ .show();
+ }
}
private void onClickDial() {
Uri uri = Uri.parse("tel:" + mPlace.phoneNumber);
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
- getCarContext().startCarApp(intent);
+
+ try {
+ getCarContext().startCarApp(intent);
+ } catch (HostException e) {
+ CarToast.makeText(
+ getCarContext(),
+ "Failure starting dialer",
+ LENGTH_LONG)
+ .show();
+ }
}
private PlaceDetailsScreen(@NonNull CarContext carContext, @NonNull PlaceInfo place) {
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/NavigatingDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/NavigatingDemoScreen.java
index 2eb638f..0f415845 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/NavigatingDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/NavigatingDemoScreen.java
@@ -46,6 +46,7 @@
.build())
.setDestinationTravelEstimate(RoutingDemoModels.getTravelEstimate())
.setActionStrip(RoutingDemoModels.getActionStrip(getCarContext(), this::finish))
+ .setMapActionStrip(RoutingDemoModels.getMapActionStrip(getCarContext()))
.setBackgroundColor(CarColor.SECONDARY)
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/RoutingDemoModels.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/RoutingDemoModels.java
index b7aed8f..248a8ea 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/RoutingDemoModels.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/RoutingDemoModels.java
@@ -151,6 +151,47 @@
.build();
}
+ /**
+ * Returns the map action strip that contains pan and zoom buttons.
+ */
+ @NonNull
+ public static ActionStrip getMapActionStrip(
+ @NonNull CarContext carContext) {
+ return new ActionStrip.Builder()
+ .addAction(
+ new Action.Builder()
+ .setOnClickListener(
+ () -> CarToast.makeText(
+ carContext,
+ "Zoomed in",
+ CarToast.LENGTH_SHORT)
+ .show())
+ .setIcon(
+ new CarIcon.Builder(
+ IconCompat.createWithResource(
+ carContext,
+ R.drawable.ic_zoom_in_24))
+ .build())
+ .build())
+ .addAction(
+ new Action.Builder()
+ .setOnClickListener(
+ () -> CarToast.makeText(
+ carContext,
+ "Zoomed out",
+ CarToast.LENGTH_SHORT)
+ .show())
+ .setIcon(
+ new CarIcon.Builder(
+ IconCompat.createWithResource(
+ carContext,
+ R.drawable.ic_zoom_out_24))
+ .build())
+ .build())
+ .addAction(Action.PAN)
+ .build();
+ }
+
/** Returns the {@link TravelEstimate} with time and distance information. */
@NonNull
public static TravelEstimate getTravelEstimate() {
diff --git a/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_in_24.xml b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_in_24.xml
new file mode 100644
index 0000000..36d3282
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_in_24.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_out_24.xml b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_out_24.xml
new file mode 100644
index 0000000..089d265
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_out_24.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,13H5v-2h14v2z"/>
+</vector>
diff --git a/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
index 9956fb8..5508c67 100644
--- a/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
@@ -35,7 +35,8 @@
<application
android:label="@string/app_name"
- android:icon="@drawable/ic_launcher">
+ android:icon="@drawable/ic_launcher"
+ android:extractNativeLibs="false">
<meta-data
android:name="com.google.android.gms.car.application"
diff --git a/car/app/app-testing/lint-baseline.xml b/car/app/app-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app/src/main/java/androidx/car/app/CarContext.java b/car/app/app/src/main/java/androidx/car/app/CarContext.java
index 19ead04..9258d2a 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarContext.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarContext.java
@@ -284,7 +284,7 @@
* @param intent the {@link Intent} to send to the target application
* @throws SecurityException if the app attempts to start a different app explicitly or
* does not have permissions for the requested action
- * @throws InvalidParameterException if {@code intent} does not meet the criteria defined
+ * @throws HostException if the remote call fails
* @throws NullPointerException if {@code intent} is {@code null}
*/
public void startCarApp(@NonNull Intent intent) {
diff --git a/cardview/cardview/lint-baseline.xml b/cardview/cardview/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/cardview/cardview/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/cleanBuild.sh b/cleanBuild.sh
index c6ed4dd..5b330b9 100755
--- a/cleanBuild.sh
+++ b/cleanBuild.sh
@@ -17,7 +17,7 @@
echo
echo "For example:"
echo
- echo " $0 assembleDebug # or any other arguments you would normally give to ./gradlew"
+ echo " $0 assembleRelease # or any other arguments you would normally give to ./gradlew"
echo
echo
echo "-y"
diff --git a/collection/collection-benchmark/lint-baseline.xml b/collection/collection-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/collection/collection-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/collection/collection-ktx/lint-baseline.xml b/collection/collection-ktx/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/collection/collection-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/collection/integration-tests/testapp/lint-baseline.xml b/collection/integration-tests/testapp/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/collection/integration-tests/testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt b/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt
index 67580ac..459b61a 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt
@@ -272,6 +272,9 @@
method public static androidx.compose.animation.core.Easing getLinearOutSlowInEasing();
}
+ @kotlin.RequiresOptIn(message="This is an experimental animation API for Transition. It may change in the future.") public @interface ExperimentalTransitionApi {
+ }
+
public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
}
@@ -404,8 +407,10 @@
ctor public MutableTransitionState(S? initialState);
method public S! getCurrentState();
method public S! getTargetState();
+ method @androidx.compose.animation.core.ExperimentalTransitionApi public boolean isIdle();
method public void setTargetState(S! p);
property public final S! currentState;
+ property @androidx.compose.animation.core.ExperimentalTransitionApi public final boolean isIdle;
property public final S! targetState;
}
@@ -518,6 +523,7 @@
method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateRect(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Rect>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Rect> targetValueByState);
method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateSize(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Size>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Size> targetValueByState);
method @androidx.compose.runtime.Composable public static inline <S, T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateValue(androidx.compose.animation.core.Transition<S>, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<T>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> targetValueByState);
+ method @androidx.compose.animation.core.ExperimentalTransitionApi @androidx.compose.runtime.Composable public static inline <S, T> androidx.compose.animation.core.Transition<T> createChildTransition(androidx.compose.animation.core.Transition<S>, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> transformToChildState);
method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(T? targetState, optional String? label);
method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(androidx.compose.animation.core.MutableTransitionState<T> transitionState, optional String? label);
}
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index 67580ac..459b61a 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -272,6 +272,9 @@
method public static androidx.compose.animation.core.Easing getLinearOutSlowInEasing();
}
+ @kotlin.RequiresOptIn(message="This is an experimental animation API for Transition. It may change in the future.") public @interface ExperimentalTransitionApi {
+ }
+
public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
}
@@ -404,8 +407,10 @@
ctor public MutableTransitionState(S? initialState);
method public S! getCurrentState();
method public S! getTargetState();
+ method @androidx.compose.animation.core.ExperimentalTransitionApi public boolean isIdle();
method public void setTargetState(S! p);
property public final S! currentState;
+ property @androidx.compose.animation.core.ExperimentalTransitionApi public final boolean isIdle;
property public final S! targetState;
}
@@ -518,6 +523,7 @@
method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateRect(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Rect>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Rect> targetValueByState);
method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateSize(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Size>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Size> targetValueByState);
method @androidx.compose.runtime.Composable public static inline <S, T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateValue(androidx.compose.animation.core.Transition<S>, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<T>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> targetValueByState);
+ method @androidx.compose.animation.core.ExperimentalTransitionApi @androidx.compose.runtime.Composable public static inline <S, T> androidx.compose.animation.core.Transition<T> createChildTransition(androidx.compose.animation.core.Transition<S>, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> transformToChildState);
method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(T? targetState, optional String? label);
method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(androidx.compose.animation.core.MutableTransitionState<T> transitionState, optional String? label);
}
diff --git a/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt b/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt
index 3062208..4d76908 100644
--- a/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt
+++ b/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt
@@ -484,13 +484,17 @@
}
public final class Transition<S> {
+ ctor @kotlin.PublishedApi internal Transition(androidx.compose.animation.core.MutableTransitionState<S> transitionState, optional String? label);
method @kotlin.PublishedApi internal boolean addAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+ method @kotlin.PublishedApi internal boolean addTransition(androidx.compose.animation.core.Transition<?> transition);
method public S! getCurrentState();
method public String? getLabel();
method public androidx.compose.animation.core.Transition.Segment<S> getSegment();
method public S! getTargetState();
method public boolean isRunning();
method @kotlin.PublishedApi internal void removeAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+ method @kotlin.PublishedApi internal boolean removeTransition(androidx.compose.animation.core.Transition<?> transition);
+ method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal void updateTarget(S? targetState);
property public final S! currentState;
property public final boolean isRunning;
property public final String? label;
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 3062208..4d76908 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -484,13 +484,17 @@
}
public final class Transition<S> {
+ ctor @kotlin.PublishedApi internal Transition(androidx.compose.animation.core.MutableTransitionState<S> transitionState, optional String? label);
method @kotlin.PublishedApi internal boolean addAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+ method @kotlin.PublishedApi internal boolean addTransition(androidx.compose.animation.core.Transition<?> transition);
method public S! getCurrentState();
method public String? getLabel();
method public androidx.compose.animation.core.Transition.Segment<S> getSegment();
method public S! getTargetState();
method public boolean isRunning();
method @kotlin.PublishedApi internal void removeAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+ method @kotlin.PublishedApi internal boolean removeTransition(androidx.compose.animation.core.Transition<?> transition);
+ method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal void updateTarget(S? targetState);
property public final S! currentState;
property public final boolean isRunning;
property public final String? label;
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt
index 7d19409..7172064 100644
--- a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt
@@ -18,11 +18,13 @@
import androidx.annotation.Sampled
import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.ExperimentalTransitionApi
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.createChildTransition
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.spring
@@ -34,9 +36,12 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.Icon
@@ -51,6 +56,8 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
@@ -324,3 +331,118 @@
}
}
}
+
+@Composable
+@Suppress("UNUSED_PARAMETER")
+@Sampled
+fun CreateChildTransitionSample() {
+ // enum class DialerState { DialerMinimized, NumberPad }
+ @OptIn(ExperimentalTransitionApi::class)
+ @Composable
+ fun DialerButton(visibilityTransition: Transition<Boolean>, modifier: Modifier) {
+ val scale by visibilityTransition.animateFloat { visible ->
+ if (visible) 1f else 2f
+ }
+ Box(modifier.scale(scale).background(Color.Black)) {
+ // Content goes here
+ }
+ }
+
+ @Composable
+ fun NumberPad(visibilityTransition: Transition<Boolean>) {
+ // Create animations using the provided Transition for visibility change here...
+ }
+
+ @OptIn(ExperimentalTransitionApi::class)
+ @Composable
+ fun childTransitionSample() {
+ var dialerState by remember { mutableStateOf(DialerState.NumberPad) }
+ Box(Modifier.fillMaxSize()) {
+ val parentTransition = updateTransition(dialerState)
+
+ // Animate to different corner radius based on target state
+ val cornerRadius by parentTransition.animateDp {
+ if (it == DialerState.NumberPad) 0.dp else 20.dp
+ }
+
+ Box(
+ Modifier.align(Alignment.BottomCenter).widthIn(50.dp).heightIn(50.dp)
+ .clip(RoundedCornerShape(cornerRadius))
+ ) {
+ NumberPad(
+ // Creates a child transition that derives its target state from the parent
+ // transition, and the mapping from parent state to child state.
+ // This will allow:
+ // 1) Parent transition to account for additional animations in the child
+ // Transitions before it considers itself finished. This is useful when you
+ // have a subsequent action after all animations triggered by a state change
+ // have finished.
+ // 2) Separation of concerns. This allows the child composable (i.e.
+ // NumberPad) to only care about its own visibility, rather than knowing about
+ // DialerState.
+ visibilityTransition = parentTransition.createChildTransition {
+ // This is the lambda that defines how the parent target state maps to
+ // child target state.
+ it == DialerState.NumberPad
+ }
+ // Note: If it's not important for the animations within the child composable to
+ // be observable, it's perfectly valid to not hoist the animations through
+ // a Transition object and instead use animate*AsState.
+ )
+ DialerButton(
+ visibilityTransition = parentTransition.createChildTransition {
+ it == DialerState.DialerMinimized
+ },
+ modifier = Modifier.matchParentSize()
+ )
+ }
+ }
+ }
+}
+
+enum class DialerState {
+ DialerMinimized,
+ NumberPad
+}
+
+@OptIn(ExperimentalTransitionApi::class)
+@Composable
+fun TransitionStateIsIdleSample() {
+ @Composable
+ fun SelectableItem(selectedState: MutableTransitionState<Boolean>) {
+ val transition = updateTransition(selectedState)
+ val cornerRadius by transition.animateDp { selected -> if (selected) 10.dp else 0.dp }
+ val backgroundColor by transition.animateColor { selected ->
+ if (selected) Color.Red else Color.White
+ }
+ Box(Modifier.background(backgroundColor, RoundedCornerShape(cornerRadius))) {
+ // Item content goes here
+ }
+ }
+
+ @OptIn(ExperimentalTransitionApi::class)
+ @Composable
+ fun ItemsSample(selectedId: Int) {
+ Column {
+ repeat(3) { id ->
+ Box {
+ // Initialize the selected state as false to produce a transition going from
+ // false to true if `selected` parameter is true when entering composition.
+ val selectedState = remember { MutableTransitionState(false) }
+ // Mutate target state as needed.
+ selectedState.targetState = id == selectedId
+ // Now we pass the `MutableTransitionState` to the `Selectable` item and
+ // observe state change.
+ SelectableItem(selectedState)
+ if (selectedState.isIdle && selectedState.targetState) {
+ // If isIdle == true, it means the transition has arrived at its target state
+ // and there is no pending animation.
+ // Now we can do something after the selection transition is
+ // finished:
+ Text("Nice choice")
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
index 157183c9..8135f03 100644
--- a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
@@ -20,16 +20,17 @@
import androidx.compose.animation.animateColor
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
-import androidx.compose.runtime.withFrameNanos
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.delay
import org.junit.Rule
@@ -48,6 +49,7 @@
To
}
+ @OptIn(InternalAnimationApi::class)
@Test
fun transitionTest() {
val target = mutableStateOf(AnimStates.From)
@@ -239,6 +241,7 @@
assertTrue(playTime > 200 * MillisToNanos)
}
+ @OptIn(InternalAnimationApi::class)
@Test
fun addNewAnimationInFlightTest() {
val target = mutableStateOf(AnimStates.From)
@@ -339,4 +342,108 @@
assertTrue(targetRecreated)
assertTrue(playTime >= 800 * MillisToNanos)
}
+
+ @OptIn(ExperimentalTransitionApi::class)
+ @Test
+ fun testMutableTransitionStateIsIdle() {
+ val mutableTransitionState = MutableTransitionState(false)
+ var transition: Transition<Boolean>? = null
+ rule.setContent {
+ transition = updateTransition(mutableTransitionState).apply {
+ animateFloat {
+ if (it) 1f else 0f
+ }
+ }
+ }
+ rule.mainClock.autoAdvance = false
+ rule.runOnIdle {
+ assertTrue(mutableTransitionState.isIdle)
+ mutableTransitionState.targetState = true
+ assertFalse(mutableTransitionState.isIdle)
+ }
+
+ while (transition?.currentState != true) {
+ // Animation has not finished or even started from Transition's perspective
+ assertFalse(mutableTransitionState.isIdle)
+ rule.mainClock.advanceTimeByFrame()
+ }
+ assertTrue(mutableTransitionState.isIdle)
+
+ // Now that transition false -> true finished, go back to false
+ rule.runOnIdle {
+ assertTrue(mutableTransitionState.isIdle)
+ mutableTransitionState.targetState = false
+ assertFalse(mutableTransitionState.isIdle)
+ }
+
+ while (transition?.currentState == true) {
+ // Animation has not finished or even started from Transition's perspective
+ assertFalse(mutableTransitionState.isIdle)
+ rule.mainClock.advanceTimeByFrame()
+ }
+ assertTrue(mutableTransitionState.isIdle)
+ }
+
+ @OptIn(ExperimentalTransitionApi::class, InternalAnimationApi::class)
+ @Test
+ fun testCreateChildTransition() {
+ val intState = mutableStateOf(1)
+ val parentTransitionFloat = mutableStateOf(1f)
+ val childTransitionFloat = mutableStateOf(1f)
+ rule.setContent {
+ val transition = updateTransition(intState.value)
+ parentTransitionFloat.value = transition.animateFloat({ tween(100) }) {
+ when (it) {
+ 0 -> 0f
+ 1 -> 1f
+ else -> 2f
+ }
+ }.value
+ val booleanTransition = transition.createChildTransition {
+ it == 1
+ }
+ childTransitionFloat.value = booleanTransition.animateFloat({ tween(500) }) {
+ if (it) 1f else 0f
+ }.value
+ LaunchedEffect(intState.value) {
+ while (true) {
+ if (transition.targetState == transition.currentState) {
+ break
+ }
+ withFrameNanos { it }
+ if (intState.value == 0) {
+ // 1 -> 0
+ if (
+ transition.playTimeNanos > 0 && transition.playTimeNanos <= 500_000_000L
+ ) {
+ assertTrue(transition.isRunning)
+ assertTrue(booleanTransition.isRunning)
+ }
+ } else if (intState.value == 2) {
+ // 0 -> 2
+ assertFalse(booleanTransition.isRunning)
+ if (transition.playTimeNanos > 120_000_000L) {
+ assertFalse(transition.isRunning)
+ } else if (transition.playTimeNanos > 0) {
+ assertTrue(transition.isRunning)
+ }
+ }
+ }
+ }
+ }
+ rule.runOnIdle {
+ assertEquals(1f, parentTransitionFloat.value)
+ assertEquals(1f, childTransitionFloat.value)
+ intState.value = 0
+ }
+ rule.runOnIdle {
+ assertEquals(0f, parentTransitionFloat.value)
+ assertEquals(0f, childTransitionFloat.value)
+ intState.value = 2
+ }
+ rule.runOnIdle {
+ assertEquals(2f, parentTransitionFloat.value)
+ assertEquals(0f, childTransitionFloat.value)
+ }
+ }
}
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ExperimentalTransitionApi.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ExperimentalTransitionApi.kt
new file mode 100644
index 0000000..9972083
--- /dev/null
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ExperimentalTransitionApi.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.compose.animation.core
+
+@RequiresOptIn(
+ message = "This is an experimental animation API for Transition. It may change in the future."
+)
+annotation class ExperimentalTransitionApi
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index 1251a0c..d0cce40 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -22,7 +22,6 @@
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.runtime.getValue
@@ -66,7 +65,14 @@
label: String? = null
): Transition<T> {
val transition = remember { Transition(targetState, label = label) }
- transition.updateTarget(targetState)
+ transition.animateTo(targetState)
+ DisposableEffect(transition) {
+ onDispose {
+ // Clean up on the way out, to ensure the observers are not stuck in an in-between
+ // state.
+ transition.onTransitionEnd()
+ }
+ }
return transition
}
@@ -101,6 +107,19 @@
* animate from the current values to the new target.
*/
var targetState: S by mutableStateOf(initialState)
+
+ /**
+ * [isIdle] returns whether the transition has finished running. This will return false once
+ * the [targetState] has been set to a different value than [currentState].
+ *
+ * @sample androidx.compose.animation.core.samples.TransitionStateIsIdleSample
+ */
+ @get:ExperimentalTransitionApi
+ val isIdle: Boolean
+ get() = (currentState == targetState) && !isRunning
+
+ // Updated from Transition
+ internal var isRunning: Boolean by mutableStateOf(false)
}
/**
@@ -130,7 +149,14 @@
val transition = remember(transitionState) {
Transition(transitionState = transitionState, label)
}
- transition.updateTarget(transitionState.targetState)
+ transition.animateTo(transitionState.targetState)
+ DisposableEffect(transition) {
+ onDispose {
+ // Clean up on the way out, to ensure the observers are not stuck in an in-between
+ // state.
+ transition.onTransitionEnd()
+ }
+ }
return transition
}
@@ -153,7 +179,7 @@
* @see androidx.compose.animation.animateColor
*/
// TODO: Support creating Transition outside of composition and support imperative use of Transition
-class Transition<S> internal constructor(
+class Transition<S> @PublishedApi internal constructor(
private val transitionState: MutableTransitionState<S>,
val label: String? = null
) {
@@ -196,15 +222,20 @@
/**
* Play time in nano-seconds. [playTimeNanos] is always non-negative. It starts from 0L at the
* beginning of the transition and increment until all child animations have finished.
+ * @suppress
*/
- internal var playTimeNanos by mutableStateOf(0L)
+ @InternalAnimationApi
+ var playTimeNanos by mutableStateOf(0L)
+ private var startTimeNanos by mutableStateOf(AnimationConstants.UnspecifiedTime)
// This gets calculated every time child is updated/added
internal var updateChildrenNeeded: Boolean by mutableStateOf(true)
- private var startTimeNanos = AnimationConstants.UnspecifiedTime
private val _animations = mutableVectorOf<TransitionAnimationState<*, *>>()
+ // TODO: Support this in animation tooling
+ private val _transitions = mutableVectorOf<Transition<*>>()
+
/** @suppress **/
@InternalAnimationApi
val animations: List<TransitionAnimationState<*, *>> = _animations.asMutableList()
@@ -219,12 +250,9 @@
var totalDurationNanos: Long by mutableStateOf(0L)
private set
- // Target state that is currently being animated to
- private var currentTargetState: S = currentState
-
internal fun onFrame(frameTimeNanos: Long) {
if (startTimeNanos == AnimationConstants.UnspecifiedTime) {
- startTimeNanos = frameTimeNanos
+ onTransitionStart(frameTimeNanos)
}
updateChildrenNeeded = false
@@ -241,11 +269,31 @@
allFinished = false
}
}
- if (allFinished) {
- startTimeNanos = AnimationConstants.UnspecifiedTime
- currentState = targetState
- playTimeNanos = 0
+ _transitions.forEach {
+ if (it.targetState != it.currentState) {
+ it.onFrame(playTimeNanos)
+ }
+ if (it.targetState != it.currentState) {
+ allFinished = false
+ }
}
+ if (allFinished) {
+ onTransitionEnd()
+ }
+ }
+
+ // onTransitionStart and onTransitionEnd are symmetric. Both are called from onFrame
+ internal fun onTransitionStart(frameTimeNanos: Long) {
+ startTimeNanos = frameTimeNanos
+ transitionState.isRunning = true
+ }
+
+ // onTransitionStart and onTransitionEnd are symmetric. Both are called from onFrame
+ internal fun onTransitionEnd() {
+ startTimeNanos = AnimationConstants.UnspecifiedTime
+ currentState = targetState
+ playTimeNanos = 0
+ transitionState.isRunning = false
}
/**
@@ -256,6 +304,7 @@
fun seek(initialState: S, targetState: S, playTimeNanos: Long) {
// Reset running state
startTimeNanos = AnimationConstants.UnspecifiedTime
+ transitionState.isRunning = false
if (!isSeeking || this.currentState != initialState || this.targetState != targetState) {
// Reset all child animations
this.currentState = initialState
@@ -274,6 +323,12 @@
}
@PublishedApi
+ internal fun addTransition(transition: Transition<*>) = _transitions.add(transition)
+
+ @PublishedApi
+ internal fun removeTransition(transition: Transition<*>) = _transitions.remove(transition)
+
+ @PublishedApi
internal fun addAnimation(
@Suppress("HiddenTypeParameter")
animation: TransitionAnimationState<*, *>
@@ -290,6 +345,7 @@
// This target state should only be used to modify "mutableState"s, as it could potentially
// roll back. The
@Suppress("ComposableNaming")
+ @PublishedApi
@Composable
internal fun updateTarget(targetState: S) {
if (!isSeeking) {
@@ -300,11 +356,25 @@
segment = Segment(this.targetState, targetState)
currentState = this.targetState
this.targetState = targetState
- }
- SideEffect {
- animateTo(targetState)
- }
+ if (!isRunning) {
+ updateChildrenNeeded = true
+ }
+ // If target state is changed, reset all the animations to be re-created in the
+ // next frame w/ their new target value. Child animations target values are updated in
+ // the side effect that may not have happened when this function in invoked.
+ _animations.forEach { it.resetAnimation() }
+ }
+ }
+ }
+
+ // This should only be called if PlayTime comes from clock directly, instead of from a parent
+ // Transition.
+ @Suppress("ComposableNaming")
+ @Composable
+ internal fun animateTo(targetState: S) {
+ if (!isSeeking) {
+ updateTarget(targetState)
// target != currentState adds LaunchedEffect into the tree in the same frame as
// target change.
if (targetState != currentState || isRunning || updateChildrenNeeded) {
@@ -319,22 +389,6 @@
}
}
- internal fun animateTo(targetState: S) {
- if (targetState != currentTargetState) {
- if (isRunning) {
- startTimeNanos += playTimeNanos
- playTimeNanos = 0
- } else {
- updateChildrenNeeded = true
- }
- currentTargetState = targetState
- // If target state is changed, reset all the animations to be re-created in the
- // next frame w/ their new target value. Child animations target values are updated in
- // the side effect that may not have happened when this function in invoked.
- _animations.forEach { it.resetAnimation() }
- }
- }
-
private fun onChildAnimationUpdated() {
updateChildrenNeeded = true
if (isSeeking) {
@@ -363,14 +417,9 @@
) : State<T> {
// Changed during composition, may rollback
- @Suppress("ShowingMemberInHiddenClass")
- @PublishedApi
- internal var targetValue: T by mutableStateOf(initialValue)
- internal set
+ private var targetValue: T by mutableStateOf(initialValue)
+ private var animationSpec: FiniteAnimationSpec<T> by mutableStateOf(spring())
- @Suppress("ShowingMemberInHiddenClass")
- @PublishedApi
- internal var animationSpec: FiniteAnimationSpec<T> by mutableStateOf(spring())
private var animation: TargetBasedAnimation<T, V> by mutableStateOf(
TargetBasedAnimation(
animationSpec, typeConverter, initialValue, targetValue,
@@ -379,11 +428,12 @@
)
internal var isFinished: Boolean by mutableStateOf(true)
private var offsetTimeNanos by mutableStateOf(0L)
+ private var needsReset by mutableStateOf(false)
// Changed during animation, no concerns of rolling back
override var value by mutableStateOf(initialValue)
internal set
- internal var velocityVector: V = initialVelocityVector
+ private var velocityVector: V = initialVelocityVector
internal val durationNanos
get() = animation.durationNanos
@@ -404,9 +454,28 @@
velocityVector = animation.getVelocityVectorFromNanos(playTimeNanos)
}
- private fun updateAnimation(initialValue: T = value) {
+ private val interruptionSpec: FiniteAnimationSpec<T>
+
+ init {
+ val visibilityThreshold: T? = visibilityThresholdMap.get(typeConverter)?.let {
+ val vector = typeConverter.convertToVector(initialValue)
+ for (id in 0 until vector.size) {
+ vector[id] = it
+ }
+ typeConverter.convertFromVector(vector)
+ }
+ interruptionSpec = spring(visibilityThreshold = visibilityThreshold)
+ }
+
+ private fun updateAnimation(initialValue: T = value, isInterrupted: Boolean = false) {
+ val spec = if (isInterrupted) {
+ // When interrupted, use the default spring, unless the spec is also a spring.
+ if (animationSpec is SpringSpec<*>) animationSpec else interruptionSpec
+ } else {
+ animationSpec
+ }
animation = TargetBasedAnimation(
- animationSpec,
+ spec,
typeConverter,
initialValue,
targetValue,
@@ -416,29 +485,34 @@
}
internal fun resetAnimation() {
- offsetTimeNanos = 0
- isFinished = false
- updateAnimation()
+ needsReset = true
}
@PublishedApi
@Suppress("ShowingMemberInHiddenClass")
// This gets called *during* composition
- internal fun updateTargetValue(targetValue: T) {
- if (this.targetValue != targetValue) {
+ internal fun updateTargetValue(targetValue: T, animationSpec: FiniteAnimationSpec<T>) {
+ if (this.targetValue != targetValue || needsReset) {
this.targetValue = targetValue
+ this.animationSpec = animationSpec
+ updateAnimation(isInterrupted = !isFinished)
isFinished = false
- updateAnimation()
// This is needed because the target change could happen during a transition
offsetTimeNanos = playTimeNanos
+ needsReset = false
}
}
@PublishedApi
@Suppress("ControlFlowWithEmptyBody", "ShowingMemberInHiddenClass")
// This gets called *during* composition
- internal fun updateInitialAndTargetValue(initialValue: T, targetValue: T) {
+ internal fun updateInitialAndTargetValue(
+ initialValue: T,
+ targetValue: T,
+ animationSpec: FiniteAnimationSpec<T>
+ ) {
this.targetValue = targetValue
+ this.animationSpec = animationSpec
if (animation.initialValue == initialValue && animation.targetValue == targetValue) {
// TODO(b/178811102): we should be able to return early here.
}
@@ -460,6 +534,160 @@
return this == initialState && targetState == this@Segment.targetState
}
}
+
+ /**
+ * [DeferredAnimation] can be constructed using [Transition.createDeferredAnimation] during
+ * composition and initialized later. It is useful for animations, the target values for
+ * which are unknown at composition time (e.g. layout size/position, etc).
+ *
+ * Once a [DeferredAnimation] is created, it can be configured and updated as needed using
+ * [DeferredAnimation.animate] method.
+ *
+ * @suppress
+ */
+ @InternalAnimationApi
+ inner class DeferredAnimation<T, V : AnimationVector> internal constructor(
+ val typeConverter: TwoWayConverter<T, V>,
+ val label: String
+ ) {
+ internal var data: DeferredAnimationData<T, V>? = null
+
+ internal inner class DeferredAnimationData<T, V : AnimationVector>(
+ val animation: Transition<S>.TransitionAnimationState<T, V>,
+ var transitionSpec: Segment<S>.() -> FiniteAnimationSpec<T>,
+ var targetValueByState: (state: S) -> T,
+ ) : State<T> {
+ override val value: T
+ get() {
+ animation.updateTargetValue(
+ targetValueByState(targetState),
+ segment.transitionSpec()
+ )
+ return animation.value
+ }
+ }
+
+ /**
+ * [DeferredAnimation] allows the animation setup to be deferred until a later time after
+ * composition. [animate] can be used to set up a [DeferredAnimation]. Like other
+ * Transition animations such as [Transition.animateFloat], [DeferredAnimation] also
+ * expects [transitionSpec] and [targetValueByState] for the mapping from target state
+ * to animation spec and target value, respectively.
+ */
+ fun animate(
+ transitionSpec: Segment<S>.() -> FiniteAnimationSpec<T>,
+ targetValueByState: (state: S) -> T
+ ): State<T> {
+ val animData: DeferredAnimationData<T, V> = data ?: DeferredAnimationData(
+ TransitionAnimationState(
+ targetValueByState(currentState),
+ typeConverter.createZeroVectorFrom(targetValueByState(currentState)),
+ typeConverter,
+ label
+ ),
+ transitionSpec,
+ targetValueByState
+ ).apply {
+ data = this
+ addAnimation(this.animation)
+ }
+ return animData.apply {
+ // Update animtion data with the latest mapping
+ this.targetValueByState = targetValueByState
+ this.transitionSpec = transitionSpec
+
+ animation.updateTargetValue(
+ targetValueByState(targetState),
+ segment.transitionSpec()
+ )
+ }
+ }
+
+ internal fun setupSeeking() {
+ data?.apply {
+ animation.updateInitialAndTargetValue(
+ targetValueByState(segment.initialState),
+ targetValueByState(segment.targetState),
+ segment.transitionSpec()
+ )
+ }
+ }
+ }
+
+ internal fun removeAnimation(deferredAnimation: DeferredAnimation<*, *>) {
+ deferredAnimation.data?.animation?.let {
+ removeAnimation(it)
+ }
+ }
+}
+
+/**
+ * This creates a [DeferredAnimation], which will not animate until it is set up using
+ * [DeferredAnimation.animate]. Once the animation is set up, it will animate from the
+ * [currentState][Transition.currentState] to [targetState][Transition.targetState]. If the
+ * [Transition] has already arrived at its target state at the time when the animation added, there
+ * will be no animation.
+ *
+ * @param typeConverter A converter to convert any value of type [T] from/to an [AnimationVector]
+ * @param label A label for differentiating this animation from others in android studio.
+ *
+ * @suppress
+ */
+@InternalAnimationApi
+@Composable
+fun <S, T, V : AnimationVector> Transition<S>.createDeferredAnimation(
+ typeConverter: TwoWayConverter<T, V>,
+ label: String = "DeferredAnimation"
+): Transition<S>.DeferredAnimation<T, V> {
+ val lazyAnim = remember(this) { DeferredAnimation(typeConverter, label) }
+ DisposableEffect(lazyAnim) {
+ onDispose {
+ removeAnimation(lazyAnim)
+ }
+ }
+ if (isSeeking) {
+ lazyAnim.setupSeeking()
+ }
+ return lazyAnim
+}
+
+/**
+ * [createChildTransition] creates a child Transition based on the mapping between parent state to
+ * child state provided in [transformToChildState]. This serves the following purposes:
+ * 1) Hoist the child transition state into parent transition. Therefore the parent Transition
+ * will be aware of whether there's any on-going animation due to the same target state change.
+ * This will further allow sequential animation to be set up when all animations have finished.
+ * 2) Separation of concerns. The child transition can respresent a much more simplified state
+ * transition when, for example, mapping from an enum parent state to a Boolean visible state for
+ * passing further down the compose tree. The child composables hence can be designed around
+ * handling a more simple and a more relevant state change.
+ *
+ * [label] is used to differentiate from other animations in the same transition in Android Studio.
+ *
+ * @sample androidx.compose.animation.core.samples.CreateChildTransitionSample
+ */
+@ExperimentalTransitionApi
+@Composable
+inline fun <S, T> Transition<S>.createChildTransition(
+ label: String = "ChildTransition",
+ transformToChildState: @Composable (parentState: S) -> T,
+): Transition<T> {
+ val initialParentState = remember(this) { this.currentState }
+ val initialState = transformToChildState(initialParentState)
+ val transition = remember(this) {
+ Transition<T>(MutableTransitionState(initialState), label)
+ }
+
+ DisposableEffect(transition) {
+ addTransition(transition)
+ onDispose {
+ removeTransition(transition)
+ }
+ }
+
+ val targetState = transformToChildState(this.targetState)
+ transition.updateTarget(targetState)
+ return transition
}
/**
@@ -496,36 +724,30 @@
targetValueByState: @Composable (state: S) -> T
): State<T> {
+ val initialValue = targetValueByState(currentState)
val targetValue = targetValueByState(targetState)
- // TODO: need a better way to store initial value.
- val initNeeded = remember(this) { mutableStateOf(true) }
- val initValue = if (initNeeded.value) targetValueByState(currentState) else targetValue
val transitionAnimation = remember(this) {
// Initialize the animation state to initialState value, so if it's added during a
// transition run, it'll participate in the animation.
// This is preferred because it's easy to opt out - Simply adding new animation once
// currentState == targetState would opt out.
TransitionAnimationState(
- initValue,
+ initialValue,
typeConverter.createZeroVectorFrom(targetValue),
typeConverter,
label
)
}
- transitionAnimation.animationSpec = transitionSpec(segment)
-
- if (initNeeded.value) {
- SideEffect {
- initNeeded.value = false
- }
- }
-
+ val animationSpec = transitionSpec(segment)
if (isSeeking) {
// In the case of seeking, we also need to update initial value as needed
- val initialValue = targetValueByState(segment.initialState)
- transitionAnimation.updateInitialAndTargetValue(initialValue, targetValue)
+ transitionAnimation.updateInitialAndTargetValue(
+ initialValue,
+ targetValue,
+ animationSpec
+ )
} else {
- transitionAnimation.updateTargetValue(targetValue)
+ transitionAnimation.updateTargetValue(targetValue, animationSpec)
}
DisposableEffect(transitionAnimation) {
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
index 14f4ce0..5b27b547 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
@@ -99,4 +99,16 @@
val Rect.Companion.VisibilityThreshold: Rect
get() = rectVisibilityThreshold
-// TODO: Add Dp.DefaultAnimation = spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)
\ No newline at end of file
+// TODO: Add Dp.DefaultAnimation = spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)
+
+internal val visibilityThresholdMap: Map<TwoWayConverter<*, *>, Float> = mapOf(
+ Int.VectorConverter to 1f,
+ IntSize.VectorConverter to 1f,
+ IntOffset.VectorConverter to 1f,
+ Float.VectorConverter to 0.01f,
+ Rect.VectorConverter to PxVisibilityThreshold,
+ Size.VectorConverter to PxVisibilityThreshold,
+ Offset.VectorConverter to PxVisibilityThreshold,
+ Dp.VectorConverter to DpVisibilityThreshold,
+ DpOffset.VectorConverter to DpVisibilityThreshold
+)
\ No newline at end of file
diff --git a/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt b/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt
index 5c07774..0f00f36 100644
--- a/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt
@@ -6,9 +6,20 @@
}
public final class AnimatedVisibilityKt {
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <T> void AnimatedVisibility(androidx.compose.animation.core.Transition<T>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @Deprecated @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ @androidx.compose.animation.ExperimentalAnimationApi public final class AnimatedVisibilityScope {
+ method public androidx.compose.ui.Modifier animateEnterExit(androidx.compose.ui.Modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit);
+ method public androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> getTransition();
+ property public final androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> transition;
}
public final class AnimationModifierKt {
@@ -23,6 +34,12 @@
method @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> content);
}
+ @androidx.compose.animation.ExperimentalAnimationApi public enum EnterExitState {
+ enum_constant public static final androidx.compose.animation.EnterExitState PostExit;
+ enum_constant public static final androidx.compose.animation.EnterExitState PreEnter;
+ enum_constant public static final androidx.compose.animation.EnterExitState Visible;
+ }
+
public final class EnterExitTransitionKt {
method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandHorizontally(optional androidx.compose.ui.Alignment.Horizontal expandFrom, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialWidth, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandIn(optional androidx.compose.ui.Alignment expandFrom, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,androidx.compose.ui.unit.IntSize> initialSize, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
@@ -42,10 +59,22 @@
@androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class EnterTransition {
method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.EnterTransition plus(androidx.compose.animation.EnterTransition enter);
+ field public static final androidx.compose.animation.EnterTransition.Companion Companion;
+ }
+
+ public static final class EnterTransition.Companion {
+ method public androidx.compose.animation.EnterTransition getNone();
+ property public final androidx.compose.animation.EnterTransition None;
}
@androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class ExitTransition {
method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.ExitTransition plus(androidx.compose.animation.ExitTransition exit);
+ field public static final androidx.compose.animation.ExitTransition.Companion Companion;
+ }
+
+ public static final class ExitTransition.Companion {
+ method public androidx.compose.animation.ExitTransition getNone();
+ property public final androidx.compose.animation.ExitTransition None;
}
@kotlin.RequiresOptIn(message="This is an experimental animation API.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalAnimationApi {
diff --git a/compose/animation/animation/api/public_plus_experimental_current.txt b/compose/animation/animation/api/public_plus_experimental_current.txt
index 5c07774..0f00f36 100644
--- a/compose/animation/animation/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation/api/public_plus_experimental_current.txt
@@ -6,9 +6,20 @@
}
public final class AnimatedVisibilityKt {
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <T> void AnimatedVisibility(androidx.compose.animation.core.Transition<T>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @Deprecated @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ @androidx.compose.animation.ExperimentalAnimationApi public final class AnimatedVisibilityScope {
+ method public androidx.compose.ui.Modifier animateEnterExit(androidx.compose.ui.Modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit);
+ method public androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> getTransition();
+ property public final androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> transition;
}
public final class AnimationModifierKt {
@@ -23,6 +34,12 @@
method @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> content);
}
+ @androidx.compose.animation.ExperimentalAnimationApi public enum EnterExitState {
+ enum_constant public static final androidx.compose.animation.EnterExitState PostExit;
+ enum_constant public static final androidx.compose.animation.EnterExitState PreEnter;
+ enum_constant public static final androidx.compose.animation.EnterExitState Visible;
+ }
+
public final class EnterExitTransitionKt {
method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandHorizontally(optional androidx.compose.ui.Alignment.Horizontal expandFrom, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialWidth, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandIn(optional androidx.compose.ui.Alignment expandFrom, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,androidx.compose.ui.unit.IntSize> initialSize, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
@@ -42,10 +59,22 @@
@androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class EnterTransition {
method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.EnterTransition plus(androidx.compose.animation.EnterTransition enter);
+ field public static final androidx.compose.animation.EnterTransition.Companion Companion;
+ }
+
+ public static final class EnterTransition.Companion {
+ method public androidx.compose.animation.EnterTransition getNone();
+ property public final androidx.compose.animation.EnterTransition None;
}
@androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class ExitTransition {
method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.ExitTransition plus(androidx.compose.animation.ExitTransition exit);
+ field public static final androidx.compose.animation.ExitTransition.Companion Companion;
+ }
+
+ public static final class ExitTransition.Companion {
+ method public androidx.compose.animation.ExitTransition getNone();
+ property public final androidx.compose.animation.ExitTransition None;
}
@kotlin.RequiresOptIn(message="This is an experimental animation API.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalAnimationApi {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt
index 24e6a0f..31d9ea7 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt
@@ -52,7 +52,7 @@
.fillMaxWidth()
.padding(50.dp)
) {
- Text()
+ MyText()
Spacer(Modifier.requiredHeight(20.dp))
Button()
Spacer(Modifier.requiredHeight(20.dp))
@@ -61,7 +61,7 @@
}
@Composable
-private fun Text() {
+private fun MyText() {
val shortText = "Click me"
val longText = "Very long text\nthat spans across\nmultiple lines"
var short by remember { mutableStateOf(true) }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
index d4f9186..f1326e7 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
@@ -34,12 +34,13 @@
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
@@ -54,6 +55,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import kotlin.math.max
+import kotlin.math.min
@Composable
fun AnimatedVisibilityDemo() {
@@ -68,60 +71,67 @@
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedItems(animateContentSize: Boolean) {
- var counter by remember { mutableStateOf(0) }
- Box(
- Modifier.padding(bottom = 20.dp)
- ) {
-
- val modifier = if (animateContentSize) Modifier.animateContentSize() else Modifier
- Column(
- Modifier.background(Color.LightGray).fillMaxWidth().then(modifier)
+ var itemNum by remember { mutableStateOf(0) }
+ Column {
+ Row(
+ Modifier.fillMaxWidth().padding(20.dp),
+ horizontalArrangement = Arrangement.SpaceBetween
) {
- val itemNum = if (counter >= 7) 12 - counter else counter
-
- AnimatedVisibility(visible = itemNum > 0) {
- Item(
- pastelColors[0],
- "Expand Vertically + Fade In\nShrink " +
- "Vertically + Fade Out\n(Column Default)"
- )
+ Button(onClick = { itemNum = min((itemNum + 1), 6) }) {
+ Text("Add")
}
- HorizontalTransition(visible = itemNum > 1) {
- Item(pastelColors[1], "Expand Horizontally\nShrink Horizontally")
- }
- SlideTransition(visible = itemNum > 2) {
- Item(
- pastelColors[2],
- "Slide In Horizontally + Fade In\nSlide Out Horizontally + " +
- "Fade Out"
- )
- }
- AnimatedVisibility(
- visible = itemNum > 3,
- enter = expandVertically(),
- exit = shrinkVertically()
- ) {
- Item(pastelColors[3], "Expand Vertically\nShrink Vertically")
- }
- FadeTransition(visible = itemNum > 4) {
- Item(pastelColors[4], "Fade In\nFade Out")
- }
- FullyLoadedTransition(visible = itemNum > 5) {
- Item(
- pastelColors[0],
- "Expand Vertically + Fade In + Slide In Vertically\n" +
- "Shrink Vertically + Fade Out + Slide Out Vertically"
- )
+ Button(onClick = { itemNum = max((itemNum - 1), 0) }) {
+ Text("Remove")
}
}
-
- Button(
- modifier = Modifier.align(Alignment.TopEnd).padding(10.dp),
- onClick = {
- counter = (counter + 1) % 12
- }
+ Box(
+ Modifier.padding(bottom = 20.dp)
) {
- Text("Click Me")
+
+ val modifier = if (animateContentSize) Modifier.animateContentSize() else Modifier
+ Column(
+ Modifier.background(Color.LightGray).fillMaxWidth().then(modifier)
+ ) {
+
+ Column(
+ Modifier.background(Color.LightGray).fillMaxWidth().then(modifier)
+ ) {
+ AnimatedVisibility(visible = itemNum > 0) {
+ Item(
+ pastelColors[0],
+ "Expand Vertically + Fade In\nShrink " +
+ "Vertically + Fade Out\n(Column Default)"
+ )
+ }
+ HorizontalTransition(visible = itemNum > 1) {
+ Item(pastelColors[1], "Expand Horizontally\nShrink Horizontally")
+ }
+ SlideTransition(visible = itemNum > 2) {
+ Item(
+ pastelColors[2],
+ "Slide In Horizontally + Fade In\nSlide Out Horizontally + " +
+ "Fade Out"
+ )
+ }
+ AnimatedVisibility(
+ visible = itemNum > 3,
+ enter = expandVertically(),
+ exit = shrinkVertically()
+ ) {
+ Item(pastelColors[3], "Expand Vertically\nShrink Vertically")
+ }
+ FadeTransition(visible = itemNum > 4) {
+ Item(pastelColors[4], "Fade In\nFade Out")
+ }
+ FullyLoadedTransition(visible = itemNum > 5) {
+ Item(
+ pastelColors[0],
+ "Expand Vertically + Fade In + Slide In Vertically\n" +
+ "Shrink Vertically + Fade Out + Slide Out Vertically"
+ )
+ }
+ }
+ }
}
}
}
@@ -164,9 +174,10 @@
targetWidth = { fullWidth -> fullWidth / 10 },
// Overwrites the default animation with tween for this shrink animation.
animationSpec = tween(durationMillis = 400)
- ) + fadeOut(),
- content = content
- )
+ ) + fadeOut()
+ ) {
+ content()
+ }
}
@OptIn(ExperimentalAnimationApi::class)
@@ -187,9 +198,10 @@
// Overwrites the ending position of the slide-out to 200 (pixels) to the right
targetOffsetX = { 200 },
animationSpec = spring(stiffness = Spring.StiffnessHigh)
- ) + fadeOut(),
- content = content
- )
+ ) + fadeOut()
+ ) {
+ content()
+ }
}
@OptIn(ExperimentalAnimationApi::class)
@@ -204,9 +216,10 @@
exit = fadeOut(
// Overwrites the default animation with tween
animationSpec = tween(durationMillis = 250)
- ),
- content = content
- )
+ )
+ ) {
+ content()
+ }
}
@OptIn(ExperimentalAnimationApi::class)
@@ -221,7 +234,8 @@
) + expandVertically(
expandFrom = Alignment.Top
) + fadeIn(initialAlpha = 0.3f),
- exit = slideOutVertically() + shrinkVertically() + fadeOut(),
- content = content
- )
+ exit = slideOutVertically() + shrinkVertically() + fadeOut()
+ ) {
+ content()
+ }
}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
index 96ed035..ef08514 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
@@ -18,65 +18,77 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.ExperimentalTransitionApi
+import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment.Companion.CenterEnd
import androidx.compose.ui.Alignment.Companion.End
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.flow.collect
-@OptIn(ExperimentalAnimationApi::class)
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
@Composable
fun AnimatedVisibilityLazyColumnDemo() {
- var itemNum by remember { mutableStateOf(0) }
Column {
+ val model = remember { MyModel() }
Row(Modifier.fillMaxWidth()) {
Button(
- { itemNum = itemNum + 1 },
- enabled = itemNum <= turquoiseColors.size - 1,
+ { model.addNewItem() },
modifier = Modifier.padding(15.dp).weight(1f)
) {
Text("Add")
}
+ }
- Button(
- { itemNum = itemNum - 1 },
- enabled = itemNum >= 1,
- modifier = Modifier.padding(15.dp).weight(1f)
- ) {
- Text("Remove")
+ LaunchedEffect(model) {
+ snapshotFlow {
+ model.items.firstOrNull { it.visible.isIdle && !it.visible.targetState }
+ }.collect {
+ if (it != null) {
+ model.pruneItems()
+ }
}
}
LazyColumn {
- itemsIndexed(turquoiseColors) { i, color ->
+ items(model.items, key = { it.itemId }) { item ->
AnimatedVisibility(
- (turquoiseColors.size - itemNum) <= i,
+ item.visible,
enter = expandVertically(),
exit = shrinkVertically()
) {
- Spacer(Modifier.fillMaxWidth().requiredHeight(90.dp).background(color))
+ Box(Modifier.fillMaxWidth().requiredHeight(90.dp).background(item.color)) {
+ Button(
+ { model.removeItem(item) },
+ modifier = Modifier.align(CenterEnd).padding(15.dp)
+ ) {
+ Text("Remove")
+ }
+ }
}
}
}
Button(
- { itemNum = 0 },
+ { model.removeAll() },
modifier = Modifier.align(End).padding(15.dp)
) {
Text("Clear All")
@@ -84,6 +96,44 @@
}
}
+private class MyModel {
+ private val _items: MutableList<ColoredItem> = mutableStateListOf()
+ private var lastItemId = 0
+ val items: List<ColoredItem> = _items
+
+ class ColoredItem(val visible: MutableTransitionState<Boolean>, val itemId: Int) {
+ val color: Color
+ get() = turquoiseColors.let {
+ it[itemId % it.size]
+ }
+ }
+
+ fun addNewItem() {
+ lastItemId++
+ _items.add(
+ ColoredItem(
+ MutableTransitionState(false).apply { targetState = true },
+ lastItemId
+ )
+ )
+ }
+
+ fun removeItem(item: ColoredItem) {
+ item.visible.targetState = false
+ }
+
+ @OptIn(ExperimentalTransitionApi::class)
+ fun pruneItems() {
+ _items.removeAll(items.filter { it.visible.isIdle && !it.visible.targetState })
+ }
+
+ fun removeAll() {
+ _items.forEach {
+ it.visible.targetState = false
+ }
+ }
+}
+
private val turquoiseColors = listOf(
Color(0xff07688C),
Color(0xff1986AF),
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
index a8aff2e..1160261 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
@@ -44,7 +44,10 @@
AnimatedVisibilityContentSizeChangeDemo()
},
ComposableDemo("Cross Fade") { CrossfadeDemo() },
- ComposableDemo("Enter/Exit Transition Demo") { EnterExitTransitionDemo() },
+ ComposableDemo("Enter/ExitTransition Combo Demo") { EnterExitCombinationDemo() },
+ ComposableDemo("Sequential Enter/Exit Demo") {
+ SequentialEnterExitDemo()
+ },
)
),
DemoCategory(
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitCombinationDemo.kt
similarity index 99%
rename from compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
rename to compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitCombinationDemo.kt
index cccb234..f25a6fb 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitCombinationDemo.kt
@@ -79,7 +79,7 @@
import androidx.compose.ui.unit.dp
@Composable
-fun EnterExitTransitionDemo() {
+fun EnterExitCombinationDemo() {
Column(Modifier.fillMaxWidth().padding(top = 20.dp)) {
val oppositeAlignment = remember { mutableStateOf(true) }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SequentialEnterExitDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SequentialEnterExitDemo.kt
new file mode 100644
index 0000000..e769f8b
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SequentialEnterExitDemo.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.compose.animation.demos
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.EnterExitState
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.ExperimentalTransitionApi
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.FloatingActionButton
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.text.font.FontWeight.Companion.Bold
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+/*
+ * This demo shows how to create a Transition<EnterExitState> with MutableTransitionState, and
+ * use the Transition<EnterExitState> to animate a few enter/exit transitions together. The
+ * MutableTransitionState is then used to add sequential enter/exit transitions.
+ *
+ * APIs used:
+ * - updateTransition
+ * - MutableTransitionState
+ * - EnterExitState
+ */
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+@Composable
+fun SequentialEnterExitDemo() {
+ Box {
+ var mainContentVisible by remember {
+ mutableStateOf(MutableTransitionState(true))
+ }
+ Column(Modifier.fillMaxSize()) {
+ Spacer(Modifier.size(40.dp))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceAround
+ ) {
+ // New MutableTransitionState instance here. This should reset the animation.
+ Button(onClick = { mainContentVisible = MutableTransitionState(false) }) {
+ Text("Reset")
+ }
+
+ Button(
+ onClick = { mainContentVisible.targetState = !mainContentVisible.targetState },
+ ) {
+ Text("Toggle Visibility")
+ }
+ }
+ Spacer(Modifier.size(40.dp))
+ AnimatedVisibility(
+ visibleState = mainContentVisible,
+ modifier = Modifier.fillMaxSize(),
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ Box {
+ Column(Modifier.fillMaxSize()) {
+ Item(Modifier.weight(1f), backgroundColor = Color(0xffff6f69))
+ Item(Modifier.weight(1f), backgroundColor = Color(0xffffcc5c))
+ }
+ FloatingActionButton(
+ onClick = {},
+ modifier = Modifier.align(Alignment.BottomEnd).padding(20.dp),
+ backgroundColor = MaterialTheme.colors.primary
+ ) {
+ Icon(Icons.Default.Favorite, contentDescription = null)
+ }
+ }
+ }
+ }
+ AnimatedVisibility(
+ visible = mainContentVisible.targetState && mainContentVisible.isIdle,
+ modifier = Modifier.align(Alignment.Center),
+ enter = expandVertically(),
+ exit = fadeOut(animationSpec = tween(50))
+ ) {
+ Text("Transition Finished", color = Color.White, fontSize = 40.sp, fontWeight = Bold)
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+private fun AnimatedVisibilityScope.Item(
+ modifier: Modifier,
+ backgroundColor: Color
+) {
+ val scale by transition.animateFloat { enterExitState ->
+ when (enterExitState) {
+ EnterExitState.PreEnter -> 0.9f
+ EnterExitState.Visible -> 1.0f
+ EnterExitState.PostExit -> 0.5f
+ }
+ }
+ Box(
+ modifier.fillMaxWidth().padding(5.dp).animateEnterExit(
+ enter = slideInVertically(initialOffsetY = { it }),
+ exit = ExitTransition.None
+ ).graphicsLayer {
+ scaleX = scale
+ scaleY = scale
+ }.clip(RoundedCornerShape(20.dp)).background(backgroundColor).fillMaxSize()
+ ) {}
+}
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
index e065cf5..2999fd4 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
@@ -18,12 +18,21 @@
import androidx.annotation.Sampled
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.EnterExitState
+import androidx.compose.animation.ExitTransition
import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.ExperimentalTransitionApi
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.expandIn
import androidx.compose.animation.expandVertically
@@ -38,32 +47,52 @@
import androidx.compose.animation.slideOut
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.flow.collect
@OptIn(ExperimentalAnimationApi::class)
@Sampled
@@ -163,6 +192,51 @@
@OptIn(ExperimentalAnimationApi::class)
@Sampled
@Composable
+fun AnimatedVisibilityWithBooleanVisibleParamNoReceiver() {
+ var visible by remember { mutableStateOf(true) }
+ Box(modifier = Modifier.clickable { visible = !visible }) {
+ AnimatedVisibility(
+ visible = visible,
+ modifier = Modifier.align(Alignment.Center),
+ enter = slideInVertically(
+ // Start the slide from 40 (pixels) above where the content is supposed to go, to
+ // produce a parallax effect
+ initialOffsetY = { -40 }
+ ) + expandVertically(
+ expandFrom = Alignment.Top
+ ) + fadeIn(initialAlpha = 0.3f),
+ exit = shrinkVertically() + fadeOut(animationSpec = tween(200))
+ ) { // Content that needs to appear/disappear goes here:
+ // Here we can optionally define a custom enter/exit animation by creating an animation
+ // using the Transition<EnterExitState> object from AnimatedVisibilityScope:
+ val scale by transition.animateFloat {
+ when (it) {
+ // Scale will be animating from 0.8f to 1.0f during enter transition.
+ EnterExitState.PreEnter -> 0.8f
+ EnterExitState.Visible -> 1f
+ // Scale will be animating from 1.0f to 1.2f during exit animation. If the
+ // targetValue specified for PreEnter is the same as PostExit, the enter and
+ // exit animation for this property will be symmetric.
+ EnterExitState.PostExit -> 1.2f
+ }
+ }
+ Text(
+ "Content to appear/disappear",
+ Modifier.fillMaxWidth().requiredHeight(100.dp).background(Color(0xffa1feff))
+ .graphicsLayer {
+ // Apply our custom enter/exit transition
+ scaleX = scale
+ scaleY = scale
+ }.padding(top = 20.dp),
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Sampled
+@Composable
fun ColumnScope.AnimatedFloatingActionButton() {
var expanded by remember { mutableStateOf(true) }
FloatingActionButton(
@@ -290,3 +364,340 @@
}
}
}
+
+@Sampled
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+@Composable
+fun AVScopeAnimateEnterExit() {
+ @OptIn(ExperimentalAnimationApi::class)
+ @Composable
+ fun AnimatedVisibilityScope.Item(
+ modifier: Modifier,
+ backgroundColor: Color
+ ) {
+ // Creates a custom enter/exit animation for scale property.
+ val scale by transition.animateFloat { enterExitState ->
+ // Enter transition will be animating the scale from 0.9f to 1.0f
+ // (i.e. PreEnter -> Visible). Exit transition will be from 1.0f to
+ // 0.5f (i.e. Visible -> PostExit)
+ when (enterExitState) {
+ EnterExitState.PreEnter -> 0.9f
+ EnterExitState.Visible -> 1.0f
+ EnterExitState.PostExit -> 0.5f
+ }
+ }
+
+ // Since we defined `Item` as an extension function on AnimatedVisibilityScope, we can use
+ // the `animateEnterExit` modifier to produce an enter/exit animation for it. This will
+ // run simultaneously with the `AnimatedVisibility`'s enter/exit.
+ Box(
+ modifier.fillMaxWidth().padding(5.dp).animateEnterExit(
+ // Slide in from below,
+ enter = slideInVertically(initialOffsetY = { it }),
+ // No slide on the way out. So the exit animation will be scale (from the custom
+ // scale animation defined above) and fade (from AnimatedVisibility)
+ exit = ExitTransition.None
+ ).graphicsLayer {
+ scaleX = scale
+ scaleY = scale
+ }.clip(RoundedCornerShape(20.dp)).background(backgroundColor).fillMaxSize()
+ ) {
+ // Content of the item goes here...
+ }
+ }
+
+ @OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+ @Composable
+ fun AnimateMainContent(mainContentVisible: MutableTransitionState<Boolean>) {
+ Box {
+ // Use the `MutableTransitionState<Boolean>` to specify whether AnimatedVisibility
+ // should be visible. This will also allow AnimatedVisibility animation states to be
+ // observed externally.
+ AnimatedVisibility(
+ visibleState = mainContentVisible,
+ modifier = Modifier.fillMaxSize(),
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ Box {
+ Column(Modifier.fillMaxSize()) {
+ // We have created `Item`s below as extension functions on
+ // AnimatedVisibilityScope in this example. So they can define their own
+ // enter/exit to run alongside the enter/exit defined in AnimatedVisibility.
+ Item(Modifier.weight(1f), backgroundColor = Color(0xffff6f69))
+ Item(Modifier.weight(1f), backgroundColor = Color(0xffffcc5c))
+ }
+ // This FAB will be simply fading in/out as specified by the AnimatedVisibility
+ FloatingActionButton(
+ onClick = {},
+ modifier = Modifier.align(Alignment.BottomEnd).padding(20.dp),
+ backgroundColor = MaterialTheme.colors.primary
+ ) { Icon(Icons.Default.Favorite, contentDescription = null) }
+ }
+ }
+
+ // Here we can get a signal for when the Enter/Exit animation of the content above
+ // has finished by inspecting the MutableTransitionState passed to the
+ // AnimatedVisibility. This allows sequential animation after the enter/exit.
+ AnimatedVisibility(
+ // Once the main content is visible (i.e. targetState == true), and no pending
+ // animations. We will start another enter animation sequentially.
+ visible = mainContentVisible.targetState && mainContentVisible.isIdle,
+ modifier = Modifier.align(Alignment.Center),
+ enter = expandVertically(),
+ exit = fadeOut(animationSpec = tween(50))
+ ) {
+ Text("Transition Finished")
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+@Sampled
+fun AddAnimatedVisibilityToGenericTransitionSample() {
+ @Composable
+ fun ItemMainContent() {
+ Row(Modifier.height(100.dp).fillMaxWidth(), Arrangement.SpaceEvenly) {
+ Box(
+ Modifier.size(60.dp).align(Alignment.CenterVertically)
+ .background(Color(0xffcdb7f6), CircleShape)
+ )
+ Column(Modifier.align(Alignment.CenterVertically)) {
+ Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray))
+ Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray))
+ }
+ }
+ }
+
+ @OptIn(ExperimentalAnimationApi::class)
+ @Composable
+ fun SelectableItem() {
+ // This sample animates a number of properties, including AnimatedVisibility, as a part of
+ // the Transition going between selected and unselected.
+ Box(Modifier.padding(15.dp)) {
+ var selected by remember { mutableStateOf(false) }
+ // Creates a transition to animate visual changes when `selected` is changed.
+ val selectionTransition = updateTransition(selected)
+ // Animates the border color as a part of the transition
+ val borderColor by selectionTransition.animateColor { isSelected ->
+ if (isSelected) Color(0xff03a9f4) else Color.White
+ }
+ // Animates the background color when selected state changes
+ val contentBackground by selectionTransition.animateColor { isSelected ->
+ if (isSelected) Color(0xffdbf0fe) else Color.White
+ }
+ // Animates elevation as a part of the transition
+ val elevation by selectionTransition.animateDp { isSelected ->
+ if (isSelected) 10.dp else 2.dp
+ }
+ Surface(
+ shape = RoundedCornerShape(10.dp),
+ border = BorderStroke(2.dp, borderColor),
+ modifier = Modifier.clickable { selected = !selected },
+ color = contentBackground,
+ elevation = elevation,
+ ) {
+ Column(Modifier.fillMaxWidth()) {
+ ItemMainContent()
+ // Creates an AnimatedVisibility as a part of the transition, so that when
+ // selected it's visible. This will hoist all the animations that are internal
+ // to AnimatedVisibility (i.e. fade, slide, etc) to the transition. As a result,
+ // `selectionTransition` will not finish until all the animations in
+ // AnimatedVisibility as well as animations added directly to it have finished.
+ selectionTransition.AnimatedVisibility(
+ visible = { it },
+ enter = expandVertically(),
+ exit = shrinkVertically()
+ ) {
+ Box(Modifier.fillMaxWidth().padding(10.dp)) {
+ Text(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed" +
+ " eiusmod tempor incididunt labore et dolore magna aliqua. " +
+ "Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
+ "laboris nisi ut aliquip ex ea commodo consequat. Duis aute " +
+ "irure dolor."
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+@Sampled
+@Composable
+fun AnimatedVisibilityLazyColumnSample() {
+ val turquoiseColors = listOf(
+ Color(0xff07688C),
+ Color(0xff1986AF),
+ Color(0xff50B6CD),
+ Color(0xffBCF8FF),
+ Color(0xff8AEAE9),
+ Color(0xff46CECA)
+ )
+
+ // MyModel class handles the data change of the items that are displayed in LazyColumn.
+ class MyModel {
+ private val _items: MutableList<ColoredItem> = mutableStateListOf()
+ private var lastItemId = 0
+ val items: List<ColoredItem> = _items
+
+ // Each item has a MutableTransitionState field to track as well as to mutate the item's
+ // visibility. When the MutableTransitionState's targetState changes, corresponding
+ // transition will be fired. MutableTransitionState allows animation lifecycle to be
+ // observed through it's [currentState] and [isIdle]. See below for details.
+ inner class ColoredItem(val visible: MutableTransitionState<Boolean>, val itemId: Int) {
+ val color: Color
+ get() = turquoiseColors.let {
+ it[itemId % it.size]
+ }
+ }
+
+ fun addNewItem() {
+ lastItemId++
+ _items.add(
+ ColoredItem(
+ // Here the initial state of the MutableTransitionState is set to false, and
+ // target state is set to true. This will result in an enter transition for
+ // the newly added item.
+ MutableTransitionState(false).apply { targetState = true },
+ lastItemId
+ )
+ )
+ }
+
+ fun removeItem(item: ColoredItem) {
+ // By setting the targetState to false, this will effectively trigger an exit
+ // animation in AnimatedVisibility.
+ item.visible.targetState = false
+ }
+
+ @OptIn(ExperimentalTransitionApi::class)
+ fun pruneItems() {
+ // Inspect the animation status through MutableTransitionState. If isIdle == true,
+ // all animations have finished for the transition.
+ _items.removeAll(
+ items.filter {
+ // This checks that the animations have finished && the animations are exit
+ // transitions. In other words, the item has finished animating out.
+ it.visible.isIdle && !it.visible.targetState
+ }
+ )
+ }
+
+ fun removeAll() {
+ _items.forEach {
+ it.visible.targetState = false
+ }
+ }
+ }
+
+ @OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+ @Composable
+ fun AnimatedVisibilityInLazyColumn() {
+ Column {
+ val model = remember { MyModel() }
+ Row(Modifier.fillMaxWidth()) {
+ Button({ model.addNewItem() }, modifier = Modifier.padding(15.dp).weight(1f)) {
+ Text("Add")
+ }
+ }
+
+ // This sets up a flow to check whether any item has finished animating out. If yes,
+ // notify the model to prune the list.
+ LaunchedEffect(model) {
+ snapshotFlow {
+ model.items.firstOrNull { it.visible.isIdle && !it.visible.targetState }
+ }.collect {
+ if (it != null) {
+ model.pruneItems()
+ }
+ }
+ }
+ LazyColumn {
+ items(model.items, key = { it.itemId }) { item ->
+ AnimatedVisibility(
+ item.visible,
+ enter = expandVertically(),
+ exit = shrinkVertically()
+ ) {
+ Box(Modifier.fillMaxWidth().requiredHeight(90.dp).background(item.color)) {
+ Button(
+ { model.removeItem(item) },
+ modifier = Modifier.align(Alignment.CenterEnd).padding(15.dp)
+ ) {
+ Text("Remove")
+ }
+ }
+ }
+ }
+ }
+
+ Button(
+ { model.removeAll() },
+ modifier = Modifier.align(Alignment.End).padding(15.dp)
+ ) {
+ Text("Clear All")
+ }
+ }
+ }
+}
+
+@Sampled
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun AVColumnScopeWithMutableTransitionState() {
+ var visible by remember { mutableStateOf(true) }
+ val colors = remember { listOf(Color(0xff2a9d8f), Color(0xffe9c46a), Color(0xfff4a261)) }
+ Column {
+ repeat(3) {
+ AnimatedVisibility(
+ visibleState = remember {
+ // This sets up the initial state of the AnimatedVisibility to false to
+ // guarantee an initial enter transition. In contrast, initializing this as
+ // `MutableTransitionState(visible)` would result in no initial enter
+ // transition.
+ MutableTransitionState(initialState = false)
+ }.apply {
+ // This changes the target state of the visible state. If it's different than
+ // the initial state, an enter/exit transition will be triggered.
+ targetState = visible
+ },
+ ) { // Content that needs to appear/disappear goes here:
+ Box(Modifier.fillMaxWidth().height(100.dp).background(colors[it]))
+ }
+ }
+ }
+}
+
+@Sampled
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun AnimateEnterExitPartialContent() {
+ @OptIn(ExperimentalAnimationApi::class)
+ @Composable
+ fun FullScreenNotification(visible: Boolean) {
+ AnimatedVisibility(
+ visible = visible,
+ enter = fadeIn(), exit = fadeOut()
+ ) {
+ // Fade in/out the background and foreground
+ Box(Modifier.fillMaxSize().background(Color(0x88000000))) {
+ Box(
+ Modifier.align(Alignment.TopStart).animateEnterExit(
+ // Slide in/out the rounded rect
+ enter = slideInVertically(),
+ exit = slideOutVertically()
+ ).clip(RoundedCornerShape(10.dp)).requiredHeight(100.dp)
+ .fillMaxWidth().background(Color.White)
+ ) {
+ // Content of the notification goes here
+ }
+ }
+ }
+ }
+}
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
index 056774b..f879f76 100644
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
+++ b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
@@ -16,29 +16,34 @@
package androidx.compose.animation
+import androidx.compose.animation.EnterExitState.PostExit
+import androidx.compose.animation.EnterExitState.Visible
+import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.InternalAnimationApi
import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -49,14 +54,13 @@
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, InternalAnimationApi::class)
class AnimatedVisibilityTest {
@get:Rule
@@ -325,60 +329,181 @@
}
}
- @Ignore
- @OptIn(ExperimentalAnimationApi::class)
+ // Test different animations for fade in and fade out, in a complete run without interruptions
+ @OptIn(ExperimentalAnimationApi::class, InternalAnimationApi::class)
@Test
fun animateVisibilityFadeTest() {
var visible by mutableStateOf(false)
- val colors = mutableListOf<Int>()
+ val easing = FastOutLinearInEasing
+ val easingOut = FastOutSlowInEasing
+ var alpha by mutableStateOf(0f)
rule.setContent {
- Box(Modifier.size(size = 20.dp).background(Color.Black)) {
- AnimatedVisibility(
- visible,
- enter = fadeIn(animationSpec = tween(500)),
- exit = fadeOut(animationSpec = tween(500)),
- modifier = Modifier.testTag("AnimV")
- ) {
- Box(modifier = Modifier.size(size = 20.dp).background(Color.White))
+ AnimatedVisibility(
+ visible,
+ enter = fadeIn(animationSpec = tween(500, easing = easing)),
+ exit = fadeOut(animationSpec = tween(300, easing = easingOut)),
+ ) {
+ Box(modifier = Modifier.size(size = 20.dp).background(Color.White))
+ LaunchedEffect(visible) {
+ var exit = false
+ val enterExit = transition
+ while (true) {
+ withFrameNanos {
+ if (enterExit.targetState == Visible) {
+ alpha = enterExit.animations.firstOrNull {
+ it.label == "alpha"
+ }?.value as Float
+ val fraction =
+ (enterExit.playTimeNanos / 1_000_000) / 500f
+ if (enterExit.currentState != Visible) {
+ assertEquals(easing.transform(fraction), alpha, 0.01f)
+ } else {
+ // When currentState = targetState, the playTime will be reset
+ // to 0. So compare alpha against expected visible value.
+ assertEquals(1f, alpha)
+ exit = true
+ }
+ } else if (enterExit.targetState == PostExit) {
+ alpha = enterExit.animations.firstOrNull {
+ it.label == "alpha"
+ }?.value as Float
+ val fraction =
+ (enterExit.playTimeNanos / 1_000_000) / 300f
+ if (enterExit.currentState != PostExit) {
+ assertEquals(
+ 1f - easingOut.transform(fraction),
+ alpha,
+ 0.01f
+ )
+ } else {
+ // When currentState = targetState, the playTime will be reset
+ // to 0. So compare alpha against expected invisible value.
+ assertEquals(0f, alpha)
+ exit = true
+ }
+ } else {
+ exit = enterExit.currentState == enterExit.targetState
+ }
+ }
+ if (exit) break
+ }
}
}
}
rule.runOnIdle {
visible = true
}
- rule.mainClock.autoAdvance = false
- while (colors.isEmpty() || colors.last() != 0xffffffff.toInt()) {
- rule.mainClock.advanceTimeByFrame()
- rule.onNodeWithTag("AnimV").apply {
- val data = IntArray(1)
- data[0] = 0
- captureToImage().readPixels(data, 10, 10, 1, 1)
- colors.add(data[0])
- }
- }
- for (i in 1 until colors.size) {
- // Check every color against the previous one to ensure the alpha is non-decreasing
- // during fade in.
- assertTrue(colors[i] >= colors[i - 1])
- }
- assertTrue(colors[0] < 0xfffffffff)
- colors.clear()
rule.runOnIdle {
+ // At this point fade in has finished, expect alpha = 1
+ assertEquals(1f, alpha)
visible = false
}
- while (colors.isEmpty() || colors.last() != 0xff000000.toInt()) {
- rule.mainClock.advanceTimeByFrame()
- rule.onNodeWithTag("AnimV").apply {
- val data = IntArray(1)
- data[0] = 0
- captureToImage().readPixels(data, 10, 10, 1, 1)
- colors.add(data[0])
+ rule.runOnIdle {
+ // At this point fade out has finished, expect alpha = 0
+ assertEquals(0f, alpha)
+ }
+ }
+
+ @OptIn(ExperimentalAnimationApi::class)
+ @Test
+ fun testEnterTransitionNoneAndExitTransitionNone() {
+ val testModifier by mutableStateOf(TestModifier())
+ val visible = MutableTransitionState(false)
+ var disposed by mutableStateOf(false)
+ rule.mainClock.autoAdvance = false
+ rule.setContent {
+ CompositionLocalProvider(LocalDensity provides Density(1f)) {
+ AnimatedVisibility(
+ visible, testModifier,
+ enter = EnterTransition.None,
+ exit = ExitTransition.None
+ ) {
+ Box(Modifier.requiredSize(100.dp, 100.dp)) {
+ DisposableEffect(Unit) {
+ onDispose {
+ disposed = true
+ }
+ }
+ }
+ }
}
}
- for (i in 1 until colors.size) {
- // Check every color against the previous one to ensure the alpha is non-increasing
- // during fade out.
- assertTrue(colors[i] <= colors[i - 1])
+
+ rule.runOnIdle {
+ assertEquals(0, testModifier.width)
+ assertEquals(0, testModifier.height)
+ visible.targetState = true
+ }
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+ rule.runOnIdle {
+ assertEquals(100, testModifier.width)
+ assertEquals(100, testModifier.height)
+ assertFalse(disposed)
+ visible.targetState = false
+ }
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+ rule.runOnIdle {
+ assertTrue(disposed)
+ }
+ }
+
+ private enum class TestState { State1, State2, State3 }
+
+ @OptIn(ExperimentalAnimationApi::class)
+ @Test
+ fun testTransitionExtensionAnimatedVisibility() {
+ val testModifier by mutableStateOf(TestModifier())
+ val testState = mutableStateOf(TestState.State1)
+ var currentState = TestState.State1
+ var disposed by mutableStateOf(false)
+ rule.mainClock.autoAdvance = false
+ rule.setContent {
+ CompositionLocalProvider(LocalDensity provides Density(1f)) {
+ val transition = updateTransition(testState.value)
+ currentState = transition.currentState
+ transition.AnimatedVisibility(
+ // Only visible in State2
+ visible = { it == TestState.State2 },
+ modifier = testModifier,
+ enter = expandIn(animationSpec = tween(100)),
+ exit = shrinkOut(animationSpec = tween(100))
+ ) {
+ Box(Modifier.requiredSize(100.dp, 100.dp)) {
+ DisposableEffect(Unit) {
+ onDispose {
+ disposed = true
+ }
+ }
+ }
+ }
+ }
+ }
+ rule.runOnIdle {
+ assertEquals(0, testModifier.width)
+ assertEquals(0, testModifier.height)
+ testState.value = TestState.State2
+ }
+ while (currentState != TestState.State2) {
+ assertTrue(testModifier.width < 100)
+ rule.mainClock.advanceTimeByFrame()
+ }
+ rule.runOnIdle {
+ assertEquals(100, testModifier.width)
+ assertEquals(100, testModifier.height)
+ testState.value = TestState.State3
+ }
+ while (currentState != TestState.State3) {
+ assertTrue(testModifier.width > 0)
+ assertFalse(disposed)
+ rule.mainClock.advanceTimeByFrame()
+ }
+ rule.mainClock.advanceTimeByFrame()
+ rule.runOnIdle {
+ assertEquals(0, testModifier.width)
+ assertEquals(0, testModifier.height)
+ assertTrue(disposed)
}
}
}
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
index 252b4b4..1a8e49a 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
@@ -16,63 +16,87 @@
package androidx.compose.animation
+import androidx.compose.animation.EnterExitState.PostExit
+import androidx.compose.animation.EnterExitState.PreEnter
+import androidx.compose.animation.EnterExitState.Visible
+import androidx.compose.animation.core.ExperimentalTransitionApi
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.core.Transition
+import androidx.compose.animation.core.createChildTransition
+import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMaxBy
+import kotlinx.coroutines.flow.collect
/**
* [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
* [visible] value changes. Different [EnterTransition]s and [ExitTransition]s can be defined in
* [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
- * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink, and Slide. The enter transitions
* and exit transitions can be combined using `+`. The order of the combination does not matter,
* as the transition animations will start simultaneously. See [EnterTransition] and
- * [ExitTransition] for details on the three types of transition. Here's an example of combining
- * all three types of transitions together:
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See the second sample code snippet below for example.
+ * These custom animations will be running alongside of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish before
+ * it considers itself idle. [content] will only be removed after all the (built-in and custom)
+ * exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. If there's a need to observe the state change of the enter/exit
+ * transition and follow up additional action (e.g. remove data, sequential animation, etc),
+ * consider the AnimatedVisibility API variant that takes a [MutableTransitionState] parameter.
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards the
+ * bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking will
+ * likely also animate the parent and siblings if they rely on the size of appearing/disappearing
+ * content. When the [AnimatedVisibility] composable is put in a [Row] or a [Column], the default
+ * enter and exit transitions are tailored to that particular container. See
+ * [RowScope.AnimatedVisibility] and [ColumnScope.AnimatedVisibility] for details.
+ *
+ * Here are two examples of [AnimatedVisibility]: one using the built-in enter/exit transition, the
+ * other using a custom enter/exit animation.
*
* @sample androidx.compose.animation.samples.FullyLoadedTransition
*
- * This composable function creates a custom [Layout] for its content. The size of the custom
- * layout is determined by the largest width and largest height of the children. All children
- * will be arranged in a box (aligned to the top start of the [Layout]).
+ * The example blow shows how a custom enter/exit animation can be created using the Transition
+ * object (i.e. Transition<EnterExitState>) from [AnimatedVisibilityScope].
*
- * __Note__: Once the exit transition is finished, the [content] composable will be skipped (i.e.
- * the content will be removed from the tree, and disposed).
- *
- * By default, the enter transition will be a combination of fading in and expanding the content in
- * from the bottom end. And the exit transition will be shrinking the content towards the bottom
- * end while fading out. The expanding and shrinking will likely also animate the parent and
- * siblings if they rely on the size of appearing/disappearing content. When the
- * [AnimatedVisibility] composable is put in a [Row] or a [Column], the default enter and exit
- * transitions are tailored to that particular container. See [RowScope.AnimatedVisibility] and
- * [ColumnScope.AnimatedVisibility] for details.
- *
- * [initiallyVisible] defaults to the same value as [visible]. This means when the
- * [AnimatedVisibility] is first added to the tree, there is no appearing animation. If it is
- * desired to show an appearing animation for the first appearance of the content,
- * [initiallyVisible] can be set to false and [visible] to true.
+ * @sample androidx.compose.animation.samples.AnimatedVisibilityWithBooleanVisibleParamNoReceiver
*
* @param visible defines whether the content should be visible
* @param modifier modifier for the [Layout] created to contain the [content]
- * @param enter [EnterTransition]s used for the appearing animation, fading in while expanding by
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding by
* default
- * @param exit [ExitTransition](s) used for the disappearing animation, fading out while
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
* shrinking by default
- * @param initiallyVisible controls whether the first appearance should be animated, defaulting
- * to match [visible] (i.e. not animating the first appearance)
+ * @param content Content to appear or disappear based on the value of [visible]
*
* @see EnterTransition
* @see ExitTransition
@@ -80,8 +104,7 @@
* @see expandIn
* @see fadeOut
* @see shrinkOut
- * @see RowScope.AnimatedVisibility
- * @see ColumnScope.AnimatedVisibility
+ * @see AnimatedVisibilityScope
*/
@ExperimentalAnimationApi
@Composable
@@ -90,51 +113,63 @@
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandIn(),
exit: ExitTransition = shrinkOut() + fadeOut(),
- initiallyVisible: Boolean = visible,
- content: @Composable () -> Unit
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
) {
- AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
+ val transition = updateTransition(visible)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}
/**
- * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
- * [visible] value changes. Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [RowScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content when the [AnimatedVisibility] is in a [Row]. The default animations are tailored
+ * specific to the [Row] layout. See more details below.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
* [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
* [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
* and exit transitions can be combined using `+`. The order of the combination does not matter,
* as the transition animations will start simultaneously. See [EnterTransition] and
- * [ExitTransition] for details on the three types of transition. Here's an example of using
- * [RowScope.AnimatedVisibility] in a [Row]:
+ * [ExitTransition] for details on the three types of transition.
*
- * @sample androidx.compose.animation.samples.AnimatedFloatingActionButton
- *
- * This composable function creates a custom [Layout] for its content. The size of the custom
- * layout is determined by the largest width and largest height of the children. All children
- * will be arranged in a box (aligned to the top start of the [Layout]).
- *
- * __Note__: Once the exit transition is finished, the [content] composable will be skipped (i.e.
- * the content will be removed from the tree, and disposed).
- *
- * By default, the enter transition will be a combination of fading in and expanding the content
- * horizontally. The end of the content will be the leading edge as the content expands to its
- * full width. And the exit transition will be shrinking the content with the end of the
+ * The default [enter] and [exit] transition is configured based on the horizontal layout of a
+ * [Row]. [enter] defaults to a combination of fading in and expanding the content horizontally.
+ * (The end of the content will be the leading edge as the content expands to its
+ * full width.) And [exit] defaults to shrinking the content horizontally with the end of the
* content being the leading edge while fading out. The expanding and shrinking will likely also
* animate the parent and siblings in the row since they rely on the size of appearing/disappearing
* content.
*
- * [initiallyVisible] defaults to the same value as [visible]. This means when the
- * [AnimatedVisibility] is first added to the tree, there is no appearing animation. If it is
- * desired to show an appearing animation for the first appearance of the content,
- * [initiallyVisible] can be set to false and [visible] to true.
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. If there's a need to observe the state change of the enter/exit
+ * transition and follow up additional action (e.g. remove data, sequential animation, etc),
+ * consider the AnimatedVisibility API variant that takes a [MutableTransitionState] parameter.
+ *
+ * Here's an example of using [RowScope.AnimatedVisibility] in a [Row]:
+ * @sample androidx.compose.animation.samples.AnimatedFloatingActionButton
*
* @param visible defines whether the content should be visible
* @param modifier modifier for the [Layout] created to contain the [content]
- * @param enter [EnterTransition]s used for the appearing animation, fading in while expanding
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
* horizontally by default
- * @param exit [ExitTransition](s) used for the disappearing animation, fading out while
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
* shrinking horizontally by default
- * @param initiallyVisible controls whether the first appearance should be animated, defaulting
- * to match [visible] (i.e. not animating the first appearance)
+ * @param content Content to appear or disappear based on the value of [visible]
*
* @see EnterTransition
* @see ExitTransition
@@ -144,6 +179,7 @@
* @see shrinkOut
* @see AnimatedVisibility
* @see ColumnScope.AnimatedVisibility
+ * @see AnimatedVisibilityScope
*/
@ExperimentalAnimationApi
@Composable
@@ -152,51 +188,62 @@
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandHorizontally(),
exit: ExitTransition = fadeOut() + shrinkHorizontally(),
- initiallyVisible: Boolean = visible,
- content: @Composable () -> Unit
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
) {
- AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
+ val transition = updateTransition(visible)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}
/**
- * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
- * [visible] value changes. Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [ColumnScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content when the [AnimatedVisibility] is in a [Column]. The default animations are tailored
+ * specific to the [Column] layout. See more details below.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
* [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
* [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
* and exit transitions can be combined using `+`. The order of the combination does not matter,
* as the transition animations will start simultaneously. See [EnterTransition] and
- * [ExitTransition] for details on the three types of transition. Here's an example of using
- * [ColumnScope.AnimatedVisibility] in a [Column]:
+ * [ExitTransition] for details on the three types of transition.
*
- * @sample androidx.compose.animation.samples.ColumnAnimatedVisibilitySample
+ * The default [enter] and [exit] transition is configured based on the vertical layout of a
+ * [Column]. [enter] defaults to a combination of fading in and expanding the content vertically.
+ * (The bottom of the content will be the leading edge as the content expands to its full height.)
+ * And the [exit] defaults to shrinking the content vertically with the bottom of the content being
+ * the leading edge while fading out. The expanding and shrinking will likely also animate the
+ * parent and siblings in the column since they rely on the size of appearing/disappearing content.
*
- * This composable function creates a custom [Layout] for its content. The size of the custom
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
* layout is determined by the largest width and largest height of the children. All children
- * will be arranged in a box (aligned to the top start of the [Layout]).
+ * will be aligned to the top start of the [Layout].
*
- * __Note__: Once the exit transition is finished, the [content] composable will be skipped (i.e.
- * the content will be removed from the tree, and disposed).
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. If there's a need to observe the state change of the enter/exit
+ * transition and follow up additional action (e.g. remove data, sequential animation, etc),
+ * consider the AnimatedVisibility API variant that takes a [MutableTransitionState] parameter.
*
- * By default, the enter transition will be a combination of fading in and expanding the content
- * vertically in the [Column]. The bottom of the content will be the leading edge as the content
- * expands to its full height. And the exit transition will be shrinking the content with the
- * bottom of the content being the leading edge while fading out. The expanding and shrinking will
- * likely also animate the parent and siblings in the column since they rely on the size of
- * appearing/disappearing content.
- *
- * [initiallyVisible] defaults to the same value as [visible]. This means when the
- * [AnimatedVisibility] is first added to the tree, there is no appearing animation. If it is
- * desired to show an appearing animation for the first appearance of the content,
- * [initiallyVisible] can be set to false and [visible] to true.
+ * Here's an example of using [ColumnScope.AnimatedVisibility] in a [Column]:
+ * @sample androidx.compose.animation.samples.ColumnAnimatedVisibilitySample
*
* @param visible defines whether the content should be visible
* @param modifier modifier for the [Layout] created to contain the [content]
- * @param enter [EnterTransition]s used for the appearing animation, fading in while expanding
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
* vertically by default
- * @param exit [ExitTransition](s) used for the disappearing animation, fading out while
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
* shrinking vertically by default
- * @param initiallyVisible controls whether the first appearance should be animated, defaulting
- * to match [visible] (i.e. not animating the first appearance)
+ * @param content Content to appear or disappear based on the value of [visible]
*
* @see EnterTransition
* @see ExitTransition
@@ -205,7 +252,7 @@
* @see fadeOut
* @see shrinkOut
* @see AnimatedVisibility
- * @see ColumnScope.AnimatedVisibility
+ * @see AnimatedVisibilityScope
*/
@ExperimentalAnimationApi
@Composable
@@ -214,97 +261,529 @@
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandVertically(),
exit: ExitTransition = fadeOut() + shrinkVertically(),
- initiallyVisible: Boolean = visible,
- content: @Composable () -> Unit
+ content: @Composable AnimatedVisibilityScope.() -> Unit
) {
- AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
+ val transition = updateTransition(visible)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * [EnterExitState] contains the three states that are involved in the enter and exit transition
+ * of [AnimatedVisibility]. More specifically, [PreEnter] and [Visible] defines the initial and
+ * target state of an *enter* transition, whereas [Visible] and [PostExit] are the initial and
+ * target state of an *exit* transition.
+ *
+ * See blow for an example of custom enter/exit animation in [AnimatedVisibility] using
+ * `Transition<EnterExitState>` (i.e. [AnimatedVisibilityScope.transition]):
+ *
+ * @sample androidx.compose.animation.samples.AnimatedVisibilityWithBooleanVisibleParamNoReceiver
+ * @see AnimatedVisibility
+ */
+@ExperimentalAnimationApi
+enum class EnterExitState {
+ /**
+ * The initial state of a custom enter animation in [AnimatedVisibility]..
+ */
+ PreEnter,
+
+ /**
+ * The `Visible` state is the target state of a custom *enter* animation, also the initial
+ * state of a custom *exit* animation in [AnimatedVisibility].
+ */
+ Visible,
+
+ /**
+ * Target state of a custom *exit* animation in [AnimatedVisibility].
+ */
+ PostExit
+}
+
+/**
+ * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
+ * [visibleState]'s [targetState][MutableTransitionState.targetState] changes. The [visibleState]
+ * can also be used to observe the state of [AnimatedVisibility]. For example:
+ * `visibleState.idIdle` indicates whether the all animations have finished in [AnimatedVisibility],
+ * and `visibleState.currentState` returns the initial state of the current animations.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. Both `currentState` and `targetState` will be `false` for
+ * [visibleState].
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards the
+ * bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking will
+ * likely also animate the parent and siblings if they rely on the size of appearing/disappearing
+ * content. When the [AnimatedVisibility] composable is put in a [Row] or a [Column], the default
+ * enter and exit transitions are tailored to that particular container. See
+ * [RowScope.AnimatedVisibility] and [ColumnScope.AnimatedVisibility] for details.
+ *
+ * @sample androidx.compose.animation.samples.AnimatedVisibilityLazyColumnSample
+ *
+ * @param visibleState defines whether the content should be visible
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ * vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ * shrinking vertically by default
+ * @param content Content to appear or disappear based on the value of [visibleState]
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibility
+ * @see Transition.AnimatedVisibility
+ * @see AnimatedVisibilityScope
+ */
+@ExperimentalAnimationApi
+@Composable
+fun AnimatedVisibility(
+ visibleState: MutableTransitionState<Boolean>,
+ modifier: Modifier = Modifier,
+ enter: EnterTransition,
+ exit: ExitTransition,
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ val transition = updateTransition(visibleState)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * [RowScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content as [visibleState]'s [targetState][MutableTransitionState.targetState] changes. The
+ * default [enter] and [exit] transitions are tailored specific to the [Row] layout. See more
+ * details below. The [visibleState] can also be used to observe the state of [AnimatedVisibility].
+ * For example: `visibleState.idIdle` indicates whether the all animations have finished in
+ * [AnimatedVisibility], and `visibleState.currentState` returns the initial state of the current
+ * animations.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * The default [enter] and [exit] transition is configured based on the horizontal layout of a
+ * [Row]. [enter] defaults to a combination of fading in and expanding the content horizontally.
+ * (The end of the content will be the leading edge as the content expands to its
+ * full width.) And [exit] defaults to shrinking the content horizontally with the end of the
+ * content being the leading edge while fading out. The expanding and shrinking will likely also
+ * animate the parent and siblings in the row since they rely on the size of appearing/disappearing
+ * content.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. Both `currentState` and `targetState` will be `false` for
+ * [visibleState].
+ *
+ * @param visibleState defines whether the content should be visible
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ * vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ * shrinking vertically by default
+ * @param content Content to appear or disappear based on the value of [visibleState]
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibility
+ * @see Transition.AnimatedVisibility
+ * @see AnimatedVisibilityScope
+ */
+@ExperimentalAnimationApi
+@Composable
+fun RowScope.AnimatedVisibility(
+ visibleState: MutableTransitionState<Boolean>,
+ modifier: Modifier = Modifier,
+ enter: EnterTransition = expandHorizontally() + fadeIn(),
+ exit: ExitTransition = shrinkHorizontally() + fadeOut(),
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ val transition = updateTransition(visibleState)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * [ColumnScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content as [visibleState]'s [targetState][MutableTransitionState.targetState] changes. The
+ * default [enter] and [exit] transitions are tailored specific to the [Column] layout. See more
+ * details below. The [visibleState] can also be used to observe the state of [AnimatedVisibility].
+ * For example: `visibleState.idIdle` indicates whether the all animations have finished in
+ * [AnimatedVisibility], and `visibleState.currentState` returns the initial state of the current
+ * animations.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * The default [enter] and [exit] transition is configured based on the vertical layout of a
+ * [Column]. [enter] defaults to a combination of fading in and expanding the content vertically.
+ * (The bottom of the content will be the leading edge as the content expands to its full height.)
+ * And the [exit] defaults to shrinking the content vertically with the bottom of the content being
+ * the leading edge while fading out. The expanding and shrinking will likely also animate the
+ * parent and siblings in the column since they rely on the size of appearing/disappearing content.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. Both `currentState` and `targetState` will be `false` for
+ * [visibleState].
+ *
+ * @sample androidx.compose.animation.samples.AVColumnScopeWithMutableTransitionState
+ *
+ * @param visibleState defines whether the content should be visible
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ * vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ * shrinking vertically by default
+ * @param content Content to appear or disappear based on of [visibleState]
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibility
+ * @see Transition.AnimatedVisibility
+ * @see AnimatedVisibilityScope
+ */
+@ExperimentalAnimationApi
+@Composable
+fun ColumnScope.AnimatedVisibility(
+ visibleState: MutableTransitionState<Boolean>,
+ modifier: Modifier = Modifier,
+ enter: EnterTransition = expandVertically() + fadeIn(),
+ exit: ExitTransition = shrinkVertically() + fadeOut(),
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ val transition = updateTransition(visibleState)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * This extension function creates an [AnimatedVisibility] composable as a child Transition of
+ * the given Transition. This means: 1) the enter/exit transition is now triggered by the provided
+ * [Transition]'s [targetState][Transition.targetState] change. When the targetState changes, the
+ * visibility will be derived using the [visible] lambda and [Transition.targetState]. 2)
+ * The enter/exit transitions, as well as any custom enter/exit animations defined in
+ * [AnimatedVisibility] are now hoisted to the parent Transition. The parent Transition will wait
+ * for all of them to finish before it considers itself finished (i.e. [Transition.currentState]
+ * = [Transition.targetState]), and subsequently removes the content in the exit case.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed.
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards the
+ * bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking will
+ * likely also animate the parent and siblings if they rely on the size of appearing/disappearing
+ * content.
+ *
+ * @sample androidx.compose.animation.samples.AddAnimatedVisibilityToGenericTransitionSample
+ *
+ * @param visible defines whether the content should be visible based on transition state T
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ * vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ * shrinking vertically by default
+ * @param content Content to appear or disappear based on the visibility derived from the
+ * [Transition.targetState] and the provided [visible] lambda
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibilityScope
+ * @see Transition.AnimatedVisibility
+ */
+@ExperimentalAnimationApi
+@Composable
+fun <T> Transition<T>.AnimatedVisibility(
+ visible: (T) -> Boolean,
+ modifier: Modifier = Modifier,
+ enter: EnterTransition = fadeIn() + expandIn(),
+ exit: ExitTransition = shrinkOut() + fadeOut(),
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) = AnimatedEnterExitImpl(this, visible, modifier, enter, exit, content)
+
+/**
+ * This is the scope for the content of [AnimatedVisibility]. In this scope, direct and
+ * indirect children of [AnimatedVisibility] will be able to define their own enter/exit
+ * transitions using the built-in options via [Modifier.animateEnterExit]. They will also be able
+ * define custom enter/exit animations using the [transition] object. [AnimatedVisibility] will
+ * ensure both custom and built-in enter/exit animations finish before it considers itself idle,
+ * and subsequently removes its content in the case of exit.
+ *
+ * __Note:__ Custom enter/exit animations that are created *independent* of the
+ * [AnimatedVisibilityScope.transition] will have no guarantee to finish when
+ * exiting, as [AnimatedVisibility] would have no visibility of such animations.
+ *
+ * @sample androidx.compose.animation.samples.AVScopeAnimateEnterExit
+ */
+@ExperimentalAnimationApi
+class AnimatedVisibilityScope internal constructor(transition: Transition<EnterExitState>) {
+ /**
+ * [transition] allows custom enter/exit animations to be specified. It will run simultaneously
+ * with the built-in enter/exit transitions specified in [AnimatedVisibility].
+ */
+ var transition: Transition<EnterExitState> = transition
+ internal set
+
+ /**
+ * [animateEnterExit] modifier can be used for any direct or indirect children of
+ * [AnimatedVisibility] to create a different enter/exit animation than what's specified in
+ * [AnimatedVisibility]. The visual effect of these children will be a combination of the
+ * [AnimatedVisibility]'s animation and their own enter/exit animations.
+ *
+ * [enter] and [exit] defines different [EnterTransition]s and [ExitTransition]s that will be
+ * used for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink, and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards
+ * the bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking
+ * will likely also animate the parent and siblings if they rely on the size of
+ * appearing/disappearing content.
+ *
+ * In some cases it may be desirable to have [AnimatedVisibility] apply no animation at all for
+ * enter and/or exit, such that children of [AnimatedVisibility] can each have their distinct
+ * animations. To achieve this, [EnterTransition.None] and/or [ExitTransition.None] can be
+ * used for [AnimatedVisibility].
+ *
+ * @sample androidx.compose.animation.samples.AnimateEnterExitPartialContent
+ */
+ fun Modifier.animateEnterExit(
+ enter: EnterTransition = fadeIn() + expandIn(),
+ exit: ExitTransition = fadeOut() + shrinkOut()
+ ): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "animateEnterExit"
+ properties["enter"] = enter
+ properties["exit"] = exit
+ }
+ ) {
+ this.then(transition.createModifier(enter, exit))
+ }
}
@ExperimentalAnimationApi
@Composable
-private fun AnimatedVisibilityImpl(
+@Deprecated(
+ "AnimatedVisibility no longer accepts initiallyVisible as a parameter",
+ replaceWith = ReplaceWith(
+ "AnimatedVisibility(" +
+ "transitionState = remember { MutableTransitionState(initiallyVisible) }\n" +
+ ".apply { targetState = visible },\n" +
+ "modifier = modifier,\n" +
+ "enter = enter,\n" +
+ "exit = exit) {\n" +
+ "content() \n" +
+ "}",
+ "androidx.compose.animation.core.MutableTransitionState"
+ )
+)
+fun AnimatedVisibility(
visible: Boolean,
- modifier: Modifier,
+ modifier: Modifier = Modifier,
enter: EnterTransition,
exit: ExitTransition,
initiallyVisible: Boolean,
content: @Composable () -> Unit
+) = AnimatedVisibility(
+ visibleState = remember { MutableTransitionState(initiallyVisible) }
+ .apply { targetState = visible },
+ modifier = modifier,
+ enter = enter,
+ exit = exit
) {
+ content()
+}
- // Set up initial transition states, based on the initial visibility.
- var transitionState by remember {
- mutableStateOf(if (initiallyVisible) AnimStates.Visible else AnimStates.Gone)
+// RowScope and ColumnScope AnimatedEnterExit extensions and AnimatedEnterExit without a receiver
+// converge here.
+@OptIn(ExperimentalTransitionApi::class)
+@ExperimentalAnimationApi
+@Composable
+private fun <T> AnimatedEnterExitImpl(
+ transition: Transition<T>,
+ visible: (T) -> Boolean,
+ modifier: Modifier,
+ enter: EnterTransition,
+ exit: ExitTransition,
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ val isAnimationVisible = remember(transition) {
+ mutableStateOf(visible(transition.currentState))
}
-
- var isAnimating by remember { mutableStateOf(false) }
-
- // Update transition states, based on the current visibility.
- if (visible) {
- if (transitionState == AnimStates.Gone ||
- transitionState == AnimStates.Exiting
- ) {
- transitionState = AnimStates.Entering
- isAnimating = true
+ if (visible(transition.targetState) || isAnimationVisible.value) {
+ val childTransition = transition.createChildTransition {
+ transition.targetEnterExit(visible, it)
}
- } else {
- if (transitionState == AnimStates.Visible ||
- transitionState == AnimStates.Entering
- ) {
- transitionState = AnimStates.Exiting
- isAnimating = true
+ LaunchedEffect(childTransition) {
+ snapshotFlow {
+ childTransition.currentState == EnterExitState.Visible ||
+ childTransition.targetState == EnterExitState.Visible
+ }.collect {
+ isAnimationVisible.value = it
+ }
}
- }
- val scope = rememberCoroutineScope()
- val animations = remember(scope, enter, exit) {
- // TODO: Should we delay changing enter/exit after on-going animations are finished?
- TransitionAnimations(enter, exit, scope) {
- isAnimating = false
- }
- }
- animations.updateState(transitionState)
-
- // If the exit animation has finished, skip the child composable altogether
- if (transitionState == AnimStates.Gone) {
- return
- }
-
- Layout(
- content = content,
- modifier = modifier.then(animations.modifier)
- ) { measureables, constraints ->
-
- val placeables = measureables.map { it.measure(constraints) }
- val maxWidth: Int = placeables.fastMaxBy { it.width }?.width ?: 0
- val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
-
- val offset: IntOffset
- val animatedSize: IntSize
- val animSize = animations.getAnimatedSize(
- IntSize(maxWidth, maxHeight)
+ AnimatedEnterExitImpl(
+ childTransition,
+ modifier,
+ enter = enter,
+ exit = exit,
+ content = content
)
- if (animSize != null) {
- offset = animSize.first
- animatedSize = animSize.second
+ }
+}
+
+@OptIn(ExperimentalTransitionApi::class)
+@ExperimentalAnimationApi
+@Composable
+private inline fun AnimatedEnterExitImpl(
+ transition: Transition<EnterExitState>,
+ modifier: Modifier,
+ enter: EnterTransition,
+ exit: ExitTransition,
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ // TODO: Get some feedback on whether there's a need to observe this state change in user
+ // code. If there is, this if check will need to be moved to measure stage, along with some
+ // structural changes.
+ if (transition.currentState == EnterExitState.Visible ||
+ transition.targetState == EnterExitState.Visible
+ ) {
+ val scope = remember(transition) { AnimatedVisibilityScope(transition) }
+ Layout(
+ content = { scope.content() },
+ modifier = modifier.then(transition.createModifier(enter, exit))
+ ) { measureables, constraints ->
+ val placeables = measureables.map { it.measure(constraints) }
+ val maxWidth: Int = placeables.fastMaxBy { it.width }?.width ?: 0
+ val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
+ // Position the children.
+ layout(maxWidth, maxHeight) {
+ placeables.fastForEach {
+ it.place(0, 0)
+ }
+ }
+ }
+ }
+}
+
+// This converts Boolean visible to EnterExitState
+@ExperimentalAnimationApi
+@Composable
+private fun <T> Transition<T>.targetEnterExit(
+ visible: (T) -> Boolean,
+ targetState: T
+): EnterExitState = key(this) {
+ val hasBeenVisible = remember { mutableStateOf(false) }
+ if (visible(currentState)) {
+ hasBeenVisible.value = true
+ }
+ if (visible(targetState)) {
+ EnterExitState.Visible
+ } else {
+ // If never been visible, visible = false means PreEnter, otherwise PostExit
+ if (hasBeenVisible.value) {
+ EnterExitState.PostExit
} else {
- offset = IntOffset.Zero
- animatedSize = IntSize(maxWidth, maxHeight)
- }
-
- // If animation has finished update state
- if (!isAnimating) {
- if (transitionState == AnimStates.Exiting) {
- transitionState = AnimStates.Gone
- } else if (transitionState == AnimStates.Entering) {
- transitionState = AnimStates.Visible
- }
- }
-
- // Position the children.
- layout(animatedSize.width, animatedSize.height) {
- placeables.fastForEach {
- it.place(offset.x, offset.y)
- }
+ EnterExitState.PreEnter
}
}
}
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
index e1f756b..b869a5e 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
@@ -14,16 +14,30 @@
* limitations under the License.
*/
+@file:OptIn(InternalAnimationApi::class, ExperimentalAnimationApi::class)
+
package androidx.compose.animation
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationEndReason
+import android.annotation.SuppressLint
import androidx.compose.animation.core.AnimationVector2D
import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.InternalAnimationApi
+import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.createDeferredAnimation
import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.VisibilityThreshold
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.key
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
@@ -37,10 +51,6 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.util.fastFirstOrNull
-import androidx.compose.ui.util.fastForEach
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
@RequiresOptIn(message = "This is an experimental animation API.")
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@@ -92,13 +102,24 @@
)
)
}
- // TODO: Support EnterTransition.None
override fun equals(other: Any?): Boolean {
return other is EnterTransition && other.data == data
}
override fun hashCode(): Int = data.hashCode()
+
+ companion object {
+ /**
+ * This can be used when no enter transition is desired. It can be useful in cases where
+ * there are other forms of enter animation defined indirectly for an
+ * [AnimatedVisibility]. e.g.The children of the [AnimatedVisibility] have all defined
+ * their own [EnterTransition], or when the parent is fading in, etc.
+ *
+ * @see [ExitTransition.None]
+ */
+ val None: EnterTransition = EnterTransitionImpl(TransitionData())
+ }
}
/**
@@ -150,12 +171,26 @@
)
}
- // TODO: Support ExitTransition.None
override fun equals(other: Any?): Boolean {
return other is ExitTransition && other.data == data
}
override fun hashCode(): Int = data.hashCode()
+
+ companion object {
+ /**
+ * This can be used when no built-in [ExitTransition] (i.e. fade/slide, etc) is desired for
+ * the [AnimatedVisibility], but rather the children are defining their own exit
+ * animation using the [Transition] scope.
+ *
+ * __Note:__ If [None] is used, and nothing is animating in the Transition<EnterExitState>
+ * scope that [AnimatedVisibility] provided, the content will be removed from
+ * [AnimatedVisibility] right away.
+ *
+ * @sample androidx.compose.animation.samples.AVScopeAnimateEnterExit
+ */
+ val None: ExitTransition = ExitTransitionImpl(TransitionData())
+ }
}
/**
@@ -556,7 +591,7 @@
*
* [targetOffsetX] is a lambda that takes the full width of the content and returns an
* offset. This allows the target offset to be defined proportional to the full size, or as an
- * absolute value. It defaults to return half of negaive width, which would slide the content to
+ * absolute value. It defaults to return half of negative width, which would slide the content to
* the left by half of its width.
*
* @sample androidx.compose.animation.samples.SlideTransition
@@ -615,7 +650,7 @@
@Immutable
internal data class ChangeSize(
val alignment: Alignment,
- val startSize: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) },
+ val size: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) },
val animationSpec: FiniteAnimationSpec<IntSize> = spring(),
val clip: Boolean = true
)
@@ -651,517 +686,285 @@
val changeSize: ChangeSize? = null
)
-/**
- * Alignment does NOT stay consistent in enter vs. exit animations. For example, the enter could be
- * expanding from top, but exit could be shrinking towards the bottom. As a result, when such an
- * animation is interrupted, it becomes very tricky to handle that. This is why there needs to be
- * two types of size animations: alignment based and rect based. When alignment stays the same,
- * size is the only value that needs to be animated. When alignment changes, however, the only
- * sensible solution is to fall back to rect based solution. Namely, this calculates the current
- * clip rect based on alignment and size, and the final rect based on the new alignment and the
- * ending size. In rect based animations, the size will still be animated using the provided size
- * animation, and the offset will be animated using physics as a part of the interruption
- * handling logic.
- */
-internal interface SizeAnimation {
- val anim: Animatable<IntSize, AnimationVector2D>
- var clip: Boolean
- val size: IntSize
- get() = anim.value
-
- val offset: (IntSize) -> IntOffset
- val isAnimating: Boolean
- val listener: (AnimationEndReason, Any) -> Unit
-
- /**
- * The instance returned may be different than the caller if the alignment has changed. Either
- * way the returned animation will be configured to animate to the new target.
- */
- fun animateTo(
- target: IntSize,
- alignment: Alignment,
- fullSize: IntSize,
- spec: FiniteAnimationSpec<IntSize>,
- scope: CoroutineScope,
- ): SizeAnimation
-
- /**
- * Returns what the offset will be once the target size is applied.
- */
- fun snapTo(target: IntSize, scope: CoroutineScope): IntOffset
-
- val alignment: Alignment
-}
-
-/**
- * This is the animation class used to animate content size. However, this animation may get
- * interrupted before it finishes. If the new size target is based on the same alignment, this
- * instance can be re-used to handle that interruption. A more complicated and hairy case is when
- * the alignment changes (from top aligned to bottom aligned), in which case we have to fall back
- * to Rect based animation to properly handle the alignment change.
- */
-private class AlignmentBasedSizeAnimation(
- override val anim: Animatable<IntSize, AnimationVector2D>,
- override val alignment: Alignment,
- override var clip: Boolean,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : SizeAnimation {
-
- override val offset: (IntSize) -> IntOffset
- get() = {
- alignment.align(it, anim.value, LayoutDirection.Ltr)
- }
-
- override fun animateTo(
- target: IntSize,
- alignment: Alignment,
- fullSize: IntSize,
- spec: FiniteAnimationSpec<IntSize>,
- scope: CoroutineScope,
- ): SizeAnimation {
- if (anim.targetValue != target) {
- scope.launch {
- anim.animateTo(target, spec)
- listener(AnimationEndReason.Finished, anim.value)
- }
- }
-
- if (alignment == this.alignment) {
- return this
- } else {
- // Alignment changed
- val offset = this.offset(fullSize)
- return RectBasedSizeAnimation(anim, offset, clip, scope, listener)
- }
- }
-
- override fun snapTo(target: IntSize, scope: CoroutineScope): IntOffset {
- scope.launch {
- anim.snapTo(target)
- }
- return alignment.align(target, target, LayoutDirection.Ltr)
- }
-
- override val isAnimating: Boolean
- get() = anim.isRunning
-}
-
-/**
- * This class animates the rect of the clip bounds, as a fallback for when enter and exit size
- * change animations have different alignment.
- */
-private class RectBasedSizeAnimation(
- override val anim: Animatable<IntSize, AnimationVector2D>,
- targetOffset: IntOffset,
- override var clip: Boolean,
- val scope: CoroutineScope,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : SizeAnimation {
- private val offsetAnim: Animatable<IntOffset, AnimationVector2D>
-
- init {
- offsetAnim = Animatable(
- IntOffset(0, 0), IntOffset.VectorConverter,
- IntOffset(1, 1)
- )
- scope.launch {
- offsetAnim.animateTo(targetOffset)
- listener(AnimationEndReason.Finished, offsetAnim.value)
- }
- }
-
- override val alignment: Alignment
- get() = Alignment.TopStart
-
- override val offset: (IntSize) -> IntOffset
- get() = {
- offsetAnim.value
- }
-
- override fun animateTo(
- target: IntSize,
- alignment: Alignment,
- fullSize: IntSize,
- spec: FiniteAnimationSpec<IntSize>,
- scope: CoroutineScope,
- ): SizeAnimation {
- val targetOffSet = alignment.align(fullSize, target, LayoutDirection.Ltr)
- if (offsetAnim.targetValue != targetOffSet) {
- scope.launch {
- offsetAnim.animateTo(targetOffSet)
- listener(AnimationEndReason.Finished, offsetAnim.value)
- }
- }
- if (target != anim.targetValue) {
- scope.launch {
- anim.animateTo(target, spec)
- listener(AnimationEndReason.Finished, anim.value)
- }
- }
- return this
- }
-
- override fun snapTo(target: IntSize, scope: CoroutineScope): IntOffset {
- val targetOffSet = alignment.align(target, target, LayoutDirection.Ltr)
- scope.launch {
- offsetAnim.snapTo(targetOffSet)
- anim.snapTo(target)
- }
- return targetOffSet
- }
-
- override val isAnimating: Boolean
- get() = (anim.isRunning || offsetAnim.isRunning)
-}
-
-private operator fun IntSize.minus(b: IntSize) =
- IntSize(width - b.width, height - b.height)
-
-internal interface TransitionAnimation {
- val isRunning: Boolean
- var state: AnimStates
- val modifier: Modifier
- fun getAnimatedSize(fullSize: IntSize): Pair<IntOffset, IntSize>? = null
- val listener: (AnimationEndReason, Any) -> Unit
-}
-
-/**
- * This class animates alpha through a graphics layer modifier.
- */
-private class FadeTransition(
- val enter: Fade? = null,
- val exit: Fade? = null,
- val scope: CoroutineScope,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : TransitionAnimation {
- override val isRunning: Boolean
- get() = alphaAnim.isRunning
- override val modifier: Modifier
- get() = Modifier.graphicsLayer {
- alpha = alphaAnim.value
- }
-
- override var state: AnimStates = AnimStates.Gone
- set(value) {
- if (value == field) {
- return
- }
- // Animation state has changed if we get here.
- if (value == AnimStates.Entering) {
- // Animation is interrupted from fade out, now fade in
- if (alphaAnim.isRunning) {
- enter?.apply {
- // If fade in animation specified, use that. Otherwise use default.
- animateTo(1f, animationSpec, listener)
- } ?: animateTo(1f, listener = listener)
- } else {
- // set up initial values for alphaAnimation
- enter?.apply {
- // If fade in is defined start from pre-defined `alphaFrom`. If no fade in is defined,
- // snap the alpha to 1f
- alphaAnim = Animatable(alpha, 0.02f)
- scope.launch {
- alphaAnim.animateTo(1f, animationSpec)
- listener(AnimationEndReason.Finished, alphaAnim.value)
- }
- // If no enter is defined and animation isn't running, snap to alpha = 1
- } ?: scope.launch {
- alphaAnim.snapTo(1f)
- }
- }
- } else if (value == AnimStates.Exiting) {
- if (alphaAnim.isRunning) {
- // interrupting alpha animation: directly animating to out value if defined,
- // otherwise let the fade-in finish
- exit?.apply {
- animateTo(alpha, animationSpec, listener)
- }
- } else {
- // set up alpha animation to fade out, if fade out is defined
- exit?.apply {
- animateTo(alpha, animationSpec, listener)
- }
- }
- }
- field = value
- }
-
- private fun animateTo(
- target: Float,
- animationSpec: FiniteAnimationSpec<Float> = spring(visibilityThreshold = 0.02f),
- listener: (AnimationEndReason, Any) -> Unit
- ) {
- scope.launch {
- alphaAnim.animateTo(target, animationSpec)
- listener(AnimationEndReason.Finished, alphaAnim.value)
- }
- }
-
- var alphaAnim = Animatable(1f, visibilityThreshold = 0.02f)
-}
-
-private class SlideTransition(
- val enter: Slide? = null,
- val exit: Slide? = null,
- val scope: CoroutineScope,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : TransitionAnimation {
- override val isRunning: Boolean
- get() {
- if (slideAnim?.isRunning == true) {
- return true
- }
- if (state != currentState) {
- if (state == AnimStates.Entering && enter != null) {
- return true
- } else if (state == AnimStates.Exiting && exit != null) {
- return true
- }
- }
- return false
- }
- override var state: AnimStates = AnimStates.Gone
- var currentState: AnimStates = AnimStates.Gone
- override val modifier: Modifier = Modifier.composed {
- SlideModifier()
- }
-
- inner class SlideModifier : LayoutModifier {
- override fun MeasureScope.measure(
- measurable: Measurable,
- constraints: Constraints
- ): MeasureResult {
- val placeable = measurable.measure(constraints)
-
- updateAnimation(IntSize(placeable.width, placeable.height))
- return layout(placeable.width, placeable.height) {
- placeable.place(slideAnim?.value ?: IntOffset.Zero)
- }
- }
- }
-
- fun updateAnimation(fullSize: IntSize) {
- if (state == currentState) {
- return
- }
- // state changed
- if (state == AnimStates.Entering) {
- // Animation is interrupted from slide out, now slide in
- enter?.apply {
- // If slide in animation specified, use that. Otherwise use default.
- val anim = if (slideAnim?.isRunning != true) {
- Animatable(
- slideOffset(fullSize), IntOffset.VectorConverter, IntOffset(1, 1)
- )
- } else {
- slideAnim
- }
- scope.launch {
- anim!!.animateTo(IntOffset.Zero, animationSpec)
- listener(AnimationEndReason.Finished, anim.value)
- }
- slideAnim = anim
- } ?: slideAnim?.also {
- scope.launch {
- it.animateTo(IntOffset.Zero)
- listener(AnimationEndReason.Finished, it.value)
- }
- }
- } else if (state == AnimStates.Exiting) {
- // interrupting alpha animation: directly animating to out value if defined,
- // otherwise let it finish
- exit?.apply {
- val anim = slideAnim
- ?: Animatable(
- IntOffset.Zero, IntOffset.VectorConverter, IntOffset(1, 1)
- )
- scope.launch {
- anim.animateTo(slideOffset(fullSize), animationSpec)
- listener(AnimationEndReason.Finished, anim.value)
- }
- slideAnim = anim
- }
- }
- currentState = state
- }
-
- var slideAnim: Animatable<IntOffset, AnimationVector2D>? = null
-}
-
-private class ChangeSizeTransition(
- val enter: ChangeSize? = null,
- val exit: ChangeSize? = null,
- val scope: CoroutineScope,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : TransitionAnimation {
-
- override val isRunning: Boolean
- get() {
- if (sizeAnim?.isAnimating == true) {
- return true
- }
-
- // If the state has changed, and corresponding animations are defined, then animation
- // will be running in this current frame in the layout stage.
- if (state != currentState) {
- if (state == AnimStates.Entering && enter != null) {
- return true
- } else if (state == AnimStates.Exiting && exit != null) {
- return true
- }
- }
- return false
- }
-
- // This is the pending state, which sets currentState in layout stage
- override var state: AnimStates = AnimStates.Gone
-
- // This tracks the current resolved state. State change happens in composition, but the
- // resolution happens during layout, since we won't know the size until then.
- var currentState: AnimStates = AnimStates.Gone
-
- override fun getAnimatedSize(fullSize: IntSize): Pair<IntOffset, IntSize> {
- sizeAnim?.apply {
- if (state == currentState) {
- // If no state change, return the current size animation value.
- if (state == AnimStates.Entering) {
- animateTo(fullSize, alignment, fullSize, spring(), scope)
- } else if (state == AnimStates.Visible) {
- return snapTo(fullSize, scope) to fullSize
- }
- return offset(fullSize) to size
- }
- }
-
- // If we get here, animate state has changed.
- if (state == AnimStates.Entering) {
- if (enter != null) {
- // if no on-going size animation, create a new one.
- val anim = sizeAnim?.run {
- // If the animation is not running and the alignment isn't the same, prefer
- // AlignmentBasedSizeAnimation over rect based animation.
- if (!isAnimating) {
- null
- } else {
- this
- }
- } ?: AlignmentBasedSizeAnimation(
- Animatable(
- enter.startSize.invoke(fullSize),
- IntSize.VectorConverter,
- visibilityThreshold = IntSize(1, 1)
- ),
- enter.alignment, enter.clip, listener
- )
- // Animate to full size
- sizeAnim = anim.animateTo(
- fullSize, enter.alignment, fullSize, enter.animationSpec, scope
- )
- } else {
- // If enter isn't defined for size change, re-target the current animation, if any
- sizeAnim?.apply {
- animateTo(fullSize, alignment, fullSize, spring(), scope)
- }
- }
- } else if (state == AnimStates.Exiting) {
- exit?.apply {
- // If a size change exit animation is defined, re-target on-going animation if
- // any, otherwise create a new one.
- val anim = sizeAnim?.run {
- // If the current size animation is idling, switch to AlignmentBasedAnimation if
- // needed.
- if (isRunning && alignment != exit.alignment) {
- null
- } else {
- this
- }
- } ?: AlignmentBasedSizeAnimation(
- Animatable(fullSize, IntSize.VectorConverter, IntSize(1, 1)),
- alignment, clip, listener
- )
-
- sizeAnim = anim.animateTo(
- startSize(fullSize), alignment, fullSize, animationSpec, scope
- )
- }
- // If exit isn't defined, but the enter animation is still on-going, let it finish
- }
- currentState = state
- return sizeAnim?.run { offset(fullSize) to size } ?: IntOffset.Zero to fullSize
- }
-
- override val modifier: Modifier
- get() {
- val clip: Boolean = sizeAnim?.clip
- ?: if (state == AnimStates.Entering) {
- enter?.clip
- } else {
- exit?.clip
- } ?: false
- return if (clip) Modifier.clipToBounds() else Modifier
- }
-
- var sizeAnim: SizeAnimation? = null
-}
-
-@OptIn(ExperimentalAnimationApi::class)
-internal class TransitionAnimations constructor(
+@SuppressLint("ModifierFactoryExtensionFunction", "ComposableModifierFactory")
+@Composable
+internal fun Transition<EnterExitState>.createModifier(
enter: EnterTransition,
- exit: ExitTransition,
- scope: CoroutineScope,
- onFinished: () -> Unit
-) {
- // This happens during composition.
- fun updateState(state: AnimStates) {
- animations.fastForEach { it.state = state }
- }
+ exit: ExitTransition
+): Modifier {
- val listener: (AnimationEndReason, Any) -> Unit = { reason, _ ->
- if (reason == AnimationEndReason.Finished && !isAnimating) {
- onFinished()
+ // Generates up to 3 modifiers, one for each type of enter/exit transition in the order:
+ // slide then shrink/expand then alpha.
+ var modifier: Modifier = Modifier
+
+ modifier = modifier.slideInOut(
+ this,
+ rememberUpdatedState(enter.data.slide),
+ rememberUpdatedState(exit.data.slide)
+ ).shrinkExpand(
+ this,
+ rememberUpdatedState(enter.data.changeSize),
+ rememberUpdatedState(exit.data.changeSize)
+ )
+
+ // Fade - it's important to put fade in the end. Otherwise fade will clip slide.
+ // We'll animate if at any point during the transition fadeIn/fadeOut becomes non-null. This
+ // would ensure the removal of fadeIn/Out amid a fade animation doesn't result in a jump.
+ var shouldAnimateAlpha by remember(this) { mutableStateOf(false) }
+ if (currentState == targetState) {
+ shouldAnimateAlpha = false
+ } else {
+ if (enter.data.fade != null || exit.data.fade != null) {
+ shouldAnimateAlpha = true
}
}
- // This is called after measure before placement.
- fun getAnimatedSize(fullSize: IntSize): Pair<IntOffset, IntSize>? {
- animations.fastForEach {
- val animSize = it.getAnimatedSize(fullSize)
- if (animSize != null) {
- return animSize
+ if (shouldAnimateAlpha) {
+ val alpha by animateFloat(
+ transitionSpec = {
+ when {
+ EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible ->
+ enter.data.fade?.animationSpec ?: spring()
+ EnterExitState.Visible isTransitioningTo EnterExitState.PostExit ->
+ exit.data.fade?.animationSpec ?: spring()
+ else -> spring()
+ }
+ },
+ label = "alpha"
+ ) {
+ when (it) {
+ EnterExitState.Visible -> 1f
+ EnterExitState.PreEnter -> enter.data.fade?.alpha ?: 1f
+ EnterExitState.PostExit -> exit.data.fade?.alpha ?: 1f
}
}
- return null
+ modifier = modifier.graphicsLayer {
+ this.alpha = alpha
+ }
}
+ return modifier
+}
- val isAnimating: Boolean
- get() = animations.fastFirstOrNull { it.isRunning }?.isRunning ?: false
-
- val animations: List<TransitionAnimation>
-
- init {
- animations = mutableListOf()
- // Only set up animations when either enter or exit transition is defined.
- if (enter.data.slide != null || exit.data.slide != null) {
- animations.add(
- SlideTransition(enter.data.slide, exit.data.slide, scope, listener)
- )
- }
- if (enter.data.changeSize != null || exit.data.changeSize != null) {
- animations.add(
- ChangeSizeTransition(enter.data.changeSize, exit.data.changeSize, scope, listener)
- )
- }
- if (enter.data.fade != null || exit.data.fade != null) {
- animations.add(
- FadeTransition(enter.data.fade, exit.data.fade, scope, listener)
- )
+@SuppressLint("ModifierInspectorInfo")
+private fun Modifier.slideInOut(
+ transition: Transition<EnterExitState>,
+ slideIn: State<Slide?>,
+ slideOut: State<Slide?>
+): Modifier = composed {
+ // We'll animate if at any point during the transition slideIn/slideOut becomes non-null. This
+ // would ensure the removal of slideIn/Out amid a slide animation doesn't result in a jump.
+ var shouldAnimate by remember(transition) { mutableStateOf(false) }
+ if (transition.currentState == transition.targetState) {
+ shouldAnimate = false
+ } else {
+ if (slideIn.value != null || slideOut.value != null) {
+ shouldAnimate = true
}
}
- val modifier: Modifier
- get() {
- var modifier: Modifier = Modifier
- animations.fastForEach { modifier = modifier.then(it.modifier) }
- return modifier
+ if (shouldAnimate) {
+ val animation = transition.createDeferredAnimation(IntOffset.VectorConverter, "slide")
+ val modifier = remember(transition) {
+ SlideModifier(animation, slideIn, slideOut)
}
+ this.then(modifier)
+ } else {
+ this
+ }
+}
+
+private val defaultOffsetAnimationSpec = spring(visibilityThreshold = IntOffset.VisibilityThreshold)
+
+private class SlideModifier(
+ val lazyAnimation: Transition<EnterExitState>.DeferredAnimation<IntOffset, AnimationVector2D>,
+ val slideIn: State<Slide?>,
+ val slideOut: State<Slide?>
+) : LayoutModifier {
+ val transitionSpec: Transition.Segment<EnterExitState>.() -> FiniteAnimationSpec<IntOffset> =
+ {
+ when {
+ EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible -> {
+ slideIn.value?.animationSpec ?: defaultOffsetAnimationSpec
+ }
+ EnterExitState.Visible isTransitioningTo EnterExitState.PostExit -> {
+ slideOut.value?.animationSpec ?: defaultOffsetAnimationSpec
+ }
+ else -> defaultOffsetAnimationSpec
+ }
+ }
+
+ fun targetValueByState(targetState: EnterExitState, fullSize: IntSize): IntOffset {
+ val preEnter = slideIn.value?.slideOffset?.invoke(fullSize) ?: IntOffset.Zero
+ val postExit = slideOut.value?.slideOffset?.invoke(fullSize) ?: IntOffset.Zero
+ return when (targetState) {
+ EnterExitState.Visible -> IntOffset.Zero
+ EnterExitState.PreEnter -> preEnter
+ EnterExitState.PostExit -> postExit
+ }
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val placeable = measurable.measure(constraints)
+
+ val measuredSize = IntSize(placeable.width, placeable.height)
+ return layout(placeable.width, placeable.height) {
+ val slideOffset = lazyAnimation.animate(
+ transitionSpec
+ ) {
+ targetValueByState(it, measuredSize)
+ }
+ placeable.placeWithLayer(slideOffset.value)
+ }
+ }
+}
+
+@SuppressLint("ModifierInspectorInfo")
+private fun Modifier.shrinkExpand(
+ transition: Transition<EnterExitState>,
+ expand: State<ChangeSize?>,
+ shrink: State<ChangeSize?>
+): Modifier = composed {
+ // We'll animate if at any point during the transition shrink/expand becomes non-null. This
+ // would ensure the removal of shrink/expand amid a size change animation doesn't result in a
+ // jump.
+ var shouldAnimate by remember(transition) { mutableStateOf(false) }
+ if (transition.currentState == transition.targetState) {
+ shouldAnimate = false
+ } else {
+ if (expand.value != null || shrink.value != null) {
+ shouldAnimate = true
+ }
+ }
+
+ if (shouldAnimate) {
+ val alignment: State<Alignment?> = rememberUpdatedState(
+ with(transition.segment) {
+ EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible
+ }.let {
+ if (it) {
+ expand.value?.alignment ?: shrink.value?.alignment
+ } else {
+ shrink.value?.alignment ?: expand.value?.alignment
+ }
+ }
+ )
+ val sizeAnimation = transition.createDeferredAnimation(
+ IntSize.VectorConverter,
+ "shrink/expand"
+ )
+ val offsetAnimation = key(transition.currentState == transition.targetState) {
+ transition.createDeferredAnimation(
+ IntOffset.VectorConverter,
+ "InterruptionHandlingOffset"
+ )
+ }
+
+ val expandShrinkModifier = remember(transition) {
+ ExpandShrinkModifier(
+ sizeAnimation,
+ offsetAnimation,
+ expand,
+ shrink,
+ alignment
+ )
+ }
+
+ if (transition.currentState == transition.targetState) {
+ expandShrinkModifier.currentAlignment = null
+ } else if (expandShrinkModifier.currentAlignment == null) {
+ expandShrinkModifier.currentAlignment = alignment.value ?: Alignment.TopStart
+ }
+ this.clipToBounds().then(expandShrinkModifier)
+ } else {
+ this
+ }
+}
+
+private val defaultSizeAnimationSpec = spring(visibilityThreshold = IntSize.VisibilityThreshold)
+
+private class ExpandShrinkModifier(
+ val sizeAnimation: Transition<EnterExitState>.DeferredAnimation<IntSize, AnimationVector2D>,
+ val offsetAnimation: Transition<EnterExitState>.DeferredAnimation<IntOffset,
+ AnimationVector2D>,
+ val expand: State<ChangeSize?>,
+ val shrink: State<ChangeSize?>,
+ val alignment: State<Alignment?>
+) : LayoutModifier {
+ var currentAlignment: Alignment? = null
+ val sizeTransitionSpec: Transition.Segment<EnterExitState>.() -> FiniteAnimationSpec<IntSize> =
+ {
+ when {
+ EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible ->
+ expand.value?.animationSpec
+ EnterExitState.Visible isTransitioningTo EnterExitState.PostExit ->
+ shrink.value?.animationSpec
+ else -> defaultSizeAnimationSpec
+ } ?: defaultSizeAnimationSpec
+ }
+
+ fun sizeByState(targetState: EnterExitState, fullSize: IntSize): IntSize {
+ val preEnterSize = expand.value?.let { it.size(fullSize) } ?: fullSize
+ val postExitSize = shrink.value?.let { it.size(fullSize) } ?: fullSize
+
+ return when (targetState) {
+ EnterExitState.Visible -> fullSize
+ EnterExitState.PreEnter -> preEnterSize
+ EnterExitState.PostExit -> postExitSize
+ }
+ }
+
+ // This offset is only needed when the alignment value changes during the shrink/expand
+ // animation. For example, if user specify an enter that expands from the left, and an exit
+ // that shrinks towards the right, the asymmetric enter/exit will be brittle to interruption.
+ // Hence the following offset animation to smooth over such interruption.
+ fun targetOffsetByState(targetState: EnterExitState, fullSize: IntSize): IntOffset =
+ when {
+ currentAlignment == null -> IntOffset.Zero
+ alignment.value == null -> IntOffset.Zero
+ currentAlignment == alignment.value -> IntOffset.Zero
+ else -> when (targetState) {
+ EnterExitState.Visible -> IntOffset.Zero
+ EnterExitState.PreEnter -> IntOffset.Zero
+ EnterExitState.PostExit -> shrink.value?.let {
+ val endSize = it.size(fullSize)
+ val targetOffset = alignment.value!!.align(
+ fullSize,
+ endSize,
+ LayoutDirection.Ltr
+ )
+ val currentOffset = currentAlignment!!.align(
+ fullSize,
+ endSize,
+ LayoutDirection.Ltr
+ )
+ targetOffset - currentOffset
+ } ?: IntOffset.Zero
+ }
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val placeable = measurable.measure(constraints)
+
+ val measuredSize = IntSize(placeable.width, placeable.height)
+ val currentSize = sizeAnimation.animate(sizeTransitionSpec) {
+ sizeByState(it, measuredSize)
+ }.value
+
+ val offsetDelta = offsetAnimation.animate({ defaultOffsetAnimationSpec }) {
+ targetOffsetByState(it, measuredSize)
+ }.value
+
+ val offset =
+ currentAlignment?.align(measuredSize, currentSize, LayoutDirection.Ltr)
+ ?: IntOffset.Zero
+ return layout(currentSize.width, currentSize.height) {
+ placeable.place(offset.x + offsetDelta.x, offset.y + offsetDelta.y)
+ }
+ }
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt
index a7e6062..5f0aad8 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt
@@ -206,6 +206,11 @@
"${it.groupValues[1]}<>"
}
}
+ .replace(
+ Regex("(sourceInformationMarkerStart\\(%composer, )([-\\d]+)")
+ ) {
+ "${it.groupValues[1]}<>"
+ }
// replace source information with source it references
.replace(
Regex(
@@ -214,10 +219,15 @@
)
) {
"${it.groupValues[1]}\"${
- generateSourceInfo(it.groupValues[3], source)
+ generateSourceInfo(it.groupValues[4], source)
}\")"
}
.replace(
+ Regex("(sourceInformation(MarkerStart)?\\(.*)\"(.*)\"\\)")
+ ) {
+ "${it.groupValues[1]}\"${generateSourceInfo(it.groupValues[3], source)}\")"
+ }
+ .replace(
Regex(
"(composableLambda[N]?\\" +
"([^\"\\n]*)\"(.*)\"\\)"
@@ -235,16 +245,15 @@
) {
"${it.groupValues[1]}<>"
}
- // composableLambdaInstance(<>, true, )
+ // composableLambdaInstance(<>, true)
.replace(
Regex(
- "(composableLambdaInstance\\()([-\\d]+, (true|false), (null|\"(.*)\")\\))"
+ "(composableLambdaInstance\\()([-\\d]+, (true|false))"
)
) {
val callStart = it.groupValues[1]
val tracked = it.groupValues[3]
- val sourceInfo = it.groupValues[5]
- "$callStart<>, $tracked, \"${generateSourceInfo(sourceInfo, source)}\")"
+ "$callStart<>, $tracked"
}
// composableLambda(%composer, <>, true)
.replace(
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
index 0c6ce92..9cddb39 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
@@ -543,7 +543,8 @@
"""
@Composable
fun A(y: Any?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<A()>,<A(Empt...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Doub...>,<A(Doub...>,<A(Doub...>,<A(Doub...>,<A(X(li...>,<A(X(li...>,<A(NonB...>,<A(NonB...>,<A(Stab...>,<A(Unst...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<A()>,<A(Empt...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Doub...>,<A(Doub...>,<A(Doub...>,<A(Doub...>,<A(X(li...>,<A(X(li...>,<A(NonB...>,<A(NonB...>,<A(Stab...>,<A(Unst...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0010
@@ -674,7 +675,8 @@
}
@Composable
fun A(y: Any, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<A(X(li...>,<A(Stab...>,<A(Unst...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<A(X(li...>,<A(Stab...>,<A(Unst...>:Test.kt")
used(y)
A(X(listOf(StableClass())), %composer, 0b1000)
A(StableDelegateProp(), %composer, 0)
@@ -708,7 +710,8 @@
"""
@Composable
fun A(y: Any, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<A(Wrap...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<A(Wrap...>:Test.kt")
used(y)
A(Wrapper(Foo()), %composer, Wrapper.%stable)
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -746,7 +749,8 @@
"""
@Composable
fun <V> B(value: V, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(B)<A(Wrap...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B)<A(Wrap...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(value)) 0b0100 else 0b0010
@@ -762,7 +766,8 @@
}
@Composable
fun <T> X(items: List<T>, itemContent: Function3<T, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(X)P(1)*<itemCo...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(X)P(1)*<itemCo...>:Test.kt")
val %dirty = %changed
val tmp0_iterator = items.iterator()
while (tmp0_iterator.hasNext()) {
@@ -775,14 +780,16 @@
}
@Composable
fun C(items: List<String>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(C)<X(item...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(C)<X(item...>:Test.kt")
X(items, ComposableSingletons%TestKt.lambda-1, %composer, 0b1000)
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
C(items, %composer, %changed or 0b0001)
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function3<String, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A(item...>,<A(Wrap...>:Test.kt") { item: String, %composer: Composer?, %changed: Int ->
+ val lambda-1: Function3<String, Composer, Int, Unit> = composableLambdaInstance(<>, false) { item: String, %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<A(item...>,<A(Wrap...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(item)) 0b0100 else 0b0010
@@ -837,7 +844,8 @@
}
@Composable
fun A(y: Int, x: Any, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)P(1)<B(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)P(1)<B(x)>:Test.kt")
used(y)
B(x, %composer, 0b1000)
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -846,7 +854,8 @@
}
@Composable
fun B(x: Any, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B):Test.kt")
used(x)
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
B(x, %composer, %changed or 0b0001)
@@ -876,7 +885,8 @@
}
@Composable
fun A(y: Int, x: Foo, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)P(1)<B(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)P(1)<B(x)>:Test.kt")
used(y)
B(x, %composer, 0b1000)
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -885,7 +895,8 @@
}
@Composable
fun B(x: Any, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B):Test.kt")
used(x)
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
B(x, %composer, %changed or 0b0001)
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
index 26c88f32..ae972e1 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
@@ -67,7 +67,8 @@
val bar: Int
@Composable @JvmName(name = "getBar")
get() {
- %composer.startReplaceableGroup(<>, "C:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C:Test.kt#2487m")
val tmp0 = 123
%composer.endReplaceableGroup()
return tmp0
@@ -75,7 +76,8 @@
@NonRestartableComposable
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<bar>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<bar>:Test.kt#2487m")
bar
%composer.endReplaceableGroup()
}
@@ -110,7 +112,8 @@
@NonRestartableComposable
@Composable
override fun bar(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(bar):Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(bar):Test.kt#2487m")
%composer.endReplaceableGroup()
}
static val %stable: Int = 0
@@ -143,18 +146,21 @@
@NonRestartableComposable
@Composable
fun Wat(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Wat):Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Wat):Test.kt#2487m")
%composer.endReplaceableGroup()
}
@NonRestartableComposable
@Composable
fun Foo(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Foo)<Wat()>,<goo()>,<baz()>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Foo)<Wat()>,<goo()>,<baz()>:Test.kt#2487m")
Wat(%composer, 0)
@NonRestartableComposable
@Composable
fun goo(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(goo)<Wat()>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(goo)<Wat()>:Test.kt#2487m")
Wat(%composer, 0)
%composer.endReplaceableGroup()
}
@@ -162,7 +168,8 @@
@NonRestartableComposable
@Composable
fun baz(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(baz)<Wat()>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(baz)<Wat()>:Test.kt#2487m")
Wat(%composer, 0)
%composer.endReplaceableGroup()
}
@@ -245,7 +252,8 @@
@NonRestartableComposable
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<Exampl...>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<Exampl...>:Test.kt#2487m")
Example(%composer, 0)
%composer.endReplaceableGroup()
}
@@ -267,16 +275,19 @@
"""
@Composable
fun Example(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<conten...>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<conten...>:Test.kt#2487m")
content(%composer, 0b1110 and %changed)
%composer.endReplaceableGroup()
}
@NonRestartableComposable
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Test)<Exampl...>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Test)<Exampl...>:Test.kt#2487m")
Example({ %composer: Composer?, %changed: Int ->
- %composer.startReplaceableGroup(<>, "C:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C:Test.kt#2487m")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
@@ -300,7 +311,8 @@
val myProperty: Function0<Unit>
@Composable @JvmName(name = "getMyProperty")
get() {
- %composer.startReplaceableGroup(<>, "C:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C:Test.kt#2487m")
val tmp0 = {
}
%composer.endReplaceableGroup()
@@ -369,7 +381,8 @@
"""
@Composable
fun Wrapper(block: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Wrapper)<block(...>:Test.kt#2487m")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Wrapper)<block(...>:Test.kt#2487m")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(block)) 0b0100 else 0b0010
@@ -385,7 +398,8 @@
}
@Composable
fun Leaf(text: String, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Leaf):Test.kt#2487m")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Leaf):Test.kt#2487m")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(text)) 0b0100 else 0b0010
@@ -401,14 +415,17 @@
}
@Composable
fun Test(value: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt#2487m")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt#2487m")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(value)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
- %composer.startMovableGroup(<>, value, "<Wrappe...>")
- Wrapper(composableLambda(%composer, <>, true, "C<Leaf("...>:Test.kt#2487m") { %composer: Composer?, %changed: Int ->
+ %composer.startMovableGroup(<>, value)
+ sourceInformation(%composer, "<Wrappe...>")
+ Wrapper(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<Leaf("...>:Test.kt#2487m")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Leaf("Value %value", %composer, 0)
} else {
@@ -486,17 +503,20 @@
"""
@Composable
fun composeVector(composable: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(composeVector)<emit>:Test.kt#2487m")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(composeVector)<emit>:Test.kt#2487m")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(composable)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
emit({ %composer: Composer?, %changed: Int ->
- %composer.startReplaceableGroup(<>, "C<emit>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<emit>:Test.kt#2487m")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
emit({ %composer: Composer?, %changed: Int ->
- %composer.startReplaceableGroup(<>, "C<compos...>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<compos...>:Test.kt#2487m")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
composable(%composer, 0b1110 and %dirty)
} else {
@@ -518,7 +538,8 @@
}
@Composable
fun emit(composable: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(emit)<compos...>:Test.kt#2487m")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(emit)<compos...>:Test.kt#2487m")
composable(%composer, 0b1110 and %changed)
%composer.endReplaceableGroup()
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index 53823d7..943df22 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -35,7 +35,8 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
NA()
}
@@ -61,9 +62,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A()>")
A(%composer, 0)
%composer.endReplaceableGroup()
} else {
@@ -94,13 +97,16 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<A(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
%composer.endReplaceableGroup()
} else {
- %composer.startReplaceableGroup(<>, "<A(b)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(b)>")
A(b, %composer, 0)
%composer.endReplaceableGroup()
}
@@ -128,7 +134,8 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<B()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<B()>:Test.kt")
if (B(%composer, 0)) {
NA()
} else {
@@ -162,13 +169,16 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
- if (%composer.startReplaceableGroup(<>, "<B(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
+ if (%composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<B(a)>")
val tmp0_group = B(a, %composer, 0)
%composer.endReplaceableGroup()
tmp0_group) {
NA()
- } else if (%composer.startReplaceableGroup(<>, "<B(b)>")
+ } else if (%composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<B(b)>")
val tmp1_group = B(b, %composer, 0)
%composer.endReplaceableGroup()
tmp1_group) {
@@ -198,7 +208,8 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val tmp0_subject = x
when {
tmp0_subject == 0 -> {
@@ -235,21 +246,25 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val tmp0_subject = x
when {
tmp0_subject == 0 -> {
- %composer.startReplaceableGroup(<>, "<A(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
%composer.endReplaceableGroup()
}
tmp0_subject == 0b0001 -> {
- %composer.startReplaceableGroup(<>, "<A(b)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(b)>")
A(b, %composer, 0)
%composer.endReplaceableGroup()
}
else -> {
- %composer.startReplaceableGroup(<>, "<A(c)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(c)>")
A(c, %composer, 0)
%composer.endReplaceableGroup()
}
@@ -278,23 +293,27 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val y = val tmp0_subject = x
when {
tmp0_subject == 0 -> {
- %composer.startReplaceableGroup(<>, "<R(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<R(a)>")
val tmp0_group = R(a, %composer, 0)
%composer.endReplaceableGroup()
tmp0_group
}
tmp0_subject == 0b0001 -> {
- %composer.startReplaceableGroup(<>, "<R(b)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<R(b)>")
val tmp1_group = R(b, %composer, 0)
%composer.endReplaceableGroup()
tmp1_group
}
else -> {
- %composer.startReplaceableGroup(<>, "<R(c)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<R(c)>")
val tmp2_group = R(c, %composer, 0)
%composer.endReplaceableGroup()
tmp2_group
@@ -324,20 +343,24 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
when {
x < 0 -> {
- %composer.startReplaceableGroup(<>, "<A(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
%composer.endReplaceableGroup()
}
x > 30 -> {
- %composer.startReplaceableGroup(<>, "<A(b)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(b)>")
A(b, %composer, 0)
%composer.endReplaceableGroup()
}
else -> {
- %composer.startReplaceableGroup(<>, "<A(c)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(c)>")
A(c, %composer, 0)
%composer.endReplaceableGroup()
}
@@ -366,10 +389,12 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
when {
x < 0 -> {
- %composer.startReplaceableGroup(<>, "<A(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
%composer.endReplaceableGroup()
}
@@ -379,7 +404,8 @@
NA()
}
else -> {
- %composer.startReplaceableGroup(<>, "<A(b)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(b)>")
A(b, %composer, 0)
%composer.endReplaceableGroup()
}
@@ -409,15 +435,18 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
when {
- %composer.startReplaceableGroup(<>, "<R(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<R(a)>")
val tmp0_group = x == R(a, %composer, 0)
%composer.endReplaceableGroup()
tmp0_group -> {
NA()
}
- %composer.startReplaceableGroup(<>, "<R(b)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<R(b)>")
val tmp1_group = x > R(b, %composer, 0)
%composer.endReplaceableGroup()
tmp1_group -> {
@@ -452,16 +481,20 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
- %composer.startReplaceableGroup(<>, "")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "")
when {
- %composer.startReplaceableGroup(<>, "<R(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<R(a)>")
val tmp0_group = x == R(a, %composer, 0)
%composer.endReplaceableGroup()
tmp0_group -> {
NA()
}
- %composer.startReplaceableGroup(<>, "<R(b)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<R(b)>")
val tmp1_group = x > R(b, %composer, 0)
%composer.endReplaceableGroup()
tmp1_group -> {
@@ -492,7 +525,8 @@
@NonRestartableComposable
@Composable
fun Example(x: Int?, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val tmp0_safe_receiver = x
when {
tmp0_safe_receiver == null -> {
@@ -501,7 +535,8 @@
null
}
else -> {
- %composer.startReplaceableGroup(<>, "<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A()>")
tmp0_safe_receiver.A(%composer, 0b1110 and %changed)
%composer.endReplaceableGroup()
}
@@ -525,11 +560,13 @@
@NonRestartableComposable
@Composable
fun Example(x: Int?, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val y = val tmp0_elvis_lhs = x
when {
tmp0_elvis_lhs == null -> {
- %composer.startReplaceableGroup(<>, "<R()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<R()>")
val tmp0_group = R(%composer, 0)
%composer.endReplaceableGroup()
tmp0_group
@@ -562,7 +599,8 @@
@NonRestartableComposable
@Composable
fun Example(items: List<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<P(i)>:Test.kt")
val tmp0_iterator = items.iterator()
while (tmp0_iterator.hasNext()) {
val i = tmp0_iterator.next()
@@ -590,8 +628,10 @@
@NonRestartableComposable
@Composable
fun Example(items: List<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
- %composer.startReplaceableGroup(<>, "*<P(i)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<P(i)>")
val tmp0_iterator = items.iterator()
while (tmp0_iterator.hasNext()) {
val i = tmp0_iterator.next()
@@ -620,7 +660,8 @@
@NonRestartableComposable
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<L()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<L()>:Test.kt")
val tmp0_iterator = L(%composer, 0).iterator()
while (tmp0_iterator.hasNext()) {
val i = tmp0_iterator.next()
@@ -650,7 +691,8 @@
@NonRestartableComposable
@Composable
fun Example(items: MutableList<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<P(item...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<P(item...>:Test.kt")
while (items.isNotEmpty()) {
val item = items.removeAt(items.size - 1)
P(item, %composer, 0)
@@ -679,8 +721,10 @@
@NonRestartableComposable
@Composable
fun Example(items: MutableList<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
- %composer.startReplaceableGroup(<>, "*<P(item...>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<P(item...>")
while (items.isNotEmpty()) {
val item = items.removeAt(items.size - 1)
P(item, %composer, 0)
@@ -709,7 +753,8 @@
@NonRestartableComposable
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<B()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<B()>:Test.kt")
while (B(%composer, 0)) {
print("hello world")
}
@@ -735,8 +780,10 @@
@NonRestartableComposable
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
- %composer.startReplaceableGroup(<>, "*<B()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<B()>")
while (B(%composer, 0)) {
print("hello world")
}
@@ -764,7 +811,8 @@
@NonRestartableComposable
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<B()>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<B()>,<A()>:Test.kt")
while (B(%composer, 0)) {
A(%composer, 0)
}
@@ -791,8 +839,10 @@
@NonRestartableComposable
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A(b)>:Test.kt")
- %composer.startReplaceableGroup(<>, "*<B()>,<A(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A(b)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<B()>,<A(a)>")
while (B(%composer, 0)) {
A(a, %composer, 0)
}
@@ -820,9 +870,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A()>")
A(%composer, 0)
%composer.endReplaceableGroup()
%composer.endReplaceableGroup()
@@ -853,7 +905,8 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>:Test.kt")
if (x > 0) {
%composer.endReplaceableGroup()
return
@@ -880,9 +933,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A()>")
A(%composer, 0)
val tmp1_return = 1
%composer.endReplaceableGroup()
@@ -915,7 +970,8 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>:Test.kt")
if (x > 0) {
val tmp1_return = 1
%composer.endReplaceableGroup()
@@ -944,7 +1000,8 @@
@NonRestartableComposable
@Composable
fun Example(%composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(Example)<A()>,<R()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>,<R()>:Test.kt")
A(%composer, 0)
val tmp0 = R(%composer, 0)
%composer.endReplaceableGroup()
@@ -968,9 +1025,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(Example)<R()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<R()>:Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<R()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<R()>")
val tmp1_return = R(%composer, 0)
%composer.endReplaceableGroup()
%composer.endReplaceableGroup()
@@ -1011,7 +1070,8 @@
@NonRestartableComposable
@Composable
fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>,<P(l)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<P(i)>,<P(l)>:Test.kt")
while (items.hasNext()) {
val i = items.next()
val j = i
@@ -1019,13 +1079,15 @@
val l = i
P(i, %composer, 0)
if (i == 0) {
- %composer.startReplaceableGroup(<>, "<P(j)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<P(j)>")
P(j, %composer, 0)
%composer.endReplaceableGroup()
%composer.endReplaceableGroup()
return
} else {
- %composer.startReplaceableGroup(<>, "<P(k)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<P(k)>")
P(k, %composer, 0)
%composer.endReplaceableGroup()
}
@@ -1060,7 +1122,8 @@
"""
@Composable
fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)*<P(i)>,<P(l)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)*<P(i)>,<P(l)>:Test.kt")
while (items.hasNext()) {
val i = items.next()
val j = i
@@ -1068,7 +1131,8 @@
val l = i
P(i, %composer, 0)
if (i == 0) {
- %composer.startReplaceableGroup(<>, "<P(j)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<P(j)>")
P(j, %composer, 0)
%composer.endReplaceableGroup()
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -1076,7 +1140,8 @@
}
return
} else {
- %composer.startReplaceableGroup(<>, "<P(k)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<P(k)>")
P(k, %composer, 0)
%composer.endReplaceableGroup()
}
@@ -1107,7 +1172,8 @@
@NonRestartableComposable
@Composable
fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<P(i)>:Test.kt")
while (items.hasNext()) {
val i = items.next()
if (i == 0) {
@@ -1138,7 +1204,8 @@
@NonRestartableComposable
@Composable
fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<P(i)>:Test.kt")
while (items.hasNext()) {
val i = items.next()
P(i, %composer, 0)
@@ -1172,7 +1239,8 @@
@NonRestartableComposable
@Composable
fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>,<P(j)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<P(i)>,<P(j)>:Test.kt")
while (items.hasNext()) {
val i = items.next()
val j = i
@@ -1208,8 +1276,10 @@
@NonRestartableComposable
@Composable
fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
- %composer.startReplaceableGroup(<>, "*<P(i)>,<P(i)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<P(i)>,<P(i)>")
while (items.hasNext()) {
val i = items.next()
P(i, %composer, 0)
@@ -1243,9 +1313,11 @@
@NonRestartableComposable
@Composable
fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
while (items.hasNext()) {
- %composer.startReplaceableGroup(<>, "<P(i)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<P(i)>")
val i = items.next()
if (i == 0) {
%composer.endReplaceableGroup()
@@ -1278,9 +1350,11 @@
@NonRestartableComposable
@Composable
fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
while (items.hasNext()) {
- %composer.startReplaceableGroup(<>, "<P(i)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<P(i)>")
val i = items.next()
P(i, %composer, 0)
if (i == 0) {
@@ -1314,9 +1388,11 @@
@NonRestartableComposable
@Composable
fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
while (items.hasNext()) {
- %composer.startReplaceableGroup(<>, "<P(i)>,<P(i)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<P(i)>,<P(i)>")
val i = items.next()
P(i, %composer, 0)
if (i == 0) {
@@ -1349,7 +1425,8 @@
@NonRestartableComposable
@Composable
fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
while (a.hasNext()) {
val x = a.next()
if (x > 100) {
@@ -1385,10 +1462,12 @@
@NonRestartableComposable
@Composable
fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
a@while (a.hasNext()) {
val x = a.next()
- %composer.startReplaceableGroup(<>, "*<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<A()>")
b@while (b.hasNext()) {
val y = b.next()
if (y == x) {
@@ -1436,13 +1515,15 @@
@NonRestartableComposable
@Composable
fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
a@while (a.hasNext()) {
val x = a.next()
if (x == 0) {
break
}
- %composer.startReplaceableGroup(<>, "*<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<A()>")
b@while (b.hasNext()) {
val y = b.next()
if (y == 0) {
@@ -1485,10 +1566,13 @@
@NonRestartableComposable
@Composable
fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
- %composer.startReplaceableGroup(<>, "*<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<A()>")
a@while (a.hasNext()) {
- %composer.startReplaceableGroup(<>, "*<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<A()>")
b@while (b.hasNext()) {
A(%composer, 0)
}
@@ -1519,10 +1603,13 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<A()>")
- %composer.startReplaceableGroup(<>, "*<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<A()>")
while (x > 0) {
A(%composer, 0)
}
@@ -1555,9 +1642,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<A()>,*<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A()>,*<A()>")
A(%composer, 0)
while (x > 0) {
A(%composer, 0)
@@ -1588,9 +1677,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "*<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<A()>")
while (x > 0) {
A(%composer, 0)
}
@@ -1620,9 +1711,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
while (x > 0) {
- %composer.startMovableGroup(<>, x, "<A()>")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A()>")
A(%composer, 0)
%composer.endMovableGroup()
}
@@ -1650,12 +1743,15 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
while (x > 0) {
- %composer.startMovableGroup(<>, x, "<A(a)>")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
%composer.endMovableGroup()
- %composer.startMovableGroup(<>, x + 1, "<A(b)>")
+ %composer.startMovableGroup(<>, x + 1)
+ sourceInformation(%composer, "<A(b)>")
A(b, %composer, 0)
%composer.endMovableGroup()
}
@@ -1681,9 +1777,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<A(b)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<A(b)>:Test.kt")
while (x > 0) {
- %composer.startMovableGroup(<>, x, "<A(a)>")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
%composer.endMovableGroup()
A(b, %composer, 0)
@@ -1710,10 +1808,12 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<A(a)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<A(a)>:Test.kt")
while (x > 0) {
A(a, %composer, 0)
- %composer.startMovableGroup(<>, x, "<A(b)>")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A(b)>")
A(b, %composer, 0)
%composer.endMovableGroup()
}
@@ -1740,10 +1840,12 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<A(a)>,<A(c)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<A(a)>,<A(c)>:Test.kt")
while (x > 0) {
A(a, %composer, 0)
- %composer.startMovableGroup(<>, x, "<A(b)>")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A(b)>")
A(b, %composer, 0)
%composer.endMovableGroup()
A(c, %composer, 0)
@@ -1767,8 +1869,10 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
- %composer.startMovableGroup(<>, x, "<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A()>")
A(%composer, 0)
%composer.endMovableGroup()
%composer.endReplaceableGroup()
@@ -1791,8 +1895,10 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A(b)>:Test.kt")
- %composer.startMovableGroup(<>, x, "<A(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A(b)>:Test.kt")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
%composer.endMovableGroup()
A(b, %composer, 0)
@@ -1816,9 +1922,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<A(a)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<A(a)>:Test.kt")
A(a, %composer, 0)
- %composer.startMovableGroup(<>, x, "<A(b)>")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A(b)>")
A(b, %composer, 0)
%composer.endMovableGroup()
%composer.endReplaceableGroup()
@@ -1842,10 +1950,13 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "")
- %composer.startMovableGroup(<>, x, "<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A()>")
A(%composer, 0)
%composer.endMovableGroup()
%composer.endReplaceableGroup()
@@ -1875,10 +1986,13 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<A(b)>")
- %composer.startMovableGroup(<>, x, "<A(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(b)>")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
%composer.endMovableGroup()
A(b, %composer, 0)
@@ -1909,11 +2023,14 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<A(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
- %composer.startMovableGroup(<>, x, "<A(b)>")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<A(b)>")
A(b, %composer, 0)
%composer.endMovableGroup()
%composer.endReplaceableGroup()
@@ -1940,8 +2057,10 @@
@NonRestartableComposable
@Composable
fun Example(a: Int, b: Int, c: Int, d: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
- %composer.startMovableGroup(<>, %composer.joinKey(%composer.joinKey(%composer.joinKey(a, b), c), d), "<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
+ %composer.startMovableGroup(<>, %composer.joinKey(%composer.joinKey(%composer.joinKey(a, b), c), d))
+ sourceInformation(%composer, "<A()>")
A(%composer, 0)
%composer.endMovableGroup()
%composer.endReplaceableGroup()
@@ -1965,9 +2084,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)*<R()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)*<R()>:Test.kt")
while (x > 0) {
- %composer.startMovableGroup(<>, R(%composer, 0), "<A()>")
+ %composer.startMovableGroup(<>, R(%composer, 0))
+ sourceInformation(%composer, "<A()>")
A(%composer, 0)
%composer.endMovableGroup()
}
@@ -1989,9 +2110,11 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<P(y)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<P(y)>:Test.kt")
val y =
- %composer.startMovableGroup(<>, x, "<R()>")
+ %composer.startMovableGroup(<>, x)
+ sourceInformation(%composer, "<R()>")
val tmp0 = R(%composer, 0)
%composer.endMovableGroup()
tmp0
@@ -2017,14 +2140,18 @@
@NonRestartableComposable
@Composable
fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val tmp0 = if (x > 0) {
- %composer.startReplaceableGroup(<>, "")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "")
val tmp4_group =
- val tmp3_group = if (%composer.startReplaceableGroup(<>, "<B()>")
+ val tmp3_group = if (%composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<B()>")
val tmp1_group = B(%composer, 0)
%composer.endReplaceableGroup()
- tmp1_group) 1 else if (%composer.startReplaceableGroup(<>, "<B()>")
+ tmp1_group) 1 else if (%composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<B()>")
val tmp2_group = B(%composer, 0)
%composer.endReplaceableGroup()
tmp2_group) 2 else 3
@@ -2091,8 +2218,10 @@
@NonRestartableComposable
@Composable
fun Simple(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Simple)<A()>:Test.kt")
- %composer.startReplaceableGroup(<>, "*<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Simple)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<A()>")
run {
A(%composer, 0)
}
@@ -2103,8 +2232,10 @@
@NonRestartableComposable
@Composable
fun WithReturn(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(WithReturn)<A()>:Test.kt")
- %composer.startReplaceableGroup(<>, "*<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(WithReturn)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<A()>")
run {
A(%composer, 0)
%composer.endReplaceableGroup()
@@ -2118,7 +2249,8 @@
@NonRestartableComposable
@Composable
fun NoCalls(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(NoCalls)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(NoCalls)<A()>:Test.kt")
run {
println("hello world")
}
@@ -2128,7 +2260,8 @@
@NonRestartableComposable
@Composable
fun NoCallsAfter(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(NoCallsAfter)*<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(NoCallsAfter)*<A()>:Test.kt")
run {
A(%composer, 0)
}
@@ -2154,7 +2287,8 @@
"""
@Composable
fun Example(x: Int?, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<A(c)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<A(c)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -2168,10 +2302,12 @@
null
}
else -> {
- %composer.startReplaceableGroup(<>, "*<A(b)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<A(b)>")
tmp0_safe_receiver.let { it: Int ->
if (it > 0) {
- %composer.startReplaceableGroup(<>, "<A(a)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(a)>")
A(a, %composer, 0)
%composer.endReplaceableGroup()
} else {
@@ -2211,7 +2347,8 @@
"""
@Composable
fun Example(x: Int?, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<A()>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<A()>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -2249,7 +2386,8 @@
"""
@Composable
fun <T> provided(value: T, %composer: Composer?, %changed: Int): State<T> {
- %composer.startReplaceableGroup(<>, "C(provided)*<rememb...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(provided)*<rememb...>:Test.kt")
val tmp0 = remember({
val tmp0_return = mutableStateOf(
value = value
@@ -2282,7 +2420,8 @@
"""
@Composable
fun Test(x: Int, %composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(Test)*<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Test)*<A()>:Test.kt")
val tmp0 =
val tmp1_group = x.let { it: Int ->
A(%composer, 0)
@@ -2309,7 +2448,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<W>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<W>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
W(ComposableSingletons%TestKt.lambda-1, %composer, 0)
} else {
@@ -2320,7 +2460,8 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A()>:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<A()>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
A(%composer, 0)
} else {
@@ -2344,10 +2485,12 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<IW>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<IW>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
IW({ %composer: Composer?, %changed: Int ->
- %composer.startReplaceableGroup(<>, "C<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
A(%composer, 0)
} else {
@@ -2383,7 +2526,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<Wrap>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<Wrap>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Wrap(ComposableSingletons%TestKt.lambda-1, %composer, 0)
} else {
@@ -2394,9 +2538,11 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<effect>:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<effect>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
- %composer.startReplaceableGroup(<>, "*<effect>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<effect>")
repeat(number) { it: Int ->
effects[it] = effect({
0
@@ -2437,7 +2583,8 @@
"""
@Composable
fun Test(value: InlineClass, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)P(0:InlineClass)<A()>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)P(0:InlineClass)<A()>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(value.value)) 0b0100 else 0b0010
@@ -2606,7 +2753,8 @@
"""
@Composable
fun Test01(p0: Int, p1: Int, p2: Int, p3: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test01):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test01):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2634,7 +2782,8 @@
}
@Composable
fun Test02(p0: Int, p1: Int, p3: Int, p2: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test02)P(!2,3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test02)P(!2,3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2662,7 +2811,8 @@
}
@Composable
fun Test03(p0: Int, p2: Int, p1: Int, p3: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test03)P(!1,2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test03)P(!1,2):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2690,7 +2840,8 @@
}
@Composable
fun Test04(p0: Int, p2: Int, p3: Int, p1: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test04)P(!1,2,3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test04)P(!1,2,3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2718,7 +2869,8 @@
}
@Composable
fun Test05(p0: Int, p3: Int, p1: Int, p2: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test05)P(!1,3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test05)P(!1,3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2746,7 +2898,8 @@
}
@Composable
fun Test06(p0: Int, p3: Int, p2: Int, p1: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test06)P(!1,3,2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test06)P(!1,3,2):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2774,7 +2927,8 @@
}
@Composable
fun Test07(p1: Int, p0: Int, p2: Int, p3: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test07)P(1):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test07)P(1):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2802,7 +2956,8 @@
}
@Composable
fun Test08(p1: Int, p0: Int, p3: Int, p2: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test08)P(1!1,3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test08)P(1!1,3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2830,7 +2985,8 @@
}
@Composable
fun Test09(p1: Int, p2: Int, p0: Int, p3: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test09)P(1,2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test09)P(1,2):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2858,7 +3014,8 @@
}
@Composable
fun Test00(p1: Int, p2: Int, p3: Int, p0: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test00)P(1,2,3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test00)P(1,2,3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2886,7 +3043,8 @@
}
@Composable
fun Test11(p1: Int, p3: Int, p0: Int, p2: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test11)P(1,3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test11)P(1,3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2914,7 +3072,8 @@
}
@Composable
fun Test12(p1: Int, p3: Int, p2: Int, p0: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test12)P(1,3,2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test12)P(1,3,2):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2942,7 +3101,8 @@
}
@Composable
fun Test13(p2: Int, p0: Int, p1: Int, p3: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test13)P(2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test13)P(2):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -2970,7 +3130,8 @@
}
@Composable
fun Test14(p2: Int, p0: Int, p3: Int, p1: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test14)P(2!1,3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test14)P(2!1,3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -2998,7 +3159,8 @@
}
@Composable
fun Test15(p2: Int, p1: Int, p0: Int, p3: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test15)P(2,1):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test15)P(2,1):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -3026,7 +3188,8 @@
}
@Composable
fun Test16(p2: Int, p1: Int, p3: Int, p0: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test16)P(2,1,3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test16)P(2,1,3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -3054,7 +3217,8 @@
}
@Composable
fun Test17(p2: Int, p3: Int, p0: Int, p1: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test17)P(2,3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test17)P(2,3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -3082,7 +3246,8 @@
}
@Composable
fun Test18(p2: Int, p3: Int, p1: Int, p0: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test18)P(2,3,1):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test18)P(2,3,1):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -3110,7 +3275,8 @@
}
@Composable
fun Test19(p3: Int, p0: Int, p1: Int, p2: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test19)P(3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test19)P(3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3138,7 +3304,8 @@
}
@Composable
fun Test20(p3: Int, p0: Int, p2: Int, p1: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test20)P(3!1,2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test20)P(3!1,2):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3166,7 +3333,8 @@
}
@Composable
fun Test21(p3: Int, p1: Int, p0: Int, p2: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test21)P(3,1):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test21)P(3,1):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3194,7 +3362,8 @@
}
@Composable
fun Test22(p3: Int, p1: Int, p2: Int, p0: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test22)P(3,1,2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test22)P(3,1,2):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3222,7 +3391,8 @@
}
@Composable
fun Test23(p3: Int, p2: Int, p0: Int, p1: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test23)P(3,2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test23)P(3,2):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3250,7 +3420,8 @@
}
@Composable
fun Test24(p3: Int, p2: Int, p1: Int, p0: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test24)P(3,2,1):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test24)P(3,2,1):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3300,7 +3471,8 @@
expectedTransformed = """
@Composable
fun Test(value: LocalInlineClass, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)P(0:c#runtime.tests.LocalInlineClass):Test.kt#992ot2")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)P(0:c#runtime.tests.LocalInlineClass):Test.kt#992ot2")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(value.value)) 0b0100 else 0b0010
@@ -3340,7 +3512,8 @@
expectedTransformed = """
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<b()>,<c()>,<d()>,<A(b(),>,<B()>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<b()>,<c()>,<d()>,<A(b(),>,<B()>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
A(b(%composer, 0), c(%composer, 0), d(%composer, 0), %composer, 0)
B(%composer, 0)
@@ -3388,7 +3561,8 @@
class SomeClass {
var a: String = "Test"
fun onCreate() {
- setContent(composableLambdaInstance(<>, true, "C<B(a)>,<B(a)>:Test.kt") { %composer: Composer?, %changed: Int ->
+ setContent(composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<B(a)>,<B(a)>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
B(a, %composer, 0)
B(a, %composer, 0)
@@ -3402,7 +3576,8 @@
}
fun Test() {
var a = "Test"
- setContent(composableLambdaInstance(<>, true, "C<B(a)>,<B(a)>:Test.kt") { %composer: Composer?, %changed: Int ->
+ setContent(composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<B(a)>,<B(a)>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
B(a, %composer, 0)
B(a, %composer, 0)
@@ -3443,7 +3618,8 @@
expectedTransformed = """
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<W>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<W>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
W(ComposableSingletons%TestKt.lambda-1, %composer, 0)
} else {
@@ -3454,13 +3630,16 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<IW>:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<IW>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
IW({ %composer: Composer?, %changed: Int ->
- %composer.startReplaceableGroup(<>, "C<T(2)>,<T(4)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<T(2)>,<T(4)>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
T(2, %composer, 0b0110)
- %composer.startReplaceableGroup(<>, "*<T(3)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "*<T(3)>")
repeat(3) { it: Int ->
T(3, %composer, 0b0110)
}
@@ -3509,27 +3688,138 @@
val current: Int
@Composable @ReadOnlyComposable @JvmName(name = "getCurrent")
get() {
- %composer.startReplaceableGroup(<>, "C:Test.kt")
+ sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
val tmp0 = 0
- %composer.endReplaceableGroup()
+ sourceInformationMarkerEnd(%composer)
return tmp0
}
@Composable
@ReadOnlyComposable
fun calculateSometing(%composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(calculateSometing):Test.kt")
+ sourceInformationMarkerStart(%composer, <>, "C(calculateSometing):Test.kt")
val tmp0 = 0
- %composer.endReplaceableGroup()
+ sourceInformationMarkerEnd(%composer)
return tmp0
}
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<curren...>,<calcul...>,<Layout>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<curren...>,<calcul...>,<Layout>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
val c = current
val cl = calculateSometing(%composer, 0)
Layout({ %composer: Composer?, %changed: Int ->
- %composer.startReplaceableGroup(<>, "C<Text("...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<Text("...>:Test.kt")
+ if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
+ Text("%c %cl", %composer, 0)
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endReplaceableGroup()
+ }, %composer, 0)
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ Test(%composer, %changed or 0b0001)
+ }
+ }
+ """,
+ """
+ import androidx.compose.runtime.Composable
+
+ @Composable
+ inline fun Layout(content: @Composable () -> Unit) { content() }
+
+ @Composable
+ fun Text(text: String) { }
+ """
+ )
+
+ @Test
+ fun testReadOnlyInlineValSourceLocations() = verifyComposeIrTransform(
+ """
+ import androidx.compose.runtime.Composable
+ import androidx.compose.runtime.ReadOnlyComposable
+
+ class CurrentHolder {
+ inline val current: Int
+ @ReadOnlyComposable
+ @Composable
+ get() = 0
+ }
+
+ class HolderHolder {
+ private val _currentHolder = CurrentHolder()
+ val current: Int
+ @ReadOnlyComposable
+ @Composable
+ get() = _currentHolder.current
+ }
+
+ val holderHolder = HolderHolder()
+
+ @Composable
+ @ReadOnlyComposable
+ fun calculateSometing(): Int {
+ return 0;
+ }
+
+ @Composable
+ fun Test() {
+ val c = holderHolder.current
+ val cl = calculateSometing()
+ Layout {
+ Text("${'$'}c ${'$'}cl")
+ }
+ }
+ """,
+ """
+ @StabilityInferred(parameters = 0)
+ class CurrentHolder {
+ val current: Int
+ @ReadOnlyComposable @Composable @JvmName(name = "getCurrent")
+ get() {
+ sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+ val tmp0 = 0
+ sourceInformationMarkerEnd(%composer)
+ return tmp0
+ }
+ static val %stable: Int = 0
+ }
+ @StabilityInferred(parameters = 0)
+ class HolderHolder {
+ val _currentHolder: CurrentHolder = CurrentHolder()
+ val current: Int
+ @ReadOnlyComposable @Composable @JvmName(name = "getCurrent")
+ get() {
+ sourceInformationMarkerStart(%composer, <>, "C<curren...>:Test.kt")
+ val tmp0 = _currentHolder.current
+ sourceInformationMarkerEnd(%composer)
+ return tmp0
+ }
+ static val %stable: Int = 0
+ }
+ val holderHolder: HolderHolder = HolderHolder()
+ @Composable
+ @ReadOnlyComposable
+ fun calculateSometing(%composer: Composer?, %changed: Int): Int {
+ sourceInformationMarkerStart(%composer, <>, "C(calculateSometing):Test.kt")
+ val tmp0 = 0
+ sourceInformationMarkerEnd(%composer)
+ return tmp0
+ }
+ @Composable
+ fun Test(%composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<curren...>,<calcul...>,<Layout>:Test.kt")
+ if (%changed !== 0 || !%composer.skipping) {
+ val c = holderHolder.current
+ val cl = calculateSometing(%composer, 0)
+ Layout({ %composer: Composer?, %changed: Int ->
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<Text("...>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Text("%c %cl", %composer, 0)
} else {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
index 9b4fca9..8ccb434 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
@@ -33,7 +33,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)")
if (%changed !== 0 || !%composer.skipping) {
A(a, %composer, 0)
A(b, %composer, 0)
@@ -86,7 +87,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)")
if (%changed !== 0 || !%composer.skipping) {
W(ComposableSingletons%TestKt.lambda-1, %composer, 0)
} else {
@@ -97,7 +99,7 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
A(%composer, 0)
} else {
@@ -121,7 +123,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)")
if (%changed !== 0 || !%composer.skipping) {
IW({ %composer: Composer?, %changed: Int ->
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt
index 1c8d410..0f07375 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt
@@ -63,7 +63,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(1)>,<B()>,<B(2)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(1)>,<B()>,<B(2)>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
A(1, %composer, 0b0110)
B(0, %composer, 0, 0b0001)
@@ -96,7 +97,8 @@
"""
@Composable
fun Example(foo: Foo, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)P(0:Foo):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)P(0:Foo):Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -117,7 +119,8 @@
}
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<Exampl...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<Exampl...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Example(Foo(0), %composer, 0, 0b0001)
} else {
@@ -145,7 +148,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(0,>,<A(a>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(0,>,<A(a>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
A(0, 1, 2, 0, 0, %composer, 0b000110110110, 0b00011000)
A(0, 0, 2, 0, 0, %composer, 0b000110000110, 0b00011010)
@@ -173,7 +177,8 @@
"""
@Composable
fun Test(x: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(x)) 0b0100 else 0b0010
@@ -217,7 +222,8 @@
"""
@Composable
fun A(a: Int, b: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A):Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -331,7 +337,8 @@
"""
@Composable
fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int, a24: Int, a25: Int, a26: Int, a27: Int, a28: Int, a29: Int, a30: Int, %composer: Composer?, %changed: Int, %changed1: Int, %changed2: Int, %changed3: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val %dirty = %changed
val %dirty1 = %changed1
val %dirty2 = %changed2
@@ -703,7 +710,8 @@
"""
@Composable
fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int, a24: Int, a25: Int, a26: Int, a27: Int, a28: Int, a29: Int, a30: Int, a31: Int, %composer: Composer?, %changed: Int, %changed1: Int, %changed2: Int, %changed3: Int, %default: Int, %default1: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val %dirty = %changed
val %dirty1 = %changed1
val %dirty2 = %changed2
@@ -1085,7 +1093,8 @@
"""
@Composable
fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Foo?, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int, a24: Int, a25: Int, a26: Int, a27: Int, a28: Int, a29: Int, a30: Int, a31: Foo?, %composer: Composer?, %changed: Int, %changed1: Int, %changed2: Int, %changed3: Int, %default: Int, %default1: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val %dirty = %changed
val %dirty1 = %changed1
val %dirty2 = %changed2
@@ -1419,7 +1428,8 @@
@NonRestartableComposable
@Composable
fun foo(x: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer.startReplaceableGroup(<>, "C(foo):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(foo):Test.kt")
if (%default and 0b0001 !== 0) {
x = 0
}
@@ -1432,7 +1442,8 @@
@NonRestartableComposable
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<foo()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<foo()>:Test.kt")
foo(0, %composer, 0b01110000 and %changed shl 0b0011, 0b0001)
%composer.endReplaceableGroup()
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
index 44c68ac..de0dc0e 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
@@ -72,7 +72,8 @@
"""
@Composable
fun Test(x: Int, y: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<Wrap>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<Wrap>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -92,14 +93,17 @@
y = 0
}
used(y)
- Wrap(composableLambda(%composer, <>, true, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ Wrap(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
if (x > 0) {
- %composer.startReplaceableGroup(<>, "<A(x)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(x)>")
A(x, 0, %composer, 0b1110 and %dirty, 0b0010)
%composer.endReplaceableGroup()
} else {
- %composer.startReplaceableGroup(<>, "<A(x)>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A(x)>")
A(x, 0, %composer, 0b1110 and %dirty, 0b0010)
%composer.endReplaceableGroup()
}
@@ -136,7 +140,8 @@
Example(class <no name provided> : A {
@Composable
override fun compute(it: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(compute)<comput...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(compute)<comput...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(it)) 0b0100 else 0b0010
@@ -186,7 +191,8 @@
"""
@Composable
fun Button(colors: ButtonColors, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Button)<getCol...>,<Text("...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Button)<getCol...>,<Text("...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(colors)) 0b0100 else 0b0010
@@ -202,12 +208,14 @@
}
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<Button>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<Button>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
Button(class <no name provided> : ButtonColors {
@Composable
override fun getColor(%composer: Composer?, %changed: Int): Color {
- %composer.startReplaceableGroup(<>, "C(getColor)<condit...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(getColor)<condit...>:Test.kt")
val tmp0 = if (condition(%composer, 0)) {
Companion.Red
} else {
@@ -308,7 +316,8 @@
"""
@Composable
fun RowColumnImpl(orientation: LayoutOrientation, modifier: Modifier?, arrangement: Vertical?, crossAxisAlignment: Horizontal?, crossAxisSize: SizeMode?, content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(RowColumnImpl)P(5,4!1,2,3)<conten...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(RowColumnImpl)P(5,4!1,2,3)<conten...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -376,7 +385,8 @@
}
@Composable
fun Column(modifier: Modifier?, verticalArrangement: Vertical?, horizontalGravity: Horizontal?, content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Column)P(2,3,1)<RowCol...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Column)P(2,3,1)<RowCol...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -448,7 +458,8 @@
"""
@Composable
fun SimpleBox(modifier: Modifier?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(SimpleBox):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(SimpleBox):Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -484,7 +495,8 @@
"""
@Composable
fun Example(a: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(a)) 0b0100 else 0b0010
@@ -531,7 +543,8 @@
"""
@Composable
fun Example(a: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<Inner(...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<Inner(...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
@@ -539,7 +552,8 @@
if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
@Composable
fun Inner(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Inner)<A(a)>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Inner)<A(a)>:Test.kt")
A(a, %composer, 0b1110 and %dirty)
%composer.endReplaceableGroup()
}
@@ -577,11 +591,13 @@
@Composable
@NonRestartableComposable
fun Example(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(Example)<Call()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Example)<Call()>:Test.kt")
Call(%composer, 0)
val tmp0_iterator = 0 .. 1.iterator()
while (tmp0_iterator.hasNext()) {
- %composer.startReplaceableGroup(<>, "<Call()>,<Call()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<Call()>,<Call()>")
val index = tmp0_iterator.next()
Call(%composer, 0)
if (condition()) {
@@ -621,7 +637,8 @@
"""
@Composable
fun SimpleBox(modifier: Modifier?, shape: Shape?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(SimpleBox):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(SimpleBox):Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -680,7 +697,8 @@
"""
@Composable
fun SimpleBox(modifier: Modifier?, content: Function2<Composer, Int, Unit>?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(SimpleBox)P(1)<conten...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(SimpleBox)P(1)<conten...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -717,7 +735,8 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
@@ -746,7 +765,8 @@
"""
val foo: Function4<Int, Foo, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
internal object ComposableSingletons%TestKt {
- val lambda-1: Function4<Int, Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A(x)>,<B(y)>:") { x: Int, y: Foo, %composer: Composer?, %changed: Int ->
+ val lambda-1: Function4<Int, Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { x: Int, y: Foo, %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<A(x)>,<B(y)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -781,7 +801,8 @@
"""
val foo: Function4<Int, Foo, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
internal object ComposableSingletons%TestKt {
- val lambda-1: Function4<Int, Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A(x)>,<B(y)>:") { x: Int, y: Foo, %composer: Composer?, %changed: Int ->
+ val lambda-1: Function4<Int, Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { x: Int, y: Foo, %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<A(x)>,<B(y)>:Test.kt")
A(x, %composer, 0b1110 and %changed)
B(y, %composer, 0b1000)
}
@@ -806,7 +827,8 @@
"""
@Composable
fun SomeThing(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(SomeThing)<conten...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(SomeThing)<conten...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
@@ -822,7 +844,8 @@
}
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<SomeTh...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<SomeTh...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
SomeThing(ComposableSingletons%TestKt.lambda-1, %composer, 0)
} else {
@@ -833,7 +856,8 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
val id = object
} else {
@@ -857,7 +881,8 @@
"""
@Composable
fun B(values: IntArray, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B):Test.kt")
val %dirty = %changed
%composer.startReplaceableGroup(values.size)
val tmp0_iterator = values.iterator()
@@ -896,7 +921,8 @@
"""
@Composable
fun B(values: Array<out Foo>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B):Test.kt")
val %dirty = %changed
%composer.startReplaceableGroup(values.size)
val tmp0_iterator = values.iterator()
@@ -935,7 +961,8 @@
"""
@Composable
fun B(values: Array<out Foo>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B):Test.kt")
print(values)
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
B(*values, %composer, %changed or 0b0001)
@@ -965,7 +992,8 @@
var counter: Int = 0
@Composable
fun A(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A):Test.kt")
if (%changed and 0b0001 !== 0 || !%composer.skipping) {
print("hello world")
} else {
@@ -978,7 +1006,8 @@
}
@Composable
fun B(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B):Test.kt")
print(counter)
val tmp0_rcvr = <this>
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -1006,7 +1035,8 @@
"""
@Composable
fun Example(a: Int, b: Int, c: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<makeIn...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<makeIn...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -1075,7 +1105,8 @@
"""
@Composable
fun Wrap(y: Int, content: Function3<@[ParameterName(name = 'x')] Int, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Wrap)P(1)<conten...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Wrap)P(1)<conten...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(y)) 0b0100 else 0b0010
@@ -1094,7 +1125,8 @@
}
@Composable
fun Test(x: Int, y: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<Wrap(1...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<Wrap(1...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -1114,7 +1146,8 @@
y = 0
}
used(y)
- Wrap(10, composableLambda(%composer, <>, true, "C<A(x)>:Test.kt") { it: Int, %composer: Composer?, %changed: Int ->
+ Wrap(10, composableLambda(%composer, <>, true) { it: Int, %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<A(x)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(it)) 0b0100 else 0b0010
@@ -1151,7 +1184,8 @@
"""
@Composable
fun Test(x: Int, y: Int, %composer: Composer?, %changed: Int, %default: Int): Int {
- %composer.startReplaceableGroup(<>, "C(Test)<A(x,>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(x,>:Test.kt")
if (%default and 0b0001 !== 0) {
x = 0
}
@@ -1179,7 +1213,8 @@
"""
val test: Function3<Int, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
internal object ComposableSingletons%TestKt {
- val lambda-1: Function3<Int, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A(x)>:") { x: Int, %composer: Composer?, %changed: Int ->
+ val lambda-1: Function3<Int, Composer, Int, Unit> = composableLambdaInstance(<>, false) { x: Int, %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<A(x)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -1205,7 +1240,8 @@
"""
@Composable
fun Test(x: Int, %composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(Test)<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Test)<A()>:Test.kt")
val tmp0 = A(0, 0, %composer, 0, 0b0011)
%composer.endReplaceableGroup()
return tmp0
@@ -1226,7 +1262,8 @@
"""
@Composable
fun Test(x: Int, y: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(y>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(y>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -1269,7 +1306,8 @@
"""
@Composable
fun CanSkip(a: Int, b: Foo?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(CanSkip):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(CanSkip):Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -1308,7 +1346,8 @@
}
@Composable
fun CannotSkip(a: Int, b: Foo, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(CannotSkip):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(CannotSkip):Test.kt")
used(a)
used(b)
print("Hello World")
@@ -1318,7 +1357,8 @@
}
@Composable
fun NoParams(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(NoParams):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(NoParams):Test.kt")
if (%changed !== 0 || !%composer.skipping) {
print("Hello World")
} else {
@@ -1345,7 +1385,8 @@
"""
@Composable
fun Bar.CanSkip(b: Foo?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(CanSkip):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(CanSkip):Test.kt")
val %dirty = %changed
if (%default.inv() and 0b0001 !== 0 || %dirty and 0b0001 !== 0 || !%composer.skipping) {
if (%changed and 0b0001 === 0 || %composer.defaultsInvalid) {
@@ -1387,7 +1428,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A()>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A()>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
A(%composer, 0)
} else {
@@ -1414,7 +1456,8 @@
"""
@Composable
fun Test(x: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(x)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -1455,7 +1498,8 @@
"""
@Composable
fun A(text: String, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<B(text...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<B(text...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(text)) 0b0100 else 0b0010
@@ -1471,7 +1515,8 @@
}
@Composable
fun B(text: String, color: Color, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(B)P(1,0:Color):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B)P(1,0:Color):Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -1563,7 +1608,8 @@
"""
@Composable
fun A(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<D>,<C({})>,<C(stab...>,<C(16.d...>,<C(Dp(1...>,<C(16.d...>,<C(norm...>,<C(Int....>,<C(stab...>,<C(Modi...>,<C(Foo....>,<C(cons...>,<C(123)>,<C(123>,<C(x)>,<C(x>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<D>,<C({})>,<C(stab...>,<C(16.d...>,<C(Dp(1...>,<C(16.d...>,<C(norm...>,<C(Int....>,<C(stab...>,<C(Modi...>,<C(Foo....>,<C(cons...>,<C(123)>,<C(123>,<C(x)>,<C(x>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
val x = 123
D(ComposableSingletons%TestKt.lambda-1, %composer, 0)
@@ -1592,7 +1638,8 @@
}
@Composable
fun B(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(B)<C(Math...>,<C(Math...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(B)<C(Math...>,<C(Math...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
C(random(), %composer, 0)
C(random() / 100.0f, %composer, 0)
@@ -1604,7 +1651,8 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
@@ -1628,7 +1676,8 @@
"""
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<D>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<D>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
D(ComposableSingletons%TestKt.lambda-1, %composer, 0)
} else {
@@ -1639,7 +1688,8 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
@@ -1664,7 +1714,8 @@
"""
@Composable
fun Test(x: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(x)>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -1701,7 +1752,8 @@
"""
@Composable
fun Test(x: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<I()>,<A(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<I()>,<A(x)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(x)) 0b0100 else 0b0010
@@ -1746,7 +1798,8 @@
"""
@Composable
fun Test(x: Foo, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(x)>:Test.kt")
A(x, %composer, 0b1000)
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Test(x, %composer, %changed or 0b0001)
@@ -1770,7 +1823,8 @@
"""
@Composable
fun Test(x: Foo?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(x)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(x)) 0b0100 else 0b0010
@@ -1815,7 +1869,8 @@
"""
@Composable
fun Test(a: Int, b: Boolean, c: Int, d: Foo?, e: List<Int>?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(a,>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(a,>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -1887,7 +1942,8 @@
"""
@Composable
fun X(x: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(X)<X(x>,<X(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(X)<X(x>,<X(x)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -1948,7 +2004,8 @@
itemsIndexed(items, ComposableSingletons%TestKt.lambda-1)
}
internal object ComposableSingletons%TestKt {
- val lambda-1: @[ExtensionFunctionType] Function5<LazyItemScope, Int, User?, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { index: Int, user: User?, %composer: Composer?, %changed: Int ->
+ val lambda-1: @[ExtensionFunctionType] Function5<LazyItemScope, Int, User?, Composer, Int, Unit> = composableLambdaInstance(<>, false) { index: Int, user: User?, %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b0001010000000001 xor 0b010000000000 !== 0 || !%composer.skipping) {
print("Hello World")
} else {
@@ -1974,7 +2031,8 @@
"""
@Composable
fun Unstable.Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<doSome...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<doSome...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(<this>)) 0b0100 else 0b0010
@@ -1990,7 +2048,8 @@
}
@Composable
fun doSomething(x: Unstable, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(doSomething):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(doSomething):Test.kt")
if (%changed and 0b0001 !== 0 || !%composer.skipping) {
} else {
%composer.skipToGroupEnd()
@@ -2074,7 +2133,8 @@
"""
@Composable
fun A(x: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<B(>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<B(>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -2118,14 +2178,16 @@
val stableUnused: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-3
val stableUsed: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-4
internal object ComposableSingletons%TestKt {
- val lambda-1: @[ExtensionFunctionType] Function3<Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+ val lambda-1: @[ExtensionFunctionType] Function3<Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b01010001 xor 0b00010000 !== 0 || !%composer.skipping) {
Unit
} else {
%composer.skipToGroupEnd()
}
}
- val lambda-2: @[ExtensionFunctionType] Function3<Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+ val lambda-2: @[ExtensionFunctionType] Function3<Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(%this%null)) 0b0100 else 0b0010
@@ -2136,14 +2198,16 @@
%composer.skipToGroupEnd()
}
}
- val lambda-3: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+ val lambda-3: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b01010001 xor 0b00010000 !== 0 || !%composer.skipping) {
Unit
} else {
%composer.skipToGroupEnd()
}
}
- val lambda-4: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+ val lambda-4: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(%this%null)) 0b0100 else 0b0010
@@ -2179,19 +2243,22 @@
"""
@Composable
fun A(x: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<Provid...>,<B(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<Provid...>,<B(x)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
- Provide(composableLambda(%composer, <>, true, "C<Provid...>,<B(x,>:Test.kt") { y: Int, %composer: Composer?, %changed: Int ->
+ Provide(composableLambda(%composer, <>, true) { y: Int, %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<Provid...>,<B(x,>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(y)) 0b0100 else 0b0010
}
if (%dirty and 0b01011011 xor 0b00010010 !== 0 || !%composer.skipping) {
- Provide(composableLambda(%composer, <>, true, "C<B(x,>:Test.kt") { z: Int, %composer: Composer?, %changed: Int ->
+ Provide(composableLambda(%composer, <>, true) { z: Int, %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<B(x,>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(z)) 0b0100 else 0b0010
@@ -2235,7 +2302,8 @@
"""
@Composable
fun A(x: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<foo(x)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<foo(x)>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -2243,7 +2311,8 @@
if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
@Composable
fun foo(y: Int, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(foo)<B(x,>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(foo)<B(x,>:Test.kt")
B(x, y, %composer, 0b1110 and %dirty or 0b01110000 and %changed shl 0b0011)
%composer.endReplaceableGroup()
}
@@ -2322,7 +2391,8 @@
"""
@Composable
fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, %composer: Composer?, %changed: Int, %changed1: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<Exampl...>,<Exampl...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<Exampl...>,<Exampl...>:Test.kt")
val %dirty = %changed
val %dirty1 = %changed1
if (%default and 0b0001 !== 0) {
@@ -2525,7 +2595,8 @@
"""
@Composable
fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, %composer: Composer?, %changed: Int, %changed1: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<Exampl...>,<Exampl...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<Exampl...>,<Exampl...>:Test.kt")
val %dirty = %changed
val %dirty1 = %changed1
if (%default and 0b0001 !== 0) {
@@ -2696,17 +2767,17 @@
val current: Int
@Composable @ReadOnlyComposable @JvmName(name = "getCurrent")
get() {
- %composer.startReplaceableGroup(<>, "C:Test.kt")
+ sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
val tmp0 = %composer.hashCode()
- %composer.endReplaceableGroup()
+ sourceInformationMarkerEnd(%composer)
return tmp0
}
@ReadOnlyComposable
@Composable
fun getHashCode(%composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(getHashCode):Test.kt")
+ sourceInformationMarkerStart(%composer, <>, "C(getHashCode):Test.kt")
val tmp0 = %composer.hashCode()
- %composer.endReplaceableGroup()
+ sourceInformationMarkerEnd(%composer)
return tmp0
}
static val %stable: Int = 0
@@ -2714,9 +2785,9 @@
@ReadOnlyComposable
@Composable
fun getHashCode(%composer: Composer?, %changed: Int): Int {
- %composer.startReplaceableGroup(<>, "C(getHashCode):Test.kt")
+ sourceInformationMarkerStart(%composer, <>, "C(getHashCode):Test.kt")
val tmp0 = %composer.hashCode()
- %composer.endReplaceableGroup()
+ sourceInformationMarkerEnd(%composer)
return tmp0
}
"""
@@ -2743,7 +2814,8 @@
"""
@Composable
fun Example(wontChange: Int, mightChange: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)P(1)<curren...>,<A(wont...>,<A(migh...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)P(1)<curren...>,<A(wont...>,<A(migh...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -2794,7 +2866,8 @@
"""
@Composable
fun Example(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<invoke...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<invoke...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
@@ -2854,7 +2927,8 @@
"""
@Composable
fun Box2(modifier: Modifier?, paddingStart: Dp, content: Function2<Composer, Int, Unit>?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Box2)P(1,2:c#ui.unit.Dp)<conten...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Box2)P(1,2:c#ui.unit.Dp)<conten...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -2902,7 +2976,8 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
@@ -2933,14 +3008,16 @@
"""
@Composable
fun Test(cond: Boolean, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(cond)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
if (cond) {
- %composer.startReplaceableGroup(<>, "<A()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A()>")
A(%composer, 0)
%composer.endReplaceableGroup()
} else {
@@ -2948,7 +3025,8 @@
%composer.endReplaceableGroup()
}
if (cond) {
- %composer.startReplaceableGroup(<>, "<B()>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<B()>")
B(%composer, 0)
%composer.endReplaceableGroup()
} else {
@@ -2991,7 +3069,8 @@
"""
@Composable
fun Unskippable(a: Unstable, b: Stable, c: MaybeStable, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Unskippable):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Unskippable):Test.kt")
used(a)
%composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
Unskippable(a, b, c, %composer, %changed or 0b0001)
@@ -2999,7 +3078,8 @@
}
@Composable
fun Skippable1(a: Unstable, b: Stable, c: MaybeStable, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Skippable1):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Skippable1):Test.kt")
val %dirty = %changed
if (%changed and 0b01110000 === 0) {
%dirty = %dirty or if (%composer.changed(b)) 0b00100000 else 0b00010000
@@ -3015,7 +3095,8 @@
}
@Composable
fun Skippable2(a: Unstable, b: Stable, c: MaybeStable, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Skippable2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Skippable2):Test.kt")
val %dirty = %changed
if (%changed and 0b001110000000 === 0) {
%dirty = %dirty or if (%composer.changed(c)) 0b000100000000 else 0b10000000
@@ -3031,7 +3112,8 @@
}
@Composable
fun Skippable3(a: Unstable, b: Stable, c: MaybeStable, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Skippable3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Skippable3):Test.kt")
if (%changed and 0b0001 !== 0 || !%composer.skipping) {
} else {
%composer.skipToGroupEnd()
@@ -3061,7 +3143,8 @@
"""
@Composable
fun MaybeStable.example(x: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(example):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(example):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(<this>)) 0b0100 else 0b0010
@@ -3081,7 +3164,8 @@
}
val example: @[ExtensionFunctionType] Function4<MaybeStable, Int, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
internal object ComposableSingletons%TestKt {
- val lambda-1: @[ExtensionFunctionType] Function4<MaybeStable, Int, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { it: Int, %composer: Composer?, %changed: Int ->
+ val lambda-1: @[ExtensionFunctionType] Function4<MaybeStable, Int, Composer, Int, Unit> = composableLambdaInstance(<>, false) { it: Int, %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(%this%null)) 0b0100 else 0b0010
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
index 838f9bf..1946801 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
@@ -36,7 +36,8 @@
@StabilityInferred(parameters = 0)
class A {
val b: String = ""
- val c: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, true, "C:") { %composer: Composer?, %changed: Int ->
+ val c: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
print(b)
} else {
@@ -66,23 +67,28 @@
"""
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example):Test.kt")
if (%changed !== 0 || !%composer.skipping) {
@Composable
fun A(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(A):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(A):Test.kt")
%composer.endReplaceableGroup()
}
@Composable
fun B(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(B)<conten...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(B)<conten...>:Test.kt")
content(%composer, 0b1110 and %changed)
%composer.endReplaceableGroup()
}
@Composable
fun C(%composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(C)<B>:Test.kt")
- B(composableLambda(%composer, <>, false, "C<A()>:Test.kt") { %composer: Composer?, %changed: Int ->
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(C)<B>:Test.kt")
+ B(composableLambda(%composer, <>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<A()>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
A(%composer, 0)
} else {
@@ -120,10 +126,12 @@
"""
@Composable
fun A(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<B>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<B>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
<<LOCALDELPROP>>
- B(composableLambda(%composer, <>, true, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ B(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
print(<get-x>())
} else {
@@ -157,14 +165,16 @@
val foo: Function2<Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
val bar: Function2<Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-2
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
%composer.skipToGroupEnd()
}
}
- val lambda-2: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+ val lambda-2: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
@@ -192,7 +202,8 @@
"""
@Composable
fun A(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<B(foo)>,<B(bar)>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<B(foo)>,<B(bar)>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
val foo = ComposableSingletons%TestKt.lambda-1
val bar = ComposableSingletons%TestKt.lambda-2
@@ -206,14 +217,16 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
%composer.skipToGroupEnd()
}
}
- val lambda-2: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-2: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
@@ -240,7 +253,8 @@
"""
@Composable
fun A(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(A)<B>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(A)<B>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
B(ComposableSingletons%TestKt.lambda-1, %composer, 0)
} else {
@@ -251,7 +265,8 @@
}
}
internal object ComposableSingletons%TestKt {
- val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+ val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Unit
} else {
@@ -282,7 +297,8 @@
"""
@Composable
fun Test(enabled: Boolean, content: Function2<Composer, Int, Unit>?, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)P(1)<Wrap(c...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)P(1)<Wrap(c...>:Test.kt")
val %dirty = %changed
if (%default and 0b0001 !== 0) {
%dirty = %dirty or 0b0110
@@ -296,7 +312,8 @@
}
if (%dirty and 0b01011011 xor 0b00010010 !== 0 || !%composer.skipping) {
if (%default and 0b0010 !== 0) {
- content = composableLambda(%composer, <>, true, "C<Displa...>:Test.kt") { %composer: Composer?, %changed: Int ->
+ content = composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<Displa...>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Display("%enabled", %composer, 0)
} else {
@@ -337,13 +354,15 @@
"""
@Composable
fun Test(enabled: Boolean, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<Wrap(c...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<Wrap(c...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(enabled)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
- val content = composableLambda(%composer, <>, true, "C<Displa...>:Test.kt") { %composer: Composer?, %changed: Int ->
+ val content = composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<Displa...>:Test.kt")
if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
Display("%enabled", %composer, 0)
} else {
@@ -388,7 +407,8 @@
"""
@Composable
fun TestLambda(content: Function0<Unit>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(TestLambda):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(TestLambda):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
@@ -404,7 +424,8 @@
}
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<TestLa...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<TestLa...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
TestLambda({
println("Doesn't capture")
@@ -440,7 +461,8 @@
"""
@Composable
fun TestLambda(content: Function0<Unit>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(TestLambda):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(TestLambda):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
@@ -456,7 +478,8 @@
}
@Composable
fun Test(a: String, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<{>,<TestLa...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<{>,<TestLa...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
index 51532ad..e0863c1 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
@@ -62,7 +62,8 @@
@Composable
@NonRestartableComposable
fun app(x: Boolean, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(app)<rememb...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(app)<rememb...>:Test.kt")
val a = if (x) {
%composer.startReplaceableGroup(<>)
val tmp0_group = %composer.cache(false) {
@@ -105,7 +106,8 @@
"""
@Composable
fun <T> loadResourceInternal(key: String, pendingResource: T?, failedResource: T?, %composer: Composer?, %changed: Int, %default: Int): Boolean {
- %composer.startReplaceableGroup(<>, "C(loadResourceInternal)P(1,2):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(loadResourceInternal)P(1,2):Test.kt")
if (%default and 0b0010 !== 0) {
pendingResource = null
}
@@ -147,7 +149,8 @@
"""
@Composable
fun test1(x: KnownStable, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(test1):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(test1):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -166,7 +169,8 @@
}
@Composable
fun test2(x: KnownUnstable, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(test2):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(test2):Test.kt")
%composer.cache(%composer.changed(x)) {
val tmp0_return = 1
tmp0_return
@@ -177,7 +181,8 @@
}
@Composable
fun test3(x: Uncertain, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(test3):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(test3):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -227,7 +232,8 @@
@Composable
@NonRestartableComposable
fun test1(x: KnownStable, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(test1):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(test1):Test.kt")
%composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(x) || %changed and 0b0110 === 0b0100) {
val tmp0_return = 1
tmp0_return
@@ -237,7 +243,8 @@
@Composable
@NonRestartableComposable
fun test2(x: KnownUnstable, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(test2):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(test2):Test.kt")
%composer.cache(%composer.changed(x)) {
val tmp0_return = 1
tmp0_return
@@ -247,7 +254,8 @@
@Composable
@NonRestartableComposable
fun test3(x: Uncertain, %composer: Composer?, %changed: Int) {
- %composer.startReplaceableGroup(<>, "C(test3):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(test3):Test.kt")
%composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(x) || %changed and 0b0110 === 0b0100) {
val tmp0_return = 1
tmp0_return
@@ -269,7 +277,8 @@
"""
@Composable
fun rememberFoo(a: Int, b: Int, %composer: Composer?, %changed: Int): Foo {
- %composer.startReplaceableGroup(<>, "C(rememberFoo):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(rememberFoo):Test.kt")
val tmp0 = %composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(a) || %changed and 0b0110 === 0b0100 or %changed and 0b01110000 xor 0b00110000 > 32 && %composer.changed(b) || %changed and 0b00110000 === 0b00100000) {
val tmp0_return = Foo(a, b)
tmp0_return
@@ -298,7 +307,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A()>,<rememb...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A()>,<rememb...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
val foo = %composer.cache(false) {
val tmp0_return = Foo()
@@ -340,7 +350,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt")
if (%changed !== 0 || !%composer.skipping) {
val a = someInt()
val b = someInt()
@@ -373,7 +384,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<CInt()...>,<rememb...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<CInt()...>,<rememb...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
val foo = remember(CInt(%composer, 0), {
val tmp0_return = Foo()
@@ -408,7 +420,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<curren...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<curren...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
val bar = compositionLocalBar.current
val foo = %composer.cache(%composer.changed(bar)) {
@@ -443,7 +456,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<curren...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<curren...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
val foo = %composer.cache(%composer.changed(compositionLocalBar.current)) {
val tmp0_return = Foo()
@@ -475,7 +489,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A()>,<rememb...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A()>,<rememb...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
A(%composer, 0)
val foo = remember({
@@ -510,7 +525,8 @@
"""
@Composable
fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A()>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A()>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
@@ -556,14 +572,16 @@
"""
@Composable
fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
}
if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
if (condition) {
- %composer.startReplaceableGroup(<>, "<A()>,<rememb...>")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "<A()>,<rememb...>")
A(%composer, 0)
val foo = remember({
val tmp0_return = Foo()
@@ -602,7 +620,8 @@
"""
@Composable
fun Test(items: List<Int>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)*<rememb...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)*<rememb...>:Test.kt")
val tmp0_iterator = items.iterator()
while (tmp0_iterator.hasNext()) {
val item = tmp0_iterator.next()
@@ -640,7 +659,8 @@
"""
@Composable
fun Test(items: List<Int>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)*<rememb...>,<A()>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)*<rememb...>,<A()>:Test.kt")
val tmp0_iterator = items.iterator()
while (tmp0_iterator.hasNext()) {
val item = tmp0_iterator.next()
@@ -674,7 +694,8 @@
"""
@Composable
fun Test(items: List<Int>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt")
val foo = %composer.cache(false) {
val tmp0_return = Foo()
tmp0_return
@@ -702,7 +723,8 @@
"""
@Composable
fun Test(a: Int, b: Int, c: Bar, d: Boolean, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
@@ -746,7 +768,8 @@
"""
@Composable
fun Test(items: Array<Bar>, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<rememb...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<rememb...>:Test.kt")
val foo = remember(*items, {
val tmp0_return = Foo()
tmp0_return
@@ -774,7 +797,8 @@
"""
@Composable
fun Test(inlineInt: InlineInt, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)P(0:InlineInt):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)P(0:InlineInt):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(inlineInt.value)) 0b0100 else 0b0010
@@ -815,7 +839,8 @@
"""
@Composable
fun Test(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt")
if (%changed !== 0 || !%composer.skipping) {
val a = someInt()
val b = someInt()
@@ -855,7 +880,8 @@
"""
@Composable
fun Test(a: Int, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
@@ -892,7 +918,8 @@
"""
@Composable
fun Test(a: Int, %composer: Composer?, %changed: Int): Foo {
- %composer.startReplaceableGroup(<>, "C(Test):Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Test):Test.kt")
val b = someInt()
val tmp0 = %composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(a) || %changed and 0b0110 === 0b0100 or %composer.changed(b)) {
val tmp0_return = Foo(a, b)
@@ -921,7 +948,8 @@
"""
@Composable
fun Test(a: Int, %composer: Composer?, %changed: Int, %default: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<rememb...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<rememb...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(a)) 0b0100 else 0b0010
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/StabilityPropagationTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/StabilityPropagationTransformTests.kt
index b434332..a056dcd 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/StabilityPropagationTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/StabilityPropagationTransformTests.kt
@@ -61,7 +61,8 @@
"""
@Composable
fun Test(x: Foo, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>,<A(Foo(...>,<rememb...>,<A(reme...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(x)>,<A(Foo(...>,<rememb...>,<A(reme...>:Test.kt")
val %dirty = %changed
if (%changed and 0b1110 === 0) {
%dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -102,7 +103,8 @@
"""
@Composable
fun Test(x: Foo, %composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>,<A(Foo(...>,<rememb...>,<A(reme...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Test)<A(x)>,<A(Foo(...>,<rememb...>,<A(reme...>:Test.kt")
A(x, %composer, 0b1000)
A(Foo(0), %composer, 0b1000)
A(remember({
@@ -130,7 +132,8 @@
"""
@Composable
fun Example(%composer: Composer?, %changed: Int) {
- %composer = %composer.startRestartGroup(<>, "C(Example)<A(list...>:Test.kt")
+ %composer = %composer.startRestartGroup(<>)
+ sourceInformation(%composer, "C(Example)<A(list...>:Test.kt")
if (%changed !== 0 || !%composer.skipping) {
A(listOf("a"), %composer, 0)
} else {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt
index 4b9c724..4db291b 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt
@@ -12,4 +12,7 @@
val STARTRESTARTGROUP = Name.identifier("startRestartGroup")
val ENDRESTARTGROUP = Name.identifier("endRestartGroup")
val UPDATE_SCOPE = Name.identifier("updateScope")
+ val SOURCEINFORMATION = "sourceInformation"
+ val SOURCEINFORMATIONMARKERSTART = "sourceInformationMarkerStart"
+ val SOURCEINFORMATIONMARKEREND = "sourceInformationMarkerEnd"
}
\ No newline at end of file
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 56ec08a..c448eee 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -515,13 +515,6 @@
}
}
- private val startReplaceableSourceFunction by guardedLazy {
- composerIrClass.functions
- .first {
- it.name.identifier == "startReplaceableGroup" && it.valueParameters.size == 2
- }
- }
-
private val endReplaceableFunction by guardedLazy {
composerIrClass.functions
.first {
@@ -550,13 +543,6 @@
}
}
- private val startMovableSourceFunction by guardedLazy {
- composerIrClass.functions
- .first {
- it.name.identifier == "startMovableGroup" && it.valueParameters.size == 3
- }
- }
-
private val endMovableFunction by guardedLazy {
composerIrClass.functions
.first {
@@ -572,13 +558,6 @@
}
}
- private val startRestartGroupSourceFunction by guardedLazy {
- composerIrClass.functions
- .first {
- it.name == KtxNameConventions.STARTRESTARTGROUP && it.valueParameters.size == 2
- }
- }
-
private val endRestartGroupFunction by guardedLazy {
composerIrClass
.functions
@@ -587,6 +566,24 @@
}
}
+ private val sourceInformationFunction by guardedLazy {
+ getTopLevelFunctions(
+ ComposeFqNames.fqNameFor(KtxNameConventions.SOURCEINFORMATION)
+ ).map { it.owner }.first()
+ }
+
+ private val sourceInformationMarkerStartFunction by guardedLazy {
+ getTopLevelFunctions(
+ ComposeFqNames.fqNameFor(KtxNameConventions.SOURCEINFORMATIONMARKERSTART)
+ ).map { it.owner }.first()
+ }
+
+ private val sourceInformationMarkerEndFunction by guardedLazy {
+ getTopLevelFunctions(
+ ComposeFqNames.fqNameFor(KtxNameConventions.SOURCEINFORMATIONMARKEREND)
+ ).map { it.owner }.first()
+ }
+
private val IrType.arguments: List<IrTypeArgument>
get() = (this as? IrSimpleType)?.arguments.orEmpty()
@@ -768,7 +765,7 @@
@OptIn(ObsoleteDescriptorBasedAPI::class)
private fun IrFunction.shouldElideGroups(): Boolean {
- return (!collectSourceInformation && descriptor.hasReadonlyComposableAnnotation()) ||
+ return descriptor.hasReadonlyComposableAnnotation() ||
descriptor.hasExplicitGroupsAnnotation()
}
@@ -822,17 +819,31 @@
body.startOffset,
body.endOffset,
listOfNotNull(
- if (!elideGroups)
- irStartReplaceableGroup(
- body,
- scope,
- declaration.irSourceKey()
- )
- else
- null,
+ when {
+ !elideGroups ->
+ irStartReplaceableGroup(
+ body,
+ scope,
+ declaration.irSourceKey()
+ )
+ collectSourceInformation &&
+ !declaration.descriptor.hasExplicitGroupsAnnotation() ->
+ irSourceInformationMarkerStart(
+ body,
+ scope,
+ declaration.irSourceKey()
+ )
+ else -> null
+ },
*bodyPreamble.statements.toTypedArray(),
*transformed.statements.toTypedArray(),
- if (!elideGroups) irEndReplaceableGroup() else null,
+ when {
+ !elideGroups -> irEndReplaceableGroup()
+ collectSourceInformation &&
+ !declaration.descriptor.hasExplicitGroupsAnnotation() ->
+ irSourceInformationMarkerEnd(body)
+ else -> null
+ },
returnVar?.let { irReturn(declaration.symbol, irGet(it)) }
)
)
@@ -855,9 +866,15 @@
// no group, since composableLambda should already create one
// no default logic
val body = declaration.body!!
+ val sourceInformationPreamble = mutableStatementContainer()
val skipPreamble = mutableStatementContainer()
val bodyPreamble = mutableStatementContainer()
+ // First generate the source information call
+ if (collectSourceInformation && !scope.isInlinedLambda) {
+ sourceInformationPreamble.statements.add(irSourceInformation(scope))
+ }
+
// we start off assuming that we *can* skip execution of the function
var canSkipExecution = declaration.returnType.isUnit() &&
scope.allTrackedParams.none { stabilityOf(it.type).knownUnstable() }
@@ -927,6 +944,7 @@
if (collectSourceInformation && scope.isInlinedLambda)
irStartReplaceableGroup(body, scope)
else null,
+ *sourceInformationPreamble.statements.toTypedArray(),
*skipPreamble.statements.toTypedArray(),
*bodyPreamble.statements.toTypedArray(),
transformedBody,
@@ -941,6 +959,7 @@
body.startOffset,
body.endOffset,
listOfNotNull(
+ *sourceInformationPreamble.statements.toTypedArray(),
*skipPreamble.statements.toTypedArray(),
*bodyPreamble.statements.toTypedArray(),
transformed,
@@ -1740,16 +1759,61 @@
scope: Scope.BlockScope,
key: IrExpression = element.irSourceKey()
): IrExpression {
- val startDescriptor = if (scope.hasSourceInformation)
- startReplaceableSourceFunction else startReplaceableFunction
- return irMethodCall(
- irCurrentComposer(),
- startDescriptor
+ return irWithSourceInformation(
+ irMethodCall(
+ irCurrentComposer(),
+ startReplaceableFunction
+ ).also {
+ it.putValueArgument(0, key)
+ },
+ scope
+ )
+ }
+
+ private fun irWithSourceInformation(
+ startGroup: IrExpression,
+ scope: Scope.BlockScope
+ ): IrExpression {
+ return if (scope.hasSourceInformation) {
+ irBlock(statements = listOf(startGroup, irSourceInformation(scope)))
+ } else startGroup
+ }
+
+ private fun irSourceInformation(scope: Scope.BlockScope): IrExpression {
+ val sourceInformation = irCall(
+ sourceInformationFunction
).also {
- it.putValueArgument(0, key)
- if (scope.hasSourceInformation) {
- recordSourceParameter(it, 1, scope)
- }
+ it.putValueArgument(0, irCurrentComposer())
+ }
+ recordSourceParameter(sourceInformation, 1, scope)
+ return sourceInformation
+ }
+
+ private fun irSourceInformationMarkerStart(
+ element: IrElement,
+ scope: Scope.BlockScope,
+ key: IrExpression = element.irSourceKey(),
+ ): IrExpression {
+ return irCall(
+ sourceInformationMarkerStartFunction,
+ element.startOffset,
+ element.endOffset
+ ).also {
+ it.putValueArgument(0, irCurrentComposer())
+ it.putValueArgument(1, key)
+ recordSourceParameter(it, 2, scope)
+ }
+ }
+
+ private fun irSourceInformationMarkerEnd(
+ element: IrElement,
+ ): IrExpression {
+ return irCall(
+ sourceInformationMarkerEndFunction,
+ element.startOffset,
+ element.endOffset
+ ).also {
+ it.putValueArgument(0, irCurrentComposer())
}
}
@@ -1767,21 +1831,19 @@
scope: Scope.BlockScope,
key: IrExpression = element.irSourceKey()
): IrExpression {
- val startDescriptor = if (scope.hasSourceInformation)
- startRestartGroupSourceFunction else startRestartGroupFunction
- return irSet(
- nearestComposer(),
- irMethodCall(
- irCurrentComposer(),
- startDescriptor,
- element.startOffset,
- element.endOffset
- ).also {
- it.putValueArgument(0, key)
- if (scope.hasSourceInformation) {
- recordSourceParameter(it, 1, scope)
+ return irWithSourceInformation(
+ irSet(
+ nearestComposer(),
+ irMethodCall(
+ irCurrentComposer(),
+ startRestartGroupFunction,
+ element.startOffset,
+ element.endOffset
+ ).also {
+ it.putValueArgument(0, key)
}
- }
+ ),
+ scope
)
}
@@ -1854,23 +1916,18 @@
joinedData: IrExpression,
scope: Scope.BlockScope
): IrExpression {
- val startFunction = if (scope.hasSourceInformation) {
- startMovableSourceFunction
- } else {
- startMovableFunction
- }
- return irMethodCall(
- irCurrentComposer(),
- startFunction,
- element.startOffset,
- element.endOffset
- ).also {
- it.putValueArgument(0, element.irSourceKey())
- it.putValueArgument(1, joinedData)
- if (scope.hasSourceInformation) {
- recordSourceParameter(it, 2, scope)
- }
- }
+ return irWithSourceInformation(
+ irMethodCall(
+ irCurrentComposer(),
+ startMovableFunction,
+ element.startOffset,
+ element.endOffset
+ ).also {
+ it.putValueArgument(0, element.irSourceKey())
+ it.putValueArgument(1, joinedData)
+ },
+ scope
+ )
}
private fun irEndMovableGroup(): IrExpression {
@@ -2372,17 +2429,6 @@
expression
}
}
- collectSourceInformation &&
- expression.symbol.descriptor.fqNameSafe == ComposeFqNames.composableLambda -> {
- // For calls to `composableLambda` we introduce a scope to collect the source
- // locations on the top level of the lambda as the startRestartGroup is in the
- // composable lambda wrapper.
- val composableLambdaScope = withScope(Scope.ComposableLambdaScope()) {
- expression.transformChildrenVoid()
- }
- recordSourceParameter(expression, 3, composableLambdaScope)
- return expression
- }
expression.isComposableSingletonGetter() -> {
// This looks like `ComposableSingletonClass.lambda-123`, which is a static/saved
// call of composableLambdaInstance. We want to transform the property here now
@@ -2393,18 +2439,6 @@
property?.transformChildrenVoid()
return super.visitCall(expression)
}
- collectSourceInformation &&
- expression.symbol.descriptor.fqNameSafe ==
- ComposeFqNames.composableLambdaInstance -> {
- // For calls to `composableLambdaInstance` that are not singletons we introduce a
- // scope to collect the source locations on the top level of the lambda as the
- // startRestartGroup is in the composable lambda wrapper.
- val composableLambdaScope = withScope(Scope.ComposableLambdaScope()) {
- expression.transformChildrenVoid()
- }
- recordSourceParameter(expression, 2, composableLambdaScope)
- return expression
- }
else -> return super.visitCall(expression)
}
}
@@ -3329,13 +3363,9 @@
override fun calculateSourceInfo(sourceInformationEnabled: Boolean): String? =
if (sourceInformationEnabled) {
- if (function.isLambda() && !isInlinedLambda) {
- super.calculateSourceInfo(sourceInformationEnabled)
- } else {
- "${callInformation()}${parameterInformation()}${
- super.calculateSourceInfo(sourceInformationEnabled) ?: ""
- }:${sourceFileInformation()}"
- }
+ "${callInformation()}${parameterInformation()}${
+ super.calculateSourceInfo(sourceInformationEnabled) ?: ""
+ }:${sourceFileInformation()}"
} else {
if (function.visibility.isPublicAPI) {
"${callInformation()}${parameterInformation()}"
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index 0bc3dcc..131e734 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -48,7 +48,6 @@
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irGetField
import org.jetbrains.kotlin.ir.builders.irInt
-import org.jetbrains.kotlin.ir.builders.irNull
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.builders.irTemporary
import org.jetbrains.kotlin.ir.declarations.IrAttributeContainer
@@ -687,9 +686,6 @@
val shouldBeTracked = collector.captures.isNotEmpty()
putValueArgument(index++, irBuilder.irBoolean(shouldBeTracked))
- // sourceInformation parameter
- putValueArgument(index++, irBuilder.irNull())
-
// ComposableLambdaN requires the arity
if (useComposableLambdaN) {
// arity parameter
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
index 66665ef..26d96f2 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
@@ -1762,6 +1762,35 @@
assertTrue(latch.await(1, TimeUnit.SECONDS))
assertEquals(size, rowWidth)
}
+
+ @Test
+ fun testRow_includesSpacing_withWeightChildren() = with(density) {
+ val rowWidth = 40
+ val space = 8
+ val latch = CountDownLatch(2)
+ show {
+ Row(
+ modifier = Modifier.widthIn(max = rowWidth.toDp()),
+ horizontalArrangement = Arrangement.spacedBy(space.toDp())
+ ) {
+ Box(
+ Modifier.weight(1f).onGloballyPositioned {
+ assertEquals((rowWidth - space) / 2, it.size.width)
+ assertEquals(0, it.positionInRoot().x.toInt())
+ latch.countDown()
+ }
+ )
+ Box(
+ Modifier.weight(1f).onGloballyPositioned {
+ assertEquals((rowWidth - space) / 2, it.size.width)
+ assertEquals((rowWidth - space) / 2 + space, it.positionInRoot().x.toInt())
+ latch.countDown()
+ }
+ )
+ }
+ }
+ assertTrue(latch.await(1, TimeUnit.SECONDS))
+ }
// endregion
// region Size tests in Column
@@ -2271,6 +2300,38 @@
assertTrue(latch.await(1, TimeUnit.SECONDS))
assertEquals(size, columnHeight)
}
+
+ @Test
+ fun testColumn_includesSpacing_withWeightChildren() = with(density) {
+ val columnHeight = 40
+ val space = 8
+ val latch = CountDownLatch(2)
+ show {
+ Column(
+ modifier = Modifier.height(columnHeight.toDp()),
+ verticalArrangement = Arrangement.spacedBy(space.toDp())
+ ) {
+ Box(
+ Modifier.weight(1f).onGloballyPositioned {
+ assertEquals((columnHeight - space) / 2, it.size.height)
+ assertEquals(0, it.positionInRoot().y.toInt())
+ latch.countDown()
+ }
+ )
+ Box(
+ Modifier.weight(1f).onGloballyPositioned {
+ assertEquals((columnHeight - space) / 2, it.size.height)
+ assertEquals(
+ (columnHeight - space) / 2 + space,
+ it.positionInRoot().y.toInt()
+ )
+ latch.countDown()
+ }
+ )
+ }
+ }
+ assertTrue(latch.await(1, TimeUnit.SECONDS))
+ }
// endregion
// region Main axis alignment tests in Row
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
index 9728409..0a5320d 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
@@ -563,12 +563,12 @@
outPositions: IntArray
) {
if (sizes.isEmpty()) return
- val spacePx = with(density) { space.roundToPx() }
+ val spacePx = space.roundToPx()
var occupied = 0
var lastSpace = 0
- if (layoutDirection == LayoutDirection.Rtl && rtlMirror) sizes.reverse()
- sizes.forEachIndexed { index, it ->
+ val reversed = rtlMirror && layoutDirection == LayoutDirection.Rtl
+ sizes.forEachIndexed(reversed) { index, it ->
outPositions[index] = min(occupied, totalSize - it)
lastSpace = min(spacePx, totalSize - outPositions[index] - it)
occupied = outPositions[index] + it + lastSpace
@@ -581,8 +581,6 @@
outPositions[index] += groupPosition
}
}
-
- if (layoutDirection == LayoutDirection.Rtl && rtlMirror) outPositions.reverse()
}
override fun Density.arrange(
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index e20038e..83a2f34 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -162,6 +162,8 @@
placeables[i] = placeable
}
}
+ weightedSpace = (weightedSpace + arrangementSpacingPx * (weightChildrenCount - 1))
+ .coerceAtMost(constraints.mainAxisMax - fixedSpace)
}
var beforeCrossAxisAlignmentLine = 0
diff --git a/compose/foundation/foundation/api/1.0.0-beta07.txt b/compose/foundation/foundation/api/1.0.0-beta07.txt
index 03f29a3..76355d9 100644
--- a/compose/foundation/foundation/api/1.0.0-beta07.txt
+++ b/compose/foundation/foundation/api/1.0.0-beta07.txt
@@ -381,6 +381,9 @@
public final class LazyListMeasureKt {
}
+ public final class LazyListPrefetcher_androidKt {
+ }
+
@androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 03f29a3..76355d9 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -381,6 +381,9 @@
public final class LazyListMeasureKt {
}
+ public final class LazyListPrefetcher_androidKt {
+ }
+
@androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta07.txt b/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta07.txt
index 4085bab..4bd7f753 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta07.txt
@@ -413,6 +413,9 @@
public final class LazyListMeasureKt {
}
+ public final class LazyListPrefetcher_androidKt {
+ }
+
@androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 4085bab..4bd7f753 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -413,6 +413,9 @@
public final class LazyListMeasureKt {
}
+ public final class LazyListPrefetcher_androidKt {
+ }
+
@androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/restricted_1.0.0-beta07.txt b/compose/foundation/foundation/api/restricted_1.0.0-beta07.txt
index 03f29a3..76355d9 100644
--- a/compose/foundation/foundation/api/restricted_1.0.0-beta07.txt
+++ b/compose/foundation/foundation/api/restricted_1.0.0-beta07.txt
@@ -381,6 +381,9 @@
public final class LazyListMeasureKt {
}
+ public final class LazyListPrefetcher_androidKt {
+ }
+
@androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 03f29a3..76355d9 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -381,6 +381,9 @@
public final class LazyListMeasureKt {
}
+ public final class LazyListPrefetcher_androidKt {
+ }
+
@androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/TextDelegateBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/TextDelegateBenchmark.kt
index f6743c2..b58f8f1 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/TextDelegateBenchmark.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/TextDelegateBenchmark.kt
@@ -22,15 +22,12 @@
import androidx.benchmark.junit4.measureRepeated
import androidx.compose.foundation.text.InternalFoundationTextApi
import androidx.compose.foundation.text.TextDelegate
-import androidx.compose.testutils.benchmark.measureRepeatedRecordingCanvas
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
-import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.text.benchmark.RandomTextGenerator
import androidx.compose.ui.text.benchmark.TextBenchmarkTestRule
import androidx.compose.ui.text.TextStyle
@@ -43,7 +40,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import kotlin.math.ceil
import kotlin.math.roundToInt
@OptIn(InternalFoundationTextApi::class)
@@ -212,34 +208,4 @@
}
}
}
-
- /**
- * Measure the time taken by TextDelegate.paintBackground.
- */
- @Test
- fun paintBackground() {
- textBenchmarkTestRule.generator { textGenerator ->
- val textDelegate = textDelegate(textGenerator)
- val layoutResult = textDelegate.layout(
- Constraints(maxWidth = width),
- layoutDirection
- )
- val paint = Paint().also { it.color = Color.Yellow }
-
- benchmarkRule.measureRepeatedRecordingCanvas(
- width = layoutResult.size.width,
- height = layoutResult.size.height
- ) { canvas ->
- TextDelegate.paintBackground(
- start = 0,
- end = textLength / 2,
- paint = paint,
- canvas = canvas,
- textLayoutResult = layoutResult
- )
- }
- }
- }
}
-
-fun Float.toIntPx(): Int = ceil(this).roundToInt()
\ No newline at end of file
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 7d4b5d7..ca1170a 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -48,7 +48,7 @@
implementation(project(":compose:ui:ui-text"))
implementation(project(":compose:ui:ui-util"))
-
+ testImplementation(project(":compose:test-utils"))
testImplementation(ANDROIDX_TEST_RULES)
testImplementation(ANDROIDX_TEST_RUNNER)
testImplementation(JUNIT)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 4540a3a..ce9cd11 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -942,6 +942,84 @@
@Test
@OptIn(ExperimentalTestApi::class)
+ fun scrollable_nestedScroll_disabledConnectionNoOp() =
+ runBlockingWithManualClock { clock ->
+ var childValue = 0f
+ var parentValue = 0f
+ var selfValue = 0f
+ val childController = ScrollableState(
+ consumeScrollDelta = {
+ childValue += it / 2
+ it / 2
+ }
+ )
+ val middleController = ScrollableState(
+ consumeScrollDelta = {
+ selfValue += it / 2
+ it / 2
+ }
+ )
+ val parentController = ScrollableState(
+ consumeScrollDelta = {
+ parentValue += it / 2
+ it / 2
+ }
+ )
+
+ rule.setContent {
+ Box {
+ Box(
+ modifier = Modifier.size(300.dp)
+ .scrollable(
+ state = parentController,
+ orientation = Orientation.Horizontal
+ )
+ ) {
+ Box(
+ Modifier.size(200.dp)
+ .scrollable(
+ enabled = false,
+ orientation = Orientation.Horizontal,
+ state = middleController
+ )
+ ) {
+ Box(
+ Modifier.size(200.dp)
+ .testTag(scrollableBoxTag)
+ .scrollable(
+ orientation = Orientation.Horizontal,
+ state = childController
+ )
+ )
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(parentValue).isEqualTo(0f)
+ assertThat(selfValue).isEqualTo(0f)
+ assertThat(childValue).isEqualTo(0f)
+ }
+
+ rule.onNodeWithTag(scrollableBoxTag)
+ .performGesture {
+ swipe(center, center.copy(x = center.x + 100f))
+ }
+
+ advanceClockWhileAwaitersExist(clock)
+
+ rule.runOnIdle {
+ assertThat(childValue).isGreaterThan(0f)
+ // disabled middle node doesn't consume
+ assertThat(selfValue).isEqualTo(0f)
+ // but allow nested scroll to propagate up correctly
+ assertThat(parentValue).isGreaterThan(0f)
+ }
+ }
+
+ @Test
+ @OptIn(ExperimentalTestApi::class)
fun scrollable_interactionSource() = runBlocking {
val interactionSource = MutableInteractionSource()
var total = 0f
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
index 3ba18ed..c0abd58 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
@@ -36,12 +36,12 @@
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.testutils.WithTouchSlop
import androidx.compose.testutils.assertIsEqualTo
import androidx.compose.testutils.assertPixels
import androidx.compose.testutils.runBlockingWithManualClock
@@ -51,8 +51,6 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -1407,26 +1405,13 @@
isIn(Range.closed(expected - tolerance, expected + tolerance))
}
-internal val TestTouchSlop = 18f
-
-private val FakeViewConfiguration = object : ViewConfiguration {
- override val longPressTimeoutMillis: Long
- get() = 500L
- override val doubleTapTimeoutMillis: Long
- get() = 300L
- override val doubleTapMinTimeMillis: Long
- get() = 40L
- override val touchSlop: Float
- get() = TestTouchSlop
-}
+internal const val TestTouchSlop = 18f
internal fun ComposeContentTestRule.setContentWithTestViewConfiguration(
composable: @Composable () -> Unit
) {
this.setContent {
- CompositionLocalProvider(LocalViewConfiguration provides FakeViewConfiguration) {
- composable()
- }
+ WithTouchSlop(TestTouchSlop, composable)
}
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
index 4788ab8..5e11763 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
@@ -18,14 +18,15 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
@@ -125,13 +126,13 @@
.assertIsDisplayed()
rule.onNodeWithTag("7")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("8")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("9")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
}
@Test
@@ -153,13 +154,13 @@
.scrollBy(y = 103.dp, density = rule.density)
rule.onNodeWithTag("1")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("2")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("3")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("4")
.assertIsDisplayed()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
index 46d4c24..8604782 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
@@ -89,7 +89,7 @@
Modifier.requiredSize(20.dp),
state = rememberLazyListState().also { state = it }
) {
- items((0..1).toList()) {
+ items((0..10).toList()) {
if (it == 0) {
realState = rememberSaveable { counter0++ }
DisposableEffect(Unit) {
@@ -106,7 +106,11 @@
rule.runOnIdle {
assertThat(realState).isEqualTo(1)
runBlocking {
- state.scrollToItem(1, 5)
+ // we scroll through multiple items to make sure the 0th element is not kept in
+ // the reusable items buffer
+ state.scrollToItem(3)
+ state.scrollToItem(5)
+ state.scrollToItem(8)
}
}
@@ -184,7 +188,7 @@
Modifier.requiredSize(20.dp),
state = rememberLazyListState().also { state = it }
) {
- items((0..1).toList()) {
+ items((0..10).toList()) {
if (it == 0) {
LazyRow {
item {
@@ -207,7 +211,11 @@
rule.runOnIdle {
assertThat(realState).isEqualTo(1)
runBlocking {
- state.scrollToItem(1, 5)
+ // we scroll through multiple items to make sure the 0th element is not kept in
+ // the reusable items buffer
+ state.scrollToItem(3)
+ state.scrollToItem(5)
+ state.scrollToItem(8)
}
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt
index 276a499..f50f8e7 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
@@ -162,7 +163,7 @@
.scrollBy(y = 105.dp, density = rule.density)
rule.onNodeWithTag(firstHeaderTag)
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag(secondHeaderTag)
.assertIsDisplayed()
@@ -289,7 +290,7 @@
.scrollBy(x = 105.dp, density = rule.density)
rule.onNodeWithTag(firstHeaderTag)
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag(secondHeaderTag)
.assertIsDisplayed()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcherTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcherTest.kt
new file mode 100644
index 0000000..5dddbcf
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcherTest.kt
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2020 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.compose.foundation.lazy
+
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class LazyListPrefetcherTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ val itemsSizePx = 30f
+ val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
+
+ lateinit var state: LazyListState
+
+ @Test
+ fun notPrefetchingForwardInitially() {
+ composeList()
+
+ rule.onNodeWithTag("2")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun notPrefetchingBackwardInitially() {
+ composeList(firstItem = 2)
+
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun prefetchingForwardAfterSmallScroll() {
+ composeList()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(5f)
+ }
+ }
+
+ waitForPrefetch(2)
+
+ rule.onNodeWithTag("2")
+ .assertExists()
+ rule.onNodeWithTag("3")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun prefetchingBackwardAfterSmallScroll() {
+ composeList(firstItem = 2, itemOffset = 10)
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(-5f)
+ }
+ }
+
+ waitForPrefetch(1)
+
+ rule.onNodeWithTag("1")
+ .assertExists()
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun prefetchingForwardAndBackward() {
+ composeList(firstItem = 1)
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(5f)
+ }
+ }
+
+ waitForPrefetch(3)
+
+ rule.onNodeWithTag("3")
+ .assertExists()
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(-2f)
+ }
+ }
+
+ waitForPrefetch(0)
+
+ rule.onNodeWithTag("0")
+ .assertExists()
+ rule.onNodeWithTag("3")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun prefetchingForwardTwice() {
+ composeList()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(5f)
+ }
+ }
+
+ waitForPrefetch(2)
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(itemsSizePx / 2f)
+ state.scrollBy(itemsSizePx / 2f)
+ }
+ }
+
+ waitForPrefetch(3)
+
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("3")
+ .assertExists()
+ rule.onNodeWithTag("4")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun prefetchingBackwardTwice() {
+ composeList(firstItem = 4)
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(-5f)
+ }
+ }
+
+ waitForPrefetch(2)
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(-itemsSizePx / 2f)
+ state.scrollBy(-itemsSizePx / 2f)
+ }
+ }
+
+ waitForPrefetch(1)
+
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("1")
+ .assertExists()
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun prefetchingForwardAndBackwardReverseLayout() {
+ composeList(firstItem = 1, reverseLayout = true)
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(5f)
+ }
+ }
+
+ waitForPrefetch(3)
+
+ rule.onNodeWithTag("3")
+ .assertExists()
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(-2f)
+ }
+ }
+
+ waitForPrefetch(0)
+
+ rule.onNodeWithTag("0")
+ .assertExists()
+ rule.onNodeWithTag("3")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun prefetchingForwardAndBackwardWithContentPadding() {
+ val halfItemSize = itemsSizeDp / 2f
+ composeList(
+ firstItem = 2,
+ itemOffset = 5,
+ contentPadding = PaddingValues(vertical = halfItemSize)
+ )
+
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("3")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+ rule.onNodeWithTag("4")
+ .assertDoesNotExist()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(5f)
+ }
+ }
+
+ waitForPrefetch(3)
+
+ rule.onNodeWithTag("4")
+ .assertExists()
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollBy(-2f)
+ }
+ }
+
+ waitForPrefetch(0)
+
+ rule.onNodeWithTag("0")
+ .assertExists()
+ }
+
+ private fun waitForPrefetch(index: Int) {
+ rule.waitUntil {
+ activeNodes.contains(index)
+ }
+ }
+
+ private val activeNodes = mutableSetOf<Int>()
+
+ private fun composeList(
+ firstItem: Int = 0,
+ itemOffset: Int = 0,
+ reverseLayout: Boolean = false,
+ contentPadding: PaddingValues = PaddingValues(0.dp)
+ ) {
+ rule.setContent {
+ state = rememberLazyListState(
+ initialFirstVisibleItemIndex = firstItem,
+ initialFirstVisibleItemScrollOffset = itemOffset
+ )
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state,
+ reverseLayout = reverseLayout,
+ contentPadding = contentPadding
+ ) {
+ items(100) {
+ DisposableEffect(it) {
+ activeNodes.add(it)
+ onDispose {
+ activeNodes.remove(it)
+ }
+ }
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListSlotsReuseTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListSlotsReuseTest.kt
new file mode 100644
index 0000000..8f626d0
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListSlotsReuseTest.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2020 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.compose.foundation.lazy
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class LazyListSlotsReuseTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ val itemsSizePx = 30f
+ val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
+
+ @Test
+ fun scroll1ItemScrolledOffItemIsKeptForReuse() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertIsDisplayed()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(1)
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scroll2ItemsScrolledOffItemsAreKeptForReuse() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(2)
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("1")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scroll3Items2OfScrolledOffItemsAreKeptForReuse() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+
+ rule.runOnIdle {
+ runBlocking {
+ // after this step 0 and 1 are in reusable buffer
+ state.scrollToItem(2)
+
+ // this step requires one item and will take the last item from the buffer - item
+ // 1 plus will put 2 in the buffer. so expected buffer is items 2 and 0
+ state.scrollToItem(3)
+ }
+ }
+
+ // recycled
+ rule.onNodeWithTag("1")
+ .assertDoesNotExist()
+
+ // in buffer
+ rule.onNodeWithTag("0")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("2")
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ // visible
+ rule.onNodeWithTag("3")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("4")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun doMultipleScrollsOneByOne() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(1) // buffer is [0]
+ state.scrollToItem(2) // 0 used, buffer is [1]
+ state.scrollToItem(3) // 1 used, buffer is [2]
+ state.scrollToItem(4) // 2 used, buffer is [3]
+ }
+ }
+
+ // recycled
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+ rule.onNodeWithTag("1")
+ .assertDoesNotExist()
+ rule.onNodeWithTag("2")
+ .assertDoesNotExist()
+
+ // in buffer
+ rule.onNodeWithTag("3")
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ // visible
+ rule.onNodeWithTag("4")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("5")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scrollBackwardOnce() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState(10)
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(8) // buffer is [10, 11]
+ }
+ }
+
+ // in buffer
+ rule.onNodeWithTag("10")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("11")
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ // visible
+ rule.onNodeWithTag("8")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("9")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scrollBackwardOneByOne() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState(10)
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(9) // buffer is [11]
+ state.scrollToItem(7) // 11 reused, buffer is [9]
+ state.scrollToItem(6) // 9 reused, buffer is [8]
+ }
+ }
+
+ // in buffer
+ rule.onNodeWithTag("8")
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ // visible
+ rule.onNodeWithTag("6")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("7")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scrollingBackReusesTheSameSlot() {
+ lateinit var state: LazyListState
+ var counter0 = 0
+ var counter1 = 10
+ var rememberedValue0 = -1
+ var rememberedValue1 = -1
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ if (it == 0) {
+ rememberedValue0 = remember { counter0++ }
+ }
+ if (it == 1) {
+ rememberedValue1 = remember { counter1++ }
+ }
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(2) // buffer is [0, 1]
+ state.scrollToItem(0) // scrolled back, 0 and 1 are reused back. buffer: [2, 3]
+ }
+ }
+
+ rule.runOnIdle {
+ Truth.assertWithMessage("Item 0 restored remembered value is $rememberedValue0")
+ .that(rememberedValue0).isEqualTo(0)
+ Truth.assertWithMessage("Item 1 restored remembered value is $rememberedValue1")
+ .that(rememberedValue1).isEqualTo(10)
+ }
+
+ rule.onNodeWithTag("0")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("2")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("3")
+ .assertExists()
+ .assertIsNotDisplayed()
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
index bd6fae3..31062be 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
@@ -42,6 +42,7 @@
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
@@ -215,7 +216,7 @@
.assertIsDisplayed()
rule.onNodeWithTag("4")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
}
@Test
@@ -236,7 +237,7 @@
.scrollBy(x = 105.dp, density = rule.density)
rule.onNodeWithTag("1")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("2")
.assertIsDisplayed()
@@ -263,7 +264,7 @@
.scrollBy(x = 150.dp, density = rule.density)
rule.onNodeWithTag("1")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("2")
.assertIsDisplayed()
@@ -523,10 +524,12 @@
@Test
fun scrollsLeftInRtl() {
+ lateinit var state: LazyListState
rule.setContentWithTestViewConfiguration {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Box(Modifier.width(100.dp)) {
- LazyRow(Modifier.testTag(LazyListTag)) {
+ state = rememberLazyListState()
+ LazyRow(Modifier.testTag(LazyListTag), state) {
items(4) {
Spacer(
Modifier.width(101.dp).fillParentMaxHeight().testTag("$it")
@@ -540,11 +543,10 @@
rule.onNodeWithTag(LazyListTag)
.scrollBy(x = (-150).dp, density = rule.density)
- rule.onNodeWithTag("0")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+ assertThat(state.firstVisibleItemScrollOffset).isGreaterThan(0)
+ }
}
@Test
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt
new file mode 100644
index 0000000..dfb9deb
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.compose.foundation.text
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.PlaceholderVerticalAlign
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class CoreTextInlineContentTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun placeholder_changeSize_updateInlineContentSize() {
+ // Callback to monitor the size changes of a composable.
+ val onSizeChanged: (IntSize) -> Unit = mock()
+ var size by mutableStateOf(IntSize(50, 50))
+
+ rule.setContent {
+ val inlineTextContent = InlineTextContent(
+ placeholder = Placeholder(
+ size.width.sp,
+ size.height.sp,
+ PlaceholderVerticalAlign.AboveBaseline
+ )
+ ) {
+ Box(modifier = Modifier.fillMaxSize().onSizeChanged(onSizeChanged))
+ }
+
+ CompositionLocalProvider(
+ LocalDensity provides Density(density = 1f, fontScale = 1f)
+ ) {
+ CoreText(
+ text = buildAnnotatedString {
+ append("Hello")
+ appendInlineContent("box")
+ append("World")
+ },
+ style = TextStyle(fontSize = 100.sp),
+ inlineContent = mapOf("box" to inlineTextContent),
+ maxLines = Int.MAX_VALUE,
+ onTextLayout = {},
+ overflow = TextOverflow.Clip,
+ softWrap = true
+ )
+ }
+ }
+
+ rule.runOnIdle {
+ // Verify that the initial size is (50, 50).
+ verify(onSizeChanged).invoke(IntSize(50, 50))
+ size = IntSize(100, 100)
+ }
+ rule.waitForIdle()
+ // Verify that the size has been updated to (100, 100).
+ verify(onSizeChanged).invoke(IntSize(100, 100))
+ }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
index 1345fae..05616c7 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
@@ -17,15 +17,10 @@
package androidx.compose.foundation.text
import android.graphics.Bitmap
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
-import androidx.compose.foundation.text.matchers.assertThat
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
@@ -90,240 +85,6 @@
}
}
- @Test
- fun testBackgroundPaint_paint_wrap_multiLines() {
- with(density) {
- // Setup test.
- val fontSize = 20.sp
- val fontSizeInPx = fontSize.toPx()
- val text = "HelloHello"
- val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
- val annotatedString = AnnotatedString(text, spanStyle)
- val textDelegate = TextDelegate(
- text = annotatedString,
- style = TextStyle.Default,
- density = this,
- resourceLoader = resourceLoader
- )
- val layoutResult = textDelegate.layout(
- Constraints(maxWidth = 120),
- LayoutDirection.Ltr
- )
-
- val expectedBitmap = layoutResult.toBitmap()
- val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
- val expectedPaint = Paint()
- val defaultSelectionColor = Color(0x6633B5E5)
- val selectionPaint = Paint().also { it.color = defaultSelectionColor }
- expectedPaint.color = defaultSelectionColor
-
- val firstLineLeft = layoutResult.multiParagraph.getLineLeft(0)
- val secondLineLeft = layoutResult.multiParagraph.getLineLeft(1)
- val firstLineRight = layoutResult.multiParagraph.getLineRight(0)
- val secondLineRight = layoutResult.multiParagraph.getLineRight(1)
- expectedCanvas.drawRect(
- Rect(firstLineLeft, 0f, firstLineRight, fontSizeInPx),
- expectedPaint
- )
- expectedCanvas.drawRect(
- Rect(
- secondLineLeft,
- fontSizeInPx,
- secondLineRight,
- layoutResult.multiParagraph.height
- ),
- expectedPaint
- )
-
- val actualBitmap = layoutResult.toBitmap()
- val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
-
- // Run.
- // Select all.
- TextDelegate.paintBackground(
- start = 0,
- end = text.length,
- paint = selectionPaint,
- canvas = actualCanvas,
- textLayoutResult = layoutResult
- )
-
- // Assert.
- assertThat(actualBitmap).isEqualToBitmap(expectedBitmap)
- }
- }
-
- @Test
- fun testBackgroundPaint_paint_with_default_color() {
- with(density) {
- // Setup test.
- val selectionStart = 0
- val selectionEnd = 3
- val fontSize = 20.sp
- val fontSizeInPx = fontSize.toPx()
- val text = "Hello"
- val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
- val annotatedString = AnnotatedString(text, spanStyle)
- val textDelegate = TextDelegate(
- text = annotatedString,
- style = TextStyle.Default,
- density = this,
- resourceLoader = resourceLoader
- )
- val layoutResult = textDelegate.layout(Constraints(), LayoutDirection.Ltr)
-
- val expectedBitmap = layoutResult.toBitmap()
- val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
- val expectedPaint = Paint()
- val defaultSelectionColor = Color(0x6633B5E5)
- val selectionPaint = Paint().also { it.color = defaultSelectionColor }
- expectedPaint.color = defaultSelectionColor
- expectedCanvas.drawRect(
- Rect(
- left = 0f,
- top = 0f,
- right = fontSizeInPx * (selectionEnd - selectionStart),
- bottom = fontSizeInPx
- ),
- expectedPaint
- )
-
- val actualBitmap = layoutResult.toBitmap()
- val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
-
- // Run.
- TextDelegate.paintBackground(
- start = selectionStart,
- end = selectionEnd,
- paint = selectionPaint,
- canvas = actualCanvas,
- textLayoutResult = layoutResult
- )
-
- // Assert
- assertThat(actualBitmap).isEqualToBitmap(expectedBitmap)
- }
- }
-
- @Test
- fun testBackgroundPaint_paint_with_default_color_bidi() {
- with(density) {
- // Setup test.
- val textLTR = "Hello"
- // From right to left: שלום
- val textRTL = "\u05e9\u05dc\u05d5\u05dd"
- val text = textLTR + textRTL
- val selectionLTRStart = 2
- val selectionRTLEnd = 2
- val fontSize = 20.sp
- val fontSizeInPx = fontSize.toPx()
- val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
- val annotatedString = AnnotatedString(text, spanStyle)
- val textDelegate = TextDelegate(
- text = annotatedString,
- style = TextStyle.Default,
- density = this,
- resourceLoader = resourceLoader
- )
- val layoutResult = textDelegate.layout(Constraints(), LayoutDirection.Ltr)
-
- val expectedBitmap = layoutResult.toBitmap()
- val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
- val expectedPaint = Paint()
- val defaultSelectionColor = Color(0x6633B5E5)
- val selectionPaint = Paint().also { it.color = defaultSelectionColor }
- expectedPaint.color = defaultSelectionColor
- // Select "llo".
- expectedCanvas.drawRect(
- Rect(
- left = fontSizeInPx * selectionLTRStart,
- top = 0f,
- right = textLTR.length * fontSizeInPx,
- bottom = fontSizeInPx
- ),
- expectedPaint
- )
-
- // Select "של"
- expectedCanvas.drawRect(
- Rect(
- left = (textLTR.length + textRTL.length - selectionRTLEnd) * fontSizeInPx,
- top = 0f,
- right = (textLTR.length + textRTL.length) * fontSizeInPx,
- bottom = fontSizeInPx
- ),
- expectedPaint
- )
-
- val actualBitmap = layoutResult.toBitmap()
- val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
-
- // Run.
- TextDelegate.paintBackground(
- start = selectionLTRStart,
- end = textLTR.length + selectionRTLEnd,
- paint = selectionPaint,
- canvas = actualCanvas,
- textLayoutResult = layoutResult
- )
-
- // Assert
- assertThat(actualBitmap).isEqualToBitmap(expectedBitmap)
- }
- }
-
- @Test
- fun testBackgroundPaint_paint_with_customized_color() {
- with(density) {
- // Setup test.
- val selectionStart = 0
- val selectionEnd = 3
- val fontSize = 20.sp
- val fontSizeInPx = fontSize.toPx()
- val text = "Hello"
- val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
- val annotatedString = AnnotatedString(text, spanStyle)
- val selectionColor = Color(0x66AABB33)
- val selectionPaint = Paint().also { it.color = selectionColor }
- val textDelegate = TextDelegate(
- text = annotatedString,
- style = TextStyle.Default,
- density = this,
- resourceLoader = resourceLoader
- )
- val layoutResult = textDelegate.layout(Constraints(), LayoutDirection.Ltr)
-
- val expectedBitmap = layoutResult.toBitmap()
- val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
- val expectedPaint = Paint()
- expectedPaint.color = selectionColor
- expectedCanvas.drawRect(
- Rect(
- left = 0f,
- top = 0f,
- right = fontSizeInPx * (selectionEnd - selectionStart),
- bottom = fontSizeInPx
- ),
- expectedPaint
- )
-
- val actualBitmap = layoutResult.toBitmap()
- val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
-
- // Run.
- TextDelegate.paintBackground(
- start = selectionStart,
- end = selectionEnd,
- paint = selectionPaint,
- canvas = actualCanvas,
- textLayoutResult = layoutResult
- )
-
- // Assert
- assertThat(actualBitmap).isEqualToBitmap(expectedBitmap)
- }
- }
-
// @Test
// fun multiParagraphIntrinsics_isReused() {
// val textDelegate = TextDelegate(
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
index b37ba843..b96602f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
@@ -60,13 +60,9 @@
val expectedBitmap = layoutResult.toBitmap()
val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
- TextDelegate.paintBackground(
- 0,
- 1,
- Paint().apply { color = selectionColor },
- expectedCanvas,
- layoutResult
- )
+ val selectionPath = layoutResult.multiParagraph.getPathForRange(0, 1)
+ expectedCanvas.drawPath(selectionPath, Paint().apply { color = selectionColor })
+
TextPainter.paint(expectedCanvas, layoutResult)
val actualBitmap = layoutResult.toBitmap()
@@ -201,4 +197,4 @@
size.width,
size.height,
Bitmap.Config.ARGB_8888
-)
\ No newline at end of file
+)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt
index 2b0bd94..258fb19 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt
@@ -34,6 +34,7 @@
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalTextToolbar
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.click
import androidx.compose.ui.test.hasTestTag
@@ -155,7 +156,8 @@
rule.setContent {
CompositionLocalProvider(
LocalHapticFeedback provides hapticFeedback,
- LocalLayoutDirection provides layoutDirection
+ LocalLayoutDirection provides layoutDirection,
+ LocalTextToolbar provides mock()
) {
Column {
SelectionContainer(
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcher.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcher.android.kt
new file mode 100644
index 0000000..b8e2b9a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcher.android.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.compose.foundation.lazy
+
+import android.view.Choreographer
+import android.view.Display
+import android.view.View
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.layout.SubcomposeLayoutState
+import androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.util.trace
+import java.util.concurrent.TimeUnit
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal actual fun LazyListPrefetcher(
+ lazyListState: LazyListState,
+ stateOfItemsProvider: State<LazyListItemsProvider>,
+ itemContentFactory: LazyListItemContentFactory,
+ subcomposeLayoutState: SubcomposeLayoutState
+) {
+ val view = LocalView.current
+ remember(subcomposeLayoutState, lazyListState, view) {
+ LazyListPrefetcher(
+ subcomposeLayoutState,
+ lazyListState,
+ stateOfItemsProvider,
+ itemContentFactory,
+ view
+ )
+ }
+}
+
+/**
+ * Android specific prefetch implementation. The only platform specific things are:
+ * 1) Calculating the deadline
+ * 2) Posting the delayed runnable
+ * This could be refactored in the future in order to keep the most logic platform agnostic to
+ * enable the prefetching on desktop.
+ *
+ * The differences with the implementation in RecyclerView:
+ *
+ * 1) Prefetch is per-list-index, and performed on whole item.
+ * With RecyclerView, nested scrolling RecyclerViews would prefetch incrementally, e.g. items
+ * like the following in a scrolling vertical list could be broken up within a frame:
+ * [Row1 [a], [b], [c]]
+ * [Row2 [d], [e]]
+ * [Row3 [f], [g], [h]]
+ * You could have frames that break up this work arbitrarily:
+ * Frame 1 - prefetch [a]
+ * Frame 2 - prefetch [b], [c]
+ * Frame 3 - prefetch [d]
+ * Frame 4 - prefetch [e], [f]
+ * Something similar is not possible with LazyColumn yet. Also, if we nest LazyRow inside
+ * LazyColumn the content of LazyRow will not be composed at all during the prefetch stage as
+ * we only compose during the measuring and the pre-measuring is not yet possible with this
+ * prefetch implementation. Tracking bug: b/187392865.
+ *
+ * 2) Prefetching time estimation only captured during the prefetch.
+ * We currently don't track the time of the regular subcompose call happened during the regular
+ * measure pass, only the ones which are done during the prefetching. The downside is we build
+ * our prefetch information only after scrolling has started and items are showing up. Your very
+ * first scroll won't know if it's safe to prefetch. Why:
+ * a) SubcomposeLayout is not exposing an API to understand if subcompose() call is going to
+ * do the real work. The work could be skipped if the same lambda was passed as for the
+ * previous invocation or if there were no recompositions scheduled. We could workaround it
+ * by keeping the extra state in LazyListState about what items we already composed and to
+ * only measure the first composition for the given slot, or consider exposing extra
+ * information in SubcomposeLayoutState API.
+ * b) It allows us to nicely decouple the logic, now the prefetching logic is build on
+ * top of the regular LazyColumn measuring functionallity and the main logic knows nothing
+ * about prefetch
+ * c) Maybe the better approach would be to wait till the low-level runtime infra is ready to
+ * do subcompositions on the different threads which illuminates the need to calculate the
+ * deadlines completely.
+ * Tracking bug: b/187393381.
+ *
+ * 3) Prefetch is not aware of item type.
+ * RecyclerView separates timing metadata about different item types. For example, in play
+ * store style UI, this allows RecyclerView to separately estimate the cost of a header,
+ * separator, and item row. In this implementation, all of these would be averaged together in
+ * the same estimation.
+ * There is no view type concept in LazyColumn at all. What can we possible do:
+ * a) Think of different item/items calls in the builder dsl as different view types
+ * automatically. It is close enough but still not entirely correct if the user have something
+ * like a list of elements which are objects of some sealed classes and they consider
+ * different classes as completely different types
+ * b) Maybe if we would be able to precompose on the different thread this issue is also not
+ * so critical given that we don't need to calculate the deadline.
+ * Tracking bug: 187393922
+ */
+private class LazyListPrefetcher(
+ private val subcomposeLayoutState: SubcomposeLayoutState,
+ private val lazyListState: LazyListState,
+ private val stateOfItemsProvider: State<LazyListItemsProvider>,
+ private val itemContentFactory: LazyListItemContentFactory,
+ private val view: View
+) : RememberObserver, LazyListOnScrolledListener, Runnable, Choreographer.FrameCallback {
+
+ /**
+ * Keeps the scrolling direction during the previous calculation in order to be able to
+ * detect the scrolling direction change.
+ */
+ private var wasScrollingForward: Boolean = false
+
+ /**
+ * The index scheduled to be prefetched (or the last prefetched index if the prefetch is
+ * done, in this case [precomposedSlotHandle] is not null and associated with this index.
+ */
+ private var indexToPrefetch: Int = -1
+
+ /**
+ * Non-null when the item with [indexToPrefetch] index was prefetched.
+ */
+ private var precomposedSlotHandle: PrecomposedSlotHandle? = null
+
+ /**
+ * Average time the prefetching operation takes. Keeping it allows us to not start the work
+ * if in this frame we are most likely not going to finish the work in time to not delay the
+ * next frame.
+ */
+ private var averagePrefetchTimeNs: Long = 0
+
+ private var prefetchScheduled = false
+
+ private val choreographer = Choreographer.getInstance()
+
+ init {
+ calculateFrameIntervalIfNeeded(view)
+ }
+
+ /**
+ * Callback to be executed when the prefetching is needed.
+ * [indexToPrefetch] will be used as an input.
+ */
+ override fun run() {
+ if (precomposedSlotHandle != null) {
+ // the precomposition happened already.
+ return
+ }
+ trace("compose:lazylist:prefetch") {
+ val latestFrameVsyncNs = TimeUnit.MILLISECONDS.toNanos(view.drawingTime)
+ val nextFrameNs = latestFrameVsyncNs + frameIntervalNs
+ val beforeNs = System.nanoTime()
+ if (beforeNs > nextFrameNs || beforeNs + averagePrefetchTimeNs < nextFrameNs) {
+ val index = indexToPrefetch
+ val itemProvider = stateOfItemsProvider.value
+ if (view.windowVisibility == View.VISIBLE &&
+ index in 0 until itemProvider.itemsCount
+ ) {
+ precomposedSlotHandle = subcompose(itemProvider, index)
+ val prefetchTime = System.nanoTime() - beforeNs
+ updateAveragePrefetchTime(prefetchTime)
+ }
+ prefetchScheduled = false
+ } else {
+ // there is not enough time left in this frame. we schedule a next frame callback
+ // in which we are going to post the message in the handler again.
+ choreographer.postFrameCallback(this)
+ }
+ }
+ }
+
+ /**
+ * Choreographer frame callback. It will be called when during the previous frame we didn't
+ * have enough time left. We will post a new message in the handler in order to try to
+ * prefetch again after this frame.
+ */
+ override fun doFrame(frameTimeNanos: Long) {
+ view.post(this)
+ }
+
+ private fun subcompose(
+ itemProvider: LazyListItemsProvider,
+ index: Int
+ ): PrecomposedSlotHandle {
+ val key = itemProvider.getKey(index)
+ val content = itemContentFactory.getContent(index, key)
+ return subcomposeLayoutState.precompose(key, content)
+ }
+
+ private fun updateAveragePrefetchTime(prefetchTime: Long) {
+ // Calculate a weighted moving average of time taken to compose an item. We use weighted
+ // moving average to bias toward more recent measurements, and to minimize storage /
+ // computation cost. (the idea is taken from RecycledViewPool)
+ averagePrefetchTimeNs = if (averagePrefetchTimeNs == 0L) {
+ prefetchTime
+ } else {
+ averagePrefetchTimeNs / 4 * 3 + prefetchTime / 4
+ }
+ }
+
+ /**
+ * The callback to be executed on every scroll.
+ */
+ override fun onScrolled(delta: Float) {
+ val info = lazyListState.layoutInfo
+ if (info.visibleItemsInfo.isNotEmpty()) {
+ val scrollingForward = delta < 0
+ val indexToPrefetch = if (scrollingForward) {
+ info.visibleItemsInfo.last().index + 1
+ } else {
+ info.visibleItemsInfo.first().index - 1
+ }
+ if (indexToPrefetch != this.indexToPrefetch &&
+ indexToPrefetch in 0 until info.totalItemsCount
+ ) {
+ val precomposedSlot = precomposedSlotHandle
+ if (precomposedSlot != null) {
+ if (wasScrollingForward != scrollingForward) {
+ // the scrolling direction has been changed which means the last prefetched
+ // is not going to be reached anytime soon so it is safer to dispose it.
+ // if this item is already visible it is safe to call the method anyway
+ // as it will be no-op
+ precomposedSlot.dispose()
+ }
+ }
+ this.wasScrollingForward = scrollingForward
+ this.indexToPrefetch = indexToPrefetch
+ this.precomposedSlotHandle = null
+ if (!prefetchScheduled) {
+ prefetchScheduled = true
+ // schedule the prefetching
+ view.post(this)
+ }
+ }
+ }
+ }
+
+ override fun onRemembered() {
+ lazyListState.onScrolledListener = this
+ }
+
+ override fun onForgotten() {
+ lazyListState.onScrolledListener = null
+ view.removeCallbacks(this)
+ }
+
+ override fun onAbandoned() {}
+
+ companion object {
+
+ /**
+ * The static cache in order to not gather the display refresh rate to often (expensive operation).
+ */
+ private var frameIntervalNs: Long = 0
+
+ private fun calculateFrameIntervalIfNeeded(view: View) {
+ // we only do this query once, statically, because it's very expensive (> 1ms)
+ if (frameIntervalNs == 0L) {
+ val display: Display? = view.display
+ var refreshRate = 60f
+ if (!view.isInEditMode && display != null) {
+ val displayRefreshRate = display.refreshRate
+ if (displayRefreshRate >= 30f) {
+ // break 60 fps assumption if data from display appears valid
+ refreshRate = displayRefreshRate
+ }
+ }
+ frameIntervalNs = (1000000000 / refreshRate).toLong()
+ }
+ }
+ }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
index 20cc811..75c988a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
@@ -36,8 +36,7 @@
import androidx.compose.ui.unit.Dp
/**
- * Modify element to add border with appearance specified with a [border] and a [shape], pad the
- * content by the [BorderStroke.width] and clip it.
+ * Modify element to add border with appearance specified with a [border] and a [shape] and clip it.
*
* @sample androidx.compose.foundation.samples.BorderSample()
*
@@ -48,8 +47,8 @@
border(width = border.width, brush = border.brush, shape = shape)
/**
- * Returns a [Modifier] that adds border with appearance specified with [width], [color] and a
- * [shape], pads the content by the [width] and clips it.
+ * Modify element to add border with appearance specified with a [width], a [color] and a [shape]
+ * and clip it.
*
* @sample androidx.compose.foundation.samples.BorderSampleWithDataClass()
*
@@ -61,8 +60,8 @@
border(width, SolidColor(color), shape)
/**
- * Returns a [Modifier] that adds border with appearance specified with [width], [brush] and a
- * [shape], pads the content by the [width] and clips it.
+ * Modify element to add border with appearance specified with a [width], a [brush] and a [shape]
+ * and clip it.
*
* @sample androidx.compose.foundation.samples.BorderSampleWithBrush()
*
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 1aa5b59..bbcdc14 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -137,7 +137,9 @@
val scrollLogic = rememberUpdatedState(
ScrollingLogic(orientation, reverseDirection, nestedScrollDispatcher, controller, fling)
)
- val nestedScrollConnection = remember { scrollableNestedScrollConnection(scrollLogic) }
+ val nestedScrollConnection = remember(enabled) {
+ scrollableNestedScrollConnection(scrollLogic, enabled)
+ }
val draggableState = remember { ScrollDraggableState(scrollLogic) }
return draggable(
@@ -269,20 +271,29 @@
}
private fun scrollableNestedScrollConnection(
- scrollLogic: State<ScrollingLogic>
+ scrollLogic: State<ScrollingLogic>,
+ enabled: Boolean
): NestedScrollConnection = object : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
- ): Offset = scrollLogic.value.performRawScroll(available)
+ ): Offset = if (enabled) {
+ scrollLogic.value.performRawScroll(available)
+ } else {
+ Offset.Zero
+ }
override suspend fun onPostFling(
consumed: Velocity,
available: Velocity
): Velocity {
- val velocityLeft = scrollLogic.value.doFlingAnimation(available)
- return available - velocityLeft
+ return if (enabled) {
+ val velocityLeft = scrollLogic.value.doFlingAnimation(available)
+ available - velocityLeft
+ } else {
+ Velocity.Zero
+ }
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 457e676e..141341d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -27,11 +27,13 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.layout.SubcomposeLayoutState
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
@@ -68,7 +70,11 @@
val itemContentFactory = rememberItemContentFactory(stateOfItemsProvider, state)
+ val subcomposeLayoutState = remember { SubcomposeLayoutState(MaxItemsToRetainForReuse) }
+ LazyListPrefetcher(state, stateOfItemsProvider, itemContentFactory, subcomposeLayoutState)
+
SubcomposeLayout(
+ subcomposeLayoutState,
modifier
.lazyListSemantics(
stateOfItemsProvider = stateOfItemsProvider,
@@ -177,3 +183,17 @@
)
}
}
+
+private const val MaxItemsToRetainForReuse = 2
+
+/**
+ * Platform specific implementation of lazy list prefetching - precomposing next items in
+ * advance during the scrolling.
+ */
+@Composable
+internal expect fun LazyListPrefetcher(
+ lazyListState: LazyListState,
+ stateOfItemsProvider: State<LazyListItemsProvider>,
+ itemContentFactory: LazyListItemContentFactory,
+ subcomposeLayoutState: SubcomposeLayoutState
+)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
index f32e896..19d0e06 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
@@ -100,8 +100,11 @@
val content: @Composable () -> Unit = @Composable {
val itemsProvider = itemsProvider.value
if (index < itemsProvider.itemsCount) {
- val content = itemsProvider.getContent(index, scope)
- saveableStateHolder.SaveableStateProvider(key, content)
+ val key = itemsProvider.getKey(index)
+ if (key == this.key) {
+ val content = itemsProvider.getContent(index, scope)
+ saveableStateHolder.SaveableStateProvider(key, content)
+ }
}
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index 1c9fe3f..082c787 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -214,6 +214,8 @@
override val isScrollInProgress: Boolean
get() = scrollableState.isScrollInProgress
+ internal var onScrolledListener: LazyListOnScrolledListener? = null
+
// TODO: Coroutine scrolling APIs will allow this to be private again once we have more
// fine-grained control over scrolling
/*@VisibleForTesting*/
@@ -232,7 +234,9 @@
// inside measuring we do scrollToBeConsumed.roundToInt() so there will be no scroll if
// we have less than 0.5 pixels
if (abs(scrollToBeConsumed) > 0.5f) {
+ val preScrollToBeConsumed = scrollToBeConsumed
remeasurement.forceRemeasure()
+ onScrolledListener?.onScrolled(preScrollToBeConsumed - scrollToBeConsumed)
}
// here scrollToBeConsumed is already consumed during the forceRemeasure invocation
@@ -356,3 +360,7 @@
override val viewportEndOffset = 0
override val totalItemsCount = 0
}
+
+internal interface LazyListOnScrolledListener {
+ fun onScrolled(delta: Float)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index 559b2da..c0c452c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -37,7 +37,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
@@ -154,7 +154,7 @@
placeholders = placeholders
)
state.onTextLayout = onTextLayout
- state.selectionPaint.color = selectionBackgroundColor
+ state.selectionBackgroundColor = selectionBackgroundColor
val controller = remember { TextController(state) }
controller.update(selectionRegistrar)
@@ -491,12 +491,9 @@
@OptIn(InternalFoundationTextApi::class)
private fun Modifier.drawTextAndSelectionBehind(): Modifier =
this.graphicsLayer().drawBehind {
- drawIntoCanvas { canvas ->
- val textLayoutResult = state.layoutResult
- val selectionPaint = state.selectionPaint
+ state.layoutResult?.let {
val selection = selectionRegistrar?.subselections?.get(state.selectableId)
- if (textLayoutResult == null) return@drawIntoCanvas
if (selection != null) {
val start = if (!selection.handlesCrossed) {
selection.start.offset
@@ -508,15 +505,15 @@
} else {
selection.start.offset
}
- TextDelegate.paintBackground(
- start,
- end,
- selectionPaint,
- canvas,
- textLayoutResult
- )
+
+ if (start != end) {
+ val selectionPath = it.multiParagraph.getPathForRange(start, end)
+ drawPath(selectionPath, state.selectionBackgroundColor)
+ }
}
- TextDelegate.paint(canvas, textLayoutResult)
+ drawIntoCanvas { canvas ->
+ TextDelegate.paint(canvas, it)
+ }
}
}
}
@@ -542,8 +539,8 @@
/** The global position calculated during the last notifyPosition callback */
var previousGlobalPosition: Offset = Offset.Zero
- /** The paint used to draw highlight background for selected text. */
- val selectionPaint: Paint = Paint()
+ /** The background color of selection */
+ var selectionBackgroundColor: Color = Color.Unspecified
}
/**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
index 00b3410..ba4f6e4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
@@ -18,7 +18,6 @@
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.MultiParagraph
import androidx.compose.ui.text.MultiParagraphIntrinsics
@@ -89,7 +88,7 @@
val overflow: TextOverflow = TextOverflow.Clip,
val density: Density,
val resourceLoader: Font.ResourceLoader,
- val placeholders: List<AnnotatedString.Range<Placeholder>> = listOf()
+ val placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList()
) {
/*@VisibleForTesting*/
internal var paragraphIntrinsics: MultiParagraphIntrinsics? = null
@@ -203,7 +202,7 @@
prevResult: TextLayoutResult? = null
): TextLayoutResult {
if (prevResult != null && prevResult.canReuse(
- text, style, maxLines, softWrap, overflow, density, layoutDirection,
+ text, style, placeholders, maxLines, softWrap, overflow, density, layoutDirection,
resourceLoader, constraints
)
) {
@@ -270,27 +269,5 @@
fun paint(canvas: Canvas, textLayoutResult: TextLayoutResult) {
TextPainter.paint(canvas, textLayoutResult)
}
-
- /**
- * Draws text background of the given range.
- *
- * If the given range is empty, do nothing.
- *
- * @param start inclusive start character offset of the drawing range.
- * @param end exclusive end character offset of the drawing range.
- * @param paint used to draw background.
- * @param canvas the target canvas.
- */
- fun paintBackground(
- start: Int,
- end: Int,
- paint: Paint,
- canvas: Canvas,
- textLayoutResult: TextLayoutResult
- ) {
- if (start == end) return
- val selectionPath = textLayoutResult.multiParagraph.getPathForRange(start, end)
- canvas.drawPath(selectionPath, paint)
- }
}
}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutHelper.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutHelper.kt
index f581b48..2f2051e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutHelper.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutHelper.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.text
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
@@ -30,17 +31,19 @@
*
* @param text a text to be used for computing text layout.
* @param style a text style to be used for computing text layout.
+ * @param placeholders a list of [Placeholder]s to be used for computing text layout.
* @param maxLines a maximum number of lines to be used for computing text layout.
* @param softWrap whether doing softwrap or not to be used for computing text layout.
* @param overflow an overflow type to be used for computing text layout.
* @param density a density to be used for computing text layout.
* @param layoutDirection a layout direction to be used for computing text layout.
- * @param resourceLoader a resource loader to be used for computing text layout.
* @param constraints a constraint to be used for computing text layout.
*/
+@OptIn(InternalFoundationTextApi::class)
internal fun TextLayoutResult.canReuse(
text: AnnotatedString,
style: TextStyle,
+ placeholders: List<AnnotatedString.Range<Placeholder>>,
maxLines: Int,
softWrap: Boolean,
overflow: TextOverflow,
@@ -55,6 +58,7 @@
if (!(
layoutInput.text == text &&
layoutInput.style.canReuseLayout(style) &&
+ layoutInput.placeholders == placeholders &&
layoutInput.maxLines == maxLines &&
layoutInput.softWrap == softWrap &&
layoutInput.overflow == overflow &&
@@ -70,7 +74,7 @@
if (constraints.minWidth != layoutInput.constraints.minWidth) return false
if (!(softWrap || overflow == TextOverflow.Ellipsis)) {
- // If width does not mattter, we can result the same layout.
+ // If width does not matter, we can result the same layout.
return true
}
return constraints.maxWidth == layoutInput.constraints.maxWidth
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
index e7ff6c5..cbacbb1 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
@@ -32,9 +32,11 @@
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
@@ -103,7 +105,7 @@
* val state = rememberScrollState(0f)
*
* Box(Modifier.fillMaxSize()) {
- * ScrollableColumn(state = state) {
+ * Box(modifier = Modifier.verticalScroll(state)) {
* ...
* }
*
@@ -135,7 +137,7 @@
/**
* Horizontal scrollbar that can be attached to some scrollable
- * component (ScrollableRow, LazyRow) and share common state with it.
+ * component (Modifier.verticalScroll(), LazyRow) and share common state with it.
*
* Can be placed independently.
*
@@ -143,7 +145,7 @@
* val state = rememberScrollState(0f)
*
* Box(Modifier.fillMaxSize()) {
- * ScrollableRow(state = state) {
+ * Box(modifier = Modifier.verticalScroll(state)) {
* ...
* }
*
@@ -194,7 +196,13 @@
}
var containerSize by remember { mutableStateOf(0) }
- var isHover by remember { mutableStateOf(false) }
+ var isHovered by remember { mutableStateOf(false) }
+
+ val isHighlighted by remember {
+ derivedStateOf {
+ isHovered || dragInteraction.value is DragInteraction.Start
+ }
+ }
val minimalHeight = style.minimalHeight.toPx()
val sliderAdapter = remember(adapter, containerSize, minimalHeight) {
@@ -213,7 +221,7 @@
}
val color by animateColorAsState(
- if (isHover) style.hoverColor else style.unhoverColor,
+ if (isHighlighted) style.hoverColor else style.unhoverColor,
animationSpec = TweenSpec(durationMillis = style.hoverDurationMillis)
)
@@ -231,8 +239,8 @@
},
modifier
.pointerMoveFilter(
- onExit = { isHover = false; true },
- onEnter = { isHover = true; true }
+ onExit = { isHovered = false; false },
+ onEnter = { isHovered = true; false }
)
.scrollOnPressOutsideSlider(isVertical, sliderAdapter, adapter, containerSize),
measurePolicy
@@ -243,24 +251,29 @@
interactionSource: MutableInteractionSource,
draggedInteraction: MutableState<DragInteraction.Start?>,
onDelta: (Offset) -> Unit
-): Modifier = pointerInput(interactionSource, draggedInteraction, onDelta) {
- forEachGesture {
- awaitPointerEventScope {
- val down = awaitFirstDown(requireUnconsumed = false)
- val interaction = DragInteraction.Start()
- interactionSource.tryEmit(interaction)
- draggedInteraction.value = interaction
- val isSuccess = drag(down.id) { change ->
- onDelta.invoke(change.positionChange())
- change.consumePositionChange()
+): Modifier = composed {
+ val currentInteractionSource by rememberUpdatedState(interactionSource)
+ val currentDraggedInteraction by rememberUpdatedState(draggedInteraction)
+ val currentOnDelta by rememberUpdatedState(onDelta)
+ pointerInput(Unit) {
+ forEachGesture {
+ awaitPointerEventScope {
+ val down = awaitFirstDown(requireUnconsumed = false)
+ val interaction = DragInteraction.Start()
+ currentInteractionSource.tryEmit(interaction)
+ currentDraggedInteraction.value = interaction
+ val isSuccess = drag(down.id) { change ->
+ currentOnDelta.invoke(change.positionChange())
+ change.consumePositionChange()
+ }
+ val finishInteraction = if (isSuccess) {
+ DragInteraction.Stop(interaction)
+ } else {
+ DragInteraction.Cancel(interaction)
+ }
+ currentInteractionSource.tryEmit(finishInteraction)
+ currentDraggedInteraction.value = null
}
- val finishInteraction = if (isSuccess) {
- DragInteraction.Stop(interaction)
- } else {
- DragInteraction.Cancel(interaction)
- }
- interactionSource.tryEmit(finishInteraction)
- draggedInteraction.value = null
}
}
}
@@ -338,7 +351,7 @@
}
/**
- * ScrollbarAdapter for ScrollableColumn and ScrollableRow
+ * ScrollbarAdapter for Modifier.verticalScroll and Modifier.horizontalScroll
*
* [scrollState] is instance of [ScrollState] which is used by scrollable component
*
@@ -346,7 +359,7 @@
* val state = rememberScrollState(0f)
*
* Box(Modifier.fillMaxSize()) {
- * ScrollableColumn(state = state) {
+ * Box(modifier = Modifier.verticalScroll(state)) {
* ...
* }
*
@@ -483,7 +496,12 @@
.coerceAtLeast(minHeight)
.coerceAtMost(containerSize.toFloat())
- private val scrollScale get() = (containerSize - size) / (contentSize - containerSize)
+ private val scrollScale: Float
+ get() {
+ val extraScrollbarSpace = containerSize - size
+ val extraContentSpace = contentSize - containerSize
+ return if (extraContentSpace == 0f) 1f else extraScrollbarSpace / extraContentSpace
+ }
var position: Float
get() = scrollScale * adapter.scrollOffset
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
index 8f78d81..20e25b8 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
@@ -16,6 +16,20 @@
package androidx.compose.foundation.lazy
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.ui.layout.SubcomposeLayoutState
+
internal actual fun getDefaultLazyKeyFor(index: Int): Any = DefaultLazyKey(index)
private data class DefaultLazyKey(private val index: Int)
+
+@Composable
+internal actual fun LazyListPrefetcher(
+ lazyListState: LazyListState,
+ stateOfItemsProvider: State<LazyListItemsProvider>,
+ itemContentFactory: LazyListItemContentFactory,
+ subcomposeLayoutState: SubcomposeLayoutState
+) {
+ // there is no prefetch implementation on desktop yet
+}
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
index 4e59d85..4b1be7e 100644
--- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
@@ -56,7 +56,6 @@
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
-import org.jetbrains.skija.Surface
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Rule
@@ -68,10 +67,6 @@
@get:Rule
val rule = createComposeRule()
- // don't inline, surface controls canvas life time
- private val surface = Surface.makeRasterN32Premul(100, 100)
- private val canvas = surface.canvas
-
@Test
fun `drag slider to the middle`() {
runBlocking(Dispatchers.Main) {
@@ -89,6 +84,21 @@
}
@Test
+ fun `drag slider when it is hidden`() {
+ runBlocking(Dispatchers.Main) {
+ rule.setContent {
+ TestBox(size = 100.dp, childSize = 20.dp, childCount = 1, scrollbarWidth = 10.dp)
+ }
+ rule.awaitIdle()
+ rule.onNodeWithTag("scrollbar").performGesture {
+ instantSwipe(start = Offset(0f, 25f), end = Offset(0f, 50f))
+ }
+ rule.awaitIdle()
+ rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(0.dp)
+ }
+ }
+
+ @Test
fun `drag slider to the edges`() {
runBlocking(Dispatchers.Main) {
rule.setContent {
@@ -126,6 +136,22 @@
}
}
+ // TODO(demin): write a test when we support DesktopComposeTestRule.mainClock:
+ // see https://github.com/JetBrains/compose-jb/issues/637
+// fun `move mouse to the slider and drag it`() {
+// ...
+// rule.performMouseMove(0, 25)
+// rule.mainClock.advanceTimeByFrame()
+// down(Offset(0f, 25f))
+// rule.mainClock.advanceTimeByFrame()
+// moveTo(Offset(0f, 30f))
+// rule.mainClock.advanceTimeByFrame()
+// moveTo(Offset(0f, 50f))
+// rule.mainClock.advanceTimeByFrame()
+// up()
+// ...
+// }
+
// TODO(demin): enable after we resolve b/171889442
@Ignore("Enable after we resolve b/171889442")
@Test
@@ -365,6 +391,10 @@
)
}
+ private fun ComposeTestRule.performMouseMove(x: Int, y: Int) {
+ (this as DesktopComposeTestRule).window.onMouseMoved(x, y)
+ }
+
@Composable
private fun TestBox(
size: Dp,
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
index 27ba0c3..48f0ef9 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
@@ -19,13 +19,14 @@
import androidx.compose.runtime.Applier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composer
-import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.Recomposer
import androidx.compose.runtime.ControlledComposition
-import androidx.compose.runtime.currentComposer
+import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.MonotonicFrameClock
+import androidx.compose.runtime.Recomposer
+import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.withRunningRecomposer
+import androidx.compose.testutils.TestViewConfiguration
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.ConsumedData
@@ -39,7 +40,6 @@
import androidx.compose.ui.materialize
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -346,18 +346,4 @@
}
override fun clear() {}
}
-
- private class TestViewConfiguration : ViewConfiguration {
- override val longPressTimeoutMillis: Long
- get() = 500
-
- override val doubleTapTimeoutMillis: Long
- get() = 300
-
- override val doubleTapMinTimeMillis: Long
- get() = 40
-
- override val touchSlop: Float
- get() = 18f
- }
}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt
index 5242735..7af3454 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt
@@ -17,6 +17,8 @@
package androidx.compose.foundation.text
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.TextLayoutInput
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
@@ -27,6 +29,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.mock
import org.junit.Before
@@ -64,12 +67,13 @@
}
@Test
- fun testCanResue_same() {
+ fun testCanReuse_same() {
val constraints = Constraints.fixedWidth(100)
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
style = TextStyle(),
+ placeholders = emptyList(),
maxLines = 1,
softWrap = true,
overflow = TextOverflow.Ellipsis,
@@ -82,12 +86,13 @@
}
@Test
- fun testCanResue_different_text() {
+ fun testCanReuse_different_text() {
val constraints = Constraints.fixedWidth(100)
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, Android").toAnnotatedString(),
style = TextStyle(),
+ placeholders = emptyList(),
maxLines = 1,
softWrap = true,
overflow = TextOverflow.Ellipsis,
@@ -100,12 +105,13 @@
}
@Test
- fun testCanResue_different_style() {
+ fun testCanReuse_different_style() {
val constraints = Constraints.fixedWidth(100)
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
style = TextStyle(fontSize = 1.5.em),
+ placeholders = emptyList(),
maxLines = 1,
softWrap = true,
overflow = TextOverflow.Ellipsis,
@@ -118,12 +124,13 @@
}
@Test
- fun testCanResue_different_maxLines() {
+ fun testCanReuse_different_maxLines() {
val constraints = Constraints.fixedWidth(100)
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
style = TextStyle(),
+ placeholders = emptyList(),
maxLines = 2,
softWrap = true,
overflow = TextOverflow.Ellipsis,
@@ -136,12 +143,13 @@
}
@Test
- fun testCanResue_different_softWrap() {
+ fun testCanReuse_different_softWrap() {
val constraints = Constraints.fixedWidth(100)
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
style = TextStyle(),
+ placeholders = emptyList(),
maxLines = 1,
softWrap = false,
overflow = TextOverflow.Ellipsis,
@@ -154,12 +162,13 @@
}
@Test
- fun testCanResue_different_overflow() {
+ fun testCanReuse_different_overflow() {
val constraints = Constraints.fixedWidth(100)
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
style = TextStyle(),
+ placeholders = emptyList(),
maxLines = 1,
softWrap = true,
overflow = TextOverflow.Clip,
@@ -172,12 +181,13 @@
}
@Test
- fun testCanResue_different_density() {
+ fun testCanReuse_different_density() {
val constraints = Constraints.fixedWidth(100)
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
style = TextStyle(),
+ placeholders = emptyList(),
maxLines = 1,
softWrap = true,
overflow = TextOverflow.Ellipsis,
@@ -190,12 +200,13 @@
}
@Test
- fun testCanResue_different_layoutDirection() {
+ fun testCanReuse_different_layoutDirection() {
val constraints = Constraints.fixedWidth(100)
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
style = TextStyle(),
+ placeholders = emptyList(),
maxLines = 1,
softWrap = true,
overflow = TextOverflow.Ellipsis,
@@ -208,12 +219,13 @@
}
@Test
- fun testCanResue_different_resourceLoader() {
+ fun testCanReuse_different_resourceLoader() {
val constraints = Constraints.fixedWidth(100)
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
style = TextStyle(),
+ placeholders = emptyList(),
maxLines = 1,
softWrap = true,
overflow = TextOverflow.Ellipsis,
@@ -226,11 +238,36 @@
}
@Test
- fun testCanResue_different_constraints() {
+ fun testCanReuse_different_constraints() {
assertThat(
referenceResult.canReuse(
text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
style = TextStyle(),
+ placeholders = emptyList(),
+ maxLines = 1,
+ softWrap = true,
+ overflow = TextOverflow.Ellipsis,
+ density = Density(1.0f),
+ layoutDirection = LayoutDirection.Ltr,
+ resourceLoader = resourceLoader,
+ constraints = Constraints.fixedWidth(200)
+ )
+ ).isFalse()
+ }
+
+ @Test
+ fun testCanReuse_different_placeholders() {
+ assertThat(
+ referenceResult.canReuse(
+ text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
+ style = TextStyle(),
+ placeholders = listOf(
+ AnnotatedString.Range(
+ item = Placeholder(10.sp, 20.sp, PlaceholderVerticalAlign.AboveBaseline),
+ start = 0,
+ end = 5
+ )
+ ),
maxLines = 1,
softWrap = true,
overflow = TextOverflow.Ellipsis,
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
new file mode 100644
index 0000000..566c2ab
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+// ktlint-disable indent https://github.com/pinterest/ktlint/issues/967
+// Ignore lint warnings in documentation snippets
+@file:Suppress(
+ "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
+ "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments",
+ "ControlFlowWithEmptyBody", "PropertyName", "CanBeParameter"
+)
+
+package androidx.compose.integration.docs.interoperability
+
+import android.content.Context
+import android.os.Bundle
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.ComposeView
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+
+/**
+ * This file lets DevRel track changes to snippets present in
+ * https://developer.android.com/jetpack/compose/interop/compose-in-existing-arch
+ *
+ * No action required if it's modified.
+ */
+
+private object InteropArchitectureSnippet1 {
+ class ExampleActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ MaterialTheme {
+ Column {
+ Greeting("user1")
+ Greeting("user2")
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun Greeting(userId: String) {
+ val greetingViewModel: GreetingViewModel = viewModel(
+ factory = GreetingViewModelFactory(userId)
+ )
+ val messageUser by greetingViewModel.message.observeAsState("")
+
+ Text(messageUser)
+ }
+
+ class GreetingViewModel(private val userId: String) : ViewModel() {
+ private val _message = MutableLiveData("Hi $userId")
+ val message: LiveData<String> = _message
+ }
+
+ class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+ return GreetingViewModel(userId) as T
+ }
+ }
+}
+
+private object InteropArchitectureSnippet2 {
+ @Composable
+ fun MyScreen() {
+ NavHost(rememberNavController(), startDestination = "profile/{userId}") {
+ /* ... */
+ composable("profile/{userId}") { backStackEntry ->
+ Greeting(backStackEntry.arguments?.getString("userId") ?: "")
+ }
+ }
+ }
+
+ @Composable
+ fun Greeting(userId: String) {
+ val greetingViewModel: GreetingViewModel = viewModel(
+ factory = GreetingViewModelFactory(userId)
+ )
+ val messageUser by greetingViewModel.message.observeAsState("")
+
+ Text(messageUser)
+ }
+}
+
+private object InteropArchitectureSnippet3 {
+ @Composable
+ fun BackHandler(
+ enabled: Boolean,
+ backDispatcher: OnBackPressedDispatcher,
+ onBack: () -> Unit
+ ) {
+
+ // Safely update the current `onBack` lambda when a new one is provided
+ val currentOnBack by rememberUpdatedState(onBack)
+
+ // Remember in Composition a back callback that calls the `onBack` lambda
+ val backCallback = remember {
+ // Always intercept back events. See the SideEffect for a more complete version
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ currentOnBack()
+ }
+ }
+ }
+
+ // On every successful composition, update the callback with the `enabled` value
+ // to tell `backCallback` whether back events should be intercepted or not
+ SideEffect {
+ backCallback.isEnabled = enabled
+ }
+
+ // If `backDispatcher` changes, dispose and reset the effect
+ DisposableEffect(backDispatcher) {
+ // Add callback to the backDispatcher
+ backDispatcher.addCallback(backCallback)
+
+ // When the effect leaves the Composition, remove the callback
+ onDispose {
+ backCallback.remove()
+ }
+ }
+ }
+}
+
+private object InteropArchitectureSnippet4 {
+ class CustomViewGroup @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+ ) : LinearLayout(context, attrs, defStyle) {
+
+ // Source of truth in the View system as mutableStateOf
+ // to make it thread-safe for Compose
+ private var text by mutableStateOf("")
+
+ private val textView: TextView
+
+ init {
+ orientation = VERTICAL
+
+ textView = TextView(context)
+ val composeView = ComposeView(context).apply {
+ setContent {
+ MaterialTheme {
+ TextField(value = text, onValueChange = { updateState(it) })
+ }
+ }
+ }
+
+ addView(textView)
+ addView(composeView)
+ }
+
+ // Update both the source of truth and the TextView
+ private fun updateState(newValue: String) {
+ text = newValue
+ textView.text = newValue
+ }
+ }
+}
+
+/*
+Fakes needed for snippets to build:
+ */
+
+private class GreetingViewModel(userId: String) : ViewModel() {
+ val _message = MutableLiveData("")
+ val message: LiveData<String> = _message
+}
+private class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+ return GreetingViewModel(userId) as T
+ }
+}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
new file mode 100644
index 0000000..5dc4770
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.
+ */
+
+// ktlint-disable indent https://github.com/pinterest/ktlint/issues/967
+// Ignore lint warnings in documentation snippets
+@file:Suppress(
+ "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
+ "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments",
+ "ControlFlowWithEmptyBody", "PropertyName", "CanBeParameter", "PackageDirectoryMismatch"
+)
+
+package androidx.compose.integration.docs.interoperabilityui
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.FloatingActionButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.unit.dp
+
+/**
+ * This file lets DevRel track changes to snippets present in
+ * https://developer.android.com/jetpack/compose/interop/compose-in-existing-ui
+ *
+ * No action required if it's modified.
+ */
+
+private object InteropUiSnippet1 {
+ @Composable
+ fun CallToActionButton(
+ text: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
+ Button(
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = MaterialTheme.colors.secondary
+ ),
+ onClick = onClick,
+ modifier = modifier,
+ ) {
+ Text(text)
+ }
+ }
+
+ class CallToActionViewButton @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+ ) : AbstractComposeView(context, attrs, defStyle) {
+
+ var text by mutableStateOf<String>("")
+ var onClick by mutableStateOf<() -> Unit>({})
+
+ @Composable
+ override fun Content() {
+ YourAppTheme {
+ CallToActionButton(text, onClick)
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet2 {
+ class ExampleActivity : Activity() {
+
+ private lateinit var binding: ActivityExampleBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityExampleBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ binding.callToAction.apply {
+ text = getString(R.string.something)
+ onClick = { /* Do something */ }
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet3 {
+ // import com.google.android.material.composethemeadapter.MdcTheme
+
+ class ExampleActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ // Use MdcTheme instead of MaterialTheme
+ // Colors, typography, and shape have been read from the
+ // View-based theme used in this Activity
+ MdcTheme {
+ ExampleComposable(/*...*/)
+ }
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet4 {
+ class ExampleActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ AppCompatTheme {
+ // Colors, typography, and shape have been read from the
+ // View-based theme used in this Activity
+ ExampleComposable(/*...*/)
+ }
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet5 {
+ class ExampleActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ setContent {
+ MaterialTheme {
+ ProvideWindowInsets {
+ MyScreen()
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun MyScreen() {
+ Box {
+ FloatingActionButton(
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .padding(16.dp) // normal 16dp of padding for FABs
+ .navigationBarsPadding(), // Move it out from under the nav bar
+ onClick = { }
+ ) {
+ Icon( /* ... */)
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet6 {
+ @Composable
+ fun MyComposable() {
+ BoxWithConstraints {
+ if (minWidth < 480.dp) {
+ /* Show grid with 4 columns */
+ } else if (minWidth < 720.dp) {
+ /* Show grid with 8 columns */
+ } else {
+ /* Show grid with 12 columns */
+ }
+ }
+ }
+}
+
+/*
+Fakes needed for snippets to build:
+ */
+
+private object R {
+ object string {
+ const val something = 1
+ }
+}
+
+private fun ExampleComposable() {}
+@Composable
+private fun MdcTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun AppCompatTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun BlueTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun PinkTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun YourAppTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun ProvideWindowInsets(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun Icon() {
+}
+
+private class WindowCompat {
+ companion object {
+ fun setDecorFitsSystemWindows(window: Any, bool: Boolean) {}
+ }
+}
+
+private fun Modifier.navigationBarsPadding(): Modifier = this
+
+private class ActivityExampleBinding {
+ val root: Int = 0
+ lateinit var callToAction: InteropUiSnippet1.CallToActionViewButton
+ companion object {
+ fun inflate(li: LayoutInflater): ActivityExampleBinding { TODO() }
+ }
+}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
index ab8da92..82f2c4a 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
@@ -24,7 +24,6 @@
package androidx.compose.integration.docs.interoperability
-import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -32,58 +31,36 @@
import android.graphics.Bitmap
import android.graphics.Color
import android.os.Bundle
-import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.OnBackPressedDispatcher
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.integration.docs.databinding.ExampleLayoutBinding
import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.FloatingActionButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
-import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.viewinterop.AndroidViewBinding
-import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
/**
* This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/interop
+ * https://developer.android.com/jetpack/compose/interop/interop-apis
*
* No action required if it's modified.
*/
@@ -251,274 +228,6 @@
}
}
-private object InteropSnippet9 {
- // import com.google.android.material.composethemeadapter.MdcTheme
-
- class ExampleActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- // Use MdcTheme instead of MaterialTheme
- // Colors, typography, and shape have been read from the
- // View-based theme used in this Activity
- MdcTheme {
- ExampleComposable(/*...*/)
- }
- }
- }
- }
-}
-
-private object InteropSnippet10 {
- class ExampleActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- AppCompatTheme {
- // Colors, typography, and shape have been read from the
- // View-based theme used in this Activity
- ExampleComposable(/*...*/)
- }
- }
- }
- }
-}
-
-private object InteropSnippet11 {
- class ExampleActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- WindowCompat.setDecorFitsSystemWindows(window, false)
-
- setContent {
- MaterialTheme {
- ProvideWindowInsets {
- MyScreen()
- }
- }
- }
- }
- }
-
- @Composable
- fun MyScreen() {
- Box {
- FloatingActionButton(
- modifier = Modifier
- .align(Alignment.BottomEnd)
- .padding(16.dp) // normal 16dp of padding for FABs
- .navigationBarsPadding(), // Move it out from under the nav bar
- onClick = { }
- ) {
- Icon( /* ... */)
- }
- }
- }
-}
-
-private object InteropSnippet12 {
- @Composable
- fun MyComposable() {
- BoxWithConstraints {
- if (minWidth < 480.dp) {
- /* Show grid with 4 columns */
- } else if (minWidth < 720.dp) {
- /* Show grid with 8 columns */
- } else {
- /* Show grid with 12 columns */
- }
- }
- }
-}
-
-private object InteropSnippet13 {
- class ExampleActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- MaterialTheme {
- Column {
- Greeting("user1")
- Greeting("user2")
- }
- }
- }
- }
- }
-
- @Composable
- fun Greeting(userId: String) {
- val greetingViewModel: GreetingViewModel = viewModel(
- factory = GreetingViewModelFactory(userId)
- )
- val messageUser by greetingViewModel.message.observeAsState("")
-
- Text(messageUser)
- }
-
- class GreetingViewModel(private val userId: String) : ViewModel() {
- private val _message = MutableLiveData("Hi $userId")
- val message: LiveData<String> = _message
- }
-}
-
-private object InteropSnippet14 {
- @Composable
- fun MyScreen() {
- NavHost(rememberNavController(), startDestination = "profile/{userId}") {
- /* ... */
- composable("profile/{userId}") { backStackEntry ->
- Greeting(backStackEntry.arguments?.getString("userId") ?: "")
- }
- }
- }
-
- @Composable
- fun Greeting(userId: String) {
- val greetingViewModel: GreetingViewModel = viewModel(
- factory = GreetingViewModelFactory(userId)
- )
- val messageUser by greetingViewModel.message.observeAsState("")
-
- Text(messageUser)
- }
-}
-
-private object InteropSnippet15 {
- @Composable
- fun BackHandler(
- enabled: Boolean,
- backDispatcher: OnBackPressedDispatcher,
- onBack: () -> Unit
- ) {
-
- // Safely update the current `onBack` lambda when a new one is provided
- val currentOnBack by rememberUpdatedState(onBack)
-
- // Remember in Composition a back callback that calls the `onBack` lambda
- val backCallback = remember {
- // Always intercept back events. See the SideEffect for a more complete version
- object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- currentOnBack()
- }
- }
- }
-
- // On every successful composition, update the callback with the `enabled` value
- // to tell `backCallback` whether back events should be intercepted or not
- SideEffect {
- backCallback.isEnabled = enabled
- }
-
- // If `backDispatcher` changes, dispose and reset the effect
- DisposableEffect(backDispatcher) {
- // Add callback to the backDispatcher
- backDispatcher.addCallback(backCallback)
-
- // When the effect leaves the Composition, remove the callback
- onDispose {
- backCallback.remove()
- }
- }
- }
-}
-
-private object InteropSnippet16 {
- class CustomViewGroup @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyle: Int = 0
- ) : LinearLayout(context, attrs, defStyle) {
-
- // Source of truth in the View system as mutableStateOf
- // to make it thread-safe for Compose
- private var text by mutableStateOf("")
-
- private val textView: TextView
-
- init {
- orientation = VERTICAL
-
- textView = TextView(context)
- val composeView = ComposeView(context).apply {
- setContent {
- MaterialTheme {
- TextField(value = text, onValueChange = { updateState(it) })
- }
- }
- }
-
- addView(textView)
- addView(composeView)
- }
-
- // Update both the source of truth and the TextView
- private fun updateState(newValue: String) {
- text = newValue
- textView.text = newValue
- }
- }
-}
-
-private object InteropSnippet17 {
- @Composable
- fun CallToActionButton(
- text: String,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- ) {
- Button(
- colors = ButtonDefaults.buttonColors(
- backgroundColor = MaterialTheme.colors.secondary
- ),
- onClick = onClick,
- modifier = modifier,
- ) {
- Text(text)
- }
- }
-
- class CallToActionViewButton @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyle: Int = 0
- ) : AbstractComposeView(context, attrs, defStyle) {
-
- var text by mutableStateOf<String>("")
- var onClick by mutableStateOf<() -> Unit>({})
-
- @Composable
- override fun Content() {
- YourAppTheme {
- CallToActionButton(text, onClick)
- }
- }
- }
-}
-
-private object InteropSnippet18 {
- class ExampleActivity : Activity() {
-
- private lateinit var binding: ActivityExampleBinding
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- binding = ActivityExampleBinding.inflate(layoutInflater)
- setContentView(binding.root)
-
- binding.callToAction.apply {
- text = getString(R.string.something)
- onClick = { /* Do something */ }
- }
- }
- }
-}
-
private object InteropSnippet19 {
@Composable
fun SystemBroadcastReceiver(
@@ -578,19 +287,18 @@
object string {
const val ok = 4
const val plane_description = 5
- const val something = 6
}
object dimen {
- const val padding_small = 7
+ const val padding_small = 6
}
object drawable {
- const val ic_plane = 8
+ const val ic_plane = 7
}
object color {
- const val Blue700 = 9
+ const val Blue700 = 8
}
}
@@ -605,7 +313,7 @@
private val data = DataExample()
private fun startActivity(): Nothing = TODO()
-class ExampleViewModel : ViewModel() {
+private class ExampleViewModel : ViewModel() {
val exampleLiveData = MutableLiveData(" ")
}
@@ -627,35 +335,6 @@
fun into(listener: ExampleImageLoader.Listener) {}
}
-private fun ExampleComposable() {}
-@Composable
-private fun MdcTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun AppCompatTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun BlueTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun PinkTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun YourAppTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun ProvideWindowInsets(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun Icon() {
-}
-
private open class Fragment {
lateinit var context: Context
@@ -669,28 +348,3 @@
fun requireContext(): Context = TODO()
}
-
-private class WindowCompat {
- companion object {
- fun setDecorFitsSystemWindows(window: Any, bool: Boolean) {}
- }
-}
-
-private fun Modifier.navigationBarsPadding(): Modifier = this
-
-private class GreetingViewModel : ViewModel() {
- val _message = MutableLiveData("")
- val message: LiveData<String> = _message
-}
-private class GreetingViewModelFactory(val userId: String) : ViewModelProvider.Factory {
- override fun <T : ViewModel?> create(modelClass: Class<T>): T {
- TODO("Not yet implemented")
- }
-}
-private class ActivityExampleBinding {
- val root: Int = 0
- lateinit var callToAction: InteropSnippet17.CallToActionViewButton
- companion object {
- fun inflate(li: LayoutInflater): ActivityExampleBinding { TODO() }
- }
-}
\ No newline at end of file
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
index 5f24b0b..58784ab 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
@@ -44,8 +44,7 @@
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
-import androidx.navigation.compose.getBackStackEntry
-import androidx.navigation.compose.navigation
+import androidx.navigation.navigation
import kotlinx.coroutines.flow.Flow
/**
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt
index 6dc9a73..c70a8db 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt
@@ -40,12 +40,10 @@
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavType
-import androidx.navigation.compose.KEY_ROUTE
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.navArgument
-import androidx.navigation.compose.navigate
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navDeepLink
@@ -160,7 +158,7 @@
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
- val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
+ val currentRoute = navBackStackEntry?.destination?.route
items.forEach { screen ->
BottomNavigationItem(
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
@@ -168,7 +166,9 @@
selected = currentRoute == screen.route,
onClick = {
// This is the equivalent to popUpTo the start destination
- navController.popBackStack(navController.graph.startDestination, false)
+ navController.popBackStack(
+ navController.graph.startDestinationId, false
+ )
// This if check gives us a "singleTop" behavior where we do not create a
// second instance of the composable if we are already on that destination
diff --git a/compose/integration-tests/macrobenchmark/build.gradle b/compose/integration-tests/macrobenchmark/build.gradle
index 6568c2a..f400f4d 100644
--- a/compose/integration-tests/macrobenchmark/build.gradle
+++ b/compose/integration-tests/macrobenchmark/build.gradle
@@ -35,6 +35,7 @@
dependencies {
androidTestImplementation(project(":benchmark:benchmark-junit4"))
androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+ androidTestImplementation(project(":internal-testutils-macrobenchmark"))
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
index 0fe6f16..cb9e113 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
@@ -20,6 +20,8 @@
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,7 +39,8 @@
@Test
fun startup() = benchmarkRule.measureStartup(
compilationMode = compilationMode,
- startupMode = startupMode
+ startupMode = startupMode,
+ packageName = "androidx.compose.integration.macrobenchmark.target"
) {
action = "androidx.compose.integration.macrobenchmark.target.LAZY_COLUMN_ACTIVITY"
putExtra("ITEM_COUNT", 5)
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt
index 1be3b5c..6d0939c 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt
@@ -26,6 +26,7 @@
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
+import androidx.testutils.createCompilationParams
import org.junit.Before
import org.junit.Rule
import org.junit.Test
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
index 45167ff..1a6254b 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
@@ -20,6 +20,8 @@
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,7 +39,8 @@
@Test
fun startup() = benchmarkRule.measureStartup(
compilationMode = compilationMode,
- startupMode = startupMode
+ startupMode = startupMode,
+ packageName = "androidx.compose.integration.macrobenchmark.target"
) {
action = "androidx.compose.integration.macrobenchmark.target.TRIVIAL_STARTUP_ACTIVITY"
}
diff --git a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/NavGraph.kt b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/NavGraph.kt
index 390c6b2..e690e58 100644
--- a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/NavGraph.kt
+++ b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/NavGraph.kt
@@ -27,7 +27,6 @@
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.navArgument
-import androidx.navigation.compose.navigate
import androidx.navigation.compose.rememberNavController
@Composable
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt
index dbb3ff1..3d6c1b2 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt
@@ -103,7 +103,7 @@
},
content = { innerPadding ->
LazyColumn(contentPadding = innerPadding) {
- items(100) {
+ items(count = 100) {
Box(
Modifier
.fillMaxWidth()
@@ -178,7 +178,7 @@
isFloatingActionButtonDocked = true,
content = { innerPadding ->
LazyColumn(contentPadding = innerPadding) {
- items(100) {
+ items(count = 100) {
Box(
Modifier
.fillMaxWidth()
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
index 13c198f..7649e03 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
@@ -60,9 +60,6 @@
* Contained buttons are high-emphasis, distinguished by their use of elevation and fill. They
* contain actions that are primary to your app.
*
- * To make a button clickable, you must provide an onClick. If no onClick is provided, this button
- * will display itself as disabled.
- *
* The default text style for internal [Text] components will be set to [Typography.button]. Text
* color will try to match the correlated color for the background color. For example if the
* background color is set to [Colors.primary] then the text will by default use
@@ -155,9 +152,6 @@
* Outlined buttons are also a lower emphasis alternative to contained buttons, or a higher emphasis
* alternative to text buttons.
*
- * To make a button clickable, you must provide an onClick. If no onClick is provided, this button
- * will display itself as disabled.
- *
* The default text style for internal [Text] components will be set to [Typography.button]. Text
* color will try to match the correlated color for the background color. For example if the
* background color is set to [Colors.primary] then the text will by default use
@@ -213,9 +207,6 @@
* Text buttons are typically used for less-pronounced actions, including those located in cards and
* dialogs.
*
- * To make a button clickable, you must provide an onClick. If no onClick is provided, this button
- * will display itself as disabled.
- *
* The default text style for internal [Text] components will be set to [Typography.button]. Text
* color will try to match the correlated color for the background color. For example if the
* background color is set to [Colors.primary] then the text will by default use
diff --git a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
index f9f5377..30224a5 100644
--- a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
+++ b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
@@ -17,9 +17,9 @@
package androidx.compose.runtime.saveable
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.key
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.ReusableContent
import androidx.compose.runtime.remember
/**
@@ -73,7 +73,7 @@
@Composable
override fun SaveableStateProvider(key: Any, content: @Composable () -> Unit) {
- key(key) {
+ ReusableContent(key) {
val registryHolder = remember {
require(parentSaveableStateRegistry?.canBeSaved(key) ?: true) {
"Type of the key $key is not supported. On Android you can only use types " +
@@ -85,7 +85,7 @@
LocalSaveableStateRegistry provides registryHolder.registry,
content = content
)
- DisposableEffect(key) {
+ DisposableEffect(Unit) {
require(key !in registryHolders) { "Key $key was used multiple times " }
savedStates -= key
registryHolders[key] = registryHolder
diff --git a/compose/runtime/runtime/api/1.0.0-beta07.txt b/compose/runtime/runtime/api/1.0.0-beta07.txt
index b1f3cf8..6b7c331 100644
--- a/compose/runtime/runtime/api/1.0.0-beta07.txt
+++ b/compose/runtime/runtime/api/1.0.0-beta07.txt
@@ -56,6 +56,10 @@
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -83,11 +87,14 @@
method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(long value);
method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(double value);
method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+ method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+ method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+ method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
method public androidx.compose.runtime.Applier<?> getApplier();
method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -101,14 +108,16 @@
method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+ method public void sourceInformation(String sourceInformation);
+ method public void sourceInformationMarkerEnd();
+ method public void sourceInformationMarkerStart(int key, String sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
- method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -130,6 +139,9 @@
public final class ComposerKt {
method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
}
public interface Composition {
@@ -489,16 +501,16 @@
}
public final class ComposableLambdaKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
}
@androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
}
public final class ComposableLambdaN_jvmKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
}
public final class DecoyKt {
diff --git a/compose/runtime/runtime/api/current.ignore b/compose/runtime/runtime/api/current.ignore
index 23a3504..87dd33f 100644
--- a/compose/runtime/runtime/api/current.ignore
+++ b/compose/runtime/runtime/api/current.ignore
@@ -1,45 +1,33 @@
// Baseline format: 1.0
-AddedAbstractMethod: androidx.compose.runtime.Composer#apply(V, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>):
- Added method androidx.compose.runtime.Composer.apply(V,kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>)
-AddedAbstractMethod: androidx.compose.runtime.Composer#changed(Object):
- Added method androidx.compose.runtime.Composer.changed(Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#createNode(kotlin.jvm.functions.Function0<? extends T>):
- Added method androidx.compose.runtime.Composer.createNode(kotlin.jvm.functions.Function0<? extends T>)
-AddedAbstractMethod: androidx.compose.runtime.Composer#endDefaults():
- Added method androidx.compose.runtime.Composer.endDefaults()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endMovableGroup():
- Added method androidx.compose.runtime.Composer.endMovableGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endNode():
- Added method androidx.compose.runtime.Composer.endNode()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endReplaceableGroup():
- Added method androidx.compose.runtime.Composer.endReplaceableGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endRestartGroup():
- Added method androidx.compose.runtime.Composer.endRestartGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#joinKey(Object, Object):
- Added method androidx.compose.runtime.Composer.joinKey(Object,Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#rememberedValue():
- Added method androidx.compose.runtime.Composer.rememberedValue()
-AddedAbstractMethod: androidx.compose.runtime.Composer#skipCurrentGroup():
- Added method androidx.compose.runtime.Composer.skipCurrentGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#skipToGroupEnd():
- Added method androidx.compose.runtime.Composer.skipToGroupEnd()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startDefaults():
- Added method androidx.compose.runtime.Composer.startDefaults()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object):
- Added method androidx.compose.runtime.Composer.startMovableGroup(int,Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object, String):
- Added method androidx.compose.runtime.Composer.startMovableGroup(int,Object,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startNode():
- Added method androidx.compose.runtime.Composer.startNode()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int):
- Added method androidx.compose.runtime.Composer.startReplaceableGroup(int)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int, String):
- Added method androidx.compose.runtime.Composer.startReplaceableGroup(int,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startRestartGroup(int):
- Added method androidx.compose.runtime.Composer.startRestartGroup(int)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startRestartGroup(int, String):
- Added method androidx.compose.runtime.Composer.startRestartGroup(int,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#updateRememberedValue(Object):
- Added method androidx.compose.runtime.Composer.updateRememberedValue(Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#useNode():
- Added method androidx.compose.runtime.Composer.useNode()
+AddedAbstractMethod: androidx.compose.runtime.Composer#disableReusing():
+ Added method androidx.compose.runtime.Composer.disableReusing()
+AddedAbstractMethod: androidx.compose.runtime.Composer#enableReusing():
+ Added method androidx.compose.runtime.Composer.enableReusing()
+AddedAbstractMethod: androidx.compose.runtime.Composer#endReusableGroup():
+ Added method androidx.compose.runtime.Composer.endReusableGroup()
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformation(String):
+ Added method androidx.compose.runtime.Composer.sourceInformation(String)
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformationMarkerEnd():
+ Added method androidx.compose.runtime.Composer.sourceInformationMarkerEnd()
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformationMarkerStart(int, String):
+ Added method androidx.compose.runtime.Composer.sourceInformationMarkerStart(int,String)
+AddedAbstractMethod: androidx.compose.runtime.Composer#startReusableGroup(int, Object):
+ Added method androidx.compose.runtime.Composer.startReusableGroup(int,Object)
+AddedAbstractMethod: androidx.compose.runtime.Composer#startReusableNode():
+ Added method androidx.compose.runtime.Composer.startReusableNode()
+
+
+RemovedMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object, String):
+ Removed method androidx.compose.runtime.Composer.startMovableGroup(int,Object,String)
+RemovedMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int, String):
+ Removed method androidx.compose.runtime.Composer.startReplaceableGroup(int,String)
+RemovedMethod: androidx.compose.runtime.Composer#startRestartGroup(int, String):
+ Removed method androidx.compose.runtime.Composer.startRestartGroup(int,String)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaKt#composableLambda(androidx.compose.runtime.Composer, int, boolean, String, Object):
+ Removed method androidx.compose.runtime.internal.ComposableLambdaKt.composableLambda(androidx.compose.runtime.Composer,int,boolean,String,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaKt#composableLambdaInstance(int, boolean, String, Object):
+ Removed method androidx.compose.runtime.internal.ComposableLambdaKt.composableLambdaInstance(int,boolean,String,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaN_jvmKt#composableLambdaN(androidx.compose.runtime.Composer, int, boolean, String, int, Object):
+ Removed method androidx.compose.runtime.internal.ComposableLambdaN_jvmKt.composableLambdaN(androidx.compose.runtime.Composer,int,boolean,String,int,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaN_jvmKt#composableLambdaNInstance(int, boolean, String, int, Object):
+ Removed method androidx.compose.runtime.internal.ComposableLambdaN_jvmKt.composableLambdaNInstance(int,boolean,String,int,Object)
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index b1f3cf8..6b7c331 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -56,6 +56,10 @@
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -83,11 +87,14 @@
method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(long value);
method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(double value);
method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+ method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+ method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+ method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
method public androidx.compose.runtime.Applier<?> getApplier();
method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -101,14 +108,16 @@
method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+ method public void sourceInformation(String sourceInformation);
+ method public void sourceInformationMarkerEnd();
+ method public void sourceInformationMarkerStart(int key, String sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
- method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -130,6 +139,9 @@
public final class ComposerKt {
method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
}
public interface Composition {
@@ -489,16 +501,16 @@
}
public final class ComposableLambdaKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
}
@androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
}
public final class ComposableLambdaN_jvmKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
}
public final class DecoyKt {
diff --git a/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta07.txt b/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta07.txt
index b73e719..3cbeb22 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta07.txt
@@ -56,6 +56,10 @@
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -86,12 +90,15 @@
method @androidx.compose.runtime.InternalComposeApi public void collectParameterInformation();
method @androidx.compose.runtime.InternalComposeApi public <T> T! consume(androidx.compose.runtime.CompositionLocal<T> key);
method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+ method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+ method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
method @androidx.compose.runtime.InternalComposeApi public void endProviders();
method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+ method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
method public androidx.compose.runtime.Applier<?> getApplier();
method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -107,15 +114,17 @@
method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+ method public void sourceInformation(String sourceInformation);
+ method public void sourceInformationMarkerEnd();
+ method public void sourceInformationMarkerStart(int key, String sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
- method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
method @androidx.compose.runtime.InternalComposeApi public void startProviders(androidx.compose.runtime.ProvidedValue<?>![] values);
method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -137,6 +146,9 @@
public final class ComposerKt {
method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
}
public interface Composition {
@@ -507,16 +519,16 @@
}
public final class ComposableLambdaKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
}
@androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
}
public final class ComposableLambdaN_jvmKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
}
@androidx.compose.runtime.ExperimentalComposeApi @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Decoy {
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index b73e719..3cbeb22 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -56,6 +56,10 @@
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -86,12 +90,15 @@
method @androidx.compose.runtime.InternalComposeApi public void collectParameterInformation();
method @androidx.compose.runtime.InternalComposeApi public <T> T! consume(androidx.compose.runtime.CompositionLocal<T> key);
method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+ method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+ method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
method @androidx.compose.runtime.InternalComposeApi public void endProviders();
method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+ method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
method public androidx.compose.runtime.Applier<?> getApplier();
method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -107,15 +114,17 @@
method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+ method public void sourceInformation(String sourceInformation);
+ method public void sourceInformationMarkerEnd();
+ method public void sourceInformationMarkerStart(int key, String sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
- method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
method @androidx.compose.runtime.InternalComposeApi public void startProviders(androidx.compose.runtime.ProvidedValue<?>![] values);
method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -137,6 +146,9 @@
public final class ComposerKt {
method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
}
public interface Composition {
@@ -507,16 +519,16 @@
}
public final class ComposableLambdaKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
}
@androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
}
public final class ComposableLambdaN_jvmKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
}
@androidx.compose.runtime.ExperimentalComposeApi @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Decoy {
diff --git a/compose/runtime/runtime/api/restricted_1.0.0-beta07.txt b/compose/runtime/runtime/api/restricted_1.0.0-beta07.txt
index e216e95..2cd34c5 100644
--- a/compose/runtime/runtime/api/restricted_1.0.0-beta07.txt
+++ b/compose/runtime/runtime/api/restricted_1.0.0-beta07.txt
@@ -57,6 +57,10 @@
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -85,11 +89,14 @@
method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(long value);
method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(double value);
method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+ method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+ method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+ method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
method public androidx.compose.runtime.Applier<?> getApplier();
method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -103,14 +110,16 @@
method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+ method public void sourceInformation(String sourceInformation);
+ method public void sourceInformationMarkerEnd();
+ method public void sourceInformationMarkerStart(int key, String sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
- method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -132,6 +141,9 @@
public final class ComposerKt {
method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
field @kotlin.PublishedApi internal static final Object compositionLocalMap;
field @kotlin.PublishedApi internal static final int compositionLocalMapKey = 202; // 0xca
field @kotlin.PublishedApi internal static final Object invocation;
@@ -144,6 +156,7 @@
field @kotlin.PublishedApi internal static final int providerValuesKey = 203; // 0xcb
field @kotlin.PublishedApi internal static final Object reference;
field @kotlin.PublishedApi internal static final int referenceKey = 206; // 0xce
+ field @kotlin.PublishedApi internal static final int reuseKey = 207; // 0xcf
}
public interface Composition {
@@ -516,16 +529,16 @@
}
public final class ComposableLambdaKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
}
@androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
}
public final class ComposableLambdaN_jvmKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
}
public final class DecoyKt {
diff --git a/compose/runtime/runtime/api/restricted_current.ignore b/compose/runtime/runtime/api/restricted_current.ignore
index 23a3504..87dd33f 100644
--- a/compose/runtime/runtime/api/restricted_current.ignore
+++ b/compose/runtime/runtime/api/restricted_current.ignore
@@ -1,45 +1,33 @@
// Baseline format: 1.0
-AddedAbstractMethod: androidx.compose.runtime.Composer#apply(V, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>):
- Added method androidx.compose.runtime.Composer.apply(V,kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>)
-AddedAbstractMethod: androidx.compose.runtime.Composer#changed(Object):
- Added method androidx.compose.runtime.Composer.changed(Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#createNode(kotlin.jvm.functions.Function0<? extends T>):
- Added method androidx.compose.runtime.Composer.createNode(kotlin.jvm.functions.Function0<? extends T>)
-AddedAbstractMethod: androidx.compose.runtime.Composer#endDefaults():
- Added method androidx.compose.runtime.Composer.endDefaults()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endMovableGroup():
- Added method androidx.compose.runtime.Composer.endMovableGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endNode():
- Added method androidx.compose.runtime.Composer.endNode()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endReplaceableGroup():
- Added method androidx.compose.runtime.Composer.endReplaceableGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endRestartGroup():
- Added method androidx.compose.runtime.Composer.endRestartGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#joinKey(Object, Object):
- Added method androidx.compose.runtime.Composer.joinKey(Object,Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#rememberedValue():
- Added method androidx.compose.runtime.Composer.rememberedValue()
-AddedAbstractMethod: androidx.compose.runtime.Composer#skipCurrentGroup():
- Added method androidx.compose.runtime.Composer.skipCurrentGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#skipToGroupEnd():
- Added method androidx.compose.runtime.Composer.skipToGroupEnd()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startDefaults():
- Added method androidx.compose.runtime.Composer.startDefaults()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object):
- Added method androidx.compose.runtime.Composer.startMovableGroup(int,Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object, String):
- Added method androidx.compose.runtime.Composer.startMovableGroup(int,Object,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startNode():
- Added method androidx.compose.runtime.Composer.startNode()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int):
- Added method androidx.compose.runtime.Composer.startReplaceableGroup(int)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int, String):
- Added method androidx.compose.runtime.Composer.startReplaceableGroup(int,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startRestartGroup(int):
- Added method androidx.compose.runtime.Composer.startRestartGroup(int)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startRestartGroup(int, String):
- Added method androidx.compose.runtime.Composer.startRestartGroup(int,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#updateRememberedValue(Object):
- Added method androidx.compose.runtime.Composer.updateRememberedValue(Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#useNode():
- Added method androidx.compose.runtime.Composer.useNode()
+AddedAbstractMethod: androidx.compose.runtime.Composer#disableReusing():
+ Added method androidx.compose.runtime.Composer.disableReusing()
+AddedAbstractMethod: androidx.compose.runtime.Composer#enableReusing():
+ Added method androidx.compose.runtime.Composer.enableReusing()
+AddedAbstractMethod: androidx.compose.runtime.Composer#endReusableGroup():
+ Added method androidx.compose.runtime.Composer.endReusableGroup()
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformation(String):
+ Added method androidx.compose.runtime.Composer.sourceInformation(String)
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformationMarkerEnd():
+ Added method androidx.compose.runtime.Composer.sourceInformationMarkerEnd()
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformationMarkerStart(int, String):
+ Added method androidx.compose.runtime.Composer.sourceInformationMarkerStart(int,String)
+AddedAbstractMethod: androidx.compose.runtime.Composer#startReusableGroup(int, Object):
+ Added method androidx.compose.runtime.Composer.startReusableGroup(int,Object)
+AddedAbstractMethod: androidx.compose.runtime.Composer#startReusableNode():
+ Added method androidx.compose.runtime.Composer.startReusableNode()
+
+
+RemovedMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object, String):
+ Removed method androidx.compose.runtime.Composer.startMovableGroup(int,Object,String)
+RemovedMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int, String):
+ Removed method androidx.compose.runtime.Composer.startReplaceableGroup(int,String)
+RemovedMethod: androidx.compose.runtime.Composer#startRestartGroup(int, String):
+ Removed method androidx.compose.runtime.Composer.startRestartGroup(int,String)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaKt#composableLambda(androidx.compose.runtime.Composer, int, boolean, String, Object):
+ Removed method androidx.compose.runtime.internal.ComposableLambdaKt.composableLambda(androidx.compose.runtime.Composer,int,boolean,String,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaKt#composableLambdaInstance(int, boolean, String, Object):
+ Removed method androidx.compose.runtime.internal.ComposableLambdaKt.composableLambdaInstance(int,boolean,String,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaN_jvmKt#composableLambdaN(androidx.compose.runtime.Composer, int, boolean, String, int, Object):
+ Removed method androidx.compose.runtime.internal.ComposableLambdaN_jvmKt.composableLambdaN(androidx.compose.runtime.Composer,int,boolean,String,int,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaN_jvmKt#composableLambdaNInstance(int, boolean, String, int, Object):
+ Removed method androidx.compose.runtime.internal.ComposableLambdaN_jvmKt.composableLambdaNInstance(int,boolean,String,int,Object)
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index e216e95..2cd34c5 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -57,6 +57,10 @@
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+ method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -85,11 +89,14 @@
method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(long value);
method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(double value);
method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+ method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+ method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+ method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
method public androidx.compose.runtime.Applier<?> getApplier();
method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -103,14 +110,16 @@
method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+ method public void sourceInformation(String sourceInformation);
+ method public void sourceInformationMarkerEnd();
+ method public void sourceInformationMarkerStart(int key, String sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
- method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
- method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+ method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -132,6 +141,9 @@
public final class ComposerKt {
method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+ method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
field @kotlin.PublishedApi internal static final Object compositionLocalMap;
field @kotlin.PublishedApi internal static final int compositionLocalMapKey = 202; // 0xca
field @kotlin.PublishedApi internal static final Object invocation;
@@ -144,6 +156,7 @@
field @kotlin.PublishedApi internal static final int providerValuesKey = 203; // 0xcb
field @kotlin.PublishedApi internal static final Object reference;
field @kotlin.PublishedApi internal static final int referenceKey = 206; // 0xce
+ field @kotlin.PublishedApi internal static final int reuseKey = 207; // 0xcf
}
public interface Composition {
@@ -516,16 +529,16 @@
}
public final class ComposableLambdaKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
}
@androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
}
public final class ComposableLambdaN_jvmKt {
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
- method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+ method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
}
public final class DecoyKt {
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index f6127c3..d8bae21 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -106,6 +106,9 @@
}
android {
+ defaultConfig {
+ consumerProguardFiles 'proguard-rules.pro'
+ }
buildTypes {
debug {
testCoverageEnabled = false
diff --git a/compose/runtime/runtime/proguard-rules.pro b/compose/runtime/runtime/proguard-rules.pro
new file mode 100644
index 0000000..0343d92
--- /dev/null
+++ b/compose/runtime/runtime/proguard-rules.pro
@@ -0,0 +1,5 @@
+-assumenosideeffects public class androidx.compose.runtime.ComposerKt {
+ void sourceInformation(androidx.compose.runtime.Composer,java.lang.String);
+ void sourceInformationMarkerStart(androidx.compose.runtime.Composer,int,java.lang.String);
+ void sourceInformationMarkerEnd(androidx.compose.runtime.Composer);
+}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
index eb599eb..09fef48 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
@@ -129,6 +129,25 @@
) = block()
/**
+ * A utility function to mark a composition as supporting recycling. If the [key] changes the
+ * composition is replaced by a new composition (as would happen for [key]) but reusable nodes
+ * that are emitted by [ReusableComposeNode] are reused.
+ *
+ * @param key the value that is used to trigger recycling. If recomposed with a different value
+ * the composer creates a new composition but tries to reuse reusable nodes.
+ * @param content the composable children that are recyclable.
+ */
+@Composable
+inline fun ReusableContent(
+ key: Any?,
+ content: @Composable () -> Unit
+) {
+ currentComposer.startReusableGroup(reuseKey, key)
+ content()
+ currentComposer.endReusableGroup()
+}
+
+/**
* TODO(lmr): provide documentation
*/
val currentComposer: Composer
@@ -198,6 +217,43 @@
}
/**
+ * Emits a recyclable node into the composition of type [T].
+ *
+ * This function will throw a runtime exception if [E] is not a subtype of the applier of the
+ * [currentComposer].
+ *
+ * @sample androidx.compose.runtime.samples.CustomTreeComposition
+ *
+ * @param factory A function which will create a new instance of [T]. This function is NOT
+ * guaranteed to be called in place.
+ * @param update A function to perform updates on the node. This will run every time emit is
+ * executed. This function is called in place and will be inlined.
+ *
+ * @see Updater
+ * @see Applier
+ * @see Composition
+ */
+// ComposeNode is a special case of readonly composable and handles creating its own groups, so
+// it is okay to use.
+@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE", "UnnecessaryLambdaCreation")
+@Composable inline fun <T : Any, reified E : Applier<*>> ReusableComposeNode(
+ noinline factory: () -> T,
+ update: @DisallowComposableCalls Updater<T>.() -> Unit
+) {
+ if (currentComposer.applier !is E) invalidApplier()
+ currentComposer.startReusableNode()
+ if (currentComposer.inserting) {
+ currentComposer.createNode { factory() }
+ } else {
+ currentComposer.useNode()
+ }
+ currentComposer.disableReusing()
+ Updater<T>(currentComposer).update()
+ currentComposer.enableReusing()
+ currentComposer.endNode()
+}
+
+/**
* Emits a node into the composition of type [T]. Nodes emitted inside of [content] will become
* children of the emitted node.
*
@@ -238,6 +294,48 @@
}
/**
+ * Emits a recyclable node into the composition of type [T]. Nodes emitted inside of [content] will
+ * become children of the emitted node.
+ *
+ * This function will throw a runtime exception if [E] is not a subtype of the applier of the
+ * [currentComposer].
+ *
+ * @sample androidx.compose.runtime.samples.CustomTreeComposition
+ *
+ * @param factory A function which will create a new instance of [T]. This function is NOT
+ * guaranteed to be called in place.
+ * @param update A function to perform updates on the node. This will run every time emit is
+ * executed. This function is called in place and will be inlined.
+ * @param content the composable content that will emit the "children" of this node.
+ *
+ * @see Updater
+ * @see Applier
+ * @see Composition
+ */
+// ComposeNode is a special case of readonly composable and handles creating its own groups, so
+// it is okay to use.
+@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE")
+@Composable
+inline fun <T : Any?, reified E : Applier<*>> ReusableComposeNode(
+ noinline factory: () -> T,
+ update: @DisallowComposableCalls Updater<T>.() -> Unit,
+ content: @Composable () -> Unit
+) {
+ if (currentComposer.applier !is E) invalidApplier()
+ currentComposer.startReusableNode()
+ if (currentComposer.inserting) {
+ currentComposer.createNode(factory)
+ } else {
+ currentComposer.useNode()
+ }
+ currentComposer.disableReusing()
+ Updater<T>(currentComposer).update()
+ currentComposer.enableReusing()
+ content()
+ currentComposer.endNode()
+}
+
+/**
* Emits a node into the composition of type [T]. Nodes emitted inside of [content] will become
* children of the emitted node.
*
@@ -284,6 +382,55 @@
currentComposer.endNode()
}
+/**
+ * Emits a recyclable node into the composition of type [T]. Nodes emitted inside of [content] will
+ * become children of the emitted node.
+ *
+ * This function will throw a runtime exception if [E] is not a subtype of the applier of the
+ * [currentComposer].
+ *
+ * @sample androidx.compose.runtime.samples.CustomTreeComposition
+ *
+ * @param factory A function which will create a new instance of [T]. This function is NOT
+ * guaranteed to be called in place.
+ * @param update A function to perform updates on the node. This will run every time emit is
+ * executed. This function is called in place and will be inlined.
+ * @param skippableUpdate A function to perform updates on the node. Unlike [update], this
+ * function is Composable and will therefore be skipped unless it has been invalidated by some
+ * other mechanism. This can be useful to perform expensive calculations for updating the node
+ * where the calculations are likely to have the same inputs over time, so the function's
+ * execution can be skipped.
+ * @param content the composable content that will emit the "children" of this node.
+ *
+ * @see Updater
+ * @see SkippableUpdater
+ * @see Applier
+ * @see Composition
+ */
+@Composable @ExplicitGroupsComposable
+inline fun <T, reified E : Applier<*>> ReusableComposeNode(
+ noinline factory: () -> T,
+ update: @DisallowComposableCalls Updater<T>.() -> Unit,
+ noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
+ content: @Composable () -> Unit
+) {
+ if (currentComposer.applier !is E) invalidApplier()
+ currentComposer.startReusableNode()
+ if (currentComposer.inserting) {
+ currentComposer.createNode(factory)
+ } else {
+ currentComposer.useNode()
+ }
+ currentComposer.disableReusing()
+ Updater<T>(currentComposer).update()
+ SkippableUpdater<T>(currentComposer).skippableUpdate()
+ currentComposer.enableReusing()
+ currentComposer.startReplaceableGroup(0x7ab4aae9)
+ content()
+ currentComposer.endReplaceableGroup()
+ currentComposer.endNode()
+}
+
@PublishedApi
internal fun invalidApplier(): Unit = error("Invalid applier")
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 422c2e3..3f84fc1 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -378,7 +378,7 @@
* execution and can only either inserted, removed, or replaced. For example, the group
* created by most control flow constructs such as an `if` statement are replacable groups.
*
- * @param key An compiler generated key based on the source location of the call.
+ * @param key A compiler generated key based on the source location of the call.
*/
@ComposeCompilerApi
fun startReplaceableGroup(key: Int)
@@ -386,20 +386,6 @@
/**
* A Compose compiler plugin API. DO NOT call directly.
*
- * Start a replacable group. A replacable group is a group that cannot be moved during
- * execution and can only either inserted, removed, or replaced. For example, the group
- * created by most control flow constructs such as an `if` statement are replacable groups.
- *
- * @param key An compiler generated key based on the source location of the call.
- * @param sourceInformation An optional string value to that provides the compose tools enough
- * information to calculate the source location calls made in the group.
- */
- @ComposeCompilerApi
- fun startReplaceableGroup(key: Int, sourceInformation: String?)
-
- /**
- * A Compose compiler plugin API. DO NOT call directly.
- *
* Called at the end of a replacable group.
*
* @see startRestartGroup
@@ -418,7 +404,7 @@
* the state and nodes generated by a loop to move with the composition implied by the key
* passed to [key][androidx.compose.runtime.key].
- * @param key An compiler generated key based on the source location of the call.
+ * @param key A compiler generated key based on the source location of the call.
*/
@ComposeCompilerApi
fun startMovableGroup(key: Int, dataKey: Any?)
@@ -426,24 +412,6 @@
/**
* A Compose compiler plugin API. DO NOT call directly.
*
- * Start a movable group. A movable group is one that can be moved based on the value of
- * [dataKey] which is typically supplied by the [key][androidx.compose.runtime.key] pseudo
- * compiler function.
- *
- * A movable group implements the semantics of [key][androidx.compose.runtime.key] which allows
- * the state and nodes generated by a loop to move with the composition implied by the key
- * passed to [key][androidx.compose.runtime.key].
-
- * @param key An compiler generated key based on the source location of the call.
- * @param sourceInformation An optional string value to that provides the compose tools enough
- * information to calculate the source location calls made in the group.
- */
- @ComposeCompilerApi
- fun startMovableGroup(key: Int, dataKey: Any?, sourceInformation: String?)
-
- /**
- * A Compose compiler plugin API. DO NOT call directly.
- *
* Called at the end of a movable group.
*
* @see startMovableGroup
@@ -481,7 +449,7 @@
* recomposed on demand based on the lambda passed to
* [updateScope][ScopeUpdateScope.updateScope] when [endRestartGroup] is called
*
- * @param key An compiler generated key based on the source location of the call.
+ * @param key A compiler generated key based on the source location of the call.
* @return the instance of the composer to use for the rest of the function.
*/
@ComposeCompilerApi
@@ -490,21 +458,6 @@
/**
* A Compose compiler plugin API. DO NOT call directly.
*
- * Called to record a group for a [Composable] function and starts a group that can be
- * recomposed on demand based on the lambda passed to
- * [updateScope][ScopeUpdateScope.updateScope] when [endRestartGroup] is called
- *
- * @param key An compiler generated key based on the source location of the call.
- * @param sourceInformation An optional string value to that provides the compose tools enough
- * information to calculate the source location calls made in the group.
- * @return the instance of the composer to use for the rest of the function.
- */
- @ComposeCompilerApi
- fun startRestartGroup(key: Int, sourceInformation: String?): Composer
-
- /**
- * A Compose compiler plugin API. DO NOT call directly.
- *
* Called to end a restart group.
*/
@ComposeCompilerApi
@@ -513,6 +466,38 @@
/**
* A Compose compiler plugin API. DO NOT call directly.
*
+ * Record the source information string for a group. This must be immediately called after the
+ * start of a group.
+ *
+ * @param sourceInformation An string value to that provides the compose tools enough
+ * information to calculate the source location of calls to composable functions.
+ */
+ fun sourceInformation(sourceInformation: String)
+
+ /**
+ * A compose compiler plugin API. DO NOT call directly.
+ *
+ * Record a source information marker. This marker can be used in place of a group that would
+ * have contained the information but was elided as the compiler plugin determined the group
+ * was not necessary such as when a function is marked with [ReadOnlyComposable].
+ *
+ * @param key A compiler generated key based on the source location of the call.
+ * @param sourceInformation An string value to that provides the compose tools enough
+ * information to calculate the source location of calls to composable functions.
+ *
+ */
+ fun sourceInformationMarkerStart(key: Int, sourceInformation: String)
+
+ /**
+ * A compose compiler plugin API. DO NOT call directly.
+ *
+ * Record the end of the marked source information range.
+ */
+ fun sourceInformationMarkerEnd()
+
+ /**
+ * A Compose compiler plugin API. DO NOT call directly.
+ *
* Skips the composer to the end of the current group. This generated by the compiler to when
* the body of a [Composable] function can be skipped typically because the parameters to the
* function are equal to the values passed to it in the previous composition.
@@ -544,6 +529,16 @@
/**
* A Compose compiler plugin API. DO NOT call directly.
*
+ * Start a group that tracks a the code that will create or update a node that is generated
+ * as part of the tree implied by the composition. A reusable node can be reused in a
+ * reusable group even if the group key is changed.
+ */
+ @ComposeCompilerApi
+ fun startReusableNode()
+
+ /**
+ * A Compose compiler plugin API. DO NOT call directly.
+ *
* Report the [factory] that will be used to create the node that will be generated into the
* tree implied by the composition. This will only be called if [inserting] is is `true`.
*
@@ -573,6 +568,48 @@
/**
* A Compose compiler plugin API. DO NOT call directly.
*
+ * Start a reuse group. Unlike a movable group, in a reuse group if the [dataKey] changes
+ * the composition shifts into a reusing state cause the composer to act like it is
+ * inserting (e.g. [cache] acts as if all values are invalid, [changed] always returns
+ * true, etc.) even though it is recomposing until it encounters a reusable node. If the
+ * node is reusable it temporarily shifts into recomposition for the node and then shifts
+ * back to reusing for the children. If a non-reusable node is generated the composer
+ * shifts to inserting for the node and all of its children.
+ *
+ * @param key An compiler generated key based on the source location of the call.
+ * @param dataKey A key provided by the [ReusableContent] composable function that is used to
+ * determine if the composition shifts into a reusing state for this group.
+ */
+ @ComposeCompilerApi
+ fun startReusableGroup(key: Int, dataKey: Any?)
+
+ /**
+ * A Compose compiler plugin API. DO NOT call directly.
+ *
+ * Called at the end of a reusable group.
+ */
+ @ComposeCompilerApi
+ fun endReusableGroup()
+
+ /**
+ * A Compose compiler plugin API. DO NOT call directly.
+ *
+ * Temporarily disable reusing if it is enabled.
+ */
+ @ComposeCompilerApi
+ fun disableReusing()
+
+ /**
+ * A Compose compiler plugin API. DO NOT call directly.
+ *
+ * Reenable reusing if it was previously enabled before the last call to [disableReusing].
+ */
+ @ComposeCompilerApi
+ fun enableReusing()
+
+ /**
+ * A Compose compiler plugin API. DO NOT call directly.
+ *
* Schedule [block] to called with [value]. This is intended to update the node generated by
* [createNode] to changes discovered by composition.
*
@@ -907,6 +944,52 @@
}
/**
+ * A Compose internal function. DO NOT call directly.
+ *
+ * Records source information that can be used for tooling to determine the source location of
+ * the corresponding composable function. By default, this function is declared as having no
+ * side-effects. It is safe for code shrinking tools (such as R8 or ProGuard) to remove it.
+ */
+@ComposeCompilerApi
+fun sourceInformation(composer: Composer, sourceInformation: String) {
+ composer.sourceInformation(sourceInformation)
+}
+
+/**
+ * A Compose internal function. DO NOT call directly.
+ *
+ * Records the start of a source information marker that can be used for tooling to determine the
+ * source location of the corresponding composable function that otherwise don't require tracking
+ * information such as [ReadOnlyComposable] functions. By default, this function is declared as
+ * having no side-effects. It is safe for code shrinking tools (such as R8 or ProGuard) to remove
+ * it.
+ *
+ * Important that both [sourceInformationMarkerStart] and [sourceInformationMarkerEnd] are removed
+ * together or both kept. Removing only one will cause incorrect runtime behavior.
+ */
+@ComposeCompilerApi
+fun sourceInformationMarkerStart(composer: Composer, key: Int, sourceInformation: String) {
+ composer.sourceInformationMarkerStart(key, sourceInformation)
+}
+
+/**
+ * A Compose internal function. DO NOT call directly.
+ *
+ * Records the end of a source information marker that can be used for tooling to determine the
+ * source location of the corresponding composable function that otherwise don't require tracking
+ * information such as [ReadOnlyComposable] functions. By default, this function is declared as
+ * having no side-effects. It is safe for code shrinking tools (such as R8 or ProGuard) to remove
+ * it.
+ *
+ * Important that both [sourceInformationMarkerStart] and [sourceInformationMarkerEnd] are removed
+ * together or both kept. Removing only one will cause incorrect runtime behavior.
+ */
+@ComposeCompilerApi
+fun sourceInformationMarkerEnd(composer: Composer) {
+ composer.sourceInformationMarkerEnd()
+}
+
+/**
* Implementation of a composer for a mutable tree.
*/
internal class ComposerImpl(
@@ -951,6 +1034,8 @@
private val providerUpdates = HashMap<Int, CompositionLocalMap>()
private var providersInvalid = false
private val providersInvalidStack = IntStack()
+ private var reusing = false
+ private var reusingGroup = -1
private var childrenComposing: Int = 0
private var snapshot = currentSnapshot()
@@ -999,10 +1084,6 @@
@ComposeCompilerApi
override fun startReplaceableGroup(key: Int) = start(key, null, false, null)
- @ComposeCompilerApi
- override fun startReplaceableGroup(key: Int, sourceInformation: String?) =
- start(key, null, false, sourceInformation)
-
/**
* Indicates the end of a "Replaceable Group" at the current execution position. A
* Replaceable Group is a group which cannot be moved between its siblings, but
@@ -1082,10 +1163,6 @@
@ComposeCompilerApi
override fun startMovableGroup(key: Int, dataKey: Any?) = start(key, dataKey, false, null)
- @ComposeCompilerApi
- override fun startMovableGroup(key: Int, dataKey: Any?, sourceInformation: String?) =
- start(key, dataKey, false, sourceInformation)
-
/**
* Indicates the end of a "Movable Group" at the current execution position. A Movable Group is
* a group which can be moved or reordered between its siblings and retain slot table state,
@@ -1173,7 +1250,7 @@
*/
@ComposeCompilerApi
override val skipping: Boolean get() {
- return !inserting &&
+ return !inserting && !reusing &&
!providersInvalid &&
currentRecomposeScope?.requiresRecompose == false
}
@@ -1195,92 +1272,6 @@
collectParameterInformation = true
}
- /**
- * Helper for collecting remember observers for later strictly ordered dispatch.
- *
- * This includes support for the deprecated [RememberObserver] which should be
- * removed with it.
- */
- private class RememberEventDispatcher(
- private val abandoning: MutableSet<RememberObserver>
- ) : RememberManager {
- private val remembering = mutableListOf<RememberObserver>()
- private val forgetting = mutableListOf<RememberObserver>()
- private val sideEffects = mutableListOf<() -> Unit>()
-
- override fun remembering(instance: RememberObserver) {
- forgetting.lastIndexOf(instance).let { index ->
- if (index >= 0) {
- forgetting.removeAt(index)
- abandoning.remove(instance)
- } else {
- remembering.add(instance)
- }
- }
- }
-
- override fun forgetting(instance: RememberObserver) {
- remembering.lastIndexOf(instance).let { index ->
- if (index >= 0) {
- remembering.removeAt(index)
- abandoning.remove(instance)
- } else {
- forgetting.add(instance)
- }
- }
- }
-
- override fun sideEffect(effect: () -> Unit) {
- sideEffects += effect
- }
-
- val hasEffects: Boolean
- get() = sideEffects.isNotEmpty() ||
- forgetting.isNotEmpty() ||
- remembering.isNotEmpty()
-
- fun dispatchRememberObservers() {
- // Send forgets
- if (forgetting.isNotEmpty()) {
- for (index in forgetting.indices.reversed()) {
- val instance = forgetting[index]
- if (instance !in abandoning)
- instance.onForgotten()
- }
- }
-
- // Send remembers
- if (remembering.isNotEmpty()) {
- remembering.fastForEach { instance ->
- abandoning.remove(instance)
- instance.onRemembered()
- }
- }
- }
-
- fun dispatchSideEffects() {
- if (sideEffects.isNotEmpty()) {
- sideEffects.fastForEach { sideEffect ->
- sideEffect()
- }
- sideEffects.clear()
- }
- }
-
- fun dispatchAbandons() {
- if (abandoning.isNotEmpty()) {
- trace("Compose:dispatchAbandons") {
- val iterator = abandoning.iterator()
- while (iterator.hasNext()) {
- val instance = iterator.next()
- iterator.remove()
- instance.onAbandoned()
- }
- }
- }
- }
- }
-
@OptIn(InternalComposeApi::class)
internal fun dispose() {
trace("Compose:Composer.dispose") {
@@ -1289,17 +1280,6 @@
invalidations.clear()
changes.clear()
applier.clear()
- if (slotTable.groupsSize > 0) {
- val manager = RememberEventDispatcher(abandonSet)
- slotTable.write { writer ->
- writer.removeCurrentGroup(manager)
- }
- providerUpdates.clear()
- applier.clear()
- manager.dispatchRememberObservers()
- } else {
- applier.clear()
- }
isDisposed = true
}
}
@@ -1336,6 +1316,16 @@
* is scheduled to be inserted at the current location.
*/
override fun startNode() {
+ val key = if (inserting) nodeKey
+ else if (reusing)
+ if (reader.groupKey == nodeKey) nodeKeyReplace else nodeKey
+ else if (reader.groupKey == nodeKeyReplace) nodeKeyReplace
+ else nodeKey
+ start(key, null, true, null)
+ nodeExpected = true
+ }
+
+ override fun startReusableNode() {
start(nodeKey, null, true, null)
nodeExpected = true
}
@@ -1383,6 +1373,31 @@
*/
override fun endNode() = end(isNode = true)
+ override fun startReusableGroup(key: Int, dataKey: Any?) {
+ if (reader.groupKey == key && reader.groupAux != dataKey && reusingGroup < 0) {
+ // Starting to reuse nodes
+ reusingGroup = reader.currentGroup
+ reusing = true
+ }
+ start(key, null, false, dataKey)
+ }
+
+ override fun endReusableGroup() {
+ if (reusing && reader.parent == reusingGroup) {
+ reusingGroup = -1
+ reusing = false
+ }
+ end(isNode = false)
+ }
+
+ override fun disableReusing() {
+ reusing = false
+ }
+
+ override fun enableReusing() {
+ reusing = reusingGroup >= 0
+ }
+
/**
* Schedule a change to be applied to a node's property. This change will be applied to the
* node that is the current node in the tree which was either created by [createNode].
@@ -1413,7 +1428,7 @@
internal fun nextSlot(): Any? = if (inserting) {
validateNodeNotExpected()
Composer.Empty
- } else reader.next()
+ } else reader.next().let { if (reusing) Composer.Empty else it }
/**
* Determine if the current slot table value is equal to the given value, if true, the value
@@ -1785,7 +1800,7 @@
private fun start(key: Int, objectKey: Any?, isNode: Boolean, data: Any?) {
validateNodeNotExpected()
- updateCompoundKeyWhenWeEnterGroup(key, objectKey)
+ updateCompoundKeyWhenWeEnterGroup(key, objectKey, data)
// Check for the insert fast path. If we are already inserting (creating nodes) then
// there is no need to track insert, deletes and moves with a pending changes object.
@@ -1923,10 +1938,18 @@
if (inserting) {
val parent = writer.parent
- updateCompoundKeyWhenWeExitGroup(writer.groupKey(parent), writer.groupObjectKey(parent))
+ updateCompoundKeyWhenWeExitGroup(
+ writer.groupKey(parent),
+ writer.groupObjectKey(parent),
+ writer.groupAux(parent)
+ )
} else {
val parent = reader.parent
- updateCompoundKeyWhenWeExitGroup(reader.groupKey(parent), reader.groupObjectKey(parent))
+ updateCompoundKeyWhenWeExitGroup(
+ reader.groupKey(parent),
+ reader.groupObjectKey(parent),
+ reader.groupAux(parent)
+ )
}
var expectedNodeCount = groupNodeCount
val pending = pending
@@ -2310,13 +2333,17 @@
recomposeGroup,
recomposeKey
) rol 3
- ) xor (
- if (reader.hasObjectKey(group))
- reader.groupObjectKey(group)?.hashCode() ?: 0
- else reader.groupKey(group)
- )
+ ) xor reader.groupCompoundKeyPart(group)
}
+ private fun SlotReader.groupCompoundKeyPart(group: Int) =
+ if (hasObjectKey(group)) groupObjectKey(group)?.hashCode() ?: 0
+ else groupKey(group).let {
+ if (it == reusingGroup) groupAux(group)?.let { aux ->
+ if (aux == Composer.Empty) it else aux.hashCode()
+ } ?: it else it
+ }
+
internal fun invalidateForResult(scope: RecomposeScopeImpl): InvalidationResult {
if (scope.defaultsInScope) {
scope.defaultsInvalid = true
@@ -2369,11 +2396,12 @@
val reader = reader
val key = reader.groupKey
val dataKey = reader.groupObjectKey
- updateCompoundKeyWhenWeEnterGroup(key, dataKey)
+ val aux = reader.groupAux
+ updateCompoundKeyWhenWeEnterGroup(key, dataKey, aux)
startReaderGroup(reader.isNode, null)
recomposeToGroupEnd()
reader.endGroup()
- updateCompoundKeyWhenWeExitGroup(key, dataKey)
+ updateCompoundKeyWhenWeExitGroup(key, dataKey, aux)
}
}
@@ -2409,13 +2437,6 @@
return this
}
- @ComposeCompilerApi
- override fun startRestartGroup(key: Int, sourceInformation: String?): Composer {
- start(key, null, false, sourceInformation)
- addRecomposeScope()
- return this
- }
-
private fun addRecomposeScope() {
if (inserting) {
val scope = RecomposeScopeImpl(composition as CompositionImpl)
@@ -2464,6 +2485,23 @@
return result
}
+ @ComposeCompilerApi
+ override fun sourceInformation(sourceInformation: String) {
+ if (inserting) {
+ writer.insertAux(sourceInformation)
+ }
+ }
+
+ @ComposeCompilerApi
+ override fun sourceInformationMarkerStart(key: Int, sourceInformation: String) {
+ start(key, objectKey = null, isNode = false, data = sourceInformation)
+ }
+
+ @ComposeCompilerApi
+ override fun sourceInformationMarkerEnd() {
+ end(isNode = false)
+ }
+
/**
* Synchronously compose the initial composition of [content]. This collects all the changes
* which must be applied by [ControlledComposition.applyChanges] to build the tree implied by
@@ -2962,9 +3000,12 @@
}
}
- private fun updateCompoundKeyWhenWeEnterGroup(groupKey: Int, dataKey: Any?) {
+ private fun updateCompoundKeyWhenWeEnterGroup(groupKey: Int, dataKey: Any?, data: Any?) {
if (dataKey == null)
- updateCompoundKeyWhenWeEnterGroupKeyHash(groupKey)
+ if (data != null && groupKey == reuseKey && data != Composer.Empty)
+ updateCompoundKeyWhenWeEnterGroupKeyHash(data.hashCode())
+ else
+ updateCompoundKeyWhenWeEnterGroupKeyHash(groupKey)
else
updateCompoundKeyWhenWeEnterGroupKeyHash(dataKey.hashCode())
}
@@ -2973,9 +3014,12 @@
compoundKeyHash = (compoundKeyHash rol 3) xor keyHash
}
- private fun updateCompoundKeyWhenWeExitGroup(groupKey: Int, dataKey: Any?) {
+ private fun updateCompoundKeyWhenWeExitGroup(groupKey: Int, dataKey: Any?, data: Any?) {
if (dataKey == null)
- updateCompoundKeyWhenWeExitGroupKeyHash(groupKey)
+ if (data != null && groupKey == reuseKey && data != Composer.Empty)
+ updateCompoundKeyWhenWeExitGroupKeyHash(data.hashCode())
+ else
+ updateCompoundKeyWhenWeExitGroupKeyHash(groupKey)
else
updateCompoundKeyWhenWeExitGroupKeyHash(dataKey.hashCode())
}
@@ -3110,7 +3154,6 @@
composer.apply<Unit, T>(Unit, { this.block() })
}
}
-
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
inline class SkippableUpdater<T> constructor(
@PublishedApi internal val composer: Composer
@@ -3122,7 +3165,7 @@
}
}
-private fun SlotWriter.removeCurrentGroup(rememberManager: RememberManager) {
+internal fun SlotWriter.removeCurrentGroup(rememberManager: RememberManager) {
// Notify the lifecycle manager of any observers leaving the slot table
// The notification order should ensure that listeners are notified of leaving
// in opposite order that they are notified of entering.
@@ -3300,9 +3343,12 @@
// a unique key.
private const val rootKey = 100
-// An arbitrary value paired with a boxed Int or a JoinKey data key.
+// An arbitrary key value for a node.
private const val nodeKey = 125
+// An arbitrary key value for a node used to force the node to be replaced.
+private const val nodeKeyReplace = 126
+
@PublishedApi
internal const val invocationKey = 200
@@ -3338,3 +3384,6 @@
@PublishedApi
internal val reference: Any = OpaqueKey("reference")
+
+@PublishedApi
+internal const val reuseKey = 207
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
index 153ba94..9303c6c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
@@ -279,7 +279,7 @@
private val parent: CompositionContext,
/**
- * The applier to use to update the tree managed by the compositon.
+ * The applier to use to update the tree managed by the composition.
*/
private val applier: Applier<*>,
@@ -344,7 +344,7 @@
* As [RecomposeScope]s are removed the corresponding entries in the observations set must be
* removed as well. This process is expensive so should only be done if it is certain the
* [observations] set contains [RecomposeScope] that is no longer needed. [pendingInvalidScopes]
- * is set to true whenver a [RecomposeScope] is removed from the [slotTable].
+ * is set to true whenever a [RecomposeScope] is removed from the [slotTable].
*/
internal var pendingInvalidScopes = false
@@ -459,8 +459,17 @@
if (!disposed) {
disposed = true
composable = {}
+ if (slotTable.groupsSize > 0) {
+ val manager = RememberEventDispatcher(abandonSet)
+ slotTable.write { writer ->
+ writer.removeCurrentGroup(manager)
+ }
+ applier.clear()
+ manager.dispatchRememberObservers()
+ }
composer.dispose()
parent.unregisterComposition(this)
+ parent.unregisterComposition(this)
}
}
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index 57cf35b..8e8a725 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -1127,6 +1127,37 @@
}
/**
+ * Insert aux data into the parent group.
+ *
+ * This must be done only after at most one value has been inserted into the slot table for
+ * the group.
+ */
+ fun insertAux(value: Any?) {
+ check(insertCount >= 0) { "Cannot insert auxiliary data when not inserting" }
+ val parent = parent
+ val parentGroupAddress = groupIndexToAddress(parent)
+ check(!groups.hasAux(parentGroupAddress)) { "Group already has auxiliary data" }
+ insertSlots(1, parent)
+ val auxIndex = groups.auxIndex(parentGroupAddress)
+ val auxAddress = dataIndexToDataAddress(auxIndex)
+ if (currentSlot > auxIndex) {
+ // One or more values were inserted into the slot table before the aux value, we need
+ // to move them. Currently we only will run into one or two slots (the recompose
+ // scope inserted by a restart group and the lambda value in a composableLambda
+ // instance) so this is the only case currently supported.
+ val slotsToMove = currentSlot - auxIndex
+ check(slotsToMove < 3) { "Moving more than two slot not supported" }
+ if (slotsToMove > 1) {
+ slots[auxAddress + 2] = slots[auxAddress + 1]
+ }
+ slots[auxAddress + 1] = slots[auxAddress]
+ }
+ groups.addAux(parentGroupAddress)
+ slots[auxAddress] = value
+ currentSlot++
+ }
+
+ /**
* Updates the node for the current node group to [value].
*/
fun updateNode(value: Any?) = updateNodeOfGroup(currentGroup, value)
@@ -1765,7 +1796,7 @@
check(!anchorsRemoved) { "Unexpectedly removed anchors" }
// Update the node count.
- nodeCount += groups.nodeCount(currentGroup)
+ nodeCount += if (groups.isNode(currentGroup)) 1 else groups.nodeCount(currentGroup)
// Move current passed the insert
this.currentGroup = currentGroup + groupsToMove
@@ -2463,7 +2494,7 @@
// 31 30 29 28_27 26 25 24_23 22 21 20_19 18 17 16__15 14 13 12_11 10 09 08_07 06 05 04_03 02 01 00
// 0 n ks ds r | node count |
// where n is set when the group represents a node
-// where ks is whether the group has a data key slot
+// where ks is whether the group has a object key slot
// where ds is whether the group has a group data slot
// where r is always 0 (future use)
@@ -2508,6 +2539,11 @@
}
private fun IntArray.hasAux(address: Int) =
this[address * Group_Fields_Size + GroupInfo_Offset] and Aux_Mask != 0
+private fun IntArray.addAux(address: Int) {
+ val arrayIndex = address * Group_Fields_Size + GroupInfo_Offset
+ this[arrayIndex] = this[arrayIndex] or Aux_Mask
+}
+
private fun IntArray.auxIndex(address: Int) = (address * Group_Fields_Size).let { slot ->
if (slot >= size) size
else this[slot + DataAnchor_Offset] +
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
index f2cdb63..368da08 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
@@ -47,8 +47,7 @@
/* ktlint-disable parameter-list-wrapping */ // TODO(https://github.com/pinterest/ktlint/issues/921): reenable
internal expect class ComposableLambdaImpl(
key: Int,
- tracked: Boolean,
- sourceInformation: String?
+ tracked: Boolean
) : ComposableLambda {
fun update(block: Any)
}
@@ -327,13 +326,12 @@
composer: Composer,
key: Int,
tracked: Boolean,
- sourceInformation: String?,
block: Any
): ComposableLambda {
composer.startReplaceableGroup(key)
val slot = composer.rememberedValue()
val result = if (slot === Composer.Empty) {
- val value = ComposableLambdaImpl(key, tracked, sourceInformation)
+ val value = ComposableLambdaImpl(key, tracked)
composer.updateRememberedValue(value)
value
} else {
@@ -349,7 +347,6 @@
fun composableLambdaInstance(
key: Int,
tracked: Boolean,
- sourceInformation: String?,
block: Any
): ComposableLambda =
- ComposableLambdaImpl(key, tracked, sourceInformation).apply { update(block) }
+ ComposableLambdaImpl(key, tracked).apply { update(block) }
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.jvm.kt
index 572c146..172a413 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.jvm.kt
@@ -36,8 +36,7 @@
/* ktlint-disable parameter-list-wrapping */ // TODO(https://github.com/pinterest/ktlint/issues/921): reenable
internal actual class ComposableLambdaImpl actual constructor(
val key: Int,
- private val tracked: Boolean,
- private val sourceInformation: String?
+ private val tracked: Boolean
) : ComposableLambda {
private var _block: Any? = null
private var scope: RecomposeScope? = null
@@ -102,7 +101,7 @@
}
override operator fun invoke(c: Composer, changed: Int): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(0) else sameBits(0)
val result = (_block as (c: Composer, changed: Int) -> Any?)(c, dirty)
@@ -111,7 +110,7 @@
}
override operator fun invoke(p1: Any?, c: Composer, changed: Int): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(1) else sameBits(1)
val result = (
@@ -130,7 +129,7 @@
}
override operator fun invoke(p1: Any?, p2: Any?, c: Composer, changed: Int): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(2) else sameBits(2)
val result = (_block as (p1: Any?, p2: Any?, c: Composer, changed: Int) -> Any?)(
@@ -144,7 +143,7 @@
}
override operator fun invoke(p1: Any?, p2: Any?, p3: Any?, c: Composer, changed: Int): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(3) else sameBits(3)
val result = (
@@ -174,7 +173,7 @@
c: Composer,
changed: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(4) else sameBits(4)
val result = (
@@ -209,7 +208,7 @@
c: Composer,
changed: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(5) else sameBits(5)
val result = (
@@ -247,7 +246,7 @@
c: Composer,
changed: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(6) else sameBits(6)
val result = (
@@ -288,7 +287,7 @@
c: Composer,
changed: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(7) else sameBits(7)
val result = (
@@ -332,7 +331,7 @@
c: Composer,
changed: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(8) else sameBits(8)
val result = (
@@ -379,7 +378,7 @@
c: Composer,
changed: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed or if (c.changed(this)) differentBits(9) else sameBits(9)
val result = (
@@ -430,7 +429,7 @@
changed: Int,
changed1: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed1 or if (c.changed(this)) differentBits(10) else sameBits(10)
val result = (
@@ -486,7 +485,7 @@
changed: Int,
changed1: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed1 or if (c.changed(this)) differentBits(11) else sameBits(11)
val result = (
@@ -545,7 +544,7 @@
changed: Int,
changed1: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed1 or if (c.changed(this)) differentBits(12) else sameBits(12)
val result = (
@@ -607,7 +606,7 @@
changed: Int,
changed1: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed1 or if (c.changed(this)) differentBits(13) else sameBits(13)
val result = (
@@ -689,7 +688,7 @@
changed: Int,
changed1: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed1 or if (c.changed(this)) differentBits(14) else sameBits(14)
val result = (
@@ -775,7 +774,7 @@
changed: Int,
changed1: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed1 or if (c.changed(this)) differentBits(15) else sameBits(15)
val result = (
@@ -865,7 +864,7 @@
changed: Int,
changed1: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed1 or if (c.changed(this)) differentBits(16) else sameBits(16)
val result = (
@@ -959,7 +958,7 @@
changed: Int,
changed1: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed1 or if (c.changed(this)) differentBits(17) else sameBits(17)
val result = (
@@ -1057,7 +1056,7 @@
changed: Int,
changed1: Int
): Any? {
- val c = c.startRestartGroup(key, sourceInformation)
+ val c = c.startRestartGroup(key)
trackRead(c)
val dirty = changed1 or if (c.changed(this)) differentBits(18) else sameBits(18)
val result = (
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt
index 08e88f1..3193f395 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt
@@ -30,7 +30,6 @@
internal class ComposableLambdaNImpl(
val key: Int,
private val tracked: Boolean,
- private val sourceInformation: String?,
override val arity: Int
) : ComposableLambdaN {
private var _block: Any? = null
@@ -112,7 +111,7 @@
var c = args[realParams] as Composer
val allArgsButLast = args.slice(0 until args.size - 1).toTypedArray()
val lastChanged = args[args.size - 1] as Int
- c = c.startRestartGroup(key, sourceInformation)
+ c = c.startRestartGroup(key)
trackRead(c)
val dirty = lastChanged or if (c.changed(this))
differentBits(realParams)
@@ -146,14 +145,13 @@
composer: Composer,
key: Int,
tracked: Boolean,
- sourceInformation: String?,
arity: Int,
block: Any
): ComposableLambdaN {
composer.startReplaceableGroup(key)
val slot = composer.rememberedValue()
val result = if (slot === Composer.Empty) {
- val value = ComposableLambdaNImpl(key, tracked, sourceInformation, arity)
+ val value = ComposableLambdaNImpl(key, tracked, arity)
composer.updateRememberedValue(value)
value
} else {
@@ -170,12 +168,10 @@
fun composableLambdaNInstance(
key: Int,
tracked: Boolean,
- sourceInformation: String?,
arity: Int,
block: Any
): ComposableLambdaN = ComposableLambdaNImpl(
key,
tracked,
- sourceInformation,
arity
).apply { update(block) }
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionReusingTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
new file mode 100644
index 0000000..b698eae
--- /dev/null
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.compose.runtime
+
+import androidx.compose.runtime.mock.CompositionTestScope
+import androidx.compose.runtime.mock.Linear
+import androidx.compose.runtime.mock.NonReusableLinear
+import androidx.compose.runtime.mock.NonReusableText
+import androidx.compose.runtime.mock.Text
+import androidx.compose.runtime.mock.View
+import androidx.compose.runtime.mock.compositionTest
+import androidx.compose.runtime.mock.expectChanges
+import androidx.compose.runtime.mock.flatten
+import androidx.compose.runtime.mock.revalidate
+import androidx.compose.runtime.mock.validate
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+
+@Stable
+class CompositionReusingTests {
+
+ @Test
+ fun canReuse() = compositionTest {
+ var lastData: State<Int>? = null
+ var key by mutableStateOf(0)
+
+ compose {
+ ReusableContent(key) {
+ Linear {
+ val data = remember { mutableStateOf(1) }
+ lastData = data
+ Text("Key = $key")
+ }
+ }
+ }
+
+ validate {
+ Linear {
+ Text("Key = $key")
+ }
+ }
+
+ val firstData = lastData
+ val nodes = root.flatten()
+
+ key++
+ expectChanges()
+ revalidate()
+
+ val nodesAfterChange = root.flatten()
+
+ // Ensure nodes are reused
+ assertArrayEquals(nodes, nodesAfterChange) { "${it.hashCode()}" }
+
+ // Ensure remembers are not reused
+ assertNotEquals(
+ firstData, lastData,
+ "Should not remember values when recycling"
+ )
+ }
+
+ @Test
+ fun canRecycleAroundNonReusable() = compositionTest {
+ var key by mutableStateOf(0)
+
+ compose {
+ ReusableContent(key) {
+ Linear {
+ Text("Key = $key")
+ NonReusableText("Non-recyclable key = $key")
+ }
+ }
+ }
+
+ validate {
+ Linear {
+ Text("Key = $key")
+ Text("Non-recyclable key = $key")
+ }
+ }
+
+ val recycleText = findTextWith("Key")
+ val nonRecycledText = findTextWith("Non-recyclable key")
+ key++
+ expectChanges()
+ revalidate()
+
+ assertEquals(recycleText, findTextWith("Key"), "Expected text to be recycled")
+ assertNotEquals(
+ nonRecycledText,
+ findTextWith("Non-recyclable key"),
+ "Expected non-recyclable text to be replaced"
+ )
+ }
+
+ @Test
+ fun recyclableNodesInNonReusableContainerNotRecycled() = compositionTest {
+ var key by mutableStateOf(0)
+
+ compose {
+ ReusableContent(key) {
+ Linear {
+ Linear {
+ Text("Key = $key")
+ }
+ NonReusableLinear {
+ Text("Non-recyclable key = $key")
+ }
+ NonReusableLinear { }
+ }
+ }
+ }
+
+ validate {
+ Linear {
+ Linear {
+ Text("Key = $key")
+ }
+ Linear {
+ Text("Non-recyclable key = $key")
+ }
+ Linear { }
+ }
+ }
+
+ val recycleText = findTextWith("Key")
+ val nonRecycledText = findTextWith("Non-recyclable key")
+ key++
+ expectChanges()
+ revalidate()
+ verifyConsistent()
+
+ assertEquals(recycleText, findTextWith("Key"), "Expected text to be recycled")
+ assertNotEquals(
+ nonRecycledText,
+ findTextWith("Non-recyclable key"),
+ "Expected non-recyclable text to be replaced"
+ )
+ }
+
+ @Test
+ fun compositeHashCodeReflectsReusableChanges() = compositionTest {
+ var key by mutableStateOf(0)
+ var lastCompositeHash = 0
+
+ compose {
+ ReusableContent(key) {
+ Linear {
+ Text("Key = $key")
+ lastCompositeHash = currentCompositeKeyHash
+ }
+ }
+ }
+
+ validate {
+ Linear {
+ Text("Key = $key")
+ }
+ }
+
+ val firstCompositeHash = lastCompositeHash
+ key++
+ expectChanges()
+ revalidate()
+ assertNotEquals(firstCompositeHash, lastCompositeHash)
+ }
+}
+
+private fun View.findTextWith(contains: String) =
+ find { it.name == "text" && it.text?.contains(contains) == true }
+private fun CompositionTestScope.findTextWith(contains: String) = root.findTextWith(contains)
+
+private fun View.find(predicate: (view: View) -> Boolean): View? {
+ if (predicate(this)) return this
+ for (child in children) {
+ val found = child.find(predicate)
+ if (found != null) return found
+ }
+ return null
+}
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
index 820b9764..f5498d2 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
@@ -1231,6 +1231,52 @@
sourceTable.verifyWellFormed()
}
+ @Test
+ fun testMovingANodeGroup() {
+ val sourceTable = SlotTable()
+ val anchors = mutableListOf<Anchor>()
+ sourceTable.write { writer ->
+ writer.beginInsert()
+ anchors.add(writer.anchor())
+ writer.startNode(10, 10)
+ writer.update(100)
+ writer.update(200)
+ writer.endGroup()
+ writer.endInsert()
+ }
+ sourceTable.verifyWellFormed()
+
+ val destinationTable = SlotTable()
+ destinationTable.write { writer ->
+ writer.beginInsert()
+ writer.startGroup(treeRoot)
+ writer.startGroup(1000)
+ writer.endGroup()
+ writer.endGroup()
+ writer.endInsert()
+ }
+ destinationTable.verifyWellFormed()
+
+ destinationTable.write { writer ->
+ writer.startGroup()
+ writer.startGroup()
+ writer.beginInsert()
+ writer.moveFrom(sourceTable, anchors.first().toIndexFor(sourceTable))
+ writer.endInsert()
+ writer.skipToGroupEnd()
+ writer.endGroup()
+ writer.skipToGroupEnd()
+ writer.endGroup()
+ }
+ destinationTable.verifyWellFormed()
+ destinationTable.read { reader ->
+ val anchor = anchors.first()
+ assertEquals(125, reader.groupKey(anchor))
+ assertEquals(10, reader.groupObjectKey(anchor.toIndexFor(destinationTable)))
+ }
+ sourceTable.verifyWellFormed()
+ }
+
@org.junit.Test
fun testMovingMultipleRootGroups() {
val sourceTable = SlotTable()
@@ -3093,6 +3139,95 @@
}
slots.verifyWellFormed()
}
+
+ @Test
+ fun canInsertAuxData() {
+ val slots = SlotTable().also {
+ it.write { writer ->
+ writer.insert {
+ // Insert a normal aux data.
+ writer.startData(10, 10, "10")
+ writer.endGroup()
+
+ // Insert using insertAux
+ writer.startGroup(20)
+ writer.insertAux("20")
+ writer.endGroup()
+
+ // Insert using insertAux after a slot value was added.
+ writer.startGroup(30)
+ writer.update(300)
+ writer.insertAux("30")
+ writer.endGroup()
+
+ // Insert using insertAux after a group with an object key
+ writer.startGroup(40, 40)
+ writer.insertAux("40")
+ writer.endGroup()
+
+ // Insert aux into an object key with a value slot and then add another value.
+ writer.startGroup(50, 50)
+ writer.update(500)
+ writer.insertAux("50")
+ writer.update(501)
+ writer.endGroup()
+
+ // Insert aux after two slot values and then add another value.
+ writer.startGroup(60)
+ writer.update(600)
+ writer.update(601)
+ writer.insertAux("60")
+ writer.update(602)
+ writer.endGroup()
+
+ // Write a trail group to ensure that the slot table is valid after the
+ // insertAux
+ writer.startGroup(1000)
+ writer.update(10000)
+ writer.update(10001)
+ writer.endGroup()
+ }
+ }
+ }
+ slots.verifyWellFormed()
+ slots.read { reader ->
+ assertEquals(10, reader.groupKey)
+ assertEquals(10, reader.groupObjectKey)
+ assertEquals("10", reader.groupAux)
+ reader.skipGroup()
+ assertEquals(20, reader.groupKey)
+ assertEquals("20", reader.groupAux)
+ reader.skipGroup()
+ assertEquals(30, reader.groupKey)
+ assertEquals("30", reader.groupAux)
+ reader.startGroup()
+ assertEquals(300, reader.next())
+ reader.endGroup()
+ assertEquals(40, reader.groupKey)
+ assertEquals(40, reader.groupObjectKey)
+ assertEquals("40", reader.groupAux)
+ reader.skipGroup()
+ assertEquals(50, reader.groupKey)
+ assertEquals(50, reader.groupObjectKey)
+ assertEquals("50", reader.groupAux)
+ reader.startGroup()
+ assertEquals(500, reader.next())
+ assertEquals(501, reader.next())
+ reader.endGroup()
+ assertEquals(60, reader.groupKey)
+ assertEquals("60", reader.groupAux)
+ reader.startGroup()
+ assertEquals(600, reader.next())
+ assertEquals(601, reader.next())
+ assertEquals(602, reader.next())
+ reader.endGroup()
+ assertEquals(1000, reader.groupKey)
+ reader.startGroup()
+ assertEquals(10000, reader.next())
+ assertEquals(10001, reader.next())
+ reader.endGroup()
+ }
+ }
}
@OptIn(InternalComposeApi::class)
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
index f2933d3..63abe7b 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
@@ -18,6 +18,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
+import androidx.compose.runtime.ControlledComposition
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.Recomposer
import androidx.compose.runtime.snapshots.Snapshot
@@ -63,6 +64,12 @@
}
return recomposer.changeCount != changeCount
}
+
+ override fun verifyConsistent() {
+ (composition as? ControlledComposition)?.verifyConsistent()
+ }
+
+ override var validator: (MockViewValidator.() -> Unit)? = null
}
scope.block()
scope.composition?.dispose()
@@ -88,16 +95,32 @@
fun advance(ignorePendingWork: Boolean = false): Boolean
/**
+ * Verify the composition is well-formed.
+ */
+ fun verifyConsistent()
+
+ /**
* The root mock view of the mock views being composed.
*/
val root: View
+
+ /**
+ * The last validator used.
+ */
+ var validator: (MockViewValidator.() -> Unit)?
}
/**
* Create a mock view validator and validate the view.
*/
fun CompositionTestScope.validate(block: MockViewValidator.() -> Unit) =
- MockViewListValidator(root.children).validate(block)
+ MockViewListValidator(root.children).validate(block).also { validator = block }
+
+/**
+ * Revalidate using the last validator
+ */
+fun CompositionTestScope.revalidate() =
+ validate(validator ?: error("validate was not called"))
/**
* Advance and expect changes
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/View.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/View.kt
index 4babb99..2f41caf 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/View.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/View.kt
@@ -100,3 +100,5 @@
it.toString()
}
}
+
+fun View.flatten(): List<View> = listOf(this) + children.flatMap { it.flatten() }
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/Views.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/Views.kt
index f4f16a0..5f90678 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/Views.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/Views.kt
@@ -18,6 +18,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeNode
+import androidx.compose.runtime.ReusableComposeNode
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.key
@@ -35,6 +36,16 @@
@Composable
fun Linear(content: @Composable () -> Unit) {
+ ReusableComposeNode<View, ViewApplier>(
+ factory = { View().also { it.name = "linear" } },
+ update = { }
+ ) {
+ content()
+ }
+}
+
+@Composable
+fun NonReusableLinear(content: @Composable () -> Unit) {
ComposeNode<View, ViewApplier>(
factory = { View().also { it.name = "linear" } },
update = { }
@@ -45,6 +56,14 @@
@Composable @NonRestartableComposable
fun Text(value: String) {
+ ReusableComposeNode<View, ViewApplier>(
+ factory = { View().also { it.name = "text" } },
+ update = { set(value) { text = it } }
+ )
+}
+
+@Composable
+fun NonReusableText(value: String) {
ComposeNode<View, ViewApplier>(
factory = { View().also { it.name = "text" } },
update = { set(value) { text = it } }
@@ -53,7 +72,7 @@
@Composable
fun Edit(value: String) {
- ComposeNode<View, ViewApplier>(
+ ReusableComposeNode<View, ViewApplier>(
factory = { View().also { it.name = "edit" } },
update = { set(value) { this.value = it } }
)
@@ -65,7 +84,7 @@
content: @Composable () -> Unit
) {
if (selected) {
- ComposeNode<View, ViewApplier>(
+ ReusableComposeNode<View, ViewApplier>(
factory = { View().also { it.name = "box" } },
update = { },
content = { content() }
diff --git a/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/TestViewConfiguration.kt b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/TestViewConfiguration.kt
new file mode 100644
index 0000000..64f01f6
--- /dev/null
+++ b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/TestViewConfiguration.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.compose.testutils
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.ViewConfiguration
+
+/**
+ * A [ViewConfiguration] that can be used for testing. The default values are representative for
+ * Android devices, but can be set to any value desired for a test. See the `With*` functions for
+ * shorthands that create a [TestViewConfiguration] and provide it as a [LocalViewConfiguration].
+ *
+ * @see WithLongPressTimeoutMillis
+ * @see WithDoubleTapTimeoutMillis
+ * @see WithDoubleTapMinTimeMillis
+ * @see WithTouchSlop
+ */
+class TestViewConfiguration(
+ override val longPressTimeoutMillis: Long = 500L,
+ override val doubleTapTimeoutMillis: Long = 300L,
+ override val doubleTapMinTimeMillis: Long = 40L,
+ override val touchSlop: Float = 18f
+) : ViewConfiguration
+
+@Composable
+fun WithLongPressTimeoutMillis(longPressTimeoutMillis: Long, content: @Composable () -> Unit) {
+ WithViewConfiguration(
+ TestViewConfiguration(longPressTimeoutMillis = longPressTimeoutMillis),
+ content = content
+ )
+}
+
+@Composable
+fun WithDoubleTapTimeoutMillis(doubleTapTimeoutMillis: Long, content: @Composable () -> Unit) {
+ WithViewConfiguration(
+ TestViewConfiguration(doubleTapTimeoutMillis = doubleTapTimeoutMillis),
+ content = content
+ )
+}
+
+@Composable
+fun WithDoubleTapMinTimeMillis(doubleTapMinTimeMillis: Long, content: @Composable () -> Unit) {
+ WithViewConfiguration(
+ TestViewConfiguration(doubleTapMinTimeMillis = doubleTapMinTimeMillis),
+ content = content
+ )
+}
+
+@Composable
+fun WithTouchSlop(touchSlop: Float, content: @Composable () -> Unit) {
+ WithViewConfiguration(
+ TestViewConfiguration(touchSlop = touchSlop),
+ content = content
+ )
+}
+
+@Composable
+fun WithViewConfiguration(
+ testViewConfiguration: TestViewConfiguration,
+ content: @Composable () -> Unit
+) {
+ CompositionLocalProvider(
+ LocalViewConfiguration provides testViewConfiguration,
+ content = content
+ )
+}
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index 1923226..9a89811 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -85,6 +85,7 @@
implementation("androidx.annotation:annotation:1.1.0")
implementation(KOTLIN_COROUTINES_CORE)
+ implementation(KOTLIN_COROUTINES_TEST)
}
androidMain.dependencies {
@@ -99,7 +100,6 @@
implementation(ANDROIDX_TEST_MONITOR)
implementation(ESPRESSO_CORE)
implementation(ESPRESSO_IDLING_RESOURCE)
- implementation(KOTLIN_COROUTINES_TEST)
}
androidAndroidTest.dependencies {
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt
index 0c5823c..01d4151 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt
@@ -23,10 +23,7 @@
import androidx.test.filters.LargeTest
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
import org.junit.runner.RunWith
-import org.junit.runners.model.Statement
@LargeTest
@RunWith(AndroidJUnit4::class)
@@ -34,23 +31,8 @@
private class TestException : Exception()
- // Need to expect the error in a test rule around the AndroidComposeTestRule, because the
- // exception can surface after the test is completed, when the AndroidComposeTestRule cleans
- // up test scoped variables.
- private val testExceptionRule = TestRule { base, _ ->
- object : Statement() {
- override fun evaluate() {
- expectError<TestException> {
- base.evaluate()
- }
- }
- }
- }
-
- private val rule = createComposeRule()
-
@get:Rule
- val testRule: TestRule = RuleChain.outerRule(testExceptionRule).around(rule)
+ val rule = createComposeRule()
// Run the test twice so we can verify if a failed test took down the test suite:
// - Results have 1 failed test:
@@ -60,12 +42,16 @@
@Test
fun test1() {
- throwInLaunchedEffect()
+ expectError<TestException> {
+ throwInLaunchedEffect()
+ }
}
@Test
fun test2() {
- throwInLaunchedEffect()
+ expectError<TestException> {
+ throwInLaunchedEffect()
+ }
}
private fun throwInLaunchedEffect() {
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
index 75569f8..b5d0310 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
@@ -61,7 +61,6 @@
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineExceptionHandler
import kotlinx.coroutines.withContext
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
@@ -185,8 +184,7 @@
private val testCoroutineDispatcher: TestCoroutineDispatcher
private val recomposerApplyCoroutineScope: CoroutineScope
private val frameCoroutineScope: CoroutineScope
- @OptIn(ExperimentalCoroutinesApi::class)
- private val coroutineExceptionHandler: TestCoroutineExceptionHandler
+ private val coroutineExceptionHandler = UncaughtExceptionHandler()
override val mainClock: MainTestClock
get() = mainClockImpl
@@ -207,8 +205,6 @@
}
}
@OptIn(ExperimentalCoroutinesApi::class)
- coroutineExceptionHandler = TestCoroutineExceptionHandler()
- @OptIn(ExperimentalCoroutinesApi::class)
recomposerApplyCoroutineScope = CoroutineScope(
testCoroutineDispatcher + frameClock + infiniteAnimationPolicy +
coroutineExceptionHandler + Job()
@@ -287,7 +283,7 @@
// Then await composition(s)
runEspressoOnIdle()
}
- checkUncaughtCoroutineExceptions()
+ coroutineExceptionHandler.throwUncaught()
}
override fun <T> runOnUiThread(action: () -> T): T {
@@ -326,18 +322,6 @@
idlingResourceRegistry.unregisterIdlingResource(idlingResource)
}
- /**
- * Checks if the [coroutineExceptionHandler] has caught uncaught exceptions. If so, will
- * rethrow the first to fail the test. Rather than only calling this only at the end of the
- * test, as recommended by [cleanupTestCoroutines][kotlinx.coroutines.test
- * .UncaughtExceptionCaptor.cleanupTestCoroutines], try calling this at a few strategic
- * points to fail the test asap after the exception was caught.
- */
- private fun checkUncaughtCoroutineExceptions() {
- @OptIn(ExperimentalCoroutinesApi::class)
- coroutineExceptionHandler.cleanupTestCoroutines()
- }
-
inner class AndroidComposeStatement(
private val base: Statement
) : Statement() {
@@ -358,15 +342,15 @@
}
base.evaluate()
} finally {
+ textInputServiceFactory = oldTextInputFactory
recomposer.cancel()
// FYI: Not canceling these scope below would end up cleanupTestCoroutines
// throwing errors on active coroutines
recomposerApplyCoroutineScope.cancel()
frameCoroutineScope.cancel()
- checkUncaughtCoroutineExceptions()
+ coroutineExceptionHandler.throwUncaught()
@OptIn(ExperimentalCoroutinesApi::class)
testCoroutineDispatcher.cleanupTestCoroutines()
- textInputServiceFactory = oldTextInputFactory
// Dispose the content
if (disposeContentHook != null) {
runOnUiThread {
@@ -456,7 +440,7 @@
// between now and when the new Activity has created its compose root, even though
// waitForComposeRoots() suggests that we are now guaranteed one.
- checkUncaughtCoroutineExceptions()
+ coroutineExceptionHandler.throwUncaught()
}
override fun getRoots(atLeastOneRootExpected: Boolean): Set<RootForTest> {
diff --git a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt
index 7038f13..e055101 100644
--- a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt
+++ b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt
@@ -49,10 +49,6 @@
@OptIn(InternalTestApi::class)
class DesktopComposeTestRule : ComposeContentTestRule {
- companion object {
- var current: DesktopComposeTestRule? = null
- }
-
override val density: Density
get() = Density(1f, 1f)
@@ -61,13 +57,13 @@
internal val testDisplaySize: IntSize get() = IntSize(1024, 768)
+ private var uncaughtExceptionHandler = UncaughtExceptionHandler()
lateinit var window: TestComposeWindow
private val testOwner = DesktopTestOwner(this)
private val testContext = createTestContext(testOwner)
override fun apply(base: Statement, description: Description?): Statement {
- current = this
return object : Statement() {
override fun evaluate() {
window = runOnUiThread(::createWindow)
@@ -77,6 +73,8 @@
} finally {
runOnUiThread(window::dispose)
}
+
+ uncaughtExceptionHandler.throwUncaught()
}
}
}
@@ -86,7 +84,7 @@
height = testDisplaySize.height,
density = density,
nanoTime = System::nanoTime, // TODO(demin): use mainClock?
- coroutineContext = Dispatchers.Swing
+ coroutineContext = Dispatchers.Swing + uncaughtExceptionHandler
)
private fun isIdle() =
@@ -96,6 +94,7 @@
override fun waitForIdle() {
while (!isIdle()) {
Thread.sleep(10)
+ uncaughtExceptionHandler.throwUncaught()
}
}
@@ -103,6 +102,7 @@
override suspend fun awaitIdle() {
while (!isIdle()) {
delay(10)
+ uncaughtExceptionHandler.throwUncaught()
}
}
diff --git a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionHandler.jvm.kt b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionHandler.jvm.kt
new file mode 100644
index 0000000..b25d2ba
--- /dev/null
+++ b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionHandler.jvm.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.compose.ui.test.junit4
+
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlin.coroutines.AbstractCoroutineContextElement
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Similar to [TestCoroutineExceptionHandler], but with clearing all thrown exceptions
+ *
+ * If we don't clear exceptions they will be thrown twice in this example
+ * (the exception will be thrown inside awaitIdle and after the test):
+ * ```
+ * @Test
+ * fun test() {
+ * runBlocking(Dispatchers.Main) {
+ * try {
+ * rule.awaitIdle()
+ * } catch (e: SomeException) {
+ * // ignore
+ * }
+ * }
+ * }
+ * ```
+ */
+internal class UncaughtExceptionHandler :
+ AbstractCoroutineContextElement(CoroutineExceptionHandler),
+ CoroutineExceptionHandler {
+ private var exception: Throwable? = null
+
+ override fun handleException(context: CoroutineContext, exception: Throwable) {
+ synchronized(this) {
+ if (this.exception == null) {
+ this.exception = exception
+ } else {
+ this.exception!!.addSuppressed(exception)
+ }
+ }
+ }
+
+ /**
+ * Checks if the [UncaughtExceptionHandler] has caught uncaught exceptions. If so, will
+ * rethrow the first to fail the test. The rest exceptions will be added to the first and
+ * marked as `suppressed`.
+ *
+ * The next call of this method will not throw already thrown exception.
+ *
+ * Rather than only calling this only at the end of the test, as recommended by
+ * [UncaughtExceptionCaptor.cleanupTestCoroutines],
+ * try calling this at a few strategic
+ * points to fail the test asap after the exception was caught.
+ */
+ fun throwUncaught() {
+ synchronized(this) {
+ val exception = exception
+ if (exception != null) {
+ this.exception = null
+ throw exception
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
index e01eadf..2fa7c14 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
@@ -24,14 +24,12 @@
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.WithTouchSlop
import androidx.compose.testutils.expectError
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.bottomCenter
@@ -266,10 +264,10 @@
@Test
fun swipeScrollable() {
- val touchSlop = TestTouchSlop
+ val touchSlop = 18f
val scrollState = ScrollState(initial = 0)
rule.setContent {
- CompositionLocalProvider(LocalViewConfiguration provides FakeViewConfiguration) {
+ WithTouchSlop(touchSlop) {
with(LocalDensity.current) {
// Scrollable with a viewport the size of 10 boxes
Column(
@@ -345,17 +343,4 @@
events.map { it.position.x }.assertIncreasing()
events.map { it.position.y }.assertSame(tolerance = 0.001f)
}
-
- internal val TestTouchSlop = 18f
-
- private val FakeViewConfiguration = object : ViewConfiguration {
- override val longPressTimeoutMillis: Long
- get() = 500L
- override val doubleTapTimeoutMillis: Long
- get() = 300L
- override val doubleTapMinTimeMillis: Long
- get() = 40L
- override val touchSlop: Float
- get() = TestTouchSlop
- }
}
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphTest.kt
index fd62ae0..32d6216 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphTest.kt
@@ -9,8 +9,6 @@
import android.text.style.LocaleSpan
import android.text.style.RelativeSizeSpan
import android.text.style.ScaleXSpan
-import android.text.style.StrikethroughSpan
-import android.text.style.UnderlineSpan
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
@@ -29,6 +27,7 @@
import androidx.compose.ui.text.android.style.LetterSpacingSpanPx
import androidx.compose.ui.text.android.style.ShadowSpan
import androidx.compose.ui.text.android.style.SkewXSpan
+import androidx.compose.ui.text.android.style.TextDecorationSpan
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontSynthesis
@@ -165,7 +164,13 @@
)
assertThat(paragraph.charSequence.toString()).isEqualTo(text)
- assertThat(paragraph.charSequence).hasSpan(StrikethroughSpan::class, 0, text.length)
+ assertThat(paragraph.charSequence).hasSpan(
+ spanClazz = TextDecorationSpan::class,
+ start = 0,
+ end = text.length
+ ) {
+ !it.isUnderlineText && it.isStrikethroughText
+ }
}
@Test
@@ -180,7 +185,13 @@
)
assertThat(paragraph.charSequence.toString()).isEqualTo(text)
- assertThat(paragraph.charSequence).hasSpan(UnderlineSpan::class, 0, text.length)
+ assertThat(paragraph.charSequence).hasSpan(
+ spanClazz = TextDecorationSpan::class,
+ start = 0,
+ end = text.length
+ ) {
+ it.isUnderlineText && !it.isStrikethroughText
+ }
}
@Test
@@ -195,7 +206,13 @@
)
assertThat(paragraph.charSequence.toString()).isEqualTo(text)
- assertThat(paragraph.charSequence).hasSpan(StrikethroughSpan::class, 0, "abc".length)
+ assertThat(paragraph.charSequence).hasSpan(
+ spanClazz = TextDecorationSpan::class,
+ start = 0,
+ end = "abc".length
+ ) {
+ !it.isUnderlineText && it.isStrikethroughText
+ }
}
@Test
@@ -210,7 +227,13 @@
)
assertThat(paragraph.charSequence.toString()).isEqualTo(text)
- assertThat(paragraph.charSequence).hasSpan(UnderlineSpan::class, 0, "abc".length)
+ assertThat(paragraph.charSequence).hasSpan(
+ spanClazz = TextDecorationSpan::class,
+ start = 0,
+ end = "abc".length
+ ) {
+ it.isUnderlineText && !it.isStrikethroughText
+ }
}
@Test
@@ -227,8 +250,13 @@
)
assertThat(paragraph.charSequence.toString()).isEqualTo(text)
- assertThat(paragraph.charSequence).hasSpan(UnderlineSpan::class, 0, "abc".length)
- assertThat(paragraph.charSequence).hasSpan(StrikethroughSpan::class, 0, "abc".length)
+ assertThat(paragraph.charSequence).hasSpan(
+ spanClazz = TextDecorationSpan::class,
+ start = 0,
+ end = "abc".length
+ ) {
+ it.isUnderlineText && it.isStrikethroughText
+ }
}
@Test
@@ -551,7 +579,7 @@
}
@Test
- fun testAnnotatedString_setTextGeometricTransformWithNull_noSpanSet() {
+ fun testAnnotatedString_setDefaultTextGeometricTransform() {
val text = "abcde"
val spanStyle = SpanStyle(textGeometricTransform = TextGeometricTransform())
@@ -561,8 +589,16 @@
width = 100.0f // width is not important
)
- assertThat(paragraph.charSequence).spans(ScaleXSpan::class).isEmpty()
- assertThat(paragraph.charSequence).spans(SkewXSpan::class).isEmpty()
+ assertThat(paragraph.charSequence).hasSpan(ScaleXSpan::class, 0, text.length) {
+ it.scaleX == 1.0f
+ }
+ assertThat(paragraph.charSequence).hasSpan(
+ spanClazz = SkewXSpan::class,
+ start = 0,
+ end = text.length
+ ) {
+ it.skewX == 0.0f
+ }
}
@Test
@@ -584,7 +620,9 @@
assertThat(paragraph.charSequence).hasSpan(ScaleXSpan::class, 0, text.length) {
it.scaleX == scaleX
}
- assertThat(paragraph.charSequence).spans(SkewXSpan::class).isEmpty()
+ assertThat(paragraph.charSequence).hasSpan(SkewXSpan::class, 0, text.length) {
+ it.skewX == 0.0f
+ }
}
@Test
@@ -602,7 +640,9 @@
assertThat(paragraph.charSequence).hasSpan(SkewXSpan::class, 0, text.length) {
it.skewX == skewX
}
- assertThat(paragraph.charSequence).spans(ScaleXSpan::class).isEmpty()
+ assertThat(paragraph.charSequence).hasSpan(ScaleXSpan::class, 0, text.length) {
+ it.scaleX == 1.0f
+ }
}
@Test
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt
index fb3c45d5..8bc21b2 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt
@@ -21,8 +21,10 @@
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ScaleXSpan
+import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.text.style.TypefaceSpan
+import android.text.style.UnderlineSpan
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
@@ -39,8 +41,8 @@
import androidx.compose.ui.text.platform.extensions.setColor
import androidx.compose.ui.text.platform.extensions.setFontSize
import androidx.compose.ui.text.platform.extensions.setLocaleList
-import androidx.compose.ui.text.platform.extensions.setTextDecoration
import androidx.compose.ui.text.platform.extensions.toSpan
+import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Density
import androidx.compose.ui.util.fastForEach
@@ -126,12 +128,29 @@
}
}
- setTextDecoration(spanStyle.textDecoration, start, end)
+ if (spanStyle.textDecoration != null) {
+ // This doesn't match how we rendering the styles. When TextDecoration.None is set, it
+ // should remove the underline and lineThrough effect on the given range. Here we didn't
+ // remove any previously applied spans yet.
+ if (TextDecoration.Underline in spanStyle.textDecoration) {
+ setSpan(
+ UnderlineSpan(),
+ start,
+ end,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ }
+ if (TextDecoration.LineThrough in spanStyle.textDecoration) {
+ setSpan(
+ StrikethroughSpan(),
+ start,
+ end,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ }
+ }
- if (
- spanStyle.textGeometricTransform != null &&
- spanStyle.textGeometricTransform.scaleX != 1f
- ) {
+ if (spanStyle.textGeometricTransform != null) {
setSpan(
ScaleXSpan(spanStyle.textGeometricTransform.scaleX),
start,
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt
index 188825d..85d8bcf 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt
@@ -248,7 +248,7 @@
else -> throw IllegalStateException("Unknown font type: $font")
}
} catch (e: Exception) {
- throw IllegalStateException("Cannot create Typeface from $font")
+ throw IllegalStateException("Cannot create Typeface from $font", e)
}
val loadedFontIsSameAsRequest = fontWeight == font.weight && fontStyle == font.style
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
index 17f672c..f109647 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
@@ -27,8 +27,6 @@
import android.text.style.MetricAffectingSpan
import android.text.style.RelativeSizeSpan
import android.text.style.ScaleXSpan
-import android.text.style.StrikethroughSpan
-import android.text.style.UnderlineSpan
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.isSpecified
@@ -44,6 +42,7 @@
import androidx.compose.ui.text.android.style.LineHeightSpan
import androidx.compose.ui.text.android.style.ShadowSpan
import androidx.compose.ui.text.android.style.SkewXSpan
+import androidx.compose.ui.text.android.style.TextDecorationSpan
import androidx.compose.ui.text.android.style.TypefaceSpan
import androidx.compose.ui.text.fastFilter
import androidx.compose.ui.text.font.FontStyle
@@ -393,12 +392,8 @@
end: Int
) {
textGeometricTransform?.let {
- if (it.scaleX != 1.0f) {
- setSpan(ScaleXSpan(it.scaleX), start, end)
- }
- if (it.skewX != 0f) {
- setSpan(SkewXSpan(it.skewX), start, end)
- }
+ setSpan(ScaleXSpan(it.scaleX), start, end)
+ setSpan(SkewXSpan(it.skewX), start, end)
}
}
@@ -427,14 +422,14 @@
}
}
+@OptIn(InternalPlatformTextApi::class)
internal fun Spannable.setTextDecoration(textDecoration: TextDecoration?, start: Int, end: Int) {
textDecoration?.let {
- if (TextDecoration.Underline in it) {
- setSpan(UnderlineSpan(), start, end)
- }
- if (TextDecoration.LineThrough in it) {
- setSpan(StrikethroughSpan(), start, end)
- }
+ val textDecorationSpan = TextDecorationSpan(
+ isUnderlineText = TextDecoration.Underline in it,
+ isStrikethroughText = TextDecoration.LineThrough in it
+ )
+ setSpan(textDecorationSpan, start, end)
}
}
diff --git a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopFontTest.kt b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopFontTest.kt
index 5d89cf1..440782a 100644
--- a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopFontTest.kt
+++ b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopFontTest.kt
@@ -19,13 +19,14 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.platform.Font
import androidx.compose.ui.text.platform.FontLoader
import androidx.compose.ui.text.platform.GenericFontFamiliesMapping
-import androidx.compose.ui.text.platform.Font
import androidx.compose.ui.text.platform.Typeface
import com.google.common.truth.Truth
import org.jetbrains.skija.Data
import org.jetbrains.skija.Typeface
+import org.junit.Assume.assumeTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -80,8 +81,11 @@
.isEqualTo(listOf("Sample Font"))
}
+ // TODO(demin): can we fix findTypeface for Windows and Linux?
@Test
fun findTypeface() {
+ assumeTrue(isMacOs)
+
Truth.assertThat(fontLoader.findTypeface(fontListFontFamily)!!.isItalic)
.isEqualTo(false)
diff --git a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt
index 13af274..10b9265 100644
--- a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt
+++ b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt
@@ -18,11 +18,11 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.platform.FontLoader
import androidx.compose.ui.text.platform.Font
+import androidx.compose.ui.text.platform.FontLoader
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.sp
@@ -72,7 +72,7 @@
@Test
fun getBoundingBox_multicodepoints() {
- assumeLinux()
+ assumeTrue(isLinux)
with(defaultDensity) {
val text = "h\uD83E\uDDD1\uD83C\uDFFF\u200D\uD83E\uDDB0"
val fontSize = 50.sp
@@ -240,8 +240,4 @@
resourceLoader = fontLoader
)
}
-
- private fun assumeLinux() {
- assumeTrue(System.getProperty("os.name").startsWith("Linux"))
- }
}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
new file mode 100644
index 0000000..3f0df87
--- /dev/null
+++ b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.compose.ui.text
+
+import java.util.Locale
+
+private val os = System.getProperty("os.name").toLowerCase(Locale.US)
+internal val isLinux = os.startsWith("linux")
+internal val isWindows = os.startsWith("win")
+internal val isMacOs = os.startsWith("mac")
\ No newline at end of file
diff --git a/compose/ui/ui-tooling/api/1.0.0-beta07.txt b/compose/ui/ui-tooling/api/1.0.0-beta07.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/1.0.0-beta07.txt
+++ b/compose/ui/ui-tooling/api/1.0.0-beta07.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/current.ignore b/compose/ui/ui-tooling/api/current.ignore
new file mode 100644
index 0000000..050747e
--- /dev/null
+++ b/compose/ui/ui-tooling/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.compose.ui.tooling.inspector:
+ Removed package androidx.compose.ui.tooling.inspector
diff --git a/compose/ui/ui-tooling/api/current.txt b/compose/ui/ui-tooling/api/current.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/current.txt
+++ b/compose/ui/ui-tooling/api/current.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt b/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt b/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt
+++ b/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/restricted_current.ignore b/compose/ui/ui-tooling/api/restricted_current.ignore
new file mode 100644
index 0000000..050747e
--- /dev/null
+++ b/compose/ui/ui-tooling/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.compose.ui.tooling.inspector:
+ Removed package androidx.compose.ui.tooling.inspector
diff --git a/compose/ui/ui-tooling/api/restricted_current.txt b/compose/ui/ui-tooling/api/restricted_current.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/restricted_current.txt
+++ b/compose/ui/ui-tooling/api/restricted_current.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/InlineClassConverterTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/InlineClassConverterTest.kt
deleted file mode 100644
index 57ae98f..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/InlineClassConverterTest.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2020 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.compose.ui.tooling.inspector
-
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.tooling.CompositionDataRecord
-import androidx.compose.ui.tooling.Inspectable
-import androidx.compose.ui.tooling.ToolingTest
-import androidx.compose.ui.tooling.data.Group
-import androidx.compose.ui.tooling.data.UiToolingDataApi
-import androidx.compose.ui.tooling.data.asTree
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@OptIn(UiToolingDataApi::class)
-class InlineClassConverterTest : ToolingTest() {
-
- @Test
- fun parameterValueTest() {
- val slotTableRecord = CompositionDataRecord.create()
- show {
- Inspectable(slotTableRecord) {
- Surface {
- Text(text = "OK", fontSize = 12.sp)
- }
- }
- }
-
- val tree = slotTableRecord.store.first().asTree()
- val groups = flatten(tree)
- val surface = find(groups, "Surface")
- val text = find(groups, "Text")
-
- val mapper = InlineClassConverter()
-
- fun validate(caller: Group, parameterName: String, valueType: Class<*>) {
- val parameter = caller.parameters.single { it.name == parameterName }
- val value = mapper.castParameterValue(parameter.inlineClass, parameter.value)
- assertThat(value).isInstanceOf(valueType)
- }
-
- validate(surface, "color", Color::class.java)
- validate(surface, "elevation", Dp::class.java)
- validate(text, "color", Color::class.java)
- validate(text, "fontSize", TextUnit::class.java)
- }
-
- private fun flatten(group: Group): Sequence<Group> =
- sequenceOf(group).plus(group.children.asSequence().flatMap { flatten(it) })
-
- private fun find(groups: Sequence<Group>, calleeName: String) =
- groups.first {
- it.parameters.isNotEmpty() && it.name == calleeName
- }
-}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
deleted file mode 100644
index 7a375f3..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright 2020 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.compose.ui.tooling.inspector
-
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.material.Button
-import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.ModalDrawer
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Call
-import androidx.compose.material.icons.filled.FavoriteBorder
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.GraphicLayerInfo
-import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.tooling.CompositionDataRecord
-import androidx.compose.ui.tooling.Inspectable
-import androidx.compose.ui.tooling.R
-import androidx.compose.ui.tooling.ToolingTest
-import androidx.compose.ui.tooling.data.Group
-import androidx.compose.ui.tooling.data.UiToolingDataApi
-import androidx.compose.ui.tooling.data.asTree
-import androidx.compose.ui.tooling.data.position
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import kotlin.math.roundToInt
-
-private const val DEBUG = false
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 29) // Render id is not returned for api < 29
-@OptIn(UiToolingDataApi::class)
-class LayoutInspectorTreeTest : ToolingTest() {
- private lateinit var density: Density
- private lateinit var view: View
-
- @Before
- fun before() {
- density = Density(activity)
- view = activityTestRule.activity.findViewById<ViewGroup>(android.R.id.content)
- isDebugInspectorInfoEnabled = true
- }
-
- @After
- fun after() {
- isDebugInspectorInfoEnabled = false
- }
-
- @Test
- fun buildTree() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- Column {
- Text(text = "Hello World", color = Color.Green)
- Icon(Icons.Filled.FavoriteBorder, null)
- Surface {
- Button(onClick = {}) { Text(text = "OK") }
- }
- }
- }
- }
-
- // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- val builder = LayoutInspectorTree()
- val nodes = builder.convert(view)
- dumpNodes(nodes, builder)
-
- validate(nodes, builder, checkParameters = false) {
- node(
- name = "Inspectable",
- fileName = "LayoutInspectorTreeTest.kt",
- left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 78.9.dp,
- children = listOf("Column")
- )
- node(
- name = "Column",
- fileName = "LayoutInspectorTreeTest.kt",
- left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 78.9.dp,
- children = listOf("Text", "Icon", "Surface")
- )
- node(
- name = "Text",
- isRenderNode = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 18.9.dp,
- )
- node(
- name = "Icon",
- isRenderNode = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 0.0.dp, top = 18.9.dp, width = 24.0.dp, height = 24.0.dp,
- )
- node(
- name = "Surface",
- fileName = "LayoutInspectorTreeTest.kt",
- isRenderNode = true,
- left = 0.0.dp,
- top = 42.9.dp, width = 64.0.dp, height = 36.0.dp,
- children = listOf("Button")
- )
- node(
- name = "Button",
- fileName = "LayoutInspectorTreeTest.kt",
- isRenderNode = true,
- left = 0.0.dp,
- top = 42.9.dp, width = 64.0.dp, height = 36.0.dp,
- children = listOf("Text")
- )
- node(
- name = "Text",
- isRenderNode = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 21.7.dp, top = 51.6.dp, width = 20.9.dp, height = 18.9.dp,
- )
- }
- }
-
- @Test
- fun buildTreeWithTransformedText() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- MaterialTheme {
- Text(
- text = "Hello World",
- modifier = Modifier.graphicsLayer(rotationZ = 225f)
- )
- }
- }
- }
-
- // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- val builder = LayoutInspectorTree()
- val nodes = builder.convert(view)
- dumpNodes(nodes, builder)
-
- validate(nodes, builder, checkParameters = false) {
- node(
- name = "Inspectable",
- hasTransformations = true,
- fileName = "LayoutInspectorTreeTest.kt",
- children = listOf("MaterialTheme")
- )
- node(
- name = "MaterialTheme",
- hasTransformations = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 65.0.dp, top = 49.7.dp, width = 86.dp, height = 21.7.dp,
- children = listOf("Text")
- )
- node(
- name = "Text",
- isRenderNode = true,
- hasTransformations = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 65.0.dp, top = 49.7.dp, width = 86.dp, height = 21.7.dp,
- )
- }
- }
-
- @Test
- fun testStitchTreeFromModelDrawerLayout() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- ModalDrawer(
- drawerContent = { Text("Something") },
- content = {
- Column {
- Text(text = "Hello World", color = Color.Green)
- Button(onClick = {}) { Text(text = "OK") }
- }
- }
- )
- }
- }
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- dumpSlotTableSet(slotTableRecord)
- val builder = LayoutInspectorTree()
- val nodes = builder.convert(view)
- dumpNodes(nodes, builder)
-
- if (DEBUG) {
- validate(nodes, builder, checkParameters = false) {
- node("Inspectable", children = listOf("Box"))
- node("Box", children = listOf("ModalDrawer"))
- node("ModalDrawer", children = listOf("Column", "Text"))
- node("Column", children = listOf("Text", "Button"))
- node("Text")
- node("Button", children = listOf("Text"))
- node("Text")
- node("Text")
- }
- }
- assertThat(nodes.size).isEqualTo(1)
- }
-
- @LargeTest
- @Test
- fun testStitchTreeFromModelDrawerLayoutWithSystemNodes() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- ModalDrawer(
- drawerContent = { Text("Something") },
- content = {
- Column {
- Text(text = "Hello World", color = Color.Green)
- Button(onClick = {}) { Text(text = "OK") }
- }
- }
- )
- }
- }
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- dumpSlotTableSet(slotTableRecord)
- val builder = LayoutInspectorTree()
- builder.hideSystemNodes = false
- val nodes = builder.convert(view)
- dumpNodes(nodes, builder)
-
- if (DEBUG) {
- validate(nodes, builder, checkParameters = false) {
- node("Inspectable", children = listOf("Box"))
- node("Box", children = listOf("ModalDrawer"))
- node("ModalDrawer", children = listOf("WithConstraints"))
- node("WithConstraints", children = listOf("SubcomposeLayout"))
- node("SubcomposeLayout", children = listOf("Box"))
- node("Box", children = listOf("Box", "Canvas", "Surface"))
- node("Box", children = listOf("Column"))
- node("Column", children = listOf("Text", "Button"))
- node("Text", children = listOf("Text"))
- node("Text", children = listOf("CoreText"))
- node("CoreText", children = listOf())
- node("Button", children = listOf("Surface"))
- node("Surface", children = listOf("ProvideTextStyle"))
- node("ProvideTextStyle", children = listOf("Row"))
- node("Row", children = listOf("Text"))
- node("Text", children = listOf("Text"))
- node("Text", children = listOf("CoreText"))
- node("CoreText", children = listOf())
- node("Canvas", children = listOf("Spacer"))
- node("Spacer", children = listOf())
- node("Surface", children = listOf("Column"))
- node("Column", children = listOf("Text"))
- node("Text", children = listOf("Text"))
- node("Text", children = listOf("CoreText"))
- node("CoreText", children = listOf())
- }
- }
- assertThat(nodes.size).isEqualTo(1)
- }
-
- @Test
- fun testSpacer() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- Column {
- Text(text = "Hello World", color = Color.Green)
- Spacer(Modifier.height(16.dp))
- Image(Icons.Filled.Call, null)
- }
- }
- }
-
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- val builder = LayoutInspectorTree()
- val node = builder.convert(view)
- .flatMap { flatten(it) }
- .firstOrNull { it.name == "Spacer" }
-
- // Spacer should show up in the Compose tree:
- assertThat(node).isNotNull()
- }
-
- @Test // regression test b/174855322
- fun testBasicText() {
- val slotTableRecord = CompositionDataRecord.create()
-
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- show {
- Inspectable(slotTableRecord) {
- Column {
- BasicText(
- text = "Some text",
- style = TextStyle(textDecoration = TextDecoration.Underline)
- )
- }
- }
- }
-
- val builder = LayoutInspectorTree()
- val node = builder.convert(view)
- .flatMap { flatten(it) }
- .firstOrNull { it.name == "BasicText" }
-
- assertThat(node).isNotNull()
-
- assertThat(node?.parameters).isNotEmpty()
- }
-
- @Test
- fun testTextId() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- Text(text = "Hello World")
- }
- }
-
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- val builder = LayoutInspectorTree()
- val node = builder.convert(view)
- .flatMap { flatten(it) }
- .firstOrNull { it.name == "Text" }
-
- // LayoutNode id should be captured by the Text node:
- assertThat(node?.id).isGreaterThan(0)
- }
-
- @Suppress("SameParameterValue")
- private fun validate(
- result: List<InspectorNode>,
- builder: LayoutInspectorTree,
- checkParameters: Boolean,
- block: TreeValidationReceiver.() -> Unit = {}
- ) {
- val nodes = result.flatMap { flatten(it) }.iterator()
- val tree = TreeValidationReceiver(nodes, density, checkParameters, builder)
- tree.block()
- }
-
- private class TreeValidationReceiver(
- val nodeIterator: Iterator<InspectorNode>,
- val density: Density,
- val checkParameters: Boolean,
- val builder: LayoutInspectorTree
- ) {
- fun node(
- name: String,
- fileName: String? = null,
- lineNumber: Int = -1,
- isRenderNode: Boolean = false,
- hasTransformations: Boolean = false,
-
- left: Dp = Dp.Unspecified,
- top: Dp = Dp.Unspecified,
- width: Dp = Dp.Unspecified,
- height: Dp = Dp.Unspecified,
- children: List<String> = listOf(),
- block: ParameterValidationReceiver.() -> Unit = {}
- ) {
- assertWithMessage("No such node found: $name").that(nodeIterator.hasNext()).isTrue()
- val node = nodeIterator.next()
- assertThat(node.name).isEqualTo(name)
- val message = "Node: $name"
- assertWithMessage(message).that(node.children.map { it.name })
- .containsExactlyElementsIn(children).inOrder()
- fileName?.let { assertWithMessage(message).that(node.fileName).isEqualTo(fileName) }
- if (lineNumber != -1) {
- assertWithMessage(message).that(node.lineNumber).isEqualTo(lineNumber)
- }
- if (isRenderNode) {
- assertWithMessage(message).that(node.id).isGreaterThan(0L)
- } else {
- assertWithMessage(message).that(node.id).isLessThan(0L)
- }
- if (hasTransformations) {
- assertWithMessage(message).that(node.bounds).isNotEmpty()
- } else {
- assertWithMessage(message).that(node.bounds).isEmpty()
- }
- if (left != Dp.Unspecified) {
- val tolerance = 5.0f
- with(density) {
- assertWithMessage(message).that(node.left.toDp().value)
- .isWithin(tolerance).of(left.value)
- assertWithMessage(message).that(node.top.toDp().value)
- .isWithin(tolerance).of(top.value)
- assertWithMessage(message).that(node.width.toDp().value)
- .isWithin(tolerance).of(width.value)
- assertWithMessage(message).that(node.height.toDp().value)
- .isWithin(tolerance).of(height.value)
- }
- }
-
- if (checkParameters) {
- val params = builder.convertParameters(node)
- val receiver = ParameterValidationReceiver(params.listIterator())
- receiver.block()
- if (receiver.parameterIterator.hasNext()) {
- val elementNames = mutableListOf<String>()
- receiver.parameterIterator.forEachRemaining { elementNames.add(it.name) }
- error("$name: has more parameters like: ${elementNames.joinToString()}")
- }
- }
- }
- }
-
- private fun flatten(node: InspectorNode): List<InspectorNode> =
- listOf(node).plus(node.children.flatMap { flatten(it) })
-
- // region DEBUG print methods
- private fun dumpNodes(nodes: List<InspectorNode>, builder: LayoutInspectorTree) {
- @Suppress("ConstantConditionIf")
- if (!DEBUG) {
- return
- }
- println()
- println("=================== Nodes ==========================")
- nodes.forEach { dumpNode(it, indent = 0) }
- println()
- println("=================== validate statements ==========================")
- nodes.forEach { generateValidate(it, builder) }
- }
-
- private fun dumpNode(node: InspectorNode, indent: Int) {
- println(
- "\"${" ".repeat(indent * 2)}\", \"${node.name}\", \"${node.fileName}\", " +
- "${node.lineNumber}, ${node.left}, ${node.top}, " +
- "${node.width}, ${node.height}"
- )
- node.children.forEach { dumpNode(it, indent + 1) }
- }
-
- private fun generateValidate(
- node: InspectorNode,
- builder: LayoutInspectorTree,
- generateParameters: Boolean = false
- ) {
- with(density) {
- val left = round(node.left.toDp())
- val top = round(node.top.toDp())
- val width = if (node.width == view.width) "viewWidth" else round(node.width.toDp())
- val height = if (node.height == view.height) "viewHeight" else round(node.height.toDp())
-
- print(
- """
- validate(
- name = "${node.name}",
- fileName = "${node.fileName}",
- left = $left, top = $top, width = $width, height = $height
- """.trimIndent()
- )
- }
- if (node.id > 0L) {
- println(",")
- print(" isRenderNode = true")
- }
- if (node.children.isNotEmpty()) {
- println(",")
- val children = node.children.joinToString { "\"${it.name}\"" }
- print(" children = listOf($children)")
- }
- println()
- print(")")
- if (generateParameters && node.parameters.isNotEmpty()) {
- generateParameters(builder.convertParameters(node), 0)
- }
- println()
- node.children.forEach { generateValidate(it, builder) }
- }
-
- private fun generateParameters(parameters: List<NodeParameter>, indent: Int) {
- val indentation = " ".repeat(indent * 2)
- println(" {")
- for (param in parameters) {
- val name = param.name
- val type = param.type
- val value = toDisplayValue(type, param.value)
- print("$indentation parameter(name = \"$name\", type = $type, value = $value)")
- if (param.elements.isNotEmpty()) {
- generateParameters(param.elements, indent + 1)
- }
- println()
- }
- print("$indentation}")
- }
-
- private fun toDisplayValue(type: ParameterType, value: Any?): String =
- when (type) {
- ParameterType.Boolean -> value.toString()
- ParameterType.Color ->
- "0x${Integer.toHexString(value as Int)}${if (value < 0) ".toInt()" else ""}"
- ParameterType.DimensionSp,
- ParameterType.DimensionDp -> "${value}f"
- ParameterType.Int32 -> value.toString()
- ParameterType.String -> "\"$value\""
- else -> value?.toString() ?: "null"
- }
-
- private fun dumpSlotTableSet(slotTableRecord: CompositionDataRecord) {
- @Suppress("ConstantConditionIf")
- if (!DEBUG) {
- return
- }
- println()
- println("=================== Groups ==========================")
- slotTableRecord.store.forEach { dumpGroup(it.asTree(), indent = 0) }
- }
-
- private fun dumpGroup(group: Group, indent: Int) {
- val position = group.position?.let { "\"$it\"" } ?: "null"
- val box = group.box
- val id = group.modifierInfo.mapNotNull { (it.extra as? GraphicLayerInfo)?.layerId }
- .singleOrNull() ?: 0
- println(
- "\"${" ".repeat(indent)}\", ${group.javaClass.simpleName}, \"${group.name}\", " +
- "params: ${group.parameters.size}, children: ${group.children.size}, " +
- "$id, $position, " +
- "${box.left}, ${box.right}, ${box.right - box.left}, ${box.bottom - box.top}"
- )
- for (parameter in group.parameters) {
- println("\"${" ".repeat(indent + 4)}\"- ${parameter.name}")
- }
- group.children.forEach { dumpGroup(it, indent + 1) }
- }
-
- private fun round(dp: Dp): Dp = Dp((dp.value * 10.0f).roundToInt() / 10.0f)
-
- //endregion
-}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt
deleted file mode 100644
index 83459eb..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt
+++ /dev/null
@@ -1,737 +0,0 @@
-/*
- * Copyright 2020 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.compose.ui.tooling.inspector
-
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentHeight
-import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.foundation.shape.CutCornerShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.shape.ZeroCornerSize
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Call
-import androidx.compose.material.icons.rounded.Add
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.AbsoluteAlignment
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.paint
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.colorspace.ColorModel
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.painter.Painter
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.intl.Locale
-import androidx.compose.ui.text.intl.LocaleList
-import androidx.compose.ui.text.style.BaselineShift
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.text.style.TextGeometricTransform
-import androidx.compose.ui.text.style.TextIndent
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.em
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.runBlocking
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@Suppress("unused")
-private fun topLevelFunction() {}
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class ParameterFactoryTest {
- private val factory = ParameterFactory(InlineClassConverter())
- private val node = MutableInspectorNode().apply {
- width = 1000
- height = 500
- }.build()
-
- @Before
- fun before() {
- factory.density = Density(2.0f)
- isDebugInspectorInfoEnabled = true
- }
-
- @After
- fun after() {
- isDebugInspectorInfoEnabled = false
- }
-
- @Test
- fun testAbsoluteAlignment() {
- assertThat(lookup(AbsoluteAlignment.TopLeft))
- .isEqualTo(ParameterType.String to "TopLeft")
- assertThat(lookup(AbsoluteAlignment.TopRight))
- .isEqualTo(ParameterType.String to "TopRight")
- assertThat(lookup(AbsoluteAlignment.CenterLeft))
- .isEqualTo(ParameterType.String to "CenterLeft")
- assertThat(lookup(AbsoluteAlignment.CenterRight))
- .isEqualTo(ParameterType.String to "CenterRight")
- assertThat(lookup(AbsoluteAlignment.BottomLeft))
- .isEqualTo(ParameterType.String to "BottomLeft")
- assertThat(lookup(AbsoluteAlignment.BottomRight))
- .isEqualTo(ParameterType.String to "BottomRight")
- assertThat(lookup(AbsoluteAlignment.Left))
- .isEqualTo(ParameterType.String to "Left")
- assertThat(lookup(AbsoluteAlignment.Right))
- .isEqualTo(ParameterType.String to "Right")
- }
-
- @Test
- fun testAlignment() {
- assertThat(lookup(Alignment.Top)).isEqualTo(ParameterType.String to "Top")
- assertThat(lookup(Alignment.Bottom)).isEqualTo(ParameterType.String to "Bottom")
- assertThat(lookup(Alignment.CenterVertically))
- .isEqualTo(ParameterType.String to "CenterVertically")
-
- assertThat(lookup(Alignment.Start)).isEqualTo(ParameterType.String to "Start")
- assertThat(lookup(Alignment.End)).isEqualTo(ParameterType.String to "End")
- assertThat(lookup(Alignment.CenterHorizontally))
- .isEqualTo(ParameterType.String to "CenterHorizontally")
-
- assertThat(lookup(Alignment.TopStart)).isEqualTo(ParameterType.String to "TopStart")
- assertThat(lookup(Alignment.TopCenter)).isEqualTo(ParameterType.String to "TopCenter")
- assertThat(lookup(Alignment.TopEnd)).isEqualTo(ParameterType.String to "TopEnd")
- assertThat(lookup(Alignment.CenterStart)).isEqualTo(ParameterType.String to "CenterStart")
- assertThat(lookup(Alignment.Center)).isEqualTo(ParameterType.String to "Center")
- assertThat(lookup(Alignment.CenterEnd)).isEqualTo(ParameterType.String to "CenterEnd")
- assertThat(lookup(Alignment.BottomStart)).isEqualTo(ParameterType.String to "BottomStart")
- assertThat(lookup(Alignment.BottomCenter)).isEqualTo(ParameterType.String to "BottomCenter")
- assertThat(lookup(Alignment.BottomEnd)).isEqualTo(ParameterType.String to "BottomEnd")
- }
-
- @Test
- fun testAnnotatedString() {
- assertThat(lookup(AnnotatedString("Hello"))).isEqualTo(ParameterType.String to "Hello")
- }
-
- @Test
- fun testArrangement() {
- assertThat(lookup(Arrangement.Top)).isEqualTo(ParameterType.String to "Top")
- assertThat(lookup(Arrangement.Bottom)).isEqualTo(ParameterType.String to "Bottom")
- assertThat(lookup(Arrangement.Center)).isEqualTo(ParameterType.String to "Center")
- assertThat(lookup(Arrangement.Start)).isEqualTo(ParameterType.String to "Start")
- assertThat(lookup(Arrangement.End)).isEqualTo(ParameterType.String to "End")
- assertThat(lookup(Arrangement.SpaceEvenly)).isEqualTo(ParameterType.String to "SpaceEvenly")
- assertThat(lookup(Arrangement.SpaceBetween))
- .isEqualTo(ParameterType.String to "SpaceBetween")
- assertThat(lookup(Arrangement.SpaceAround)).isEqualTo(ParameterType.String to "SpaceAround")
- }
-
- @Test
- fun testBaselineShift() {
- assertThat(lookup(BaselineShift.None)).isEqualTo(ParameterType.String to "None")
- assertThat(lookup(BaselineShift.Subscript)).isEqualTo(ParameterType.String to "Subscript")
- assertThat(lookup(BaselineShift.Superscript))
- .isEqualTo(ParameterType.String to "Superscript")
- assertThat(lookup(BaselineShift(0.0f))).isEqualTo(ParameterType.String to "None")
- assertThat(lookup(BaselineShift(-0.5f))).isEqualTo(ParameterType.String to "Subscript")
- assertThat(lookup(BaselineShift(0.5f))).isEqualTo(ParameterType.String to "Superscript")
- assertThat(lookup(BaselineShift(0.1f))).isEqualTo(ParameterType.Float to 0.1f)
- assertThat(lookup(BaselineShift(0.75f))).isEqualTo(ParameterType.Float to 0.75f)
- }
-
- @Test
- fun testBoolean() {
- assertThat(lookup(true)).isEqualTo(ParameterType.Boolean to true)
- assertThat(lookup(false)).isEqualTo(ParameterType.Boolean to false)
- }
-
- @Test
- fun testBorder() {
- validate(factory.create(node, "borderstroke", BorderStroke(2.0.dp, Color.Magenta))!!) {
- parameter("borderstroke", ParameterType.String, "BorderStroke") {
- parameter("brush", ParameterType.Color, Color.Magenta.toArgb())
- parameter("width", ParameterType.DimensionDp, 2.0f)
- }
- }
- }
-
- @Test
- fun testBrush() {
- assertThat(lookup(SolidColor(Color.Red)))
- .isEqualTo(ParameterType.Color to Color.Red.toArgb())
- validate(
- factory.create(
- node,
- "brush",
- Brush.linearGradient(
- colors = listOf(Color.Red, Color.Blue),
- start = Offset(0.0f, 0.5f),
- end = Offset(5.0f, 10.0f)
- )
- )!!
- ) {
- parameter("brush", ParameterType.String, "LinearGradient") {
- parameter("colors", ParameterType.String, "") {
- parameter("0", ParameterType.Color, Color.Red.toArgb())
- parameter("1", ParameterType.Color, Color.Blue.toArgb())
- }
- // Parameters are traversed in alphabetical order through reflection queries.
- // Validate createdSize exists before validating end parameter
- parameter("createdSize", ParameterType.String, "Unspecified")
- parameter("end", ParameterType.String, Offset::class.java.simpleName) {
- parameter("x", ParameterType.DimensionDp, 2.5f)
- parameter("y", ParameterType.DimensionDp, 5.0f)
- }
- parameter("start", ParameterType.String, Offset::class.java.simpleName) {
- parameter("x", ParameterType.DimensionDp, 0.0f)
- parameter("y", ParameterType.DimensionDp, 0.25f)
- }
- parameter("tileMode", ParameterType.String, "Clamp")
- }
- }
- // TODO: add tests for RadialGradient & ShaderBrush
- }
-
- @Test
- fun testColor() {
- assertThat(lookup(Color.Blue)).isEqualTo(ParameterType.Color to 0xff0000ff.toInt())
- assertThat(lookup(Color.Red)).isEqualTo(ParameterType.Color to 0xffff0000.toInt())
- assertThat(lookup(Color.Transparent)).isEqualTo(ParameterType.Color to 0x00000000)
- assertThat(lookup(Color.Unspecified)).isEqualTo(ParameterType.String to "Unspecified")
- }
-
- @Test
- fun testComposableLambda() = runBlocking {
- // capture here to force the lambda to not be created as a singleton.
- val capture = "Hello World"
- val c: @Composable () -> Unit = { Text(text = capture) }
- val result = lookup(c as Any) ?: error("Lookup of ComposableLambda failed")
- val array = result.second as Array<*>
- assertThat(result.first).isEqualTo(ParameterType.Lambda)
- assertThat(array).hasLength(1)
- assertThat(array[0]?.javaClass?.name).isEqualTo(
- "${ParameterFactoryTest::class.java.name}\$testComposableLambda\$1\$c\$1"
- )
- }
-
- @Test
- fun testCornerBasedShape() {
- validate(
- factory.create(
- node, "corner",
- RoundedCornerShape(2.0.dp, 0.5.dp, 2.5.dp, 0.7.dp)
- )!!
- ) {
- parameter("corner", ParameterType.String, RoundedCornerShape::class.java.simpleName) {
- parameter("bottomEnd", ParameterType.DimensionDp, 2.5f)
- parameter("bottomStart", ParameterType.DimensionDp, 0.7f)
- parameter("topEnd", ParameterType.DimensionDp, 0.5f)
- parameter("topStart", ParameterType.DimensionDp, 2.0f)
- }
- }
- validate(factory.create(node, "corner", CutCornerShape(2))!!) {
- parameter("corner", ParameterType.String, CutCornerShape::class.java.simpleName) {
- parameter("bottomEnd", ParameterType.DimensionDp, 5.0f)
- parameter("bottomStart", ParameterType.DimensionDp, 5.0f)
- parameter("topEnd", ParameterType.DimensionDp, 5.0f)
- parameter("topStart", ParameterType.DimensionDp, 5.0f)
- }
- }
- validate(factory.create(node, "corner", RoundedCornerShape(1.0f, 10.0f, 2.0f, 3.5f))!!) {
- parameter("corner", ParameterType.String, RoundedCornerShape::class.java.simpleName) {
- parameter("bottomEnd", ParameterType.DimensionDp, 1.0f)
- parameter("bottomStart", ParameterType.DimensionDp, 1.75f)
- parameter("topEnd", ParameterType.DimensionDp, 5.0f)
- parameter("topStart", ParameterType.DimensionDp, 0.5f)
- }
- }
- }
-
- @Test
- fun testCornerSize() {
- assertThat(lookup(ZeroCornerSize)).isEqualTo(ParameterType.String to "ZeroCornerSize")
- assertThat(lookup(CornerSize(2.4.dp))).isEqualTo(ParameterType.DimensionDp to 2.4f)
- assertThat(lookup(CornerSize(2.4f))).isEqualTo(ParameterType.DimensionDp to 1.2f)
- assertThat(lookup(CornerSize(3))).isEqualTo(ParameterType.DimensionDp to 7.5f)
- }
-
- @Test
- fun testDouble() {
- assertThat(lookup(3.1428)).isEqualTo(ParameterType.Double to 3.1428)
- }
-
- @Test
- fun testDp() {
- assertThat(lookup(2.0.dp)).isEqualTo(ParameterType.DimensionDp to 2.0f)
- assertThat(lookup(Dp.Hairline)).isEqualTo(ParameterType.DimensionDp to 0.0f)
- assertThat(lookup(Dp.Unspecified)).isEqualTo(ParameterType.DimensionDp to Float.NaN)
- assertThat(lookup(Dp.Infinity))
- .isEqualTo(ParameterType.DimensionDp to Float.POSITIVE_INFINITY)
- }
-
- @Test
- fun testEnum() {
- assertThat(lookup(ColorModel.Lab)).isEqualTo(ParameterType.String to "Lab")
- assertThat(lookup(ColorModel.Rgb)).isEqualTo(ParameterType.String to "Rgb")
- assertThat(lookup(ColorModel.Cmyk)).isEqualTo(ParameterType.String to "Cmyk")
- }
-
- @Test
- fun testFloat() {
- assertThat(lookup(3.1428f)).isEqualTo(ParameterType.Float to 3.1428f)
- }
-
- @Test
- fun testFontFamily() {
- assertThat(lookup(FontFamily.Cursive)).isEqualTo(ParameterType.String to "Cursive")
- assertThat(lookup(FontFamily.Default)).isEqualTo(ParameterType.String to "Default")
- assertThat(lookup(FontFamily.SansSerif)).isEqualTo(ParameterType.String to "SansSerif")
- assertThat(lookup(FontFamily.Serif)).isEqualTo(ParameterType.String to "Serif")
- assertThat(lookup(FontFamily.Monospace)).isEqualTo(ParameterType.String to "Monospace")
- }
-
- @Test
- fun testFontListFontFamily() {
- val family = FontFamily(
- listOf(
- Font(1234, FontWeight.Normal, FontStyle.Italic),
- Font(1235, FontWeight.Normal, FontStyle.Normal),
- Font(1236, FontWeight.Bold, FontStyle.Italic),
- Font(1237, FontWeight.Bold, FontStyle.Normal)
- )
- )
- assertThat(lookup(family)).isEqualTo(ParameterType.Resource to 1235)
- }
-
- @Test
- fun testFontWeight() {
- assertThat(lookup(FontWeight.Thin)).isEqualTo(ParameterType.String to "W100")
- assertThat(lookup(FontWeight.ExtraLight)).isEqualTo(ParameterType.String to "W200")
- assertThat(lookup(FontWeight.Light)).isEqualTo(ParameterType.String to "W300")
- assertThat(lookup(FontWeight.Normal)).isEqualTo(ParameterType.String to "W400")
- assertThat(lookup(FontWeight.Medium)).isEqualTo(ParameterType.String to "W500")
- assertThat(lookup(FontWeight.SemiBold)).isEqualTo(ParameterType.String to "W600")
- assertThat(lookup(FontWeight.Bold)).isEqualTo(ParameterType.String to "W700")
- assertThat(lookup(FontWeight.ExtraBold)).isEqualTo(ParameterType.String to "W800")
- assertThat(lookup(FontWeight.Black)).isEqualTo(ParameterType.String to "W900")
- assertThat(lookup(FontWeight.W100)).isEqualTo(ParameterType.String to "W100")
- assertThat(lookup(FontWeight.W200)).isEqualTo(ParameterType.String to "W200")
- assertThat(lookup(FontWeight.W300)).isEqualTo(ParameterType.String to "W300")
- assertThat(lookup(FontWeight.W400)).isEqualTo(ParameterType.String to "W400")
- assertThat(lookup(FontWeight.W500)).isEqualTo(ParameterType.String to "W500")
- assertThat(lookup(FontWeight.W600)).isEqualTo(ParameterType.String to "W600")
- assertThat(lookup(FontWeight.W700)).isEqualTo(ParameterType.String to "W700")
- assertThat(lookup(FontWeight.W800)).isEqualTo(ParameterType.String to "W800")
- assertThat(lookup(FontWeight.W900)).isEqualTo(ParameterType.String to "W900")
- }
-
- @Test
- fun testFunctionReference() {
- val ref1 = ::testInt
- val map1 = lookup(ref1)!!
- val array1 = map1.second as Array<*>
- assertThat(map1.first).isEqualTo(ParameterType.FunctionReference)
- assertThat(array1.contentEquals(arrayOf(ref1, "testInt"))).isTrue()
- val ref2 = ::topLevelFunction
- val map2 = lookup(ref2)!!
- val array2 = map2.second as Array<*>
- assertThat(map2.first).isEqualTo(ParameterType.FunctionReference)
- assertThat(array2.contentEquals(arrayOf(ref2, "topLevelFunction"))).isTrue()
- }
-
- @Test
- fun testPaddingValues() {
- validate(factory.create(node, "padding", PaddingValues(2.0.dp, 0.5.dp, 2.5.dp, 0.7.dp))!!) {
- parameter(
- "padding",
- ParameterType.String,
- "PaddingValuesImpl"
- ) {
- parameter("bottom", ParameterType.DimensionDp, 0.7f)
- parameter("end", ParameterType.DimensionDp, 2.5f)
- parameter("start", ParameterType.DimensionDp, 2.0f)
- parameter("top", ParameterType.DimensionDp, 0.5f)
- }
- }
- }
-
- @Test
- fun testInt() {
- assertThat(lookup(12345)).isEqualTo(ParameterType.Int32 to 12345)
- }
-
- @Test
- fun testLambda() {
- val a: (Int) -> Int = { it }
- val map = lookup(a)!!
- val array = map.second as Array<*>
- assertThat(map.first).isEqualTo(ParameterType.Lambda)
- assertThat(array.contentEquals(arrayOf<Any>(a))).isTrue()
- }
-
- @Test
- fun testLocale() {
- assertThat(lookup(Locale("fr-CA"))).isEqualTo(ParameterType.String to "fr-CA")
- assertThat(lookup(Locale("fr-BE"))).isEqualTo(ParameterType.String to "fr-BE")
- }
-
- @Test
- fun testLocaleList() {
- validate(factory.create(node, "locales", LocaleList(Locale("fr-ca"), Locale("fr-be")))!!) {
- parameter("locales", ParameterType.String, "") {
- parameter("0", ParameterType.String, "fr-CA")
- parameter("1", ParameterType.String, "fr-BE")
- }
- }
- }
-
- @Test
- fun testLong() {
- assertThat(lookup(12345L)).isEqualTo(ParameterType.Int64 to 12345L)
- }
-
- @Test
- fun testModifier() {
- validate(
- factory.create(
- node, "modifier",
- Modifier
- .background(Color.Blue)
- .border(width = 5.dp, color = Color.Red)
- .padding(2.0.dp)
- .fillMaxWidth()
- .wrapContentHeight(Alignment.Bottom)
- .width(30.0.dp)
- .paint(TestPainter(10f, 20f))
- )!!
- ) {
- parameter("modifier", ParameterType.String, "") {
- parameter("background", ParameterType.Color, Color.Blue.toArgb()) {
- parameter("color", ParameterType.Color, Color.Blue.toArgb())
- parameter("shape", ParameterType.String, "RectangleShape")
- }
- parameter("border", ParameterType.Color, Color.Red.toArgb()) {
- parameter("color", ParameterType.Color, Color.Red.toArgb())
- parameter("shape", ParameterType.String, "RectangleShape")
- parameter("width", ParameterType.DimensionDp, 5.0f)
- }
- parameter("padding", ParameterType.DimensionDp, 2.0f)
- parameter("fillMaxWidth", ParameterType.String, "") {
- parameter("fraction", ParameterType.Float, 1.0f)
- }
- parameter("wrapContentHeight", ParameterType.String, "") {
- parameter("align", ParameterType.String, "Bottom")
- parameter("unbounded", ParameterType.Boolean, false)
- }
- parameter("width", ParameterType.DimensionDp, 30.0f)
- parameter("paint", ParameterType.String, "") {
- parameter("alignment", ParameterType.String, "Center")
- parameter("alpha", ParameterType.Float, 1.0f)
- parameter("contentScale", ParameterType.String, "Inside")
- parameter("painter", ParameterType.String, "TestPainter") {
- parameter("alpha", ParameterType.Float, 1.0f)
- parameter("color", ParameterType.Color, Color.Red.toArgb())
- parameter("drawLambda", ParameterType.Lambda, null)
- parameter("height", ParameterType.Float, 20.0f)
- parameter("intrinsicSize", ParameterType.String, "Size") {
- parameter("height", ParameterType.Float, 20.0f)
- parameter("maxDimension", ParameterType.Float, 20.0f)
- parameter("minDimension", ParameterType.Float, 10.0f)
- parameter("packedValue", ParameterType.Int64, 4692750812821061632L)
- parameter("width", ParameterType.Float, 10.0f)
- }
- parameter("layoutDirection", ParameterType.String, "Ltr")
- parameter("useLayer", ParameterType.Boolean, false)
- parameter("width", ParameterType.Float, 10.0f)
- }
- parameter("sizeToIntrinsics", ParameterType.Boolean, true)
- }
- }
- }
- }
-
- @Test
- fun testSingleModifier() {
- validate(factory.create(node, "modifier", Modifier.padding(2.0.dp))!!) {
- parameter("modifier", ParameterType.String, "") {
- parameter("padding", ParameterType.DimensionDp, 2.0f)
- }
- }
- }
-
- @Test
- fun testSingleModifierWithParameters() {
- validate(factory.create(node, "modifier", Modifier.padding(1.dp, 2.dp, 3.dp, 4.dp))!!) {
- parameter("modifier", ParameterType.String, "") {
- parameter("padding", ParameterType.String, "") {
- parameter("bottom", ParameterType.DimensionDp, 4.0f)
- parameter("end", ParameterType.DimensionDp, 3.0f)
- parameter("start", ParameterType.DimensionDp, 1.0f)
- parameter("top", ParameterType.DimensionDp, 2.0f)
- }
- }
- }
- }
-
- @Test
- fun testOffset() {
- validate(factory.create(node, "offset", Offset(1.0f, 5.0f))!!) {
- parameter("offset", ParameterType.String, Offset::class.java.simpleName) {
- parameter("x", ParameterType.DimensionDp, 0.5f)
- parameter("y", ParameterType.DimensionDp, 2.5f)
- }
- }
- validate(factory.create(node, "offset", Offset.Zero)!!) {
- parameter("offset", ParameterType.String, "Zero")
- }
- }
-
- @Test
- fun testRecursiveStructure() {
- val v1 = MyClass()
- val v2 = MyClass()
- v1.other = v2
- v2.other = v1
- val name = MyClass::class.java.simpleName
- validate(factory.create(node, "mine", v1)!!) {
- parameter("mine", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name)
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- @Test
- fun testDoNotRecurseIntoAndroidAndJavaPackages() {
- runBlocking {
- assertThat(factory.create(node, "v1", java.net.URL("http://domain.com"))).isNull()
- assertThat(factory.create(node, "v1", android.app.Notification())).isNull()
- }
- }
-
- @Test
- fun testShadow() {
- assertThat(lookup(Shadow.None)).isEqualTo(ParameterType.String to "None")
- validate(factory.create(node, "shadow", Shadow(Color.Cyan, Offset.Zero, 2.5f))!!) {
- parameter("shadow", ParameterType.String, Shadow::class.java.simpleName) {
- parameter("blurRadius", ParameterType.DimensionDp, 1.25f)
- parameter("color", ParameterType.Color, Color.Cyan.toArgb())
- parameter("offset", ParameterType.String, "Zero")
- }
- }
- validate(factory.create(node, "shadow", Shadow(Color.Blue, Offset(1.0f, 4.0f), 1.5f))!!) {
- parameter("shadow", ParameterType.String, Shadow::class.java.simpleName) {
- parameter("blurRadius", ParameterType.DimensionDp, 0.75f)
- parameter("color", ParameterType.Color, Color.Blue.toArgb())
- parameter("offset", ParameterType.String, Offset::class.java.simpleName) {
- parameter("x", ParameterType.DimensionDp, 0.5f)
- parameter("y", ParameterType.DimensionDp, 2.0f)
- }
- }
- }
- }
-
- @Test
- fun testShape() {
- assertThat(lookup(RectangleShape)).isEqualTo(ParameterType.String to "RectangleShape")
- }
-
- @Test
- fun testString() {
- assertThat(lookup("Hello")).isEqualTo(ParameterType.String to "Hello")
- }
-
- @Test
- fun testTextDecoration() {
- assertThat(lookup(TextDecoration.None)).isEqualTo(ParameterType.String to "None")
- assertThat(lookup(TextDecoration.LineThrough))
- .isEqualTo(ParameterType.String to "LineThrough")
- assertThat(lookup(TextDecoration.Underline))
- .isEqualTo(ParameterType.String to "Underline")
- assertThat(lookup(TextDecoration.LineThrough + TextDecoration.Underline))
- .isEqualTo(ParameterType.String to "LineThrough+Underline")
- }
-
- @Test
- fun testTextGeometricTransform() {
- validate(factory.create(node, "transform", TextGeometricTransform(2.0f, 1.5f))!!) {
- parameter(
- "transform", ParameterType.String,
- TextGeometricTransform::class.java.simpleName
- ) {
- parameter("scaleX", ParameterType.Float, 2.0f)
- parameter("skewX", ParameterType.Float, 1.5f)
- }
- }
- }
-
- @Test
- fun testTextIndent() {
- assertThat(lookup(TextIndent.None)).isEqualTo(ParameterType.String to "None")
-
- validate(factory.create(node, "textIndent", TextIndent(4.0.sp, 0.5.sp))!!) {
- parameter("textIndent", ParameterType.String, "TextIndent") {
- parameter("firstLine", ParameterType.DimensionSp, 4.0f)
- parameter("restLine", ParameterType.DimensionSp, 0.5f)
- }
- }
- }
-
- @Test
- fun testTextStyle() {
- val style = TextStyle(
- color = Color.Red,
- textDecoration = TextDecoration.Underline
- )
- validate(factory.create(node, "style", style)!!) {
- parameter("style", ParameterType.String, TextStyle::class.java.simpleName) {
- parameter("background", ParameterType.String, "Unspecified")
- parameter("color", ParameterType.Color, Color.Red.toArgb())
- parameter("fontSize", ParameterType.String, "Unspecified")
- parameter("letterSpacing", ParameterType.String, "Unspecified")
- parameter("lineHeight", ParameterType.String, "Unspecified")
- parameter("textDecoration", ParameterType.String, "Underline")
- }
- }
- }
-
- @Test
- fun testTextUnit() {
- assertThat(lookup(TextUnit.Unspecified)).isEqualTo(ParameterType.String to "Unspecified")
- assertThat(lookup(12.0.sp)).isEqualTo(ParameterType.DimensionSp to 12.0f)
- assertThat(lookup(2.0.em)).isEqualTo(ParameterType.DimensionEm to 2.0f)
- assertThat(lookup(9.0f.sp)).isEqualTo(ParameterType.DimensionSp to 9.0f)
- assertThat(lookup(10.sp)).isEqualTo(ParameterType.DimensionSp to 10.0f)
- assertThat(lookup(26.0.sp)).isEqualTo(ParameterType.DimensionSp to 26.0f)
- assertThat(lookup(2.0f.em)).isEqualTo(ParameterType.DimensionEm to 2.0f)
- assertThat(lookup(1.em)).isEqualTo(ParameterType.DimensionEm to 1.0f)
- assertThat(lookup(3.0.em)).isEqualTo(ParameterType.DimensionEm to 3.0f)
- }
-
- @Test
- fun testVectorAssert() {
- assertThat(lookup(Icons.Filled.Call)).isEqualTo(ParameterType.String to "Filled.Call")
- assertThat(lookup(Icons.Rounded.Add)).isEqualTo(ParameterType.String to "Rounded.Add")
- }
-
- private fun lookup(value: Any): Pair<ParameterType, Any?>? {
- val parameter = factory.create(node, "property", value) ?: return null
- assertThat(parameter.elements).isEmpty()
- return Pair(parameter.type, parameter.value)
- }
-
- private fun validate(
- parameter: NodeParameter,
- expected: ParameterValidationReceiver.() -> Unit = {}
- ) {
- val elements = ParameterValidationReceiver(listOf(parameter).listIterator())
- elements.expected()
- }
-}
-
-private class TestPainter(
- val width: Float,
- val height: Float
-) : Painter() {
-
- var color = Color.Red
-
- override val intrinsicSize: Size
- get() = Size(width, height)
-
- override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
- color = if (layoutDirection == LayoutDirection.Rtl) Color.Blue else Color.Red
- return true
- }
-
- override fun DrawScope.onDraw() {
- drawRect(color = color)
- }
-}
-
-class ParameterValidationReceiver(val parameterIterator: Iterator<NodeParameter>) {
- fun parameter(
- name: String,
- type: ParameterType,
- value: Any?,
- block: ParameterValidationReceiver.() -> Unit = {}
- ) {
- assertWithMessage("No such element found: $name").that(parameterIterator.hasNext()).isTrue()
- val parameter = parameterIterator.next()
- assertThat(parameter.name).isEqualTo(name)
- assertWithMessage(name).that(parameter.type).isEqualTo(type)
- if (type != ParameterType.Lambda || value != null) {
- assertWithMessage(name).that(parameter.value).isEqualTo(value)
- }
- var elements: List<NodeParameter> = parameter.elements
- if (name != "modifier") {
- // Do not sort modifiers: the order is important
- elements = elements.sortedBy { it.name }
- }
- val children = ParameterValidationReceiver(elements.listIterator())
- children.block()
- if (children.parameterIterator.hasNext()) {
- val elementNames = mutableListOf<String>()
- while (children.parameterIterator.hasNext()) {
- elementNames.add(children.parameterIterator.next().name)
- }
- error("$name: has more elements like: ${elementNames.joinToString()}")
- }
- }
-}
-
-class MyClass {
- var other: MyClass? = null
-}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/SynthesizedLambdaNameTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/SynthesizedLambdaNameTest.kt
deleted file mode 100644
index 68c361e..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/SynthesizedLambdaNameTest.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2020 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.compose.ui.tooling.inspector
-
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-private val topLambda1 = {}
-private val topLambda2 = withArgument {}
-private val topLambda3 = withArguments({}, {})
-
-private fun withArgument(a: (Int) -> Unit = {}): (Int) -> Unit = a
-private fun withArguments(a1: () -> Unit = {}, a2: () -> Unit = {}): List<() -> Unit> =
- listOf(a1, a2)
-
-/**
- * Test the compiler generated lambda names.
- *
- * There is code in Studio that relies on this format.
- * If this test should start to fail, please check the LambdaResolver in the Layout Inspector.
- */
-@SmallTest
-@Suppress("JoinDeclarationAndAssignment")
-class SynthesizedLambdaNameTest {
- private val cls = SynthesizedLambdaNameTest::class.java.name
- private val memberLambda1 = {}
- private val memberLambda2 = withArgument {}
- private val memberLambda3 = withArguments({}, {})
- private val initLambda1: (Int) -> Unit
- private val initLambda2: (Int) -> Unit
- private val defaultLambda1 = withArgument()
- private val defaultLambda2 = withArguments()
-
- init {
- initLambda1 = withArgument {}
- initLambda2 = withArgument {}
- }
-
- @Test
- fun testSynthesizedNames() {
- assertThat(name(topLambda1)).isEqualTo("${cls}Kt\$topLambda1$1")
- assertThat(name(topLambda2)).isEqualTo("${cls}Kt\$topLambda2$1")
- assertThat(name(topLambda3[0])).isEqualTo("${cls}Kt\$topLambda3$1")
- assertThat(name(topLambda3[1])).isEqualTo("${cls}Kt\$topLambda3$2")
- assertThat(name(memberLambda1)).isEqualTo("$cls\$memberLambda1$1")
- assertThat(name(memberLambda2)).isEqualTo("$cls\$memberLambda2$1")
- assertThat(name(memberLambda3[0])).isEqualTo("$cls\$memberLambda3$1")
- assertThat(name(memberLambda3[1])).isEqualTo("$cls\$memberLambda3$2")
- assertThat(name(initLambda1)).isEqualTo("$cls$1")
- assertThat(name(initLambda2)).isEqualTo("$cls$2")
- assertThat(name(defaultLambda1)).isEqualTo("${cls}Kt\$withArgument$1")
- assertThat(name(defaultLambda2[0])).isEqualTo("${cls}Kt\$withArguments$1")
- assertThat(name(defaultLambda2[1])).isEqualTo("${cls}Kt\$withArguments$2")
- }
-
- private fun name(lambda: Any) = lambda.javaClass.name
-}
\ No newline at end of file
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InlineClassConverter.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InlineClassConverter.kt
deleted file mode 100644
index e80975f..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InlineClassConverter.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2020 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.compose.ui.tooling.inspector
-
-/**
- * Converter for casting a parameter represented by its primitive value to its inline class type.
- *
- * For example: an androidx.compose.ui.graphics.Color instance is often represented by a long
- */
-internal class InlineClassConverter {
- // Map from inline type name to inline class and conversion lambda
- private val typeMap = mutableMapOf<String, (Any) -> Any>()
- // Return value used in functions
- private val notInlineType: (Any) -> Any = { it }
-
- /**
- * Clear any cached data.
- */
- fun clear() {
- typeMap.clear()
- }
-
- /**
- * Cast the specified [value] to a value of type [inlineClassName] if possible.
- *
- * @param inlineClassName the fully qualified name of the inline class.
- * @param value the value to convert to an instance of [inlineClassName].
- */
- fun castParameterValue(inlineClassName: String?, value: Any?): Any? =
- if (value != null && inlineClassName != null)
- typeMapperFor(inlineClassName)(value) else value
-
- private fun typeMapperFor(typeName: String): (Any) -> (Any) =
- typeMap.getOrPut(typeName) { loadTypeMapper(typeName.replace('.', '/')) }
-
- private fun loadTypeMapper(className: String): (Any) -> Any {
- val javaClass = loadClassOrNull(className) ?: return notInlineType
- val create = javaClass.declaredConstructors.singleOrNull() ?: return notInlineType
- create.isAccessible = true
- return { value -> create.newInstance(value) }
- }
-
- private fun loadClassOrNull(className: String): Class<*>? =
- try {
- javaClass.classLoader!!.loadClass(className)
- } catch (ex: Exception) {
- null
- }
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt
deleted file mode 100644
index ae66150..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright 2020 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.compose.ui.tooling.inspector
-
-import androidx.compose.ui.layout.LayoutInfo
-
-private val EmptyIntArray = IntArray(0)
-
-/**
- * Node representing a Composable for the Layout Inspector.
- */
-class InspectorNode internal constructor(
- /**
- * The associated render node id or 0.
- */
- val id: Long,
-
- /**
- * The name of the Composable.
- */
- val name: String,
-
- /**
- * The fileName where the Composable was called.
- */
- val fileName: String,
-
- /**
- * A hash of the package name to help disambiguate duplicate [fileName] values.
- *
- * This hash is calculated by,
- *
- * `packageName.fold(0) { hash, current -> hash * 31 + current.toInt() }?.absoluteValue`
- *
- * where the package name is the dotted name of the package. This can be used to disambiguate
- * which file is referenced by [fileName]. This number is -1 if there was no package hash
- * information generated such as when the file does not contain a package declaration.
- */
- val packageHash: Int,
-
- /**
- * The line number where the Composable was called.
- */
- val lineNumber: Int,
-
- /**
- * The UTF-16 offset in the file where the Composable was called
- */
- val offset: Int,
-
- /**
- * The number of UTF-16 code point comprise the Composable call
- */
- val length: Int,
-
- /**
- * Left side of the Composable in pixels.
- */
- val left: Int,
-
- /**
- * Top of the Composable in pixels.
- */
- val top: Int,
-
- /**
- * Width of the Composable in pixels.
- */
- val width: Int,
-
- /**
- * Width of the Composable in pixels.
- */
- val height: Int,
-
- /**
- * The 4 corners of the node after modifier transformations.
- * If there are no coordinate transforms the array will be empty.
- * Otherwise the content will be 8 integers representing 4 (x,y) corners
- * in clockwise order of the original, untransformed coordinates.
- */
- val bounds: IntArray,
-
- /**
- * The parameters of this Composable.
- */
- val parameters: List<RawParameter>,
-
- /**
- * The children nodes of this Composable.
- */
- val children: List<InspectorNode>
-)
-
-/**
- * Parameter definition with a raw value reference.
- */
-class RawParameter(val name: String, val value: Any?)
-
-/**
- * Mutable version of [InspectorNode].
- */
-internal class MutableInspectorNode {
- var id = 0L
- var layoutNodes = mutableListOf<LayoutInfo>()
- var name = ""
- var fileName = ""
- var packageHash = -1
- var lineNumber = 0
- var offset = 0
- var length = 0
- var left = 0
- var top = 0
- var width = 0
- var height = 0
- var bounds = EmptyIntArray
- val parameters = mutableListOf<RawParameter>()
- val children = mutableListOf<InspectorNode>()
-
- fun reset() {
- markUnwanted()
- id = 0L
- left = 0
- top = 0
- width = 0
- height = 0
- layoutNodes.clear()
- bounds = EmptyIntArray
- children.clear()
- }
-
- fun markUnwanted() {
- name = ""
- fileName = ""
- packageHash = -1
- lineNumber = 0
- offset = 0
- length = 0
- parameters.clear()
- }
-
- fun shallowCopy(node: InspectorNode): MutableInspectorNode = apply {
- id = node.id
- name = node.name
- fileName = node.fileName
- packageHash = node.packageHash
- lineNumber = node.lineNumber
- offset = node.offset
- length = node.length
- left = node.left
- top = node.top
- width = node.width
- height = node.height
- bounds = node.bounds
- parameters.addAll(node.parameters)
- children.addAll(node.children)
- }
-
- fun build(): InspectorNode =
- InspectorNode(
- id, name, fileName, packageHash, lineNumber, offset, length, left, top, width, height,
- bounds, parameters.toList(), children.toList()
- )
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt
deleted file mode 100644
index 85f0933..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright 2020 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.compose.ui.tooling.inspector
-
-import android.view.View
-import androidx.compose.runtime.tooling.CompositionData
-import androidx.compose.runtime.InternalComposeApi
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.layout.GraphicLayerInfo
-import androidx.compose.ui.layout.LayoutInfo
-import androidx.compose.ui.tooling.R
-import androidx.compose.ui.tooling.data.Group
-import androidx.compose.ui.tooling.data.NodeGroup
-import androidx.compose.ui.tooling.data.ParameterInformation
-import androidx.compose.ui.tooling.data.UiToolingDataApi
-import androidx.compose.ui.tooling.data.asTree
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.toSize
-import java.util.ArrayDeque
-import java.util.Collections
-import java.util.IdentityHashMap
-import kotlin.math.absoluteValue
-import kotlin.math.roundToInt
-
-private val systemPackages = setOf(
- -1,
- packageNameHash("androidx.compose.animation"),
- packageNameHash("androidx.compose.animation.core"),
- packageNameHash("androidx.compose.desktop"),
- packageNameHash("androidx.compose.foundation"),
- packageNameHash("androidx.compose.foundation.layout"),
- packageNameHash("androidx.compose.foundation.text"),
- packageNameHash("androidx.compose.material"),
- packageNameHash("androidx.compose.material.ripple"),
- packageNameHash("androidx.compose.runtime"),
- packageNameHash("androidx.compose.ui"),
- packageNameHash("androidx.compose.ui.layout"),
- packageNameHash("androidx.compose.ui.platform"),
- packageNameHash("androidx.compose.ui.tooling"),
- packageNameHash("androidx.compose.ui.selection"),
- packageNameHash("androidx.compose.ui.semantics"),
- packageNameHash("androidx.compose.ui.viewinterop"),
- packageNameHash("androidx.compose.ui.window"),
-)
-
-private val unwantedCalls = setOf(
- "emit",
- "remember",
- "CompositionLocalProvider",
- "Content",
- "Inspectable",
- "ProvideAndroidCompositionLocals",
- "ProvideCommonCompositionLocals",
-)
-
-private fun packageNameHash(packageName: String) =
- packageName.fold(0) { hash, char -> hash * 31 + char.toInt() }.absoluteValue
-
-/**
- * Generator of a tree for the Layout Inspector.
- */
-class LayoutInspectorTree {
- @Suppress("MemberVisibilityCanBePrivate")
- var hideSystemNodes = true
- private val inlineClassConverter = InlineClassConverter()
- private val parameterFactory = ParameterFactory(inlineClassConverter)
- private val cache = ArrayDeque<MutableInspectorNode>()
- private var generatedId = -1L
- /** Map from [LayoutInfo] to the nearest [InspectorNode] that contains it */
- private val claimedNodes = IdentityHashMap<LayoutInfo, InspectorNode>()
- /** Map from parent tree to child trees that are about to be stitched together */
- private val treeMap = IdentityHashMap<MutableInspectorNode, MutableList<MutableInspectorNode>>()
- /** Map from owner node to child trees that are about to be stitched to this owner */
- private val ownerMap = IdentityHashMap<InspectorNode, MutableList<MutableInspectorNode>>()
- /** Set of tree nodes that were stitched into another tree */
- private val stitched =
- Collections.newSetFromMap(IdentityHashMap<MutableInspectorNode, Boolean>())
-
- /**
- * Converts the [CompositionData] set held by [view] into a list of root nodes.
- */
- @OptIn(InternalComposeApi::class)
- fun convert(view: View): List<InspectorNode> {
- parameterFactory.density = Density(view.context)
- @Suppress("UNCHECKED_CAST")
- val tables = view.getTag(R.id.inspection_slot_table_set) as? Set<CompositionData>
- ?: return emptyList()
- clear()
- val result = convert(tables)
- clear()
- return result
- }
-
- /**
- * Converts the [RawParameter]s of the [node] into displayable parameters.
- */
- fun convertParameters(node: InspectorNode): List<NodeParameter> {
- return node.parameters.mapNotNull { parameterFactory.create(node, it.name, it.value) }
- }
-
- /**
- * Reset the generated id. Nodes are assigned an id if there isn't a layout node id present.
- */
- @Suppress("unused")
- fun resetGeneratedId() {
- generatedId = -1L
- }
-
- private fun clear() {
- cache.clear()
- inlineClassConverter.clear()
- claimedNodes.clear()
- treeMap.clear()
- ownerMap.clear()
- stitched.clear()
- }
-
- @OptIn(InternalComposeApi::class)
- private fun convert(tables: Set<CompositionData>): List<InspectorNode> {
- val trees = tables.map { convert(it) }
- return when (trees.size) {
- 0 -> listOf()
- 1 -> addTree(mutableListOf(), trees.single())
- else -> stitchTreesByLayoutInfo(trees)
- }
- }
-
- /**
- * Stitch separate trees together using the [LayoutInfo]s found in the [CompositionData]s.
- *
- * Some constructs in Compose (e.g. ModalDrawer) will result is multiple
- * [CompositionData]s. This code will attempt to stitch the resulting [InspectorNode] trees
- * together by looking at the parent of each [LayoutInfo].
- *
- * If this algorithm is successful the result of this function will be a list with a single
- * tree.
- */
- private fun stitchTreesByLayoutInfo(trees: List<MutableInspectorNode>): List<InspectorNode> {
- val layoutToTreeMap = IdentityHashMap<LayoutInfo, MutableInspectorNode>()
- trees.forEach { tree -> tree.layoutNodes.forEach { layoutToTreeMap[it] = tree } }
- trees.forEach { tree ->
- val layout = tree.layoutNodes.lastOrNull()
- val parentLayout = generateSequence(layout) { it.parentInfo }.firstOrNull {
- val otherTree = layoutToTreeMap[it]
- otherTree != null && otherTree != tree
- }
- if (parentLayout != null) {
- val ownerNode = claimedNodes[parentLayout]
- val ownerTree = layoutToTreeMap[parentLayout]
- if (ownerNode != null && ownerTree != null) {
- ownerMap.getOrPut(ownerNode) { mutableListOf() }.add(tree)
- treeMap.getOrPut(ownerTree) { mutableListOf() }.add(tree)
- }
- }
- }
- var parentTree = findDeepParentTree()
- while (parentTree != null) {
- addSubTrees(parentTree)
- treeMap.remove(parentTree)
- parentTree = findDeepParentTree()
- }
- val result = mutableListOf<InspectorNode>()
- trees.asSequence().filter { !stitched.contains(it) }.forEach { addTree(result, it) }
- return result
- }
-
- /**
- * Return a parent tree where the children trees (to be stitched under the parent) are not
- * a parent themselves. Do this to avoid rebuilding the same tree more than once.
- */
- private fun findDeepParentTree(): MutableInspectorNode? =
- treeMap.entries.asSequence()
- .filter { (_, children) -> children.none { treeMap.containsKey(it) } }
- .firstOrNull()?.key
-
- private fun addSubTrees(tree: MutableInspectorNode) {
- for ((index, child) in tree.children.withIndex()) {
- tree.children[index] = addSubTrees(child) ?: child
- }
- }
-
- /**
- * Rebuild [node] with any possible sub trees added (stitched in).
- * Return the rebuild node, or null if no changes were found in this node or its children.
- * Lazily allocate the new node to avoid unnecessary allocations.
- */
- private fun addSubTrees(node: InspectorNode): InspectorNode? {
- var newNode: MutableInspectorNode? = null
- for ((index, child) in node.children.withIndex()) {
- val newChild = addSubTrees(child)
- if (newChild != null) {
- val newCopy = newNode ?: newNode(node)
- newCopy.children[index] = newChild
- newNode = newCopy
- }
- }
- val trees = ownerMap[node]
- if (trees == null && newNode == null) {
- return null
- }
- val newCopy = newNode ?: newNode(node)
- if (trees != null) {
- trees.forEach { addTree(newCopy.children, it) }
- stitched.addAll(trees)
- }
- return buildAndRelease(newCopy)
- }
-
- /**
- * Add [tree] to the end of the [out] list.
- * The root nodes of [tree] may be a fake node that hold a list of [LayoutInfo].
- */
- private fun addTree(
- out: MutableList<InspectorNode>,
- tree: MutableInspectorNode
- ): List<InspectorNode> {
- tree.children.forEach {
- if (it.name.isNotEmpty()) {
- out.add(it)
- } else {
- out.addAll(it.children)
- }
- }
- return out
- }
-
- @OptIn(InternalComposeApi::class, UiToolingDataApi::class)
- private fun convert(table: CompositionData): MutableInspectorNode {
- val fakeParent = newNode()
- addToParent(fakeParent, listOf(convert(table.asTree())), buildFakeChildNodes = true)
- return fakeParent
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun convert(group: Group): MutableInspectorNode {
- val children = convertChildren(group)
- val parent = parse(group)
- addToParent(parent, children)
- return parent
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun convertChildren(group: Group): List<MutableInspectorNode> {
- if (group.children.isEmpty()) {
- return emptyList()
- }
- val result = mutableListOf<MutableInspectorNode>()
- for (child in group.children) {
- val node = convert(child)
- if (node.name.isNotEmpty() || node.children.isNotEmpty() ||
- node.id != 0L || node.layoutNodes.isNotEmpty()
- ) {
- result.add(node)
- } else {
- release(node)
- }
- }
- return result
- }
-
- /**
- * Adds the nodes in [input] to the children of [parentNode].
- * Nodes without a reference to a wanted Composable are skipped unless [buildFakeChildNodes].
- * A single skipped render id and layoutNode will be added to [parentNode].
- */
- private fun addToParent(
- parentNode: MutableInspectorNode,
- input: List<MutableInspectorNode>,
- buildFakeChildNodes: Boolean = false
- ) {
- var id: Long? = null
- input.forEach { node ->
- if (node.name.isEmpty() && !(buildFakeChildNodes && node.layoutNodes.isNotEmpty())) {
- parentNode.children.addAll(node.children)
- if (node.id != 0L) {
- // If multiple siblings with a render ids are dropped:
- // Ignore them all. And delegate the drawing to a parent in the inspector.
- id = if (id == null) node.id else 0L
- }
- } else {
- node.id = if (node.id != 0L) node.id else --generatedId
- val resultNode = node.build()
- // TODO: replace getOrPut with putIfAbsent which requires API level 24
- node.layoutNodes.forEach { claimedNodes.getOrPut(it) { resultNode } }
- parentNode.children.add(resultNode)
- }
- if (node.bounds.isNotEmpty() && sameBoundingRectangle(parentNode, node)) {
- parentNode.bounds = node.bounds
- }
- parentNode.layoutNodes.addAll(node.layoutNodes)
- release(node)
- }
- val nodeId = id
- parentNode.id = if (parentNode.id == 0L && nodeId != null) nodeId else parentNode.id
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun parse(group: Group): MutableInspectorNode {
- val node = newNode()
- node.id = getRenderNode(group)
- parsePosition(group, node)
- parseLayoutInfo(group, node)
- if (node.height <= 0 && node.width <= 0) {
- return markUnwanted(node)
- }
- if (!parseCallLocation(group, node) && group.name.isNullOrEmpty()) {
- return markUnwanted(node)
- }
- group.name?.let { node.name = it }
- if (unwantedGroup(node)) {
- return markUnwanted(node)
- }
- addParameters(group.parameters, node)
- return node
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun parsePosition(group: Group, node: MutableInspectorNode) {
- val box = group.box
- node.top = box.top
- node.left = box.left
- node.height = box.bottom - box.top
- node.width = box.right - box.left
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun parseLayoutInfo(group: Group, node: MutableInspectorNode) {
- val layoutInfo = (group as? NodeGroup)?.node as? LayoutInfo ?: return
- node.layoutNodes.add(layoutInfo)
- val box = group.box
- val size = box.size.toSize()
- val coordinates = layoutInfo.coordinates
- val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
- val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
- val bottomRight = toIntOffset(coordinates.localToWindow(Offset(size.width, size.height)))
- val bottomLeft = toIntOffset(coordinates.localToWindow(Offset(0f, size.height)))
- if (
- topLeft.x == box.left && topLeft.y == box.top &&
- topRight.x == box.right && topRight.y == box.top &&
- bottomRight.x == box.right && bottomRight.y == box.bottom &&
- bottomLeft.x == box.left && bottomLeft.y == box.bottom
- ) {
- return
- }
- node.bounds = intArrayOf(
- topLeft.x, topLeft.y,
- topRight.x, topRight.y,
- bottomRight.x, bottomRight.y,
- bottomLeft.x, bottomLeft.y
- )
- }
-
- private fun toIntOffset(offset: Offset): IntOffset =
- IntOffset(offset.x.roundToInt(), offset.y.roundToInt())
-
- private fun markUnwanted(node: MutableInspectorNode): MutableInspectorNode =
- node.apply { markUnwanted() }
-
- @OptIn(UiToolingDataApi::class)
- private fun parseCallLocation(group: Group, node: MutableInspectorNode): Boolean {
- val location = group.location ?: return false
- val fileName = location.sourceFile ?: return false
- node.fileName = fileName
- node.packageHash = location.packageHash
- node.lineNumber = location.lineNumber
- node.offset = location.offset
- node.length = location.length
- return true
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun getRenderNode(group: Group): Long =
- group.modifierInfo.asSequence()
- .map { it.extra }
- .filterIsInstance<GraphicLayerInfo>()
- .map { it.layerId }
- .firstOrNull() ?: 0
-
- @OptIn(UiToolingDataApi::class)
- private fun addParameters(parameters: List<ParameterInformation>, node: MutableInspectorNode) =
- parameters.forEach { addParameter(it, node) }
-
- @OptIn(UiToolingDataApi::class)
- private fun addParameter(parameter: ParameterInformation, node: MutableInspectorNode) {
- val castedValue = castValue(parameter)
- node.parameters.add(RawParameter(parameter.name, castedValue))
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun castValue(parameter: ParameterInformation): Any? {
- val value = parameter.value ?: return null
- if (parameter.inlineClass == null) return value
- return inlineClassConverter.castParameterValue(parameter.inlineClass, value)
- }
-
- private fun unwantedGroup(node: MutableInspectorNode): Boolean =
- node.packageHash in systemPackages && (hideSystemNodes || node.name in unwantedCalls)
-
- private fun newNode(): MutableInspectorNode =
- if (cache.isNotEmpty()) cache.pop() else MutableInspectorNode()
-
- private fun newNode(copyFrom: InspectorNode): MutableInspectorNode =
- newNode().shallowCopy(copyFrom)
-
- private fun release(node: MutableInspectorNode) {
- node.reset()
- cache.add(node)
- }
-
- private fun buildAndRelease(node: MutableInspectorNode): InspectorNode {
- val result = node.build()
- release(node)
- return result
- }
-
- private fun sameBoundingRectangle(
- node1: MutableInspectorNode,
- node2: MutableInspectorNode
- ): Boolean =
- node1.left == node2.left &&
- node1.top == node2.top &&
- node1.width == node2.width &&
- node1.height == node2.height
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/NodeParameter.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/NodeParameter.kt
deleted file mode 100644
index 86bd7ad..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/NodeParameter.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2020 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.compose.ui.tooling.inspector
-
-/**
- * Holds data representing a Composable parameter for the Layout Inspector.
- */
-class NodeParameter internal constructor(
- /**
- * The name of the parameter.
- */
- val name: String,
-
- /**
- * The type of the parameter.
- */
- val type: ParameterType,
-
- /**
- * The value of the parameter.
- */
- val value: Any?
-) {
- /**
- * Sub elements of the parameter.
- */
- val elements = mutableListOf<NodeParameter>()
-}
-
-/**
- * The type of a parameter.
- */
-enum class ParameterType {
- String,
- Boolean,
- Double,
- Float,
- Int32,
- Int64,
- Color,
- Resource,
- DimensionDp,
- DimensionSp,
- DimensionEm,
- Lambda,
- FunctionReference,
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt
deleted file mode 100644
index 0e935b5..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt
+++ /dev/null
@@ -1,481 +0,0 @@
-/*
- * Copyright 2020 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.compose.ui.tooling.inspector
-
-import android.util.Log
-import android.view.View
-import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.runtime.internal.ComposableLambda
-import androidx.compose.ui.AbsoluteAlignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.platform.InspectableValue
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.font.FontListFontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.font.ResourceFont
-import androidx.compose.ui.text.intl.Locale
-import androidx.compose.ui.text.intl.LocaleList
-import androidx.compose.ui.text.style.BaselineShift
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.tooling.inspector.ParameterType.DimensionDp
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.TextUnitType
-import java.lang.reflect.Field
-import kotlin.jvm.internal.FunctionReference
-import kotlin.jvm.internal.Lambda
-import kotlin.math.abs
-import kotlin.reflect.KClass
-import kotlin.reflect.KProperty1
-import kotlin.reflect.full.allSuperclasses
-import kotlin.reflect.full.declaredMemberProperties
-import kotlin.reflect.jvm.isAccessible
-import kotlin.reflect.jvm.javaField
-import kotlin.reflect.jvm.javaGetter
-import java.lang.reflect.Modifier as JavaModifier
-
-private const val MAX_RECURSIONS = 10
-private const val MAX_ITERABLE = 25
-
-/**
- * Factory of [NodeParameter]s.
- *
- * Each parameter value is converted to a user readable value.
- */
-internal class ParameterFactory(private val inlineClassConverter: InlineClassConverter) {
- /**
- * A map from known values to a user readable string representation.
- */
- private val valueLookup = mutableMapOf<Any, String>()
-
- /**
- * The classes we have loaded constants from.
- */
- private val valuesLoaded = mutableSetOf<Class<*>>()
-
- /**
- * Do not load constant names from instances of these classes.
- * We prefer showing the raw values of Color and Dimensions.
- */
- private val ignoredClasses = listOf(Color::class.java, Dp::class.java)
- private var creatorCache: ParameterCreator? = null
- private val kotlinReflectionSupported = try {
- Class.forName("kotlin.reflect.full.KClasses")
- true
- } catch (ex: Exception) {
- false
- }
-
- /**
- * Do not decompose instances or lookup constants from these package prefixes
- *
- * The following instances are known to contain self recursion:
- * - kotlinx.coroutines.flow.StateFlowImpl
- * - androidx.compose.ui.node.LayoutNode
- */
- private val ignoredPackagePrefixes = listOf(
- "android.", "java.", "javax.", "kotlinx.", "androidx.compose.ui.node."
- )
-
- var density = Density(1.0f)
-
- init {
- val textDecorationCombination = TextDecoration.combine(
- listOf(TextDecoration.LineThrough, TextDecoration.Underline)
- )
- valueLookup[textDecorationCombination] = "LineThrough+Underline"
- valueLookup[Color.Unspecified] = "Unspecified"
- valueLookup[RectangleShape] = "RectangleShape"
- valuesLoaded.add(Enum::class.java)
- valuesLoaded.add(Any::class.java)
-
- // AbsoluteAlignment is not found from an instance of BiasAbsoluteAlignment,
- // because Alignment has no file level class.
- loadConstantsFromEnclosedClasses(AbsoluteAlignment::class.java)
- }
-
- /**
- * Create a [NodeParameter] from the specified parameter [name] and [value].
- *
- * Attempt to convert the value to a user readable value.
- * For now: return null when a conversion is not possible/found.
- */
- fun create(node: InspectorNode, name: String, value: Any?): NodeParameter? {
- val creator = creatorCache ?: ParameterCreator()
- try {
- return creator.create(node, name, value)
- } finally {
- creatorCache = creator
- }
- }
-
- private fun loadConstantsFrom(javaClass: Class<*>) {
- if (valuesLoaded.contains(javaClass) ||
- ignoredPackagePrefixes.any { javaClass.name.startsWith(it) }
- ) {
- return
- }
- val related = generateSequence(javaClass) { it.superclass }.plus(javaClass.interfaces)
- related.forEach { aClass ->
- val topClass = generateSequence(aClass) { safeEnclosingClass(it) }.last()
- loadConstantsFromEnclosedClasses(topClass)
- findPackageLevelClass(topClass)?.let { loadConstantsFromStaticFinal(it) }
- }
- }
-
- private fun safeEnclosingClass(klass: Class<*>): Class<*>? = try {
- klass.enclosingClass
- } catch (_: Error) {
- // Exceptions seen on API 23...
- null
- }
-
- private fun findPackageLevelClass(javaClass: Class<*>): Class<*>? = try {
- // Note: This doesn't work when @file.JvmName is specified
- Class.forName("${javaClass.name}Kt")
- } catch (ex: Throwable) {
- null
- }
-
- private fun loadConstantsFromEnclosedClasses(javaClass: Class<*>) {
- if (valuesLoaded.contains(javaClass)) {
- return
- }
- loadConstantsFromObjectInstance(javaClass.kotlin)
- loadConstantsFromStaticFinal(javaClass)
- valuesLoaded.add(javaClass)
- javaClass.declaredClasses.forEach { loadConstantsFromEnclosedClasses(it) }
- }
-
- /**
- * Load all constants from companion objects and singletons
- *
- * Exclude: primary types and types of ignoredClasses, open and lateinit vals.
- */
- private fun loadConstantsFromObjectInstance(kClass: KClass<*>) {
- try {
- val instance = kClass.objectInstance ?: return
- kClass.declaredMemberProperties.asSequence()
- .filter { it.isFinal && !it.isLateinit }
- .mapNotNull { constantValueOf(it, instance)?.let { key -> Pair(key, it.name) } }
- .filter { !ignoredValue(it.first) }
- .toMap(valueLookup)
- } catch (_: Throwable) {
- // KT-16479 : kotlin reflection does currently not support packages and files.
- // We load top level values using Java reflection instead.
- // Ignore other reflection errors as well
- }
- }
-
- /**
- * Load all constants from top level values from Java.
- *
- * Exclude: primary types and types of ignoredClasses.
- * Since this is Java, inline types will also (unfortunately) be excluded.
- */
- private fun loadConstantsFromStaticFinal(javaClass: Class<*>) {
- try {
- javaClass.declaredMethods.asSequence()
- .filter {
- it.returnType != Void.TYPE &&
- JavaModifier.isStatic(it.modifiers) &&
- JavaModifier.isFinal(it.modifiers) &&
- !it.returnType.isPrimitive &&
- it.parameterTypes.isEmpty() &&
- it.name.startsWith("get")
- }
- .mapNotNull { javaClass.getDeclaredField(it.name.substring(3)) }
- .mapNotNull { constantValueOf(it)?.let { key -> Pair(key, it.name) } }
- .filter { !ignoredValue(it.first) }
- .toMap(valueLookup)
- } catch (_: ReflectiveOperationException) {
- // ignore reflection errors
- } catch (_: NoClassDefFoundError) {
- // ignore missing classes on lower level SDKs
- }
- }
-
- private fun constantValueOf(field: Field?): Any? = try {
- field?.isAccessible = true
- field?.get(null)
- } catch (_: ReflectiveOperationException) {
- // ignore reflection errors
- null
- }
-
- private fun constantValueOf(property: KProperty1<out Any, *>, instance: Any): Any? = try {
- val field = property.javaField
- field?.isAccessible = true
- inlineClassConverter.castParameterValue(inlineResultClass(property), field?.get(instance))
- } catch (_: ReflectiveOperationException) {
- // ignore reflection errors
- null
- }
-
- private fun inlineResultClass(property: KProperty1<out Any, *>): String? {
- // The Java getter name will be mangled if it contains parameters of an inline class.
- // The mangled part starts with a '-'.
- if (property.javaGetter?.name?.contains('-') == true) {
- return property.returnType.toString()
- }
- return null
- }
-
- private fun ignoredValue(value: Any?): Boolean =
- value == null ||
- ignoredClasses.any { ignored -> ignored.isInstance(value) } ||
- value::class.java.isPrimitive
-
- /**
- * Convenience class for building [NodeParameter]s.
- */
- private inner class ParameterCreator {
- private var node: InspectorNode? = null
- private var recursions = 0
-
- fun create(node: InspectorNode, name: String, value: Any?): NodeParameter? = try {
- this.node = node
- recursions = 0
- create(name, value)
- } finally {
- this.node = null
- }
-
- private fun create(name: String, value: Any?): NodeParameter? {
- if (value == null || recursions >= MAX_RECURSIONS) {
- return null
- }
- try {
- recursions++
- createFromConstant(name, value)?.let { return it }
- return when (value) {
- is AnnotatedString -> NodeParameter(name, ParameterType.String, value.text)
- is BaselineShift -> createFromBaselineShift(name, value)
- is Boolean -> NodeParameter(name, ParameterType.Boolean, value)
- is ComposableLambda -> createFromCLambda(name, value)
- is Color -> NodeParameter(name, ParameterType.Color, value.toArgb())
- is CornerSize -> createFromCornerSize(name, value)
- is Double -> NodeParameter(name, ParameterType.Double, value)
- is Dp -> NodeParameter(name, DimensionDp, value.value)
- is Enum<*> -> NodeParameter(name, ParameterType.String, value.toString())
- is Float -> NodeParameter(name, ParameterType.Float, value)
- is FunctionReference -> NodeParameter(
- name, ParameterType.FunctionReference, arrayOf<Any>(value, value.name)
- )
- is FontListFontFamily -> createFromFontListFamily(name, value)
- is FontWeight -> NodeParameter(name, ParameterType.Int32, value.weight)
- is Modifier -> createFromModifier(name, value)
- is InspectableValue -> createFromInspectableValue(name, value)
- is Int -> NodeParameter(name, ParameterType.Int32, value)
- is Iterable<*> -> createFromIterable(name, value)
- is Lambda<*> -> createFromLambda(name, value)
- is Locale -> NodeParameter(name, ParameterType.String, value.toString())
- is LocaleList ->
- NodeParameter(name, ParameterType.String, value.localeList.joinToString())
- is Long -> NodeParameter(name, ParameterType.Int64, value)
- is Offset -> createFromOffset(name, value)
- is Shadow -> createFromShadow(name, value)
- is SolidColor -> NodeParameter(name, ParameterType.Color, value.value.toArgb())
- is String -> NodeParameter(name, ParameterType.String, value)
- is TextUnit -> createFromTextUnit(name, value)
- is ImageVector -> createFromImageVector(name, value)
- is View -> NodeParameter(name, ParameterType.String, value.javaClass.simpleName)
- else -> createFromKotlinReflection(name, value)
- }
- } finally {
- recursions--
- }
- }
-
- private fun createFromBaselineShift(name: String, value: BaselineShift): NodeParameter {
- val converted = when (value.multiplier) {
- BaselineShift.None.multiplier -> "None"
- BaselineShift.Subscript.multiplier -> "Subscript"
- BaselineShift.Superscript.multiplier -> "Superscript"
- else -> return NodeParameter(name, ParameterType.Float, value.multiplier)
- }
- return NodeParameter(name, ParameterType.String, converted)
- }
-
- private fun createFromCLambda(name: String, value: ComposableLambda): NodeParameter? = try {
- val lambda = value.javaClass.getDeclaredField("_block")
- .apply { isAccessible = true }
- .get(value)
- NodeParameter(name, ParameterType.Lambda, arrayOf<Any?>(lambda))
- } catch (_: Throwable) {
- null
- }
-
- private fun createFromConstant(name: String, value: Any): NodeParameter? {
- if (!kotlinReflectionSupported) {
- return null
- }
- loadConstantsFrom(value.javaClass)
- return valueLookup[value]?.let { NodeParameter(name, ParameterType.String, it) }
- }
-
- private fun createFromCornerSize(name: String, value: CornerSize): NodeParameter {
- val size = Size(node!!.width.toFloat(), node!!.height.toFloat())
- val pixels = value.toPx(size, density)
- return NodeParameter(name, DimensionDp, with(density) { pixels.toDp().value })
- }
-
- // For now: select ResourceFontFont closest to W400 and Normal, and return the resId
- private fun createFromFontListFamily(
- name: String,
- value: FontListFontFamily
- ): NodeParameter? =
- findBestResourceFont(value)?.let {
- NodeParameter(name, ParameterType.Resource, it.resId)
- }
-
- private fun createFromKotlinReflection(name: String, value: Any): NodeParameter? {
- val kClass = value::class
- val qualifiedName = kClass.qualifiedName
- if (kClass.simpleName == null ||
- qualifiedName == null ||
- ignoredPackagePrefixes.any { qualifiedName.startsWith(it) } ||
- !kotlinReflectionSupported
- ) {
- // Exit without creating a parameter for:
- // - internal synthetic classes
- // - certain android packages
- // - if kotlin reflection library not available
- return null
- }
- val parameter = NodeParameter(name, ParameterType.String, kClass.simpleName)
- val properties = mutableMapOf<String, KProperty1<Any, *>>()
- try {
- sequenceOf(kClass).plus(kClass.allSuperclasses.asSequence())
- .flatMap { it.declaredMemberProperties.asSequence() }
- .filterIsInstance<KProperty1<Any, *>>()
- .associateByTo(properties) { it.name }
- } catch (ex: Throwable) {
- Log.w("Compose", "Could not decompose ${kClass.simpleName}")
- return parameter
- }
- properties.values.mapNotNullTo(parameter.elements) {
- create(it.name, valueOf(it, value))
- }
- return parameter
- }
-
- private fun valueOf(property: KProperty1<Any, *>, instance: Any): Any? = try {
- property.isAccessible = true
- // Bug in kotlin reflection API: if the type is a nullable inline type with a null
- // value, we get an IllegalArgumentException in this line:
- property.get(instance)
- } catch (ex: Throwable) {
- // TODO: Remove this warning since this is expected with nullable inline types
- Log.w("Compose", "Could not get value of ${property.name}")
- null
- }
-
- private fun createFromInspectableValue(
- name: String,
- value: InspectableValue
- ): NodeParameter {
- val tempValue = value.valueOverride ?: ""
- val parameterName = name.ifEmpty { value.nameFallback } ?: "element"
- val parameterValue = if (tempValue is InspectableValue) "" else tempValue
- val parameter = create(parameterName, parameterValue)
- ?: NodeParameter(parameterName, ParameterType.String, "")
- val elements = parameter.elements
- value.inspectableElements.mapNotNullTo(elements) { create(it.name, it.value) }
- return parameter
- }
-
- private fun createFromIterable(name: String, value: Iterable<*>): NodeParameter {
- val parameter = NodeParameter(name, ParameterType.String, "")
- val elements = parameter.elements
- value.asSequence()
- .mapNotNull { create(elements.size.toString(), it) }
- .takeWhile { elements.size < MAX_ITERABLE }
- .toCollection(elements)
- return parameter
- }
-
- private fun createFromLambda(name: String, value: Lambda<*>): NodeParameter =
- NodeParameter(name, ParameterType.Lambda, arrayOf<Any>(value))
-
- private fun createFromModifier(name: String, value: Modifier): NodeParameter? =
- when {
- name.isNotEmpty() -> {
- val parameter = NodeParameter(name, ParameterType.String, "")
- val elements = parameter.elements
- value.foldIn(elements) { acc, m ->
- create("", m)?.let { param -> acc.apply { add(param) } } ?: acc
- }
- parameter
- }
- value is InspectableValue -> createFromInspectableValue(name, value)
- else -> null
- }
-
- private fun createFromOffset(name: String, value: Offset): NodeParameter {
- val parameter = NodeParameter(name, ParameterType.String, Offset::class.java.simpleName)
- val elements = parameter.elements
- elements.add(NodeParameter("x", DimensionDp, with(density) { value.x.toDp().value }))
- elements.add(NodeParameter("y", DimensionDp, with(density) { value.y.toDp().value }))
- return parameter
- }
-
- // Special handling of blurRadius: convert to dp:
- private fun createFromShadow(name: String, value: Shadow): NodeParameter? {
- val parameter = createFromKotlinReflection(name, value) ?: return null
- val elements = parameter.elements
- val index = elements.indexOfFirst { it.name == "blurRadius" }
- if (index >= 0) {
- val blurRadius = with(density) { value.blurRadius.toDp().value }
- elements[index] = NodeParameter("blurRadius", DimensionDp, blurRadius)
- }
- return parameter
- }
-
- @Suppress("DEPRECATION")
- private fun createFromTextUnit(name: String, value: TextUnit): NodeParameter =
- when (value.type) {
- TextUnitType.Sp -> NodeParameter(name, ParameterType.DimensionSp, value.value)
- TextUnitType.Em -> NodeParameter(name, ParameterType.DimensionEm, value.value)
- TextUnitType.Unspecified ->
- NodeParameter(name, ParameterType.String, "Unspecified")
- }
-
- private fun createFromImageVector(name: String, value: ImageVector): NodeParameter =
- NodeParameter(name, ParameterType.String, value.name)
-
- /**
- * Select a resource font among the font in the family to represent the font
- *
- * Prefer the font closest to [FontWeight.Normal] and [FontStyle.Normal]
- */
- private fun findBestResourceFont(value: FontListFontFamily): ResourceFont? =
- value.fonts.asSequence().filterIsInstance<ResourceFont>().minByOrNull {
- abs(it.weight.weight - FontWeight.Normal.weight) + it.style.ordinal
- }
- }
-}
diff --git a/compose/ui/ui/api/1.0.0-beta07.txt b/compose/ui/ui/api/1.0.0-beta07.txt
index ac74859..25aed62 100644
--- a/compose/ui/ui/api/1.0.0-beta07.txt
+++ b/compose/ui/ui/api/1.0.0-beta07.txt
@@ -236,13 +236,35 @@
method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
}
- public enum FocusDirection {
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+ public final inline class FocusDirection {
+ ctor public FocusDirection();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+ }
+
+ public static final class FocusDirection.Companion {
+ method public int getDown-dhqQ-8s();
+ method public int getIn-dhqQ-8s();
+ method public int getLeft-dhqQ-8s();
+ method public int getNext-dhqQ-8s();
+ method public int getOut-dhqQ-8s();
+ method public int getPrevious-dhqQ-8s();
+ method public int getRight-dhqQ-8s();
+ method public int getUp-dhqQ-8s();
+ property public final int Down;
+ property public final int In;
+ property public final int Left;
+ property public final int Next;
+ property public final int Out;
+ property public final int Previous;
+ property public final int Right;
+ property public final int Up;
}
public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -255,7 +277,7 @@
public interface FocusManager {
method public void clearFocus(optional boolean forcedClear);
- method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
+ method public boolean moveFocus-3ESFkO8(int focusDirection);
}
public final class FocusModifierKt {
@@ -1219,15 +1241,30 @@
property public final android.view.KeyEvent nativeKeyEvent;
}
- public enum KeyEventType {
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+ public final inline class KeyEventType {
+ ctor public KeyEventType();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+ }
+
+ public static final class KeyEventType.Companion {
+ method public int getKeyDown-CS__XNY();
+ method public int getKeyUp-CS__XNY();
+ method public int getUnknown-CS__XNY();
+ property public final int KeyDown;
+ property public final int KeyUp;
+ property public final int Unknown;
}
public final class KeyEvent_androidKt {
method public static long getKey-ZmokQxo(android.view.KeyEvent);
- method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+ method public static int getType-ZmokQxo(android.view.KeyEvent);
method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1734,6 +1771,17 @@
public final class SubcomposeLayoutKt {
method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ }
+
+ public final class SubcomposeLayoutState {
+ ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+ ctor public SubcomposeLayoutState();
+ method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+ method public void dispose();
}
public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
index 42d6635..260888e 100644
--- a/compose/ui/ui/api/current.ignore
+++ b/compose/ui/ui/api/current.ignore
@@ -1,3 +1,45 @@
// Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.ui.focus.FocusManager#moveFocus-3ESFkO8(int):
+ Added method androidx.compose.ui.focus.FocusManager.moveFocus-3ESFkO8(int)
+
+
+ChangedSuperclass: androidx.compose.ui.focus.FocusDirection:
+ Class androidx.compose.ui.focus.FocusDirection superclass changed from java.lang.Enum to java.lang.Object
+ChangedSuperclass: androidx.compose.ui.input.key.KeyEventType:
+ Class androidx.compose.ui.input.key.KeyEventType superclass changed from java.lang.Enum to java.lang.Object
+
+
+ChangedType: androidx.compose.ui.input.key.KeyEvent_androidKt#getType-ZmokQxo(android.view.KeyEvent):
+ Method androidx.compose.ui.input.key.KeyEvent_androidKt.getType-ZmokQxo has changed return type from androidx.compose.ui.input.key.KeyEventType to int
+
+
+InvalidNullConversion: androidx.compose.ui.input.key.KeyEvent_androidKt#getType-ZmokQxo(android.view.KeyEvent):
+ Attempted to remove @NonNull annotation from method androidx.compose.ui.input.key.KeyEvent_androidKt.getType-ZmokQxo(android.view.KeyEvent)
+
+
RemovedClass: androidx.compose.ui.input.pointer.HitPathTrackerKt:
Removed class androidx.compose.ui.input.pointer.HitPathTrackerKt
+
+
+RemovedField: androidx.compose.ui.focus.FocusDirection#Down:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Down
+RemovedField: androidx.compose.ui.focus.FocusDirection#Left:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Left
+RemovedField: androidx.compose.ui.focus.FocusDirection#Next:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Next
+RemovedField: androidx.compose.ui.focus.FocusDirection#Previous:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Previous
+RemovedField: androidx.compose.ui.focus.FocusDirection#Right:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Right
+RemovedField: androidx.compose.ui.focus.FocusDirection#Up:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Up
+RemovedField: androidx.compose.ui.input.key.KeyEventType#KeyDown:
+ Removed enum constant androidx.compose.ui.input.key.KeyEventType.KeyDown
+RemovedField: androidx.compose.ui.input.key.KeyEventType#KeyUp:
+ Removed enum constant androidx.compose.ui.input.key.KeyEventType.KeyUp
+RemovedField: androidx.compose.ui.input.key.KeyEventType#Unknown:
+ Removed enum constant androidx.compose.ui.input.key.KeyEventType.Unknown
+
+
+RemovedMethod: androidx.compose.ui.focus.FocusManager#moveFocus(androidx.compose.ui.focus.FocusDirection):
+ Removed method androidx.compose.ui.focus.FocusManager.moveFocus(androidx.compose.ui.focus.FocusDirection)
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index ac74859..25aed62 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -236,13 +236,35 @@
method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
}
- public enum FocusDirection {
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+ public final inline class FocusDirection {
+ ctor public FocusDirection();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+ }
+
+ public static final class FocusDirection.Companion {
+ method public int getDown-dhqQ-8s();
+ method public int getIn-dhqQ-8s();
+ method public int getLeft-dhqQ-8s();
+ method public int getNext-dhqQ-8s();
+ method public int getOut-dhqQ-8s();
+ method public int getPrevious-dhqQ-8s();
+ method public int getRight-dhqQ-8s();
+ method public int getUp-dhqQ-8s();
+ property public final int Down;
+ property public final int In;
+ property public final int Left;
+ property public final int Next;
+ property public final int Out;
+ property public final int Previous;
+ property public final int Right;
+ property public final int Up;
}
public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -255,7 +277,7 @@
public interface FocusManager {
method public void clearFocus(optional boolean forcedClear);
- method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
+ method public boolean moveFocus-3ESFkO8(int focusDirection);
}
public final class FocusModifierKt {
@@ -1219,15 +1241,30 @@
property public final android.view.KeyEvent nativeKeyEvent;
}
- public enum KeyEventType {
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+ public final inline class KeyEventType {
+ ctor public KeyEventType();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+ }
+
+ public static final class KeyEventType.Companion {
+ method public int getKeyDown-CS__XNY();
+ method public int getKeyUp-CS__XNY();
+ method public int getUnknown-CS__XNY();
+ property public final int KeyDown;
+ property public final int KeyUp;
+ property public final int Unknown;
}
public final class KeyEvent_androidKt {
method public static long getKey-ZmokQxo(android.view.KeyEvent);
- method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+ method public static int getType-ZmokQxo(android.view.KeyEvent);
method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1734,6 +1771,17 @@
public final class SubcomposeLayoutKt {
method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ }
+
+ public final class SubcomposeLayoutState {
+ ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+ ctor public SubcomposeLayoutState();
+ method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+ method public void dispose();
}
public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta07.txt b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta07.txt
index 86f417b..9fa0749 100644
--- a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta07.txt
@@ -307,13 +307,35 @@
method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
}
- public enum FocusDirection {
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+ public final inline class FocusDirection {
+ ctor public FocusDirection();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+ }
+
+ public static final class FocusDirection.Companion {
+ method public int getDown-dhqQ-8s();
+ method public int getIn-dhqQ-8s();
+ method public int getLeft-dhqQ-8s();
+ method public int getNext-dhqQ-8s();
+ method public int getOut-dhqQ-8s();
+ method public int getPrevious-dhqQ-8s();
+ method public int getRight-dhqQ-8s();
+ method public int getUp-dhqQ-8s();
+ property public final int Down;
+ property public final int In;
+ property public final int Left;
+ property public final int Next;
+ property public final int Out;
+ property public final int Previous;
+ property public final int Right;
+ property public final int Up;
}
public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -326,9 +348,9 @@
public interface FocusManager {
method public void clearFocus(optional boolean forcedClear);
- method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean moveFocusIn();
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean moveFocusOut();
+ method public boolean moveFocus-3ESFkO8(int focusDirection);
+ method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusIn();
+ method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusOut();
}
public final class FocusModifierKt {
@@ -1321,15 +1343,30 @@
property public final android.view.KeyEvent nativeKeyEvent;
}
- public enum KeyEventType {
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+ public final inline class KeyEventType {
+ ctor public KeyEventType();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+ }
+
+ public static final class KeyEventType.Companion {
+ method public int getKeyDown-CS__XNY();
+ method public int getKeyUp-CS__XNY();
+ method public int getUnknown-CS__XNY();
+ property public final int KeyDown;
+ property public final int KeyUp;
+ property public final int Unknown;
}
public final class KeyEvent_androidKt {
method public static long getKey-ZmokQxo(android.view.KeyEvent);
- method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+ method public static int getType-ZmokQxo(android.view.KeyEvent);
method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1838,6 +1875,17 @@
public final class SubcomposeLayoutKt {
method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ }
+
+ public final class SubcomposeLayoutState {
+ ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+ ctor public SubcomposeLayoutState();
+ method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+ method public void dispose();
}
public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 86f417b..9fa0749 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -307,13 +307,35 @@
method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
}
- public enum FocusDirection {
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+ public final inline class FocusDirection {
+ ctor public FocusDirection();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+ }
+
+ public static final class FocusDirection.Companion {
+ method public int getDown-dhqQ-8s();
+ method public int getIn-dhqQ-8s();
+ method public int getLeft-dhqQ-8s();
+ method public int getNext-dhqQ-8s();
+ method public int getOut-dhqQ-8s();
+ method public int getPrevious-dhqQ-8s();
+ method public int getRight-dhqQ-8s();
+ method public int getUp-dhqQ-8s();
+ property public final int Down;
+ property public final int In;
+ property public final int Left;
+ property public final int Next;
+ property public final int Out;
+ property public final int Previous;
+ property public final int Right;
+ property public final int Up;
}
public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -326,9 +348,9 @@
public interface FocusManager {
method public void clearFocus(optional boolean forcedClear);
- method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean moveFocusIn();
- method @androidx.compose.ui.ExperimentalComposeUiApi public boolean moveFocusOut();
+ method public boolean moveFocus-3ESFkO8(int focusDirection);
+ method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusIn();
+ method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusOut();
}
public final class FocusModifierKt {
@@ -1321,15 +1343,30 @@
property public final android.view.KeyEvent nativeKeyEvent;
}
- public enum KeyEventType {
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+ public final inline class KeyEventType {
+ ctor public KeyEventType();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+ }
+
+ public static final class KeyEventType.Companion {
+ method public int getKeyDown-CS__XNY();
+ method public int getKeyUp-CS__XNY();
+ method public int getUnknown-CS__XNY();
+ property public final int KeyDown;
+ property public final int KeyUp;
+ property public final int Unknown;
}
public final class KeyEvent_androidKt {
method public static long getKey-ZmokQxo(android.view.KeyEvent);
- method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+ method public static int getType-ZmokQxo(android.view.KeyEvent);
method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1838,6 +1875,17 @@
public final class SubcomposeLayoutKt {
method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ }
+
+ public final class SubcomposeLayoutState {
+ ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+ ctor public SubcomposeLayoutState();
+ method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+ method public void dispose();
}
public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/restricted_1.0.0-beta07.txt b/compose/ui/ui/api/restricted_1.0.0-beta07.txt
index d741df2..d35964c 100644
--- a/compose/ui/ui/api/restricted_1.0.0-beta07.txt
+++ b/compose/ui/ui/api/restricted_1.0.0-beta07.txt
@@ -236,13 +236,35 @@
method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
}
- public enum FocusDirection {
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+ public final inline class FocusDirection {
+ ctor public FocusDirection();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+ }
+
+ public static final class FocusDirection.Companion {
+ method public int getDown-dhqQ-8s();
+ method public int getIn-dhqQ-8s();
+ method public int getLeft-dhqQ-8s();
+ method public int getNext-dhqQ-8s();
+ method public int getOut-dhqQ-8s();
+ method public int getPrevious-dhqQ-8s();
+ method public int getRight-dhqQ-8s();
+ method public int getUp-dhqQ-8s();
+ property public final int Down;
+ property public final int In;
+ property public final int Left;
+ property public final int Next;
+ property public final int Out;
+ property public final int Previous;
+ property public final int Right;
+ property public final int Up;
}
public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -255,7 +277,7 @@
public interface FocusManager {
method public void clearFocus(optional boolean forcedClear);
- method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
+ method public boolean moveFocus-3ESFkO8(int focusDirection);
}
public final class FocusModifierKt {
@@ -1219,15 +1241,30 @@
property public final android.view.KeyEvent nativeKeyEvent;
}
- public enum KeyEventType {
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+ public final inline class KeyEventType {
+ ctor public KeyEventType();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+ }
+
+ public static final class KeyEventType.Companion {
+ method public int getKeyDown-CS__XNY();
+ method public int getKeyUp-CS__XNY();
+ method public int getUnknown-CS__XNY();
+ property public final int KeyDown;
+ property public final int KeyUp;
+ property public final int Unknown;
}
public final class KeyEvent_androidKt {
method public static long getKey-ZmokQxo(android.view.KeyEvent);
- method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+ method public static int getType-ZmokQxo(android.view.KeyEvent);
method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1735,6 +1772,17 @@
public final class SubcomposeLayoutKt {
method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ }
+
+ public final class SubcomposeLayoutState {
+ ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+ ctor public SubcomposeLayoutState();
+ method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+ method public void dispose();
}
public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
index 42d6635..260888e 100644
--- a/compose/ui/ui/api/restricted_current.ignore
+++ b/compose/ui/ui/api/restricted_current.ignore
@@ -1,3 +1,45 @@
// Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.ui.focus.FocusManager#moveFocus-3ESFkO8(int):
+ Added method androidx.compose.ui.focus.FocusManager.moveFocus-3ESFkO8(int)
+
+
+ChangedSuperclass: androidx.compose.ui.focus.FocusDirection:
+ Class androidx.compose.ui.focus.FocusDirection superclass changed from java.lang.Enum to java.lang.Object
+ChangedSuperclass: androidx.compose.ui.input.key.KeyEventType:
+ Class androidx.compose.ui.input.key.KeyEventType superclass changed from java.lang.Enum to java.lang.Object
+
+
+ChangedType: androidx.compose.ui.input.key.KeyEvent_androidKt#getType-ZmokQxo(android.view.KeyEvent):
+ Method androidx.compose.ui.input.key.KeyEvent_androidKt.getType-ZmokQxo has changed return type from androidx.compose.ui.input.key.KeyEventType to int
+
+
+InvalidNullConversion: androidx.compose.ui.input.key.KeyEvent_androidKt#getType-ZmokQxo(android.view.KeyEvent):
+ Attempted to remove @NonNull annotation from method androidx.compose.ui.input.key.KeyEvent_androidKt.getType-ZmokQxo(android.view.KeyEvent)
+
+
RemovedClass: androidx.compose.ui.input.pointer.HitPathTrackerKt:
Removed class androidx.compose.ui.input.pointer.HitPathTrackerKt
+
+
+RemovedField: androidx.compose.ui.focus.FocusDirection#Down:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Down
+RemovedField: androidx.compose.ui.focus.FocusDirection#Left:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Left
+RemovedField: androidx.compose.ui.focus.FocusDirection#Next:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Next
+RemovedField: androidx.compose.ui.focus.FocusDirection#Previous:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Previous
+RemovedField: androidx.compose.ui.focus.FocusDirection#Right:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Right
+RemovedField: androidx.compose.ui.focus.FocusDirection#Up:
+ Removed enum constant androidx.compose.ui.focus.FocusDirection.Up
+RemovedField: androidx.compose.ui.input.key.KeyEventType#KeyDown:
+ Removed enum constant androidx.compose.ui.input.key.KeyEventType.KeyDown
+RemovedField: androidx.compose.ui.input.key.KeyEventType#KeyUp:
+ Removed enum constant androidx.compose.ui.input.key.KeyEventType.KeyUp
+RemovedField: androidx.compose.ui.input.key.KeyEventType#Unknown:
+ Removed enum constant androidx.compose.ui.input.key.KeyEventType.Unknown
+
+
+RemovedMethod: androidx.compose.ui.focus.FocusManager#moveFocus(androidx.compose.ui.focus.FocusDirection):
+ Removed method androidx.compose.ui.focus.FocusManager.moveFocus(androidx.compose.ui.focus.FocusDirection)
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index d741df2..d35964c 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -236,13 +236,35 @@
method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
}
- public enum FocusDirection {
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
- enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+ public final inline class FocusDirection {
+ ctor public FocusDirection();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+ }
+
+ public static final class FocusDirection.Companion {
+ method public int getDown-dhqQ-8s();
+ method public int getIn-dhqQ-8s();
+ method public int getLeft-dhqQ-8s();
+ method public int getNext-dhqQ-8s();
+ method public int getOut-dhqQ-8s();
+ method public int getPrevious-dhqQ-8s();
+ method public int getRight-dhqQ-8s();
+ method public int getUp-dhqQ-8s();
+ property public final int Down;
+ property public final int In;
+ property public final int Left;
+ property public final int Next;
+ property public final int Out;
+ property public final int Previous;
+ property public final int Right;
+ property public final int Up;
}
public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -255,7 +277,7 @@
public interface FocusManager {
method public void clearFocus(optional boolean forcedClear);
- method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
+ method public boolean moveFocus-3ESFkO8(int focusDirection);
}
public final class FocusModifierKt {
@@ -1219,15 +1241,30 @@
property public final android.view.KeyEvent nativeKeyEvent;
}
- public enum KeyEventType {
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
- enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+ public final inline class KeyEventType {
+ ctor public KeyEventType();
+ method public static int constructor-impl(int value);
+ method public static inline boolean equals-impl(int p, Object? p1);
+ method public static boolean equals-impl0(int p1, int p2);
+ method public int getValue();
+ method public static inline int hashCode-impl(int p);
+ method public static String toString-impl(int $this);
+ property public final int value;
+ field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+ }
+
+ public static final class KeyEventType.Companion {
+ method public int getKeyDown-CS__XNY();
+ method public int getKeyUp-CS__XNY();
+ method public int getUnknown-CS__XNY();
+ property public final int KeyDown;
+ property public final int KeyUp;
+ property public final int Unknown;
}
public final class KeyEvent_androidKt {
method public static long getKey-ZmokQxo(android.view.KeyEvent);
- method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+ method public static int getType-ZmokQxo(android.view.KeyEvent);
method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1735,6 +1772,17 @@
public final class SubcomposeLayoutKt {
method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+ }
+
+ public final class SubcomposeLayoutState {
+ ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+ ctor public SubcomposeLayoutState();
+ method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+ method public void dispose();
}
public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt
index d434562..b82e2f7 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt
@@ -36,12 +36,12 @@
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Next
-import androidx.compose.ui.focus.FocusDirection.Previous
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusModifier
import androidx.compose.ui.focus.focusOrder
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/keyinput/KeyInputDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/keyinput/KeyInputDemo.kt
index 126a3cd..68bff5d 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/keyinput/KeyInputDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/keyinput/KeyInputDemo.kt
@@ -35,7 +35,7 @@
import androidx.compose.ui.focus.isFocused
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.key.utf16CodePoint
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
new file mode 100644
index 0000000..f01d362
--- /dev/null
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.compose.ui.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.platform.LocalFocusManager
+
+@Sampled
+@Composable
+fun MoveFocusSample() {
+ val focusManager = LocalFocusManager.current
+ Column {
+ Row {
+ Box(Modifier.focusable())
+ Box(Modifier.focusable())
+ }
+ Row {
+ Box(Modifier.focusable())
+ Box(Modifier.focusable())
+ }
+ Button(onClick = { focusManager.moveFocus(FocusDirection.Right) }) { Text("Right") }
+ Button(onClick = { focusManager.moveFocus(FocusDirection.Left) }) { Text("Left") }
+ Button(onClick = { focusManager.moveFocus(FocusDirection.Up) }) { Text("Up") }
+ Button(onClick = { focusManager.moveFocus(FocusDirection.Down) }) { Text("Down") }
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 01171d3..65bb050 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -57,6 +57,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.toAndroidRect
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.AndroidComposeView
import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat
import androidx.compose.ui.platform.LocalDensity
@@ -1356,6 +1357,87 @@
}
@Test
+ fun testLayerParamChange_setCorrectBounds_syntaxOne() {
+ var scale by mutableStateOf(1f)
+ container.setContent {
+ // testTag must not be on the same node with graphicsLayer, otherwise we will have
+ // semantics change notification.
+ Box(Modifier.graphicsLayer(scaleX = scale, scaleY = scale).requiredSize(300.dp)) {
+ Box(Modifier.matchParentSize().testTag("node"))
+ }
+ }
+
+ val node = rule.onNodeWithTag("node").fetchSemanticsNode()
+ var info: AccessibilityNodeInfo = AccessibilityNodeInfo.obtain()
+ rule.runOnUiThread {
+ info = provider.createAccessibilityNodeInfo(node.id)
+ }
+ with(rule.density) {
+ val size = 300.dp.roundToPx()
+ val rect = Rect()
+ info.getBoundsInScreen(rect)
+ assertEquals(size, rect.width())
+ assertEquals(size, rect.height())
+ }
+
+ scale = 0.5f
+ info.recycle()
+ rule.runOnIdle {
+ info = provider.createAccessibilityNodeInfo(node.id)
+ }
+ with(rule.density) {
+ val size = 150.dp.roundToPx()
+ val rect = Rect()
+ info.getBoundsInScreen(rect)
+ assertEquals(size, rect.width())
+ assertEquals(size, rect.height())
+ }
+ }
+
+ @Test
+ fun testLayerParamChange_setCorrectBounds_syntaxTwo() {
+ var scale by mutableStateOf(1f)
+ container.setContent {
+ // testTag must not be on the same node with graphicsLayer, otherwise we will have
+ // semantics change notification.
+ Box(
+ Modifier.graphicsLayer {
+ scaleX = scale
+ scaleY = scale
+ }.requiredSize(300.dp)
+ ) {
+ Box(Modifier.matchParentSize().testTag("node"))
+ }
+ }
+
+ val node = rule.onNodeWithTag("node").fetchSemanticsNode()
+ var info: AccessibilityNodeInfo = AccessibilityNodeInfo.obtain()
+ rule.runOnUiThread {
+ info = provider.createAccessibilityNodeInfo(node.id)
+ }
+ with(rule.density) {
+ val size = 300.dp.roundToPx()
+ val rect = Rect()
+ info.getBoundsInScreen(rect)
+ assertEquals(size, rect.width())
+ assertEquals(size, rect.height())
+ }
+
+ scale = 0.5f
+ info.recycle()
+ rule.runOnIdle {
+ info = provider.createAccessibilityNodeInfo(node.id)
+ }
+ with(rule.density) {
+ val size = 150.dp.roundToPx()
+ val rect = Rect()
+ info.getBoundsInScreen(rect)
+ assertEquals(size, rect.width())
+ assertEquals(size, rect.height())
+ }
+ }
+
+ @Test
fun testDialog_setCorrectBounds() {
var dialogComposeView: AndroidComposeView? = null
container.setContent {
@@ -1389,10 +1471,10 @@
val textPositionOnScreenX = viewPosition[0] + offset
val textPositionOnScreenY = viewPosition[1] + offset
- val textRect = android.graphics.Rect()
+ val textRect = Rect()
info.getBoundsInScreen(textRect)
assertEquals(
- android.graphics.Rect(
+ Rect(
textPositionOnScreenX,
textPositionOnScreenY,
textPositionOnScreenX + size,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
index d389b61..d3805e3 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
@@ -16,14 +16,15 @@
package androidx.compose.ui.focus
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.In
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Next
-import androidx.compose.ui.focus.FocusDirectionInternal.Out
-import androidx.compose.ui.focus.FocusDirectionInternal.Previous
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.In
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.nativeKeyCode
@@ -137,6 +138,7 @@
val focusDirection = owner.getFocusDirection(keyEvent)
// Assert.
+ @OptIn(ExperimentalComposeUiApi::class)
assertThat(focusDirection).isEqualTo(In)
}
@@ -149,6 +151,7 @@
val focusDirection = owner.getFocusDirection(keyEvent)
// Assert.
+ @OptIn(ExperimentalComposeUiApi::class)
assertThat(focusDirection).isEqualTo(Out)
}
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt
index b758a44..a8d6b64 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt
@@ -20,6 +20,7 @@
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.focus.FocusDirection.Companion.In
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
@@ -56,7 +57,7 @@
}
// Act.
- focusManager.moveFocusIn()
+ focusManager.moveFocus(In)
// Assert.
rule.runOnIdle {
@@ -84,7 +85,7 @@
}
// Act.
- focusManager.moveFocusIn()
+ focusManager.moveFocus(In)
// Assert.
rule.runOnIdle {
@@ -117,7 +118,7 @@
}
// Act.
- focusManager.moveFocusIn()
+ focusManager.moveFocus(In)
// Assert.
rule.runOnIdle {
@@ -156,7 +157,7 @@
}
// Act.
- focusManager.moveFocusIn()
+ focusManager.moveFocus(In)
// Assert.
rule.runOnIdle {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
index b9cdd1a..2980541 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
@@ -25,10 +25,10 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.test.junit4.createComposeRule
@@ -43,14 +43,16 @@
@MediumTest
@RunWith(Parameterized::class)
-class TwoDimensionalFocusTraversalInitialFocusTest(private val focusDirection: FocusDirection) {
+class TwoDimensionalFocusTraversalInitialFocusTest(focusDirectionInt: Int) {
@get:Rule
val rule = createComposeRule()
+ private val focusDirection = FocusDirection(focusDirectionInt)
+
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
- fun initParameters() = listOf(Left, Right, Up, Down)
+ fun initParameters() = listOf(Left, Right, Up, Down).map { it.value }
}
@Test
@@ -93,7 +95,7 @@
@Test
fun initialFocus_whenThereIsOnlyOneFocusable() {
// Arrange.
- var isFocused = mutableStateOf(false)
+ val isFocused = mutableStateOf(false)
lateinit var view: View
lateinit var focusManager: FocusManager
rule.setContent {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt
index 671cd23..9af59e7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt
@@ -19,10 +19,11 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
@@ -57,7 +58,7 @@
}
// Act.
- focusManager.moveFocusOut()
+ focusManager.moveFocus(Out)
// Assert.
rule.runOnIdle {
@@ -84,7 +85,7 @@
}
// Act.
- focusManager.moveFocusOut()
+ focusManager.moveFocus(Out)
// Assert.
rule.runOnIdle {
@@ -117,7 +118,7 @@
}
// Act.
- focusManager.moveFocusOut()
+ focusManager.moveFocus(Out)
// Assert.
rule.runOnIdle {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalThreeItemsTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalThreeItemsTest.kt
index 603bca7..faef106 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalThreeItemsTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalThreeItemsTest.kt
@@ -18,10 +18,10 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
@@ -36,17 +36,18 @@
private const val invalid = "Not applicable to a 2D focus search."
@RunWith(Parameterized::class)
-class TwoDimensionalFocusTraversalThreeItemsTest(private val focusDirection: FocusDirection) {
+class TwoDimensionalFocusTraversalThreeItemsTest(focusDirectionInt: Int) {
@get:Rule
val rule = createComposeRule()
private lateinit var focusManager: FocusManager
private val initialFocus: FocusRequester = FocusRequester()
+ private val focusDirection = FocusDirection(focusDirectionInt)
companion object {
@JvmStatic
- @Parameterized.Parameters(name = "direction={0}")
- fun initParameters() = listOf(Left, Right, Up, Down)
+ @Parameterized.Parameters(name = "{0}")
+ fun initParameters() = listOf(Left, Right, Up, Down).map { it.value }
}
/**
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTwoItemsTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTwoItemsTest.kt
index 067199a..c5459ce 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTwoItemsTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTwoItemsTest.kt
@@ -19,10 +19,10 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
@@ -37,7 +37,7 @@
private const val invalid = "Not applicable to a 2D focus search."
@RunWith(Parameterized::class)
-class TwoDimensionalFocusTraversalTwoItemsTest(private val focusDirection: FocusDirection) {
+class TwoDimensionalFocusTraversalTwoItemsTest(focusDirectionInt: Int) {
@get:Rule
val rule = createComposeRule()
@@ -45,11 +45,12 @@
private val initialFocus: FocusRequester = FocusRequester()
private val focusedItem = mutableStateOf(false)
private val candidate = mutableStateOf(false)
+ private val focusDirection = FocusDirection(focusDirectionInt)
companion object {
@JvmStatic
- @Parameterized.Parameters(name = "direction={0}")
- fun initParameters() = listOf(Left, Right, Up, Down)
+ @Parameterized.Parameters(name = "{0}")
+ fun initParameters() = listOf(Left, Right, Up, Down).map { it.value }
}
/**
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/AndroidProcessKeyInputTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/AndroidProcessKeyInputTest.kt
index 750100f..085ecbd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/AndroidProcessKeyInputTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/AndroidProcessKeyInputTest.kt
@@ -27,8 +27,8 @@
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.setFocusableContent
import androidx.compose.ui.input.key.Key.Companion.A
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
-import androidx.compose.ui.input.key.KeyEventType.KeyUp
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.filters.SmallTest
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
index d719480..7aad0ba 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
@@ -27,9 +27,8 @@
import android.view.KeyEvent.ACTION_DOWN
import android.view.KeyEvent.ACTION_UP
import androidx.compose.ui.input.key.Key.Companion.A
-import androidx.compose.ui.input.key.KeyEventType.KeyUp
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
-import androidx.compose.ui.input.key.KeyEventType.Unknown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performKeyPress
@@ -400,7 +399,7 @@
val action = when (keyEventType) {
KeyDown -> ACTION_DOWN
KeyUp -> ACTION_UP
- Unknown -> error("Unknown key event type")
+ else -> error("Unknown key event type")
}
return KeyEvent(AndroidKeyEvent(0L, 0L, action, keycode, 0, 0))
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
index 622151f..d74228e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
@@ -443,10 +443,12 @@
@Test
fun dispatchChanges_noNodes_nothingChanges() {
- val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(5)))
+ val internalPointerEvent = internalPointerEventOf(down(5))
+
+ hitPathTracker.dispatchChanges(internalPointerEvent)
PointerInputChangeSubject
- .assertThat(result.changes.values.first())
+ .assertThat(internalPointerEvent.changes.values.first())
.isStructurallyEqualTo(down(5))
}
@@ -463,10 +465,12 @@
hitPathTracker.addHitPath(PointerId(13), listOf(pif1))
- val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
+ val internalPointerEvent = internalPointerEventOf(down(13))
+
+ hitPathTracker.dispatchChanges(internalPointerEvent)
PointerInputChangeSubject
- .assertThat(result.changes.values.first())
+ .assertThat(internalPointerEvent.changes.values.first())
.isStructurallyEqualTo(down(13).apply { consumeDownChange() })
}
@@ -508,7 +512,9 @@
val expectedChange = actualChange.deepCopy()
val consumedExpectedChange = actualChange.deepCopy().apply { consumePositionChange() }
- val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(actualChange))
+ val internalPointerEvent = internalPointerEventOf(actualChange)
+
+ hitPathTracker.dispatchChanges(internalPointerEvent)
val log1 = log.getOnPointerEventLog()
.filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
@@ -570,7 +576,7 @@
assertThat(log1[5].pass).isEqualTo(PointerEventPass.Main)
PointerInputChangeSubject
- .assertThat(result.changes.values.first())
+ .assertThat(internalPointerEvent.changes.values.first())
.isStructurallyEqualTo(
consumedExpectedChange
)
@@ -628,9 +634,9 @@
val consumedExpectedEvent1 = expectedEvent1.deepCopy().apply { consumePositionChange() }
val consumedExpectedEvent2 = expectedEvent2.deepCopy().apply { consumePositionChange() }
- val (result, _) = hitPathTracker.dispatchChanges(
- internalPointerEventOf(actualEvent1, actualEvent2)
- )
+ val internalPointerEvent = internalPointerEventOf(actualEvent1, actualEvent2)
+
+ hitPathTracker.dispatchChanges(internalPointerEvent)
val log1 = log.getOnPointerEventLog()
.filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
@@ -712,14 +718,14 @@
)
assertThat(log2[3].pass).isEqualTo(PointerEventPass.Main)
- assertThat(result.changes).hasSize(2)
+ assertThat(internalPointerEvent.changes).hasSize(2)
PointerInputChangeSubject
- .assertThat(result.changes[actualEvent1.id])
+ .assertThat(internalPointerEvent.changes[actualEvent1.id])
.isStructurallyEqualTo(
consumedExpectedEvent1
)
PointerInputChangeSubject
- .assertThat(result.changes[actualEvent2.id])
+ .assertThat(internalPointerEvent.changes[actualEvent2.id])
.isStructurallyEqualTo(
consumedExpectedEvent2
)
@@ -770,9 +776,9 @@
val consumedEvent1 = expectedEvent1.deepCopy().apply { consumePositionChange() }
val consumedEvent2 = expectedEvent2.deepCopy().apply { consumePositionChange() }
- val (result, _) = hitPathTracker.dispatchChanges(
- internalPointerEventOf(actualEvent1, actualEvent2)
- )
+ val internalPointerEvent = internalPointerEventOf(actualEvent1, actualEvent2)
+
+ hitPathTracker.dispatchChanges(internalPointerEvent)
val log1 = log.getOnPointerEventLog()
.filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
@@ -823,13 +829,13 @@
)
assertThat(log1[5].pass).isEqualTo(PointerEventPass.Main)
- assertThat(result.changes).hasSize(2)
+ assertThat(internalPointerEvent.changes).hasSize(2)
PointerInputChangeSubject
- .assertThat(result.changes[actualEvent1.id])
+ .assertThat(internalPointerEvent.changes[actualEvent1.id])
.isStructurallyEqualTo(consumedEvent1)
PointerInputChangeSubject
- .assertThat(result.changes[actualEvent2.id])
+ .assertThat(internalPointerEvent.changes[actualEvent2.id])
.isStructurallyEqualTo(consumedEvent2)
}
@@ -866,9 +872,9 @@
val consumedEvent1 = expectedEvent1.deepCopy().apply { consumePositionChange() }
val consumedEvent2 = expectedEvent2.deepCopy().apply { consumePositionChange() }
- val (result, _) = hitPathTracker.dispatchChanges(
- internalPointerEventOf(actualEvent1, actualEvent2)
- )
+ val internalPointerEvent = internalPointerEventOf(actualEvent1, actualEvent2)
+
+ hitPathTracker.dispatchChanges(internalPointerEvent)
val log1 = log.getOnPointerEventLog()
.filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
@@ -912,14 +918,14 @@
)
assertThat(log1[3].pass).isEqualTo(PointerEventPass.Main)
- assertThat(result.changes).hasSize(2)
+ assertThat(internalPointerEvent.changes).hasSize(2)
PointerInputChangeSubject
- .assertThat(result.changes[actualEvent1.id])
+ .assertThat(internalPointerEvent.changes[actualEvent1.id])
.isStructurallyEqualTo(
consumedEvent1
)
PointerInputChangeSubject
- .assertThat(result.changes[actualEvent2.id])
+ .assertThat(internalPointerEvent.changes[actualEvent2.id])
.isStructurallyEqualTo(
consumedEvent2
)
@@ -2842,7 +2848,7 @@
@Test
fun dispatchChanges_noNodes_reportsWasDispatchedToNothing() {
- val (_, hitSomething) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(0)))
+ val hitSomething = hitPathTracker.dispatchChanges(internalPointerEventOf(down(0)))
assertThat(hitSomething).isFalse()
}
@@ -2851,7 +2857,7 @@
val pif = PointerInputFilterMock()
hitPathTracker.addHitPath(PointerId(13), listOf(pif))
- val (_, hitSomething) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
+ val hitSomething = hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
assertThat(hitSomething).isTrue()
}
@@ -2861,7 +2867,7 @@
val pif = PointerInputFilterMock()
hitPathTracker.addHitPath(PointerId(13), listOf(pif))
- val (_, hitSomething) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(69)))
+ val hitSomething = hitPathTracker.dispatchChanges(internalPointerEventOf(down(69)))
assertThat(hitSomething).isFalse()
}
@@ -3109,7 +3115,7 @@
if (actualNode.children.size != expectedNode.children.size) {
return false
}
- for (child in actualNode.children) {
+ actualNode.children.forEach { child ->
check = check && expectedNode.children.any {
areEqual(child, it)
}
@@ -3137,7 +3143,7 @@
if (actualNode.children.size != expectedNode.children.size) {
return false
}
- for (child in actualNode.children) {
+ actualNode.children.forEach { child ->
check = check && expectedNode.children.any {
areEqual(child, it)
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt
index b55dd28..db78ad7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt
@@ -1050,25 +1050,32 @@
arrayOf(PointerCoords(30f, 31f))
)
+ // Test the different events sequentially, since the returned event contains a list that
+ // will be reused by convertToPointerInputEvent for performance, so it shouldn't be held
+ // for longer than needed during the sequential dispatch.
+
val pointerInputEventDown1 = motionEventAdapter.convertToPointerInputEvent(down1)
- val pointerInputEventUp1 = motionEventAdapter.convertToPointerInputEvent(up1)
- val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
- val pointerInputEventUp2 = motionEventAdapter.convertToPointerInputEvent(up2)
- val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
- val pointerInputEventUp3 = motionEventAdapter.convertToPointerInputEvent(up3)
-
assertThat(pointerInputEventDown1).isNotNull()
- assertThat(pointerInputEventUp1).isNotNull()
- assertThat(pointerInputEventDown2).isNotNull()
- assertThat(pointerInputEventUp2).isNotNull()
- assertThat(pointerInputEventDown3).isNotNull()
- assertThat(pointerInputEventUp3).isNotNull()
-
assertThat(pointerInputEventDown1!!.pointers[0].id).isEqualTo(PointerId(0))
+
+ val pointerInputEventUp1 = motionEventAdapter.convertToPointerInputEvent(up1)
+ assertThat(pointerInputEventUp1).isNotNull()
assertThat(pointerInputEventUp1!!.pointers[0].id).isEqualTo(PointerId(0))
+
+ val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
+ assertThat(pointerInputEventDown2).isNotNull()
assertThat(pointerInputEventDown2!!.pointers[0].id).isEqualTo(PointerId(1))
+
+ val pointerInputEventUp2 = motionEventAdapter.convertToPointerInputEvent(up2)
+ assertThat(pointerInputEventUp2).isNotNull()
assertThat(pointerInputEventUp2!!.pointers[0].id).isEqualTo(PointerId(1))
+
+ val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
+ assertThat(pointerInputEventDown3).isNotNull()
assertThat(pointerInputEventDown3!!.pointers[0].id).isEqualTo(PointerId(2))
+
+ val pointerInputEventUp3 = motionEventAdapter.convertToPointerInputEvent(up3)
+ assertThat(pointerInputEventUp3).isNotNull()
assertThat(pointerInputEventUp3!!.pointers[0].id).isEqualTo(PointerId(2))
}
@@ -1119,21 +1126,26 @@
)
)
+ // Test the different events sequentially, since the returned event contains a list that
+ // will be reused by convertToPointerInputEvent for performance, so it shouldn't be held
+ // for longer than needed during the sequential dispatch.
+
val pointerInputEventDown1 = motionEventAdapter.convertToPointerInputEvent(down1)
- val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
- val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
assertThat(pointerInputEventDown1).isNotNull()
- assertThat(pointerInputEventDown2).isNotNull()
- assertThat(pointerInputEventDown3).isNotNull()
-
assertThat(pointerInputEventDown1!!.pointers).hasSize(1)
assertThat(pointerInputEventDown1.pointers[0].id).isEqualTo(PointerId(0))
+ val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
+
+ assertThat(pointerInputEventDown2).isNotNull()
assertThat(pointerInputEventDown2!!.pointers).hasSize(2)
assertThat(pointerInputEventDown2.pointers[0].id).isEqualTo(PointerId(0))
assertThat(pointerInputEventDown2.pointers[1].id).isEqualTo(PointerId(1))
+ val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
+
+ assertThat(pointerInputEventDown3).isNotNull()
assertThat(pointerInputEventDown3!!.pointers).hasSize(3)
assertThat(pointerInputEventDown2.pointers[0].id).isEqualTo(PointerId(0))
assertThat(pointerInputEventDown2.pointers[1].id).isEqualTo(PointerId(1))
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
index 4bd3812..f67e8b7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
@@ -21,7 +21,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.Autofill
import androidx.compose.ui.autofill.AutofillTree
-import androidx.compose.ui.focus.FocusDirectionInternal
+import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
@@ -3085,7 +3085,7 @@
override fun onLayoutChange(layoutNode: LayoutNode) {
}
- override fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal? {
+ override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
TODO("Not yet implemented")
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
index 9046500..49c2c27 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
@@ -22,11 +22,11 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.testutils.TestViewConfiguration
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.InspectableValue
import androidx.compose.ui.platform.ValueElement
-import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.test.TestActivity
import androidx.compose.ui.unit.IntSize
@@ -68,7 +68,7 @@
@Test
fun testAwaitSingleEvent(): Unit = runBlockingTest {
- val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
+ val filter = SuspendingPointerInputFilter(TestViewConfiguration())
val result = CompletableDeferred<PointerEvent>()
launch {
@@ -97,7 +97,7 @@
@Test
fun testAwaitSeveralEvents(): Unit = runBlockingTest {
- val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
+ val filter = SuspendingPointerInputFilter(TestViewConfiguration())
val results = Channel<PointerEvent>(Channel.UNLIMITED)
launch {
with(filter) {
@@ -132,7 +132,7 @@
@Test
fun testSyntheticCancelEvent(): Unit = runBlockingTest {
- val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
+ val filter = SuspendingPointerInputFilter(TestViewConfiguration())
val results = Channel<PointerEvent>(Channel.UNLIMITED)
launch {
with(filter) {
@@ -200,7 +200,7 @@
@Test
fun testCancelledHandlerBlock() = runBlockingTest {
- val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
+ val filter = SuspendingPointerInputFilter(TestViewConfiguration())
val counter = TestCounter()
val handler = launch {
with(filter) {
@@ -305,17 +305,6 @@
}
}
-private class FakeViewConfiguration : ViewConfiguration {
- override val longPressTimeoutMillis: Long
- get() = 500
- override val doubleTapTimeoutMillis: Long
- get() = 300
- override val doubleTapMinTimeMillis: Long
- get() = 40
- override val touchSlop: Float
- get() = 18f
-}
-
private class TestCounter {
private var count = 0
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
index 22e4869..88cd537 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
@@ -20,17 +20,22 @@
import android.widget.FrameLayout
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.background
import androidx.compose.ui.draw.assertColor
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.layout.RootMeasurePolicy.measure
import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalDensity
@@ -66,6 +71,7 @@
@get:Rule
val rule = createAndroidComposeRule<TestActivity>()
+
@get:Rule
val excessiveAssertions = AndroidOwnerExtraAssertionsRule()
@@ -555,6 +561,450 @@
stateUsedLatch.await(1, TimeUnit.SECONDS)
)
}
+
+ @Test
+ fun precompose() {
+ val addSlot = mutableStateOf(false)
+ var composingCounter = 0
+ var composedDuringMeasure = false
+ val state = SubcomposeLayoutState()
+ val content: @Composable () -> Unit = {
+ composingCounter++
+ }
+
+ rule.setContent {
+ SubcomposeLayout(state) {
+ if (addSlot.value) {
+ composedDuringMeasure = true
+ subcompose(Unit, content)
+ }
+ layout(10, 10) {}
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(composingCounter).isEqualTo(0)
+ state.precompose(Unit, content)
+ }
+
+ rule.runOnIdle {
+ assertThat(composingCounter).isEqualTo(1)
+
+ assertThat(composedDuringMeasure).isFalse()
+ addSlot.value = true
+ }
+
+ rule.runOnIdle {
+ assertThat(composedDuringMeasure).isTrue()
+ assertThat(composingCounter).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun disposePrecomposedItem() {
+ var composed = false
+ var disposed = false
+ val state = SubcomposeLayoutState()
+
+ rule.setContent {
+ SubcomposeLayout(state) {
+ layout(10, 10) {}
+ }
+ }
+
+ val slot = rule.runOnIdle {
+ state.precompose(Unit) {
+ DisposableEffect(Unit) {
+ composed = true
+ onDispose {
+ disposed = true
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(composed).isTrue()
+ assertThat(disposed).isFalse()
+
+ slot.dispose()
+ }
+
+ rule.runOnIdle {
+ assertThat(disposed).isTrue()
+ }
+ }
+
+ @Test
+ fun composeItemRegularlyAfterDisposingPrecomposedItem() {
+ val addSlot = mutableStateOf(false)
+ var composingCounter = 0
+ var enterCounter = 0
+ var exitCounter = 0
+ val state = SubcomposeLayoutState()
+ val content: @Composable () -> Unit = @Composable {
+ composingCounter++
+ DisposableEffect(Unit) {
+ enterCounter++
+ onDispose {
+ exitCounter++
+ }
+ }
+ }
+
+ rule.setContent {
+ SubcomposeLayout(state) {
+ if (addSlot.value) {
+ subcompose(Unit, content)
+ }
+ layout(10, 10) {}
+ }
+ }
+
+ val slot = rule.runOnIdle {
+ state.precompose(Unit, content)
+ }
+
+ rule.runOnIdle {
+ slot.dispose()
+ }
+
+ rule.runOnIdle {
+ assertThat(composingCounter).isEqualTo(1)
+ assertThat(enterCounter).isEqualTo(1)
+ assertThat(exitCounter).isEqualTo(1)
+
+ addSlot.value = true
+ }
+
+ rule.runOnIdle {
+ assertThat(composingCounter).isEqualTo(2)
+ assertThat(enterCounter).isEqualTo(2)
+ assertThat(exitCounter).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun precomposeTwoItems() {
+ val addSlots = mutableStateOf(false)
+ var composing1Counter = 0
+ var composing2Counter = 0
+ val state = SubcomposeLayoutState()
+ val content1: @Composable () -> Unit = {
+ composing1Counter++
+ }
+ val content2: @Composable () -> Unit = {
+ composing2Counter++
+ }
+
+ rule.setContent {
+ SubcomposeLayout(state) {
+ subcompose(0) { }
+ if (addSlots.value) {
+ subcompose(1, content1)
+ subcompose(2, content2)
+ }
+ subcompose(3) { }
+ layout(10, 10) {}
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(composing1Counter).isEqualTo(0)
+ assertThat(composing2Counter).isEqualTo(0)
+ state.precompose(1, content1)
+ state.precompose(2, content2)
+ }
+
+ rule.runOnIdle {
+ assertThat(composing1Counter).isEqualTo(1)
+ assertThat(composing2Counter).isEqualTo(1)
+ addSlots.value = true
+ }
+
+ rule.runOnIdle {
+ assertThat(composing1Counter).isEqualTo(1)
+ assertThat(composing2Counter).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun precomposedItemDisposedWhenSubcomposeLayoutIsDisposed() {
+ val emitLayout = mutableStateOf(true)
+ var enterCounter = 0
+ var exitCounter = 0
+ val state = SubcomposeLayoutState()
+ val content: @Composable () -> Unit = @Composable {
+ DisposableEffect(Unit) {
+ enterCounter++
+ onDispose {
+ exitCounter++
+ }
+ }
+ }
+
+ rule.setContent {
+ if (emitLayout.value) {
+ SubcomposeLayout(state) {
+ layout(10, 10) {}
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ state.precompose(Unit, content)
+ }
+
+ rule.runOnIdle {
+ assertThat(enterCounter).isEqualTo(1)
+ assertThat(exitCounter).isEqualTo(0)
+ emitLayout.value = false
+ }
+
+ rule.runOnIdle {
+ assertThat(exitCounter).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun precomposeIsNotTriggeringParentRemeasure() {
+ val state = SubcomposeLayoutState()
+
+ var measureCount = 0
+ var layoutCount = 0
+
+ rule.setContent {
+ SubcomposeLayout(state) {
+ measureCount++
+ layout(10, 10) {
+ layoutCount++
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(measureCount).isEqualTo(1)
+ assertThat(layoutCount).isEqualTo(1)
+ state.precompose(Unit) {
+ Box(Modifier.fillMaxSize())
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(measureCount).isEqualTo(1)
+ assertThat(layoutCount).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun precomposedItemDisposalIsNotTriggeringParentRemeasure() {
+ val state = SubcomposeLayoutState()
+
+ var measureCount = 0
+ var layoutCount = 0
+
+ rule.setContent {
+ SubcomposeLayout(state) {
+ measureCount++
+ layout(10, 10) {
+ layoutCount++
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(measureCount).isEqualTo(1)
+ assertThat(layoutCount).isEqualTo(1)
+ val handle = state.precompose(Unit) {
+ Box(Modifier.fillMaxSize())
+ }
+ handle.dispose()
+ }
+
+ rule.runOnIdle {
+ assertThat(measureCount).isEqualTo(1)
+ assertThat(layoutCount).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun slotsKeptForReuse() {
+ val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+ val state = SubcomposeLayoutState(2)
+
+ composeItems(state, items)
+
+ rule.runOnIdle {
+ items.value = listOf(2, 3)
+ }
+
+ assertNodes(
+ exists = /*active*/ listOf(2, 3) + /*reusable*/ listOf(1, 4),
+ doesNotExist = /*disposed*/ listOf(0)
+ )
+ }
+
+ @Test
+ fun newSlotIsUsingReusedSlot() {
+ val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+ val state = SubcomposeLayoutState(2)
+
+ composeItems(state, items)
+
+ rule.runOnIdle {
+ items.value = listOf(2, 3)
+ // 1 and 4 are now in reusable buffer
+ }
+
+ rule.runOnIdle {
+ items.value = listOf(2, 3, 5)
+ // the last reusable slot (4) will be used for composing 5
+ }
+
+ assertNodes(
+ exists = /*active*/ listOf(2, 3, 5) + /*reusable*/ listOf(1),
+ doesNotExist = /*disposed*/ listOf(0, 4)
+ )
+ }
+
+ @Test
+ fun theSameSlotIsUsedWhileItIsInReusableList() {
+ val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+ val state = SubcomposeLayoutState(2)
+
+ composeItems(state, items)
+
+ rule.runOnIdle {
+ items.value = listOf(2, 3)
+ // 1 and 4 are now in reusable buffer
+ }
+
+ rule.runOnIdle {
+ items.value = listOf(2, 3, 1)
+ // slot 1 should be taken back from reusable
+ }
+
+ assertNodes(
+ exists = /*active*/ listOf(2, 3, 1) + /*reusable*/ listOf(4)
+ )
+ }
+
+ @Test
+ fun prefetchIsUsingReusableNodes() {
+ val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+ val state = SubcomposeLayoutState(2)
+
+ composeItems(state, items)
+
+ rule.runOnIdle {
+ items.value = listOf(2, 3)
+ // 1 and 4 are now in reusable buffer
+ }
+
+ rule.runOnIdle {
+ state.precompose(5) {
+ ItemContent(5)
+ }
+ // prefetch should take slot 4 from reuse
+ }
+
+ assertNodes(
+ exists = /*active*/ listOf(2, 3) + /*prefetch*/ listOf(5) + /*reusable*/ listOf(1)
+ )
+ }
+
+ @Test
+ fun prefetchSlotWhichIsInReusableList() {
+ val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+ val state = SubcomposeLayoutState(3)
+
+ composeItems(state, items)
+
+ rule.runOnIdle {
+ items.value = listOf(2)
+ // 1, 3 and 4 are now in reusable buffer
+ }
+
+ rule.runOnIdle {
+ state.precompose(3) {
+ ItemContent(3)
+ }
+ // prefetch should take slot 3 from reuse
+ }
+
+ assertNodes(
+ exists = /*active*/ listOf(2) + /*prefetch*/ listOf(3) + /*reusable*/ listOf(1, 4),
+ doesNotExist = listOf(0)
+ )
+ }
+
+ @Test
+ fun nothingIsReusedWhenMaxSlotsAre0() {
+ val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+ val state = SubcomposeLayoutState(0)
+
+ composeItems(state, items)
+
+ rule.runOnIdle {
+ items.value = listOf(2, 4)
+ }
+
+ assertNodes(
+ exists = listOf(2, 4),
+ doesNotExist = listOf(0, 1, 3)
+ )
+ }
+
+ @Test
+ fun reuse1Node() {
+ val items = mutableStateOf(listOf(0, 1, 2, 3))
+ val state = SubcomposeLayoutState(1)
+
+ composeItems(state, items)
+
+ rule.runOnIdle {
+ items.value = listOf(0, 1)
+ }
+
+ assertNodes(
+ exists = /*active*/ listOf(0, 1) + /*reusable*/ listOf(3),
+ doesNotExist = /*disposed*/ listOf(2)
+ )
+ }
+
+ private fun composeItems(
+ state: SubcomposeLayoutState,
+ items: MutableState<List<Int>>
+ ) {
+ rule.setContent {
+ SubcomposeLayout(state) { constraints ->
+ items.value.forEach {
+ subcompose(it) {
+ ItemContent(it)
+ }.forEach {
+ it.measure(constraints)
+ }
+ }
+ layout(10, 10) {}
+ }
+ }
+ }
+
+ @Composable
+ private fun ItemContent(index: Int) {
+ Box(Modifier.fillMaxSize().testTag("$index"))
+ }
+
+ private fun assertNodes(exists: List<Int>, doesNotExist: List<Int> = emptyList()) {
+ exists.forEach {
+ rule.onNodeWithTag("$it")
+ .assertExists()
+ }
+ doesNotExist.forEach {
+ rule.onNodeWithTag("$it")
+ .assertDoesNotExist()
+ }
+ }
}
fun ImageBitmap.assertCenterPixelColor(expectedColor: Color) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
index fec9aaa..e0a35a3 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
@@ -18,9 +18,9 @@
import android.view.KeyEvent.ACTION_DOWN
import android.view.KeyEvent.ACTION_UP
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
-import androidx.compose.ui.input.key.KeyEventType.KeyUp
-import androidx.compose.ui.input.key.KeyEventType.Unknown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
+import androidx.compose.ui.input.key.KeyEventType.Companion.Unknown
/**
* The native Android [KeyEvent][NativeKeyEvent].
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
index 1284beb..7ebf120 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
@@ -43,6 +43,8 @@
@VisibleForTesting
internal val motionEventToComposePointerIdMap: MutableMap<Int, PointerId> = mutableMapOf()
+ private val pointers: MutableList<PointerInputEventData> = mutableListOf()
+
/**
* Converts a single [MotionEvent] from an Android event stream into a [PointerInputEvent], or
* null if the [MotionEvent.getActionMasked] is [ACTION_CANCEL].
@@ -76,12 +78,10 @@
else -> null
}
- // TODO(shepshapard): Avoid allocating for every event.
- val pointers: MutableList<PointerInputEventData> = mutableListOf()
+ pointers.clear()
// This converts the MotionEvent into a list of PointerInputEventData, and updates
// internal record keeping.
- @Suppress("NAME_SHADOWING")
for (i in 0 until motionEvent.pointerCount) {
pointers.add(
createPointerInputEventData(
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index ab7fbac..cdff2ff 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -19,7 +19,6 @@
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
-import android.graphics.Matrix
import android.graphics.Rect
import android.os.Build
import android.os.Looper
@@ -52,20 +51,21 @@
import androidx.compose.ui.autofill.unregisterCallback
import androidx.compose.ui.focus.FOCUS_TAG
import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusDirectionInternal
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.In
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Next
-import androidx.compose.ui.focus.FocusDirectionInternal.Out
-import androidx.compose.ui.focus.FocusDirectionInternal.Previous
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.In
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.FocusManagerImpl
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.CanvasHolder
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.setFrom
import androidx.compose.ui.hapticfeedback.AndroidHapticFeedback
import androidx.compose.ui.hapticfeedback.HapticFeedback
import androidx.compose.ui.input.key.Key.Companion.Back
@@ -76,7 +76,7 @@
import androidx.compose.ui.input.key.Key.Companion.DirectionUp
import androidx.compose.ui.input.key.Key.Companion.Tab
import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
import androidx.compose.ui.input.key.KeyInputModifier
import androidx.compose.ui.input.key.isShiftPressed
import androidx.compose.ui.input.key.key
@@ -163,21 +163,8 @@
val focusDirection = getFocusDirection(it)
if (focusDirection == null || it.type != KeyDown) return@KeyInputModifier false
- val focusMoveSuccess = with(focusManager) {
- when (focusDirection) {
- Up -> moveFocus(FocusDirection.Up)
- Down -> moveFocus(FocusDirection.Down)
- Left -> moveFocus(FocusDirection.Left)
- Right -> moveFocus(FocusDirection.Right)
- In -> moveFocusIn()
- Out -> moveFocusOut()
- Next -> moveFocus(FocusDirection.Next)
- Previous -> moveFocus(FocusDirection.Previous)
- }
- }
-
// Consume the key event if we moved focus.
- focusMoveSuccess
+ focusManager.moveFocus(focusDirection)
},
onPreviewKeyEvent = null
)
@@ -279,9 +266,9 @@
private var globalPosition: IntOffset = IntOffset.Zero
private val tmpPositionArray = intArrayOf(0, 0)
- private val tmpOffsetArray = floatArrayOf(0f, 0f)
private val viewToWindowMatrix = Matrix()
private val windowToViewMatrix = Matrix()
+ private val tmpCalculationMatrix = Matrix()
private var lastMatrixRecalculationAnimationTime = -1L
/**
@@ -633,7 +620,7 @@
accessibilityDelegate.onLayoutChange(layoutNode)
}
- override fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal? {
+ override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
return when (keyEvent.key) {
Tab -> if (keyEvent.isShiftPressed) Previous else Next
DirectionRight -> Right
@@ -796,23 +783,18 @@
override fun localToScreen(localPosition: Offset): Offset {
recalculateWindowPosition()
- val points = tmpOffsetArray
- points[0] = localPosition.x
- points[1] = localPosition.y
- viewToWindowMatrix.mapPoints(points)
+ val local = viewToWindowMatrix.map(localPosition)
return Offset(
- points[0] + windowPosition.x,
- points[1] + windowPosition.y
+ local.x + windowPosition.x,
+ local.y + windowPosition.y
)
}
override fun screenToLocal(positionOnScreen: Offset): Offset {
recalculateWindowPosition()
- val points = tmpOffsetArray
- points[0] = positionOnScreen.x - windowPosition.x
- points[1] = positionOnScreen.y - windowPosition.y
- windowToViewMatrix.mapPoints(points)
- return Offset(points[0], points[1])
+ val x = positionOnScreen.x - windowPosition.x
+ val y = positionOnScreen.y - windowPosition.y
+ return windowToViewMatrix.map(Offset(x, y))
}
private fun recalculateWindowPosition() {
@@ -839,7 +821,7 @@
private fun recalculateWindowViewTransforms() {
viewToWindowMatrix.reset()
transformMatrixToWindow(this, viewToWindowMatrix)
- viewToWindowMatrix.invert(windowToViewMatrix)
+ viewToWindowMatrix.invertTo(windowToViewMatrix)
}
override fun onCheckIsTextEditor(): Boolean = textInputServiceAndroid.isEditorFocused()
@@ -849,20 +831,12 @@
override fun calculateLocalPosition(positionInWindow: Offset): Offset {
recalculateWindowPosition()
- val points = tmpOffsetArray
- points[0] = positionInWindow.x
- points[1] = positionInWindow.y
- windowToViewMatrix.mapPoints(points)
- return Offset(points[0], points[1])
+ return windowToViewMatrix.map(positionInWindow)
}
override fun calculatePositionInWindow(localPosition: Offset): Offset {
recalculateWindowPosition()
- val points = tmpOffsetArray
- points[0] = localPosition.x
- points[1] = localPosition.y
- viewToWindowMatrix.mapPoints(points)
- return Offset(points[0], points[1])
+ return viewToWindowMatrix.map(localPosition)
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -922,6 +896,7 @@
* Q and later, AccessibilityInteractionController#findViewByAccessibilityId uses
* AccessibilityNodeIdManager and findViewByAccessibilityIdTraversal is only used by autofill.
*/
+ @Suppress("unused")
fun findViewByAccessibilityIdTraversal(accessibilityId: Int): View? {
try {
// AccessibilityInteractionController#findViewByAccessibilityId doesn't call this
@@ -965,6 +940,24 @@
}
}
+ /**
+ * Like [android.graphics.Matrix.preConcat], for a Compose [Matrix] that accepts an [other]
+ * [android.graphics.Matrix].
+ */
+ private fun Matrix.preConcat(other: android.graphics.Matrix) {
+ tmpCalculationMatrix.setFrom(other)
+ preTransform(tmpCalculationMatrix)
+ }
+
+ /**
+ * Like [android.graphics.Matrix.preTranslate], for a Compose [Matrix]
+ */
+ private fun Matrix.preTranslate(x: Float, y: Float) {
+ tmpCalculationMatrix.reset()
+ tmpCalculationMatrix.translate(x, y)
+ preTransform(tmpCalculationMatrix)
+ }
+
companion object {
private var systemPropertiesClass: Class<*>? = null
private var getBooleanMethod: Method? = null
@@ -1041,3 +1034,105 @@
view.defaultFocusHighlightEnabled = defaultFocusHighlightEnabled
}
}
+
+/**
+ * Sets this [Matrix] to be the result of this * [other]
+ */
+private fun Matrix.preTransform(other: Matrix) {
+ val v00 = dot(other, 0, this, 0)
+ val v01 = dot(other, 0, this, 1)
+ val v02 = dot(other, 0, this, 2)
+ val v03 = dot(other, 0, this, 3)
+ val v10 = dot(other, 1, this, 0)
+ val v11 = dot(other, 1, this, 1)
+ val v12 = dot(other, 1, this, 2)
+ val v13 = dot(other, 1, this, 3)
+ val v20 = dot(other, 2, this, 0)
+ val v21 = dot(other, 2, this, 1)
+ val v22 = dot(other, 2, this, 2)
+ val v23 = dot(other, 2, this, 3)
+ val v30 = dot(other, 3, this, 0)
+ val v31 = dot(other, 3, this, 1)
+ val v32 = dot(other, 3, this, 2)
+ val v33 = dot(other, 3, this, 3)
+ this[0, 0] = v00
+ this[0, 1] = v01
+ this[0, 2] = v02
+ this[0, 3] = v03
+ this[1, 0] = v10
+ this[1, 1] = v11
+ this[1, 2] = v12
+ this[1, 3] = v13
+ this[2, 0] = v20
+ this[2, 1] = v21
+ this[2, 2] = v22
+ this[2, 3] = v23
+ this[3, 0] = v30
+ this[3, 1] = v31
+ this[3, 2] = v32
+ this[3, 3] = v33
+}
+
+// Taken from Matrix.kt
+private fun dot(m1: Matrix, row: Int, m2: Matrix, column: Int): Float {
+ return m1[row, 0] * m2[0, column] +
+ m1[row, 1] * m2[1, column] +
+ m1[row, 2] * m2[2, column] +
+ m1[row, 3] * m2[3, column]
+}
+
+/**
+ * Sets [other] to be the inverse of this
+ */
+private fun Matrix.invertTo(other: Matrix) {
+ val a00 = this[0, 0]
+ val a01 = this[0, 1]
+ val a02 = this[0, 2]
+ val a03 = this[0, 3]
+ val a10 = this[1, 0]
+ val a11 = this[1, 1]
+ val a12 = this[1, 2]
+ val a13 = this[1, 3]
+ val a20 = this[2, 0]
+ val a21 = this[2, 1]
+ val a22 = this[2, 2]
+ val a23 = this[2, 3]
+ val a30 = this[3, 0]
+ val a31 = this[3, 1]
+ val a32 = this[3, 2]
+ val a33 = this[3, 3]
+ val b00 = a00 * a11 - a01 * a10
+ val b01 = a00 * a12 - a02 * a10
+ val b02 = a00 * a13 - a03 * a10
+ val b03 = a01 * a12 - a02 * a11
+ val b04 = a01 * a13 - a03 * a11
+ val b05 = a02 * a13 - a03 * a12
+ val b06 = a20 * a31 - a21 * a30
+ val b07 = a20 * a32 - a22 * a30
+ val b08 = a20 * a33 - a23 * a30
+ val b09 = a21 * a32 - a22 * a31
+ val b10 = a21 * a33 - a23 * a31
+ val b11 = a22 * a33 - a23 * a32
+ val det =
+ (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06)
+ if (det == 0.0f) {
+ return
+ }
+ val invDet = 1.0f / det
+ other[0, 0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet)
+ other[0, 1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet)
+ other[0, 2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet)
+ other[0, 3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet)
+ other[1, 0] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet)
+ other[1, 1] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet)
+ other[1, 2] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet)
+ other[1, 3] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet)
+ other[2, 0] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet)
+ other[2, 1] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet)
+ other[2, 2] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet)
+ other[2, 3] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet)
+ other[3, 0] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet)
+ other[3, 1] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet)
+ other[3, 2] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet)
+ other[3, 3] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet)
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index 29e2ebb..a11842b 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -1390,17 +1390,17 @@
// are covered and which nodes are not, so the currentSemanticsNodes is not
// up to date anymore.
// After the subtree events are sent, accessibility services will get the
- // current visible/invisible state. We also update our copy here so that our
- // incremental changes (represented by accessibility events) are consistent
+ // current visible/invisible state. We also try to do semantics tree diffing
+ // to send out the proper accessibility events and update our copy here so that
+ // our incremental changes (represented by accessibility events) are consistent
// with accessibility services. That is: change - notify - new change -
- // notify, if we don't update our copy here, we will combine change and new
- // change, which is missing finer-grained notification.
- // Note that we could update our copy before this delay by posting an update
- // copy runnable in onLayoutChange(a code version is in aosp/1553311), similar
- // to semanticsChangeChecker, but I think update copy after the subtree
- // change events are sent is more accurate because before accessibility
- // services receive subtree events, they are not aware of the subtree change.
- updateSemanticsNodesCopyAndPanes()
+ // notify, if we don't do the tree diffing and update our copy here, we will
+ // combine old change and new change, which is missing finer-grained
+ // notification.
+ if (!checkingForSemanticsChanges) {
+ checkingForSemanticsChanges = true
+ handler.post(semanticsChangeChecker)
+ }
}
subtreeChangedLayoutNodes.clear()
delay(SendRecurringAccessibilityEventsIntervalMillis)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputState.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputState.android.kt
index e8fc13b..aa9fbe2 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputState.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputState.android.kt
@@ -21,6 +21,7 @@
internal fun TextFieldValue.toExtractedText(): ExtractedText {
val res = ExtractedText()
res.text = text
+ res.startOffset = 0
res.partialEndOffset = text.length
res.partialStartOffset = -1 // -1 means full text
res.selectionStart = selection.min
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
index 8d3b70f..ef70243 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
@@ -55,7 +55,7 @@
@VisibleForTesting
internal var mTextFieldValue: TextFieldValue = initState
set(value) {
- if (DEBUG) { Log.d(TAG, "New InputState has set: $value -> $mTextFieldValue") }
+ if (DEBUG) { Log.d(TAG, "RecordingInputConnection.mTextFieldValue : $field -> $value") }
field = value
}
@@ -74,6 +74,9 @@
*/
private var extractedTextMonitorMode = false
+ // The recoding editing ops.
+ private val editCommands = mutableListOf<EditCommand>()
+
/**
* Updates the input state and tells it to the IME.
*
@@ -81,7 +84,7 @@
* contents has changed if needed.
*/
fun updateInputState(state: TextFieldValue, imm: InputMethodManager, view: View) {
- if (DEBUG) { Log.d(TAG, "updateInputState: $state") }
+ if (DEBUG) { Log.d(TAG, "RecordingInputConnection.updateInputState: $state") }
mTextFieldValue = state
if (extractedTextMonitorMode) {
@@ -94,7 +97,7 @@
if (DEBUG) {
Log.d(
TAG,
- "updateSelection(" +
+ "RecordingInputConnection.updateSelection(" +
"selection = (${state.selection.min},${state.selection.max}), " +
"composition = ($compositionStart, $compositionEnd)"
)
@@ -104,9 +107,6 @@
)
}
- // The recoding editing ops.
- private val editCommands = mutableListOf<EditCommand>()
-
// Add edit op to internal list with wrapping batch edit.
private fun addEditCommandWithBatch(editCommand: EditCommand) {
beginBatchEdit()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
index ccb0f0d..f58d72c 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
@@ -181,11 +181,15 @@
}
override fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue) {
- if (DEBUG) { Log.d(TAG, "InputService.updateState: $oldValue -> $newValue") }
+ if (DEBUG) {
+ Log.d(TAG, "TextInputServiceAndroid.updateState called: $oldValue -> $newValue")
+ }
// update the latest TextFieldValue in InputConnection
ic?.mTextFieldValue = newValue
+ if (DEBUG) { Log.d(TAG, "TextInputServiceAndroid.updateState early return") }
+
if (oldValue == newValue) return
this.state = newValue
@@ -196,7 +200,9 @@
(it.selection == newValue.selection && it.composition != newValue.composition)
} ?: false
- if (DEBUG) { Log.d(TAG, "InputService.updateState: $state / $restartInput(restartInput) ") }
+ if (DEBUG) {
+ Log.d(TAG, "TextInputServiceAndroid.updateState: restart($restartInput), state: $state")
+ }
if (restartInput) {
restartInput()
@@ -279,9 +285,9 @@
// TextView.java#setInputTypeSingleLine
outInfo.inputType = outInfo.inputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE
- // adding this flag caused b/171598334, leaving here on purpose for future reference
- // TextView.java#onCreateInputConnection
- // outInfo.imeOptions = outInfo.imeOptions or EditorInfo.IME_FLAG_NO_ENTER_ACTION
+ if (imeOptions.imeAction == ImeAction.Default) {
+ outInfo.imeOptions = outInfo.imeOptions or EditorInfo.IME_FLAG_NO_ENTER_ACTION
+ }
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
index f83d3e3..5c7561e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
@@ -60,25 +60,39 @@
*/
fun moveFocus(focusDirection: FocusDirection): Boolean
- // TODO(b/184086802): In 2.0, after deleting FocusDirectionInternal and adding "In" to
- // FocusDirection, deprecate and remove this function.
/**
* Moves focus to one of the children of the currently focused item.
*
- * @return true if focus was moved successfully.
- */
- @ExperimentalComposeUiApi
- fun moveFocusIn(): Boolean
-
- // TODO(b/184086802): In 2.0, after deleting FocusDirectionInternal and adding "Out" to
- // FocusDirection, deprecate and remove this function.
- /**
- * Moves focus to the nearest focusable parent of the currently focused item.
+ * This function is deprecated. Use FocusManager.moveFocus(FocusDirection.In) instead.
*
* @return true if focus was moved successfully.
*/
@ExperimentalComposeUiApi
- fun moveFocusOut(): Boolean
+ @Deprecated(
+ message = "Use FocusManager.moveFocus(FocusDirection.In) instead",
+ ReplaceWith(
+ "moveFocus(In)",
+ "androidx.compose.ui.focus.FocusDirection.Companion.In"
+ )
+ )
+ fun moveFocusIn(): Boolean = false
+
+ /**
+ * Moves focus to the nearest focusable parent of the currently focused item.
+ *
+ * This function is deprecated. Use FocusManager.moveFocus(FocusDirection.Out) instead.
+ *
+ * @return true if focus was moved successfully.
+ */
+ @ExperimentalComposeUiApi
+ @Deprecated(
+ message = "Use FocusManager.moveFocus(FocusDirection.Out) instead",
+ ReplaceWith(
+ "moveFocus(Out)",
+ "androidx.compose.ui.focus.FocusDirection.Companion.Out"
+ )
+ )
+ fun moveFocusOut(): Boolean = false
}
/**
@@ -173,35 +187,7 @@
* @return true if focus was moved successfully. false if the focused item is unchanged.
*/
override fun moveFocus(focusDirection: FocusDirection): Boolean {
- val internalFocusDirection = when (focusDirection) {
- FocusDirection.Next -> FocusDirectionInternal.Next
- FocusDirection.Previous -> FocusDirectionInternal.Previous
- FocusDirection.Left -> FocusDirectionInternal.Left
- FocusDirection.Right -> FocusDirectionInternal.Right
- FocusDirection.Up -> FocusDirectionInternal.Up
- FocusDirection.Down -> FocusDirectionInternal.Down
- }
- return focusModifier.focusNode.moveFocus(internalFocusDirection)
- }
-
- /**
- * Moves focus to one of the children of the currently focused item.
- *
- * @return true if focus was moved successfully.
- */
- @ExperimentalComposeUiApi
- override fun moveFocusIn(): Boolean {
- return focusModifier.focusNode.moveFocus(FocusDirectionInternal.In)
- }
-
- /**
- * Moves focus to the nearest focusable parent of the currently focused item.
- *
- * @return true if focus was moved successfully.
- */
- @ExperimentalComposeUiApi
- override fun moveFocusOut(): Boolean {
- return focusModifier.focusNode.moveFocus(FocusDirectionInternal.Out)
+ return focusModifier.focusNode.moveFocus(focusDirection)
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
index 8a902f0..66cd24e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
@@ -16,14 +16,15 @@
package androidx.compose.ui.focus
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.In
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Next
-import androidx.compose.ui.focus.FocusDirectionInternal.Out
-import androidx.compose.ui.focus.FocusDirectionInternal.Previous
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.In
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.focus.FocusRequester.Companion.Default
import androidx.compose.ui.focus.FocusState.Active
import androidx.compose.ui.focus.FocusState.ActiveParent
@@ -35,20 +36,84 @@
import androidx.compose.ui.unit.LayoutDirection.Ltr
import androidx.compose.ui.unit.LayoutDirection.Rtl
-/**
- * This enum specifies the direction of the requested focus change.
- */
-enum class FocusDirection { Next, Previous, Left, Right, Up, Down }
+private const val invalidFocusDirection = "Invalid FocusDirection"
-// TODO(b/184086802): In 2.0, delete FocusDirectionInternal and add In and Out to FocusDirection.
/**
- * This enum specifies the direction of the requested focus change.
+ * The [FocusDirection] is used to specify direction when a focus change request is made via
+ * [moveFocus].
*
- * This enum is similar to [FocusDirection], but it contains the additional values [In] which can
- * be used to move focus from a parent to one of its children, and [Out] which moves focus to
- * the parent.
+ * @sample androidx.compose.ui.samples.MoveFocusSample
*/
-internal enum class FocusDirectionInternal { Next, Previous, Left, Right, Up, Down, In, Out }
+inline class FocusDirection(val value: Int) {
+
+ override fun toString(): String {
+ return when (this) {
+ Next -> "Next"
+ Previous -> "Previous"
+ Left -> "Left"
+ Right -> "Right"
+ Up -> "Up"
+ Down -> "Down"
+ @OptIn(ExperimentalComposeUiApi::class)
+ In -> "In"
+ @OptIn(ExperimentalComposeUiApi::class)
+ Out -> "Out"
+ else -> invalidFocusDirection
+ }
+ }
+
+ companion object {
+ /**
+ * Direction used in [moveFocus] to indicate that you are searching for the next
+ * focusable item.
+ */
+ val Next: FocusDirection = FocusDirection(1)
+
+ /**
+ * Direction used in [moveFocus] to indicate that you are searching for the previous
+ * focusable item.
+ */
+ val Previous: FocusDirection = FocusDirection(2)
+
+ /**
+ * Direction used in [moveFocus] to indicate that you are searching for the next
+ * focusable item to the left of the currently focused item.
+ */
+ val Left: FocusDirection = FocusDirection(3)
+
+ /**
+ * Direction used in [moveFocus] to indicate that you are searching for the next
+ * focusable item to the right of the currently focused item.
+ */
+ val Right: FocusDirection = FocusDirection(4)
+
+ /**
+ * Direction used in [moveFocus] to indicate that you are searching for the next
+ * focusable item that is above the currently focused item.
+ */
+ val Up: FocusDirection = FocusDirection(5)
+
+ /**
+ * Direction used in [moveFocus] to indicate that you are searching for the next
+ * focusable item that is below the currently focused item.
+ */
+ val Down: FocusDirection = FocusDirection(6)
+
+ /**
+ * Direction used in [moveFocus] to indicate that you are searching for the next
+ * focusable item that is a child of the currently focused item.
+ */
+ @ExperimentalComposeUiApi
+ val In: FocusDirection = FocusDirection(7)
+
+ /**
+ * Direction used in [moveFocus] to indicate that you want to move focus to the parent
+ * of the currently focused item.
+ */
+ @ExperimentalComposeUiApi
+ val Out: FocusDirection = FocusDirection(8)
+ }
+}
/**
* Moves focus based on the requested focus direction.
@@ -57,7 +122,7 @@
* @return whether a focus node was found. If a focus node was found and the focus request was
* not granted, this function still returns true.
*/
-internal fun ModifiedFocusNode.moveFocus(focusDirection: FocusDirectionInternal): Boolean {
+internal fun ModifiedFocusNode.moveFocus(focusDirection: FocusDirection): Boolean {
// TODO(b/176847718): Pass the layout direction as a parameter instead of this hardcoded value.
val layoutDirection: LayoutDirection = Ltr
@@ -81,12 +146,15 @@
val nextNode = when (focusDirection) {
Next, Previous -> null // TODO(b/170155659): Perform one dimensional focus search.
Left, Right, Up, Down -> twoDimensionalFocusSearch(focusDirection)
+ @OptIn(ExperimentalComposeUiApi::class)
In -> {
// we search among the children of the active item.
val direction = when (layoutDirection) { Rtl -> Left; Ltr -> Right }
activeNode.twoDimensionalFocusSearch(direction)
}
+ @OptIn(ExperimentalComposeUiApi::class)
Out -> activeNode.findParentFocusNode()
+ else -> error(invalidFocusDirection)
} ?: return false
// If we found a potential next item, call requestFocus() to move focus to it.
@@ -108,7 +176,7 @@
* children.
*/
private fun ModifiedFocusNode.customFocusSearch(
- focusDirection: FocusDirectionInternal,
+ focusDirection: FocusDirection,
layoutDirection: LayoutDirection
): FocusRequester {
val focusOrder = FocusOrder()
@@ -132,6 +200,10 @@
// the user presses dPad center. (They can also redirect the "In" to some other item).
// Developers can specify a custom "Out" to specify which composable should take focus
// when the user presses the back button.
- In, Out -> Default
+ @OptIn(ExperimentalComposeUiApi::class)
+ In -> Default
+ @OptIn(ExperimentalComposeUiApi::class)
+ Out -> Default
+ else -> error(invalidFocusDirection)
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
index 1fbe574..c5d8b9e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
@@ -16,10 +16,10 @@
package androidx.compose.ui.focus
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.focus.FocusState.Active
import androidx.compose.ui.focus.FocusState.ActiveParent
import androidx.compose.ui.focus.FocusState.Captured
@@ -36,13 +36,13 @@
/**
* Perform a search among the immediate children of this [node][ModifiedFocusNode] in the
- * specified [direction][FocusDirectionInternal] and return the node that is to be focused next. If one
+ * specified [direction][FocusDirection] and return the node that is to be focused next. If one
* of the children is currently focused, we start from that point and search in the specified
- * [direction][FocusDirectionInternal]. If none of the children are currently focused, we pick the
- * top-left or bottom right based on the specified [direction][FocusDirectionInternal].
+ * [direction][FocusDirection]. If none of the children are currently focused, we pick the
+ * top-left or bottom right based on the specified [direction][FocusDirection].
*/
internal fun ModifiedFocusNode.twoDimensionalFocusSearch(
- direction: FocusDirectionInternal
+ direction: FocusDirection
): ModifiedFocusNode? {
return when (focusState) {
Inactive -> this
@@ -89,7 +89,7 @@
// valid candidates.
private fun List<ModifiedFocusNode>.findBestCandidate(
focusRect: Rect,
- direction: FocusDirectionInternal
+ direction: FocusDirection
): ModifiedFocusNode? {
// Pick an impossible rectangle as the initial best candidate Rect.
var bestCandidate = when (direction) {
@@ -118,7 +118,7 @@
proposedCandidate: Rect,
currentCandidate: Rect,
focusedRect: Rect,
- direction: FocusDirectionInternal
+ direction: FocusDirection
): Boolean {
// Is this Rect a candidate for the next focus given the direction? This checks whether the
@@ -191,7 +191,7 @@
source: Rect,
rect1: Rect,
rect2: Rect,
- direction: FocusDirectionInternal
+ direction: FocusDirection
): Boolean {
// Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
fun Rect.inSourceBeam() = when (direction) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
index 088b52f..c14d519 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
@@ -79,19 +79,31 @@
/**
* The type of Key Event.
*/
-enum class KeyEventType {
- /**
- * Unknown key event.
- */
- Unknown,
+inline class KeyEventType(val value: Int) {
- /**
- * Type of KeyEvent sent when the user lifts their finger off a key on the keyboard.
- */
- KeyUp,
+ override fun toString(): String {
+ return when (this) {
+ KeyUp -> "KeyUp"
+ KeyDown -> "KeyDown"
+ Unknown -> "Unknown"
+ else -> "Invalid"
+ }
+ }
- /**
- * Type of KeyEvent sent when the user presses down their finger on a key on the keyboard.
- */
- KeyDown
+ companion object {
+ /**
+ * Unknown key event.
+ */
+ val Unknown: KeyEventType = KeyEventType(0)
+
+ /**
+ * Type of KeyEvent sent when the user lifts their finger off a key on the keyboard.
+ */
+ val KeyUp: KeyEventType = KeyEventType(1)
+
+ /**
+ * Type of KeyEvent sent when the user presses down their finger on a key on the keyboard.
+ */
+ val KeyDown: KeyEventType = KeyEventType(2)
+ }
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
index ea55590..8bf9f48 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
@@ -16,6 +16,8 @@
package androidx.compose.ui.input.pointer
+import androidx.compose.runtime.collection.MutableVector
+import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.node.InternalCoreApi
@@ -32,23 +34,6 @@
/*@VisibleForTesting*/
internal val root: NodeParent = NodeParent()
- private val retainedHitPaths: MutableSet<PointerId> = mutableSetOf()
-
- internal interface DispatchChangesRetVal {
- operator fun component1(): InternalPointerEvent
- operator fun component2(): Boolean
- }
-
- private class DispatchChangesRetValImpl : DispatchChangesRetVal {
- lateinit var internalPointerEvent: InternalPointerEvent
- var wasDispatchedToSomething: Boolean = false
- override operator fun component1() = internalPointerEvent
- override operator fun component2() = wasDispatchedToSomething
- }
-
- // See https://youtrack.jetbrains.com/issue/KT-39905.
- private val dispatchChangesRetVal = DispatchChangesRetValImpl()
-
/**
* Associates a [pointerId] to a list of hit [pointerInputFilters] and keeps track of them.
*
@@ -67,9 +52,11 @@
eachPin@ for (i in pointerInputFilters.indices) {
val pointerInputFilter = pointerInputFilters[i]
if (merging) {
- val node = parent.children.find { it.pointerInputFilter == pointerInputFilter }
+ val node = parent.children.firstOrNull {
+ it.pointerInputFilter == pointerInputFilter
+ }
if (node != null) {
- node.pointerIds.add(pointerId)
+ if (pointerId !in node.pointerIds) node.pointerIds.add(pointerId)
parent = node
continue@eachPin
} else {
@@ -89,26 +76,17 @@
* therefore no longer associated with any pointer ids.
*/
fun removeHitPath(pointerId: PointerId) {
- removeHitPathInternal(pointerId)
- removeHitPathInternal(pointerId)
+ root.recursivelyRemovePointerId(pointerId)
}
/**
* Dispatches [internalPointerEvent] through the hierarchy.
*
- * Returns a [DispatchChangesRetVal] that should not be referenced directly, but instead
- * should be destrutured immediately. Each instance of [HitPathTracker] reuses a single
- * [DispatchChangesRetVal] and mutates it for each return for performance reasons.
- *
- * [DispatchChangesRetVal.component1] references the resulting changes after dispatch.
- * [DispatchChangesRetVal.component2] is true if the dispatch reached at least one
- * [PointerInputModifier].
- *
* @param internalPointerEvent The change to dispatch.
*
- * @return The DispatchChangesRetVal that should be destructured immediately.
+ * @return whether this event was dispatched to a [PointerInputFilter]
*/
- fun dispatchChanges(internalPointerEvent: InternalPointerEvent): DispatchChangesRetVal {
+ fun dispatchChanges(internalPointerEvent: InternalPointerEvent): Boolean {
var dispatchHit = root.dispatchMainEventPass(
internalPointerEvent.changes,
rootCoordinates,
@@ -116,9 +94,7 @@
)
dispatchHit = root.dispatchFinalEventPass() || dispatchHit
- dispatchChangesRetVal.wasDispatchedToSomething = dispatchHit
- dispatchChangesRetVal.internalPointerEvent = internalPointerEvent
- return dispatchChangesRetVal
+ return dispatchHit
}
/**
@@ -128,7 +104,6 @@
* data.
*/
fun processCancel() {
- retainedHitPaths.clear()
root.dispatchCancel()
root.clear()
}
@@ -142,13 +117,6 @@
fun removeDetachedPointerInputFilters() {
root.removeDetachedPointerInputFilters()
}
-
- /**
- * Actually removes hit paths.
- */
- private fun removeHitPathInternal(pointerId: PointerId) {
- root.removePointerId(pointerId)
- }
}
/**
@@ -159,7 +127,7 @@
/*@VisibleForTesting*/
@OptIn(InternalCoreApi::class)
internal open class NodeParent {
- val children: MutableSet<Node> = mutableSetOf()
+ val children: MutableVector<Node> = mutableVectorOf()
/**
* Dispatches [changes] down the tree, for the initial and main pass.
@@ -221,54 +189,33 @@
* Removes all child [Node]s that are no longer attached to the compose tree.
*/
fun removeDetachedPointerInputFilters() {
- children.removeAndProcess(
- removeIf = {
- !it.pointerInputFilter.isAttached
- },
- ifRemoved = {
- it.dispatchCancel()
- },
- ifKept = {
- it.removeDetachedPointerInputFilters()
+ var index = 0
+ while (index < children.size) {
+ val child = children[index]
+ if (!child.pointerInputFilter.isAttached) {
+ children.removeAt(index)
+ child.dispatchCancel()
+ } else {
+ index++
+ child.removeDetachedPointerInputFilters()
}
- )
+ }
}
/**
* Removes the tracking of [pointerId] and removes all child [Node]s that are no longer
- * tracking
- * any [PointerId]s.
+ * tracking any [PointerId]s.
*/
- fun removePointerId(pointerId: PointerId) {
- children.forEach {
- it.pointerIds.remove(pointerId)
- }
- children.removeAll {
- it.pointerIds.isEmpty()
- }
- children.forEach {
- it.removePointerId(pointerId)
- }
- }
-
- /**
- * With each item, if calling [removeIf] with it is true, removes the item from [this] and calls
- * [ifRemoved] with it, otherwise calls [ifKept] with it.
- */
- private fun <T> MutableIterable<T>.removeAndProcess(
- removeIf: (T) -> Boolean,
- ifRemoved: (T) -> Unit,
- ifKept: (T) -> Unit
- ) {
- with(iterator()) {
- while (hasNext()) {
- val next = next()
- if (removeIf(next)) {
- remove()
- ifRemoved(next)
- } else {
- ifKept(next)
- }
+ fun recursivelyRemovePointerId(pointerId: PointerId) {
+ var index = 0
+ while (index < children.size) {
+ val child = children[index]
+ child.pointerIds.remove(pointerId)
+ if (child.pointerIds.isEmpty()) {
+ children.removeAt(index)
+ } else {
+ child.recursivelyRemovePointerId(pointerId)
+ index++
}
}
}
@@ -282,7 +229,13 @@
@OptIn(InternalCoreApi::class)
internal class Node(val pointerInputFilter: PointerInputFilter) : NodeParent() {
- val pointerIds: MutableSet<PointerId> = mutableSetOf()
+ // Note: this is essentially a set, and writes should be guarded accordingly. We use a
+ // MutableVector here instead since a set ends up being quite heavy, and calls to
+ // set.contains() show up noticeably (~1%) in traces. Since the maximum size of this vector
+ // is small (due to the limited amount of concurrent PointerIds there _could_ be), iterating
+ // through the small vector in most cases should have a lower performance impact than using a
+ // set.
+ val pointerIds: MutableVector<PointerId> = mutableVectorOf()
/**
* Cached properties that will be set before the main event pass, and reset after the final
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
index 54db299..0278801 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
@@ -34,6 +34,7 @@
private val hitPathTracker = HitPathTracker(root.coordinates)
private val pointerInputChangeEventProducer = PointerInputChangeEventProducer()
+ private val hitResult: MutableList<PointerInputFilter> = mutableListOf()
/**
* Receives [PointerInputEvent]s and process them through the tree rooted on [root].
@@ -54,44 +55,41 @@
val internalPointerEvent =
pointerInputChangeEventProducer.produce(pointerEvent, positionCalculator)
- // TODO(shepshapard): Create fast forEach for maps?
-
// Add new hit paths to the tracker due to down events.
- internalPointerEvent
- .changes
- .filter { (_, pointerInputChange) -> pointerInputChange.changedToDownIgnoreConsumed() }
- .forEach { (_, pointerInputChange) ->
- val hitResult: MutableList<PointerInputFilter> = mutableListOf()
+ internalPointerEvent.changes.values.forEach { pointerInputChange ->
+ if (pointerInputChange.changedToDownIgnoreConsumed()) {
root.hitTest(
pointerInputChange.position,
hitResult
)
if (hitResult.isNotEmpty()) {
hitPathTracker.addHitPath(pointerInputChange.id, hitResult)
+ hitResult.clear()
}
}
+ }
// Remove [PointerInputFilter]s that are no longer valid and refresh the offset information
// for those that are.
hitPathTracker.removeDetachedPointerInputFilters()
// Dispatch to PointerInputFilters
- val (resultingChanges, dispatchedToSomething) =
- hitPathTracker.dispatchChanges(internalPointerEvent)
+ val dispatchedToSomething = hitPathTracker.dispatchChanges(internalPointerEvent)
- // Remove hit paths from the tracker due to up events.
- internalPointerEvent
- .changes
- .filter { (_, pointerInputChange) -> pointerInputChange.changedToUpIgnoreConsumed() }
- .forEach { (_, pointerInputChange) ->
+ var anyMovementConsumed = false
+
+ // Remove hit paths from the tracker due to up events, and calculate if we have consumed
+ // any movement
+ internalPointerEvent.changes.values.forEach { pointerInputChange ->
+ if (pointerInputChange.changedToUpIgnoreConsumed()) {
hitPathTracker.removeHitPath(pointerInputChange.id)
}
+ if (pointerInputChange.positionChangeConsumed()) {
+ anyMovementConsumed = true
+ }
+ }
- // TODO(shepshapard): Don't allocate on every call.
- return ProcessResult(
- dispatchedToSomething,
- resultingChanges.changes.any { (_, value) -> value.positionChangeConsumed() }
- )
+ return ProcessResult(dispatchedToSomething, anyMovementConsumed)
}
/**
@@ -120,7 +118,9 @@
*/
fun produce(pointerInputEvent: PointerInputEvent, positionCalculator: PositionCalculator):
InternalPointerEvent {
- val changes: MutableMap<PointerId, PointerInputChange> = mutableMapOf()
+ val changes: MutableMap<PointerId, PointerInputChange> =
+ // Set initial capacity to avoid resizing - we know the size the map will be.
+ LinkedHashMap(pointerInputEvent.pointers.size)
pointerInputEvent.pointers.fastForEach {
val previousTime: Long
val previousPosition: Offset
@@ -154,8 +154,7 @@
previousPointerInputData[it.id] = PointerInputData(
it.uptime,
it.positionOnScreen,
- it.down,
- it.type
+ it.down
)
} else {
previousPointerInputData.remove(it.id)
@@ -167,15 +166,14 @@
/**
* Clears all tracked information.
*/
- internal fun clear() {
+ fun clear() {
previousPointerInputData.clear()
}
private class PointerInputData(
val uptime: Long,
val positionOnScreen: Offset,
- val down: Boolean,
- val type: PointerType
+ val down: Boolean
)
}
@@ -183,7 +181,6 @@
* The result of a call to [PointerInputEventProcessor.process].
*/
// TODO(shepshpard): Not sure if storing these values in a int is most efficient overall.
-@Suppress("EXPERIMENTAL_FEATURE_WARNING")
internal inline class ProcessResult(private val value: Int) {
val dispatchedToAPointerInputModifier
get() = (value and 1) != 0
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
index aef6f62..53cccdb 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
@@ -147,7 +147,8 @@
* [pointerInput] [block]s may call [PointerInputScope.awaitPointerEventScope] to install a pointer
* input handler that can [AwaitPointerEventScope.awaitPointerEvent] to receive and consume
* pointer input events. Extension functions on [PointerInputScope] or [AwaitPointerEventScope]
- * may be defined to perform higher-level gesture detection.
+ * may be defined to perform higher-level gesture detection. The pointer input handling [block]
+ * will be cancelled and **re-started** when [pointerInput] is recomposed with a different [key1].
*/
fun Modifier.pointerInput(
key1: Any?,
@@ -174,7 +175,9 @@
* [pointerInput] [block]s may call [PointerInputScope.awaitPointerEventScope] to install a pointer
* input handler that can [AwaitPointerEventScope.awaitPointerEvent] to receive and consume
* pointer input events. Extension functions on [PointerInputScope] or [AwaitPointerEventScope]
- * may be defined to perform higher-level gesture detection.
+ * may be defined to perform higher-level gesture detection. The pointer input handling [block]
+ * will be cancelled and **re-started** when [pointerInput] is recomposed with a different [key1] or
+ * [key2].
*/
fun Modifier.pointerInput(
key1: Any?,
@@ -203,7 +206,8 @@
* [pointerInput] [block]s may call [PointerInputScope.awaitPointerEventScope] to install a pointer
* input handler that can [AwaitPointerEventScope.awaitPointerEvent] to receive and consume
* pointer input events. Extension functions on [PointerInputScope] or [AwaitPointerEventScope]
- * may be defined to perform higher-level gesture detection.
+ * may be defined to perform higher-level gesture detection. The pointer input handling [block]
+ * will be cancelled and **re-started** when [pointerInput] is recomposed with any different [keys].
*/
fun Modifier.pointerInput(
vararg keys: Any?,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index 335f16c..b15b091 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -20,7 +20,7 @@
import androidx.compose.runtime.Applier
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ComposeNode
+import androidx.compose.runtime.ReusableComposeNode
import androidx.compose.runtime.SkippableUpdater
import androidx.compose.runtime.currentComposer
import androidx.compose.ui.Modifier
@@ -71,7 +71,7 @@
) {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
- ComposeNode<ComposeUiNode, Applier<Any>>(
+ ReusableComposeNode<ComposeUiNode, Applier<Any>>(
factory = ComposeUiNode.Constructor,
update = {
set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
@@ -192,7 +192,7 @@
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
- ComposeNode<LayoutNode, Applier<Any>>(
+ ReusableComposeNode<LayoutNode, Applier<Any>>(
factory = LayoutNode.Constructor,
update = {
set(materialized, ComposeUiNode.SetModifier)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index 6c6820c1..92a69d7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -60,7 +60,39 @@
modifier: Modifier = Modifier,
measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
) {
- val state = remember { SubcomposeLayoutState() }
+ SubcomposeLayout(
+ state = remember { SubcomposeLayoutState() },
+ modifier = modifier,
+ measurePolicy = measurePolicy
+ )
+}
+
+/**
+ * Analogue of [Layout] which allows to subcompose the actual content during the measuring stage
+ * for example to use the values calculated during the measurement as params for the composition
+ * of the children.
+ *
+ * Possible use cases:
+ * * You need to know the constraints passed by the parent during the composition and can't solve
+ * your use case with just custom [Layout] or [LayoutModifier].
+ * See [androidx.compose.foundation.layout.BoxWithConstraints].
+ * * You want to use the size of one child during the composition of the second child.
+ * * You want to compose your items lazily based on the available size. For example you have a
+ * list of 100 items and instead of composing all of them you only compose the ones which are
+ * currently visible(say 5 of them) and compose next items when the component is scrolled.
+ *
+ * @sample androidx.compose.ui.samples.SubcomposeLayoutSample
+ *
+ * @param state the state object to be used by the layout.
+ * @param modifier [Modifier] to apply for the layout.
+ * @param measurePolicy Measure policy which provides ability to subcompose during the measuring.
+ */
+@Composable
+fun SubcomposeLayout(
+ state: SubcomposeLayoutState,
+ modifier: Modifier = Modifier,
+ measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
+) {
state.compositionContext = rememberCompositionContext()
DisposableEffect(state) {
onDispose {
@@ -103,34 +135,67 @@
}
/**
- * Contains the state used by [SubcomposeLayout].
+ * State used by [SubcomposeLayout].
+ *
+ * @param maxSlotsToRetainForReuse when non-zero the layout will keep active up to this count
+ * slots which we were used but not used anymore instead of disposing them. Later when you try to
+ * compose a new slot instead of creating a completely new slot the layout would reuse the
+ * previous slot which allows to do less work especially if the slot contents are similar.
*/
-internal class SubcomposeLayoutState {
+class SubcomposeLayoutState(
+ private val maxSlotsToRetainForReuse: Int
+) {
+ /**
+ * State used by [SubcomposeLayout].
+ */
+ constructor() : this(0)
+
internal var compositionContext: CompositionContext? = null
// Pre-allocated lambdas to update LayoutNode
- internal val setRoot: LayoutNode.() -> Unit = { root = this }
+ internal val setRoot: LayoutNode.() -> Unit = { _root = this }
internal val setMeasurePolicy:
LayoutNode.(SubcomposeMeasureScope.(Constraints) -> MeasureResult) -> Unit =
{ measurePolicy = createMeasurePolicy(it) }
// inner state
- private var root: LayoutNode? = null
+ private var _root: LayoutNode? = null
+ private val root: LayoutNode get() = requireNotNull(_root)
private var currentIndex = 0
private val nodeToNodeState = mutableMapOf<LayoutNode, NodeState>()
- private val slodIdToNode = mutableMapOf<Any?, LayoutNode>()
+ // this map contains active slotIds (without precomposed or reusable nodes)
+ private val slotIdToNode = mutableMapOf<Any?, LayoutNode>()
private val scope = Scope()
+ private val precomposeMap = mutableMapOf<Any?, LayoutNode>()
+
+ /**
+ * `root.foldedChildren` list consist of:
+ * 1) all the active children (used during the last measure pass)
+ * 2) `reusableCount` nodes in the middle of the list which were active and stopped being
+ * used. now we keep them (up to `maxCountOfSlotsToReuse`) in order to reuse next time we
+ * will need to compose a new item
+ * 4) `precomposedCount` nodes in the end of the list which were precomposed and
+ * are waiting to be used during the next measure passes.
+ */
+ private var reusableCount = 0
+ private var precomposedCount = 0
internal fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable> {
- val root = root!!
val layoutState = root.layoutState
check(layoutState == LayoutState.Measuring || layoutState == LayoutState.LayingOut) {
"subcompose can only be used inside the measure or layout blocks"
}
- val node = slodIdToNode.getOrPut(slotId) {
- LayoutNode(isVirtual = true).also {
- root.insertAt(currentIndex, it)
+ val node = slotIdToNode.getOrPut(slotId) {
+ val precomposed = precomposeMap.remove(slotId)
+ if (precomposed != null) {
+ check(precomposedCount > 0)
+ precomposedCount--
+ precomposed
+ } else if (reusableCount > 0) {
+ takeNodeFromReusables(slotId)
+ } else {
+ createNodeAt(currentIndex)
}
}
@@ -141,10 +206,15 @@
)
}
if (currentIndex != itemIndex) {
- root.move(itemIndex, currentIndex, 1)
+ move(itemIndex, currentIndex)
}
currentIndex++
+ subcompose(node, slotId, content)
+ return node.children
+ }
+
+ private fun subcompose(node: LayoutNode, slotId: Any?, content: @Composable () -> Unit) {
val nodeState = nodeToNodeState.getOrPut(node) {
NodeState(slotId, {})
}
@@ -153,21 +223,22 @@
nodeState.content = content
subcompose(node, nodeState)
}
- return node.children
}
private fun subcompose(node: LayoutNode, nodeState: NodeState) {
node.withNoSnapshotReadObservation {
- val content = nodeState.content
- nodeState.composition = subcomposeInto(
- existing = nodeState.composition,
- container = node,
- parent = compositionContext ?: error("parent composition reference not set"),
- // Do not optimize this by passing nodeState.content directly; the additional
- // composable function call from the lambda expression affects the scope of
- // recomposition and recomposition of siblings.
- composable = { content() }
- )
+ ignoreRemeasureRequests {
+ val content = nodeState.content
+ nodeState.composition = subcomposeInto(
+ existing = nodeState.composition,
+ container = node,
+ parent = compositionContext ?: error("parent composition reference not set"),
+ // Do not optimize this by passing nodeState.content directly; the additional
+ // composable function call from the lambda expression affects the scope of
+ // recomposition and recomposition of siblings.
+ composable = { content() }
+ )
+ }
}
}
@@ -188,14 +259,64 @@
}
private fun disposeAfterIndex(currentIndex: Int) {
- val root = root!!
- for (i in currentIndex until root.foldedChildren.size) {
+ val precomposedNodesSectionStart = root.foldedChildren.size - precomposedCount
+ val reusableNodesSectionStart = maxOf(
+ currentIndex,
+ precomposedNodesSectionStart - maxSlotsToRetainForReuse
+ )
+
+ // keep up to maxCountOfSlotsToReuse last nodes to be reused later
+ reusableCount = precomposedNodesSectionStart - reusableNodesSectionStart
+ for (i in reusableNodesSectionStart until reusableNodesSectionStart + reusableCount) {
val node = root.foldedChildren[i]
- val nodeState = nodeToNodeState.remove(node)!!
- nodeState.composition!!.dispose()
- slodIdToNode.remove(nodeState.slotId)
+ val state = nodeToNodeState[node]!!
+ // remove them from slotIdToNode so they are not considered active
+ slotIdToNode.remove(state.slotId)
}
- root.removeAt(currentIndex, root.foldedChildren.size - currentIndex)
+
+ // dispose the rest of the nodes
+ val nodesToDispose = reusableNodesSectionStart - currentIndex
+ if (nodesToDispose > 0) {
+ ignoreRemeasureRequests {
+ for (i in currentIndex until currentIndex + nodesToDispose) {
+ disposeNode(root.foldedChildren[i])
+ }
+ root.removeAt(currentIndex, nodesToDispose)
+ }
+ }
+ }
+
+ private fun takeNodeFromReusables(slotId: Any?): LayoutNode {
+ check(reusableCount > 0)
+ val reusableNodesSectionEnd = root.foldedChildren.size - precomposedCount
+ val reusableNodesSectionStart = reusableNodesSectionEnd - reusableCount
+ var index = reusableNodesSectionStart
+ while (true) {
+ val node = root.foldedChildren[index]
+ val nodeState = nodeToNodeState.getValue(node)
+ if (nodeState.slotId == slotId) {
+ // we have a node with the same slotId
+ break
+ } else if (index == reusableNodesSectionEnd - 1) {
+ // it is the last available reusable node
+ nodeState.slotId = slotId
+ break
+ } else {
+ index++
+ }
+ }
+ if (index != reusableNodesSectionStart) {
+ // we need to rearrange the items
+ move(index, reusableNodesSectionStart, 1)
+ }
+ reusableCount--
+ return root.foldedChildren[reusableNodesSectionStart]
+ }
+
+ private fun disposeNode(node: LayoutNode) {
+ val nodeState = nodeToNodeState.remove(node)!!
+ nodeState.composition!!.dispose()
+ slotIdToNode.remove(nodeState.slotId)
}
private fun createMeasurePolicy(
@@ -235,11 +356,81 @@
it.composition!!.dispose()
}
nodeToNodeState.clear()
- slodIdToNode.clear()
+ slotIdToNode.clear()
}
+ /**
+ * Composes the content for the given [slotId]. This makes the next scope.subcompose(slotId)
+ * call during the measure pass faster as the content is already composed.
+ *
+ * If the [slotId] was precomposed already but after the future calculations ended up to not be
+ * needed anymore (meaning this slotId is not going to be used during the measure pass
+ * anytime soon) you can use [PrecomposedSlotHandle.dispose] on a returned object to dispose the
+ * content.
+ *
+ * @param slotId unique id which represents the slot we are composing into.
+ * @param content the composable content which defines the slot.
+ * @return [PrecomposedSlotHandle] instance which allows you to dispose the content.
+ */
+ fun precompose(slotId: Any?, content: @Composable () -> Unit): PrecomposedSlotHandle {
+ if (!slotIdToNode.containsKey(slotId)) {
+ val node = precomposeMap.getOrPut(slotId) {
+ if (reusableCount > 0) {
+ val node = takeNodeFromReusables(slotId)
+ // now move this node to the end where we keep precomposed items
+ val nodeIndex = root.foldedChildren.indexOf(node)
+ move(nodeIndex, root.foldedChildren.size, 1)
+ precomposedCount++
+ node
+ } else {
+ createNodeAt(root.foldedChildren.size).also {
+ precomposedCount++
+ }
+ }
+ }
+ subcompose(node, slotId, content)
+ }
+ return object : PrecomposedSlotHandle {
+ override fun dispose() {
+ val node = precomposeMap.remove(slotId)
+ if (node != null) {
+ val itemIndex = root.foldedChildren.indexOf(node)
+ check(itemIndex != -1)
+ if (reusableCount < maxSlotsToRetainForReuse) {
+ val reusableNodesSectionStart =
+ root.foldedChildren.size - precomposedCount - reusableCount
+ move(itemIndex, reusableNodesSectionStart, 1)
+ reusableCount++
+ } else {
+ ignoreRemeasureRequests {
+ disposeNode(node)
+ root.removeAt(itemIndex, 1)
+ }
+ }
+ check(precomposedCount > 0)
+ precomposedCount--
+ }
+ }
+ }
+ }
+
+ private fun createNodeAt(index: Int) = LayoutNode(isVirtual = true).also {
+ ignoreRemeasureRequests {
+ root.insertAt(index, it)
+ }
+ }
+
+ private fun move(from: Int, to: Int, count: Int = 1) {
+ ignoreRemeasureRequests {
+ root.move(from, to, count)
+ }
+ }
+
+ private inline fun ignoreRemeasureRequests(block: () -> Unit) =
+ root.ignoreRemeasureRequests(block)
+
private class NodeState(
- val slotId: Any?,
+ var slotId: Any?,
var content: @Composable () -> Unit,
var composition: Composition? = null
)
@@ -253,4 +444,22 @@
override fun subcompose(slotId: Any?, content: @Composable () -> Unit) =
this@SubcomposeLayoutState.subcompose(slotId, content)
}
+
+ /**
+ * Instance of this interface is returned by [precompose] function.
+ */
+ interface PrecomposedSlotHandle {
+
+ /**
+ * This function allows to dispose the content for the slot which was precomposed
+ * previously via [precompose].
+ *
+ * If this slot was already used during the regular measure pass via
+ * [SubcomposeMeasureScope.subcompose] this function will do nothing.
+ *
+ * This could be useful if after the future calculations this item is not anymore expected to
+ * be used during the measure pass anytime soon.
+ */
+ fun dispose()
+ }
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index c381939..ce522152 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -145,7 +145,7 @@
/**
* The parent node in the LayoutNode hierarchy. This is `null` when the [LayoutNode]
- * is not attached attached to a hierarchy or is the root of the hierarchy.
+ * is not attached to a hierarchy or is the root of the hierarchy.
*/
internal var parent: LayoutNode? = null
get() {
@@ -185,6 +185,11 @@
private var wrapperCache = mutableVectorOf<DelegatingLayoutNodeWrapper<*>>()
/**
+ * [requestRemeasure] calls will be ignored while this flag is true.
+ */
+ private var ignoreRemeasureRequests = false
+
+ /**
* Inserts a child [LayoutNode] at a particular index. If this LayoutNode [owner] is not `null`
* then [instance] will become [attach]ed also. [instance] must have a `null` [parent].
*/
@@ -1036,7 +1041,15 @@
* Used to request a new measurement + layout pass from the owner.
*/
internal fun requestRemeasure() {
- owner?.onRequestMeasure(this)
+ if (!ignoreRemeasureRequests) {
+ owner?.onRequestMeasure(this)
+ }
+ }
+
+ internal inline fun ignoreRemeasureRequests(block: () -> Unit) {
+ ignoreRemeasureRequests = true
+ block()
+ ignoreRemeasureRequests = false
}
/**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index c0a7ed7..b584d63 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -251,6 +251,9 @@
it.destroy()
layoutNode.innerLayerWrapperIsDirty = true
invalidateParentLayer()
+ if (isAttached) {
+ layoutNode.owner?.onLayoutChange(layoutNode)
+ }
}
layer = null
}
@@ -285,6 +288,7 @@
} else {
require(layerBlock == null)
}
+ layoutNode.owner?.onLayoutChange(layoutNode)
}
private val invalidateParentLayer: () -> Unit = {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
index 03306b7..5a70959 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
@@ -18,7 +18,7 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.autofill.Autofill
import androidx.compose.ui.autofill.AutofillTree
-import androidx.compose.ui.focus.FocusDirectionInternal
+import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
@@ -180,9 +180,9 @@
fun onLayoutChange(layoutNode: LayoutNode)
/**
- * The [FocusDirection][FocusDirectionInternal] represented by the specified keyEvent.
+ * The [FocusDirection] represented by the specified keyEvent.
*/
- fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal?
+ fun getFocusDirection(keyEvent: KeyEvent): FocusDirection?
val measureIteration: Long
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.desktop.kt
index 92cfd5c..296411b 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.desktop.kt
@@ -175,7 +175,7 @@
})
wrapped.addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseDragged(event: MouseEvent) = events.post {
- owners.onMouseDragged(
+ owners.onMouseMoved(
(event.x * density.density).toInt(),
(event.y * density.density).toInt(),
event
@@ -185,7 +185,8 @@
override fun mouseMoved(event: MouseEvent) = events.post {
owners.onMouseMoved(
(event.x * density.density).toInt(),
- (event.y * density.density).toInt()
+ (event.y * density.density).toInt(),
+ event
)
}
})
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt
index c8f41fd..2b768bc 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt
@@ -25,15 +25,14 @@
import androidx.compose.ui.autofill.Autofill
import androidx.compose.ui.autofill.AutofillTree
import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusDirectionInternal
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.In
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Next
-import androidx.compose.ui.focus.FocusDirectionInternal.Out
-import androidx.compose.ui.focus.FocusDirectionInternal.Previous
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.In
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.FocusManagerImpl
import androidx.compose.ui.geometry.Offset
@@ -48,7 +47,7 @@
import androidx.compose.ui.input.key.Key.Companion.Tab
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
import androidx.compose.ui.input.key.KeyInputModifier
import androidx.compose.ui.input.key.isShiftPressed
import androidx.compose.ui.input.key.key
@@ -60,6 +59,7 @@
import androidx.compose.ui.input.pointer.PointerInputFilter
import androidx.compose.ui.input.pointer.PointerMoveEventFilter
import androidx.compose.ui.input.pointer.PositionCalculator
+import androidx.compose.ui.input.pointer.ProcessResult
import androidx.compose.ui.input.pointer.TestPointerInputEventData
import androidx.compose.ui.layout.RootMeasurePolicy
import androidx.compose.ui.layout.boundsInWindow
@@ -118,21 +118,8 @@
val focusDirection = getFocusDirection(it)
if (focusDirection == null || it.type != KeyDown) return@KeyInputModifier false
- val focusMoveSuccess = with(focusManager) {
- when (focusDirection) {
- Up -> moveFocus(FocusDirection.Up)
- Down -> moveFocus(FocusDirection.Down)
- Left -> moveFocus(FocusDirection.Left)
- Right -> moveFocus(FocusDirection.Right)
- In -> moveFocusIn()
- Out -> moveFocusOut()
- Next -> moveFocus(FocusDirection.Next)
- Previous -> moveFocus(FocusDirection.Previous)
- }
- }
-
// Consume the key event if we moved focus.
- focusMoveSuccess
+ focusManager.moveFocus(focusDirection)
},
onPreviewKeyEvent = null
)
@@ -286,7 +273,7 @@
override fun onLayoutChange(layoutNode: LayoutNode) = Unit
- override fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal? {
+ override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
return when (keyEvent.key) {
Tab -> if (keyEvent.isShiftPressed) Previous else Next
DirectionRight -> Right
@@ -317,9 +304,9 @@
root.draw(DesktopCanvas(canvas))
}
- internal fun processPointerInput(event: PointerInputEvent) {
+ internal fun processPointerInput(event: PointerInputEvent): ProcessResult {
measureAndLayout()
- pointerInputEventProcessor.process(event, this)
+ return pointerInputEventProcessor.process(event, this)
}
override fun processPointerInput(nanoTime: Long, pointers: List<TestPointerInputEventData>) {
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt
index 89d44f4..7a27e67 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt
@@ -73,7 +73,7 @@
private val dispatcher = FlushCoroutineDispatcher(coroutineScope)
private val frameClock = BroadcastFrameClock(onNewAwaiters = ::invalidateIfNeeded)
- private val coroutineContext = dispatcher + frameClock
+ private val coroutineContext = coroutineScope.coroutineContext + dispatcher + frameClock
internal val recomposer = Recomposer(coroutineContext)
internal val platformInputService: DesktopPlatformInput = DesktopPlatformInput(component)
@@ -143,8 +143,13 @@
pointerId += 1
}
- fun onMouseDragged(x: Int, y: Int, nativeEvent: MouseEvent? = null) {
- lastOwner?.processPointerInput(pointerInputEvent(nativeEvent, x, y, isMousePressed))
+ fun onMouseMoved(x: Int, y: Int, nativeEvent: MouseEvent? = null) {
+ val event = pointerInputEvent(nativeEvent, x, y, isMousePressed)
+ val result = lastOwner?.processPointerInput(event)
+ if (result?.anyMovementConsumed != true) {
+ val position = Offset(x.toFloat(), y.toFloat())
+ lastOwner?.onPointerMove(position)
+ }
}
fun onMouseScroll(x: Int, y: Int, event: MouseScrollEvent) {
@@ -152,11 +157,6 @@
lastOwner?.onMouseScroll(position, event)
}
- fun onMouseMoved(x: Int, y: Int) {
- val position = Offset(x.toFloat(), y.toFloat())
- lastOwner?.onPointerMove(position)
- }
-
fun onMouseEntered(x: Int, y: Int) {
val position = Offset(x.toFloat(), y.toFloat())
lastOwner?.onPointerEnter(position)
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.desktop.kt
index 02855bf..fd87f34 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.desktop.kt
@@ -17,10 +17,11 @@
package androidx.compose.ui.platform
import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.runtime.snapshots.ObserverHandle
+import androidx.compose.ui.platform.GlobalSnapshotManager.ensureStarted
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.swing.Swing
import java.util.concurrent.atomic.AtomicBoolean
@@ -38,56 +39,17 @@
*/
internal object GlobalSnapshotManager {
private val started = AtomicBoolean(false)
- private var commitPending = false
- private var removeWriteObserver: ObserverHandle? = null
-
- private val scheduleScope = CoroutineScope(Dispatchers.Swing + SupervisorJob())
fun ensureStarted() {
if (started.compareAndSet(false, true)) {
- removeWriteObserver = Snapshot.registerGlobalWriteObserver(globalWriteObserver)
- }
- }
-
- private val globalWriteObserver: (Any) -> Unit = {
- // Race, but we don't care too much if we end up with multiple calls scheduled.
- if (!commitPending) {
- commitPending = true
- schedule {
- commitPending = false
- Snapshot.sendApplyNotifications()
+ val channel = Channel<Unit>(Channel.CONFLATED)
+ CoroutineScope(Dispatchers.Swing).launch {
+ channel.consumeEach {
+ Snapshot.sendApplyNotifications()
+ }
}
- }
- }
-
- /**
- * List of deferred callbacks to run serially. Guarded by its own monitor lock.
- */
- private val scheduledCallbacks = mutableListOf<() -> Unit>()
-
- /**
- * Guarded by [scheduledCallbacks]'s monitor lock.
- */
- private var isSynchronizeScheduled = false
-
- /**
- * Synchronously executes any outstanding callbacks and brings snapshots into a
- * consistent, updated state.
- */
- private fun synchronize() {
- synchronized(scheduledCallbacks) {
- scheduledCallbacks.forEach { it.invoke() }
- scheduledCallbacks.clear()
- isSynchronizeScheduled = false
- }
- }
-
- private fun schedule(block: () -> Unit) {
- synchronized(scheduledCallbacks) {
- scheduledCallbacks.add(block)
- if (!isSynchronizeScheduled) {
- isSynchronizeScheduled = true
- scheduleScope.launch { synchronize() }
+ Snapshot.registerGlobalWriteObserver {
+ channel.offer(Unit)
}
}
}
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt
index 15d19ba..cddba9b 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.unit.Density
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.cancel
import org.jetbrains.skija.Surface
@@ -59,7 +60,7 @@
private val canvas = surface.canvas
private var owner: DesktopOwner? = null
- private val coroutineScope = CoroutineScope(coroutineContext)
+ private val coroutineScope = CoroutineScope(coroutineContext + Job())
private val frameDispatcher: FrameDispatcher = FrameDispatcher(
onFrame = { onFrame() },
context = coroutineScope.coroutineContext
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/key/KeyInputUtil.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/key/KeyInputUtil.kt
index 24533e3..87efdf6 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/key/KeyInputUtil.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/key/KeyInputUtil.kt
@@ -32,7 +32,7 @@
val action = when (keyEventType) {
KeyEventType.KeyDown -> KEY_PRESSED
KeyEventType.KeyUp -> KEY_RELEASED
- KeyEventType.Unknown -> error("Unknown key event type")
+ else -> error("Unknown key event type")
}
return KeyEvent(
KeyEventAwt(
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
index d3345e2..b6073f5 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
@@ -46,8 +46,11 @@
import androidx.compose.ui.input.mouse.MouseScrollUnit
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.test.junit4.DesktopScreenshotTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.dp
import com.google.common.truth.Truth
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse
import org.junit.Rule
import org.junit.Test
@@ -55,6 +58,8 @@
class DesktopOwnerTest {
@get:Rule
val screenshotRule = DesktopScreenshotTestRule("ui/ui-desktop/ui")
+ @get:Rule
+ val composeRule = createComposeRule()
@Test(timeout = 5000)
fun `rendering of Box state change`() = renderingTest(width = 40, height = 40) {
@@ -349,4 +354,18 @@
awaitNextRender()
Truth.assertThat(effectIsLaunched).isTrue()
}
+
+ @Test(expected = TestException::class)
+ fun `catch exception in LaunchedEffect`() {
+ runBlocking(Dispatchers.Main) {
+ composeRule.setContent {
+ LaunchedEffect(Unit) {
+ throw TestException()
+ }
+ }
+ composeRule.awaitIdle()
+ }
+ }
+
+ private class TestException : RuntimeException()
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
index 8da0ff2..f61bb8e 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
@@ -156,8 +156,6 @@
val matrix = layer.matrix
val y = 10 * (1 - cos45.toFloat())
- println(matrix.map(Offset(0f, 0f)))
- println(matrix.map(Offset(100f, 10f)))
assertEquals(Offset(0f, y), matrix.map(Offset(0f, 0f)))
assertEquals(Offset(100f, 10f), matrix.map(Offset(100f, 10f)))
}
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/TestComposeWindowTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/TestComposeWindowTest.kt
index a85f9da..7fd119a 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/TestComposeWindowTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/TestComposeWindowTest.kt
@@ -23,6 +23,9 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountBox
import androidx.compose.ui.Modifier
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.swing.Swing
import org.junit.Test
class TestComposeWindowTest {
@@ -41,4 +44,12 @@
}
}
}
+
+ @Test(timeout = 5000)
+ fun `disposing TestComposeWindow should not cancel coroutineContext's Job`() {
+ runBlocking(Dispatchers.Swing) {
+ val window = TestComposeWindow(100, 100, coroutineContext = coroutineContext)
+ window.dispose()
+ }
+ }
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt
index 9ba82a1..9773822 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt
@@ -418,6 +418,26 @@
}
@Test
+ fun test_fill_editor_info_multi_line_with_default_action() {
+ textInputService.startInput(
+ value = TextFieldValue(""),
+ imeOptions = ImeOptions(
+ singleLine = false,
+ keyboardType = KeyboardType.Text,
+ imeAction = ImeAction.Default
+ ),
+ onEditCommand = {},
+ onImeActionPerformed = {}
+ )
+
+ EditorInfo().let { info ->
+ textInputService.createInputConnection(info)
+ assertFalse((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0)
+ assertFalse((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0)
+ }
+ }
+
+ @Test
fun test_fill_editor_info_single_line() {
textInputService.startInput(
value = TextFieldValue(""),
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index e8d13fe..bdf16a9 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -21,7 +21,7 @@
import androidx.compose.ui.autofill.AutofillTree
import androidx.compose.ui.draw.DrawModifier
import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.focus.FocusDirectionInternal
+import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.geometry.MutableRect
import androidx.compose.ui.geometry.Offset
@@ -1682,6 +1682,22 @@
}
@Test
+ fun layerParamChangeCallsOnLayoutChange() {
+ val node = LayoutNode(20, 20, 100, 100, Modifier.graphicsLayer())
+ val owner = MockOwner()
+ node.attach(owner)
+ assertEquals(0, owner.layoutChangeCount)
+ node.innerLayoutNodeWrapper.onLayerBlockUpdated { scaleX = 0.5f }
+ assertEquals(1, owner.layoutChangeCount)
+ repeat(2) {
+ node.innerLayoutNodeWrapper.onLayerBlockUpdated { scaleX = 1f }
+ }
+ assertEquals(2, owner.layoutChangeCount)
+ node.innerLayoutNodeWrapper.onLayerBlockUpdated(null)
+ assertEquals(3, owner.layoutChangeCount)
+ }
+
+ @Test
fun reuseModifiersThatImplementMultipleModifierInterfaces() {
val drawAndLayoutModifier: Modifier = object : DrawModifier, LayoutModifier {
override fun MeasureScope.measure(
@@ -1868,7 +1884,7 @@
layoutChangeCount++
}
- override fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal? {
+ override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
TODO("Not yet implemented")
}
diff --git a/concurrent/futures-ktx/lint-baseline.xml b/concurrent/futures-ktx/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/concurrent/futures-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/concurrent/listenablefuture-callback/api/1.0.0-beta02.txt b/concurrent/listenablefuture-callback/api/1.0.0-beta02.txt
deleted file mode 100644
index d95deda..0000000
--- a/concurrent/listenablefuture-callback/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent.callback {
-
- public final class CallbackToFutureAdapter {
- method public static <T> androidx.concurrent.ListenableFuture<T!> getFuture(androidx.concurrent.callback.CallbackToFutureAdapter.Resolver<T!>);
- }
-
- public static final class CallbackToFutureAdapter.Completer<T> {
- method public void addCancellationListener(Runnable, java.util.concurrent.Executor);
- method protected void finalize();
- method public boolean set(T!);
- method public boolean setCancelled();
- method public boolean setException(Throwable);
- }
-
- public static interface CallbackToFutureAdapter.Resolver<T> {
- method public Object? attachCompleter(androidx.concurrent.callback.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
- }
-
-}
-
diff --git a/concurrent/listenablefuture-callback/api/1.0.0-rc01.txt b/concurrent/listenablefuture-callback/api/1.0.0-rc01.txt
deleted file mode 100644
index d95deda..0000000
--- a/concurrent/listenablefuture-callback/api/1.0.0-rc01.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent.callback {
-
- public final class CallbackToFutureAdapter {
- method public static <T> androidx.concurrent.ListenableFuture<T!> getFuture(androidx.concurrent.callback.CallbackToFutureAdapter.Resolver<T!>);
- }
-
- public static final class CallbackToFutureAdapter.Completer<T> {
- method public void addCancellationListener(Runnable, java.util.concurrent.Executor);
- method protected void finalize();
- method public boolean set(T!);
- method public boolean setCancelled();
- method public boolean setException(Throwable);
- }
-
- public static interface CallbackToFutureAdapter.Resolver<T> {
- method public Object? attachCompleter(androidx.concurrent.callback.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
- }
-
-}
-
diff --git a/concurrent/listenablefuture-callback/api/restricted_1.0.0-beta02.txt b/concurrent/listenablefuture-callback/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index 0f47ccd..0000000
--- a/concurrent/listenablefuture-callback/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent.callback {
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class AbstractResolvableFuture<V> implements androidx.concurrent.ListenableFuture<V> {
- ctor protected AbstractResolvableFuture();
- method public final void addListener(Runnable!, java.util.concurrent.Executor!);
- method protected void afterDone();
- method public final boolean cancel(boolean);
- method public final V! get(long, java.util.concurrent.TimeUnit!) throws java.util.concurrent.ExecutionException, java.lang.InterruptedException, java.util.concurrent.TimeoutException;
- method public final V! get() throws java.util.concurrent.ExecutionException, java.lang.InterruptedException;
- method protected void interruptTask();
- method public final boolean isCancelled();
- method public final boolean isDone();
- method protected String? pendingToString();
- method protected boolean set(V?);
- method protected boolean setException(Throwable!);
- method protected boolean setFuture(androidx.concurrent.ListenableFuture<? extends V>!);
- method protected final boolean wasInterrupted();
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.callback.AbstractResolvableFuture<V> {
- method public static <V> androidx.concurrent.callback.ResolvableFuture<V!>! create();
- method public boolean set(V?);
- method public boolean setException(Throwable!);
- method public boolean setFuture(androidx.concurrent.ListenableFuture<? extends V>!);
- }
-
-}
-
diff --git a/concurrent/listenablefuture-callback/api/restricted_1.0.0-rc01.txt b/concurrent/listenablefuture-callback/api/restricted_1.0.0-rc01.txt
deleted file mode 100644
index 0f47ccd..0000000
--- a/concurrent/listenablefuture-callback/api/restricted_1.0.0-rc01.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent.callback {
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class AbstractResolvableFuture<V> implements androidx.concurrent.ListenableFuture<V> {
- ctor protected AbstractResolvableFuture();
- method public final void addListener(Runnable!, java.util.concurrent.Executor!);
- method protected void afterDone();
- method public final boolean cancel(boolean);
- method public final V! get(long, java.util.concurrent.TimeUnit!) throws java.util.concurrent.ExecutionException, java.lang.InterruptedException, java.util.concurrent.TimeoutException;
- method public final V! get() throws java.util.concurrent.ExecutionException, java.lang.InterruptedException;
- method protected void interruptTask();
- method public final boolean isCancelled();
- method public final boolean isDone();
- method protected String? pendingToString();
- method protected boolean set(V?);
- method protected boolean setException(Throwable!);
- method protected boolean setFuture(androidx.concurrent.ListenableFuture<? extends V>!);
- method protected final boolean wasInterrupted();
- }
-
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.callback.AbstractResolvableFuture<V> {
- method public static <V> androidx.concurrent.callback.ResolvableFuture<V!>! create();
- method public boolean set(V?);
- method public boolean setException(Throwable!);
- method public boolean setFuture(androidx.concurrent.ListenableFuture<? extends V>!);
- }
-
-}
-
diff --git a/concurrent/listenablefuture-callback/lint-baseline.xml b/concurrent/listenablefuture-callback/lint-baseline.xml
deleted file mode 100644
index bdd761e..0000000
--- a/concurrent/listenablefuture-callback/lint-baseline.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 3.5.0-beta04" client="gradle" version="3.5.0-beta04">
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public final V get(long timeout, TimeUnit unit)"
- errorLine2=" ~">
- <location
- file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
- line="353"
- column="18"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public final V get(long timeout, TimeUnit unit)"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
- line="353"
- column="38"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public final V get() throws InterruptedException, ExecutionException {"
- errorLine2=" ~">
- <location
- file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
- line="468"
- column="18"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public final void addListener(Runnable listener, Executor executor) {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
- line="651"
- column="35"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public final void addListener(Runnable listener, Executor executor) {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
- line="651"
- column="54"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" protected boolean setException(Throwable throwable) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
- line="708"
- column="36"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" protected boolean setFuture(ListenableFuture<? extends V> future) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
- line="744"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public boolean set(T value) {"
- errorLine2=" ~">
- <location
- file="src/main/java/androidx/concurrent/callback/CallbackToFutureAdapter.java"
- line="245"
- column="28"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static <V> ResolvableFuture<V> create() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/concurrent/callback/ResolvableFuture.java"
- line="43"
- column="23"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public boolean setException(Throwable throwable) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/concurrent/callback/ResolvableFuture.java"
- line="53"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public boolean setFuture(ListenableFuture<? extends V> future) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/concurrent/callback/ResolvableFuture.java"
- line="58"
- column="30"/>
- </issue>
-
-</issues>
diff --git a/concurrent/listenablefuture/api/1.0.0-beta02.txt b/concurrent/listenablefuture/api/1.0.0-beta02.txt
deleted file mode 100644
index 54c6f60..0000000
--- a/concurrent/listenablefuture/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent {
-
- public interface ListenableFuture<V> extends java.util.concurrent.Future<V> {
- method public void addListener(Runnable, java.util.concurrent.Executor);
- }
-
-}
-
diff --git a/concurrent/listenablefuture/api/1.0.0-rc01.txt b/concurrent/listenablefuture/api/1.0.0-rc01.txt
deleted file mode 100644
index 54c6f60..0000000
--- a/concurrent/listenablefuture/api/1.0.0-rc01.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent {
-
- public interface ListenableFuture<V> extends java.util.concurrent.Future<V> {
- method public void addListener(Runnable, java.util.concurrent.Executor);
- }
-
-}
-
diff --git a/concurrent/listenablefuture/api/restricted_1.0.0-beta02.txt b/concurrent/listenablefuture/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/concurrent/listenablefuture/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/concurrent/listenablefuture/api/restricted_1.0.0-rc01.txt b/concurrent/listenablefuture/api/restricted_1.0.0-rc01.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/concurrent/listenablefuture/api/restricted_1.0.0-rc01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/core/core-animation-integration-tests/testapp/lint-baseline.xml b/core/core-animation-integration-tests/testapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/core/core-animation-integration-tests/testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/core/core-animation-testing/lint-baseline.xml b/core/core-animation-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/core/core-animation-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/core/core-appdigest/lint-baseline.xml b/core/core-appdigest/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/core/core-appdigest/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/core/core-google-shortcuts/api/1.0.0-beta01.txt b/core/core-google-shortcuts/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-google-shortcuts/api/1.0.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-google-shortcuts/api/public_plus_experimental_1.0.0-beta01.txt b/core/core-google-shortcuts/api/public_plus_experimental_1.0.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-google-shortcuts/api/public_plus_experimental_1.0.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-google-shortcuts/api/res-1.0.0-beta01.txt b/core/core-google-shortcuts/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/core-google-shortcuts/api/res-1.0.0-beta01.txt
diff --git a/core/core-google-shortcuts/api/restricted_1.0.0-beta01.txt b/core/core-google-shortcuts/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..bdb2746
--- /dev/null
+++ b/core/core-google-shortcuts/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) androidx.core.google.shortcuts {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class ShortcutInfoChangeListenerImpl extends androidx.core.content.pm.ShortcutInfoChangeListener {
+ method public static androidx.core.google.shortcuts.ShortcutInfoChangeListenerImpl getInstance(android.content.Context);
+ }
+
+}
+
diff --git a/core/core-google-shortcuts/build.gradle b/core/core-google-shortcuts/build.gradle
index e89eb00..ea77000 100644
--- a/core/core-google-shortcuts/build.gradle
+++ b/core/core-google-shortcuts/build.gradle
@@ -27,17 +27,15 @@
android {
defaultConfig {
- minSdkVersion 23
+ multiDexEnabled = true
}
}
dependencies {
- api(KOTLIN_STDLIB)
- api("androidx.core:core:1.6.0-alpha02")
+ api(project(":core:core"))
implementation("com.google.firebase:firebase-appindexing:19.2.0")
implementation("com.google.crypto.tink:tink-android:1.5.0")
- implementation("androidx.security:security-crypto:1.0.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
@@ -48,6 +46,7 @@
androidTestImplementation(MOCKITO_CORE, excludes.bytebuddy)
androidTestImplementation(DEXMAKER_MOCKITO, excludes.bytebuddy)
androidTestImplementation(TRUTH)
+ androidTestImplementation(MULTIDEX)
}
androidx {
diff --git a/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml b/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml
index 8a5a29d..f5f3686 100644
--- a/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml
+++ b/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml
@@ -15,8 +15,10 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="androidx.core.google.shortcuts.test">
- <application>
+ <uses-sdk tools:overrideLibrary="com.google.firebase.icing"/>
+ <application android:name="androidx.multidex.MultiDexApplication">
<activity android:name=".TestActivity"/>
</application>
</manifest>
diff --git a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImplTest.java b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImplTest.java
index 834246e..97b77a7 100644
--- a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImplTest.java
+++ b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImplTest.java
@@ -16,11 +16,6 @@
package androidx.core.google.shortcuts;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_DESCRIPTION_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_LABEL_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_TAG_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_URL_KEY;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -30,17 +25,18 @@
import android.content.Context;
import android.content.Intent;
-import android.util.Base64;
import androidx.core.content.pm.ShortcutInfoCompat;
+import androidx.core.google.shortcuts.builders.CapabilityBuilder;
+import androidx.core.google.shortcuts.builders.ParameterBuilder;
+import androidx.core.google.shortcuts.builders.ShortcutBuilder;
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.google.common.collect.ImmutableList;
-import com.google.crypto.tink.KeysetHandle;
-import com.google.crypto.tink.Mac;
import com.google.firebase.appindexing.Action;
import com.google.firebase.appindexing.FirebaseAppIndex;
import com.google.firebase.appindexing.FirebaseUserActions;
@@ -51,13 +47,13 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 21) // This module should only be called for version 21+.
public class ShortcutInfoChangeListenerImplTest {
private FirebaseAppIndex mFirebaseAppIndex;
private FirebaseUserActions mFirebaseUserActions;
@@ -78,7 +74,7 @@
public void onShortcutUpdated_publicIntent_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
- Intent intent = Intent.parseUri("http://www.google.com", 0);
+ Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
.setShortLabel("short label")
.setLongLabel("long label")
@@ -90,15 +86,12 @@
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
- Indexable expected = new Indexable.Builder()
- .setName("short label")
+ Indexable expected = new ShortcutBuilder()
.setId("publicIntent")
+ .setShortcutLabel("short label")
+ .setShortcutDescription("long label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
- .put(SHORTCUT_LABEL_KEY, "short label")
- .put(SHORTCUT_DESCRIPTION_KEY, "long label")
- .put(SHORTCUT_URL_KEY, ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
- null))
- .setImage("content://abc")
+ .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
.build();
assertThat(allValues).containsExactly(expected);
}
@@ -108,7 +101,7 @@
public void onShortcutUpdated_withCapabilityBinding_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
- Intent intent = Intent.parseUri("http://www.google.com", 0);
+ Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
.setShortLabel("short label")
.setLongLabel("long label")
@@ -123,38 +116,40 @@
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
- Indexable.Builder expectedBuilder = new Indexable.Builder()
- .setName("short label")
+ ShortcutBuilder expectedBuilder = new ShortcutBuilder()
.setId("publicIntent")
+ .setShortcutLabel("short label")
+ .setShortcutDescription("long label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
- .put(SHORTCUT_LABEL_KEY, "short label")
- .put(SHORTCUT_DESCRIPTION_KEY, "long label")
- .put(SHORTCUT_URL_KEY, ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
- null));
- // The order of isPartOf field matters during comparison. However since the order is not
+ .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null));
+ // The order of capability field matters during comparison. However since the order is not
// deterministic because the data is stored in maps and sets in ShortcutInfoCompat, we
// check for all possible orderings to make the test more reliable.
Indexable expected1 = expectedBuilder
- .setIsPartOf(
- new Indexable.Builder()
- .setId("actions.intent.STOP_EXERCISE/exercise.name")
- .setName("stop running")
- .setAlternateName("stop jogging"),
- new Indexable.Builder()
- .setId("actions.intent.START_EXERCISE/exercise.name")
- .setName("start running")
- .setAlternateName("start jogging"))
+ .setCapability(
+ new CapabilityBuilder()
+ .setName("actions.intent.STOP_EXERCISE")
+ .setParameter(new ParameterBuilder()
+ .setName("exercise.name")
+ .setValue("stop running", "stop jogging")),
+ new CapabilityBuilder()
+ .setName("actions.intent.START_EXERCISE")
+ .setParameter(new ParameterBuilder()
+ .setName("exercise.name")
+ .setValue("start running", "start jogging")))
.build();
Indexable expected2 = expectedBuilder
- .setIsPartOf(
- new Indexable.Builder()
- .setId("actions.intent.START_EXERCISE/exercise.name")
- .setName("start running")
- .setAlternateName("start jogging"),
- new Indexable.Builder()
- .setId("actions.intent.STOP_EXERCISE/exercise.name")
- .setName("stop running")
- .setAlternateName("stop jogging"))
+ .setCapability(
+ new CapabilityBuilder()
+ .setName("actions.intent.START_EXERCISE")
+ .setParameter(new ParameterBuilder()
+ .setName("exercise.name")
+ .setValue("start running", "start jogging")),
+ new CapabilityBuilder()
+ .setName("actions.intent.STOP_EXERCISE")
+ .setParameter(new ParameterBuilder()
+ .setName("exercise.name")
+ .setValue("stop running", "stop jogging")))
.build();
assertThat(allValues).hasSize(1);
assertThat(allValues).containsAnyOf(expected1, expected2);
@@ -165,7 +160,7 @@
public void onShortcutUpdated_withCapabilityBindingNoParams_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
- Intent intent = Intent.parseUri("http://www.google.com", 0);
+ Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
.setShortLabel("short label")
.setLongLabel("long label")
@@ -177,15 +172,13 @@
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
- Indexable expected = new Indexable.Builder()
- .setName("short label")
+ Indexable expected = new ShortcutBuilder()
.setId("publicIntent")
+ .setShortcutLabel("short label")
+ .setShortcutDescription("long label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
- .put(SHORTCUT_LABEL_KEY, "short label")
- .put(SHORTCUT_DESCRIPTION_KEY, "long label")
- .put(SHORTCUT_URL_KEY, ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
- null))
- .setIsPartOf(new Indexable.Builder().setId("actions.intent.TWEET"))
+ .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
+ .setCapability(new CapabilityBuilder().setName("actions.intent.TWEET"))
.build();
assertThat(allValues).containsExactly(expected);
}
@@ -208,13 +201,12 @@
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
- Indexable expected = new Indexable.Builder()
+ Indexable expected = new ShortcutBuilder()
.setName("short label")
- .setUrl(ShortcutUtils.getIndexableUrl(mContext, "privateIntent"))
.setId("privateIntent")
- .put("shortcutLabel", "short label")
- .put("shortcutUrl", ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
- null))
+ .setShortcutLabel("short label")
+ .setUrl(ShortcutUtils.getIndexableUrl(mContext, "privateIntent"))
+ .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
.build();
assertThat(allValues).containsExactly(expected);
}
@@ -224,7 +216,7 @@
public void onShortcutAdded_savesToAppIndex() throws Exception {
ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
- Intent intent = Intent.parseUri("http://www.google.com", 0);
+ Intent intent = Intent.parseUri("app://shortcut", 0);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "intent")
.setShortLabel("short label")
.setLongLabel("long label")
@@ -236,55 +228,18 @@
verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
List<Indexable> allValues = indexableCaptor.getAllValues();
- Indexable expected = new Indexable.Builder()
+ Indexable expected = new ShortcutBuilder()
.setName("short label")
.setId("intent")
+ .setShortcutLabel("short label")
+ .setDescription("long label")
+ .setShortcutDescription("long label")
.setUrl(ShortcutUtils.getIndexableUrl(mContext, "intent"))
- .put(SHORTCUT_LABEL_KEY, "short label")
- .put(SHORTCUT_DESCRIPTION_KEY, "long label")
- .put(SHORTCUT_URL_KEY, ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
- null))
- .setImage("content://abc")
+ .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
.build();
assertThat(allValues).containsExactly(expected);
}
- @SmallTest
- @Test
- public void onShortcutAdded_withMacSignature_canVerifySignature() throws Exception {
- ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
-
- KeysetHandle keysetHandle = ShortcutUtils.getOrCreateShortcutKeysetHandle(mContext);
- // Make sure keyset can be created.
- assertThat(keysetHandle).isNotNull();
-
- mShortcutInfoChangeListener = new ShortcutInfoChangeListenerImpl(
- mContext, mFirebaseAppIndex, mFirebaseUserActions, keysetHandle);
-
- Intent intent = Intent.parseUri("http://www.google.com", 0);
- ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "intent")
- .setShortLabel("short label")
- .setIntent(intent)
- .build();
- mShortcutInfoChangeListener.onShortcutAdded(Collections.singletonList(shortcut));
-
- verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
- String trampolineIntentString = ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
- keysetHandle);
-
- Intent trampolineIntent = Intent.parseUri(trampolineIntentString, 0);
- // Make sure the trampoline activity is package restricted.
- assertThat(trampolineIntent.getPackage()).isEqualTo(mContext.getPackageName());
-
- String tag = trampolineIntent.getStringExtra(SHORTCUT_TAG_KEY);
- String shortcutUrl = trampolineIntent.getStringExtra(SHORTCUT_URL_KEY);
- Mac mac = keysetHandle.getPrimitive(Mac.class);
-
- // Will throw exception if verification fails.
- mac.verifyMac(Base64.decode(tag, Base64.DEFAULT),
- shortcutUrl.getBytes(StandardCharsets.UTF_8));
- }
-
@Test
@SmallTest
public void onShortcutRemoved_removeFromAppIndex() {
diff --git a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutUtilsTest.java b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutUtilsTest.java
index f731f1d..f30f720 100644
--- a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutUtilsTest.java
+++ b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutUtilsTest.java
@@ -30,6 +30,7 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.google.crypto.tink.KeysetHandle;
@@ -38,6 +39,7 @@
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 21) // This module should only be called for version 21+.
public class ShortcutUtilsTest {
private static final String TEST_PACKAGE = "com.test.package";
@@ -65,7 +67,7 @@
String shortcutUrl = ShortcutUtils.getIndexableShortcutUrl(context, intent, null);
- String expectedShortcutUrl = "http://www.google.com";
+ String expectedShortcutUrl = "intent://www.google.com#Intent;scheme=http;end";
assertThat(shortcutUrl).isEqualTo(expectedShortcutUrl);
}
@@ -84,7 +86,7 @@
assertThat(trampolineIntent.getPackage()).isEqualTo(context.getPackageName());
assertThat(trampolineIntent.getAction()).isEqualTo(SHORTCUT_LISTENER_INTENT_FILTER_ACTION);
assertThat(trampolineIntent.getStringExtra(SHORTCUT_TAG_KEY)).isNotEmpty();
- String expectedShortcutUrl = "http://www.google.com";
+ String expectedShortcutUrl = "intent://www.google.com#Intent;scheme=http;end";
assertThat(trampolineIntent.getStringExtra(SHORTCUT_URL_KEY))
.isEqualTo(expectedShortcutUrl);
}
diff --git a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/TrampolineActivityTest.java b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/TrampolineActivityTest.java
index 6a82d85..8705d6f 100644
--- a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/TrampolineActivityTest.java
+++ b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/TrampolineActivityTest.java
@@ -35,6 +35,7 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.espresso.intent.Intents;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.google.crypto.tink.KeysetHandle;
@@ -47,6 +48,7 @@
import java.util.List;
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 21) // This module should only be called for version 21+.
public class TrampolineActivityTest {
private static final String SHORTCUT_LISTENER_INTENT_FILTER_ACTION = "androidx.core.content.pm"
+ ".SHORTCUT_LISTENER";
diff --git a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/builders/ShortcutBuilderTest.java b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/builders/ShortcutBuilderTest.java
new file mode 100644
index 0000000..6b4763f
--- /dev/null
+++ b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/builders/ShortcutBuilderTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import com.google.firebase.appindexing.Indexable;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 21) // This module should only be called for version 21+.
+public class ShortcutBuilderTest {
+
+ @SmallTest
+ @Test
+ public void testBuildShortcut() {
+ Indexable shortcutIndexable = new ShortcutBuilder()
+ .setId("id")
+ .setShortcutLabel("shortcut label")
+ .setShortcutDescription("shortcut description")
+ .setUrl("url")
+ .setShortcutUrl("shortcut url")
+ .build();
+
+ assertThat(shortcutIndexable).isEqualTo(new Indexable.Builder("Shortcut")
+ .setId("id")
+ .setName("shortcut label")
+ .setDescription("shortcut description")
+ .setUrl("url")
+ .put("shortcutLabel", "shortcut label")
+ .put("shortcutDescription", "shortcut description")
+ .put("shortcutUrl", "shortcut url")
+ .build());
+ }
+
+ @SmallTest
+ @Test
+ public void testBuildShortcutWithCapability() throws Exception {
+ Indexable shortcutIndexable = new ShortcutBuilder()
+ .setCapability(
+ new CapabilityBuilder().setName("capability1"),
+ new CapabilityBuilder().setName("capability2"))
+ .build();
+
+ assertThat(shortcutIndexable).isEqualTo(new Indexable.Builder("Shortcut")
+ .put("capability",
+ new Indexable.Builder("Capability").setName("capability1").build(),
+ new Indexable.Builder("Capability").setName("capability2").build())
+ .build());
+ }
+
+ @SmallTest
+ @Test
+ public void testBuildShortcutWithCapabilityAndParameter() throws Exception {
+ Indexable shortcutIndexable = new ShortcutBuilder()
+ .setCapability(new CapabilityBuilder()
+ .setName("capability")
+ .setParameter(
+ new ParameterBuilder()
+ .setName("parameter1")
+ .setValue("value1", "value2"),
+ new ParameterBuilder()
+ .setName("parameter2")
+ .setValue("value3", "value4")))
+ .build();
+
+ assertThat(shortcutIndexable).isEqualTo(new Indexable.Builder("Shortcut")
+ .put("capability", new Indexable.Builder("Capability")
+ .setName("capability")
+ .put("parameter",
+ new Indexable.Builder("Parameter")
+ .setName("parameter1")
+ .put("value", "value1", "value2")
+ .build(),
+ new Indexable.Builder("Parameter")
+ .setName("parameter2")
+ .put("value", "value3", "value4")
+ .build())
+ .build())
+ .build());
+ }
+}
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImpl.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImpl.java
index 826b04b..88e9a59 100644
--- a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImpl.java
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImpl.java
@@ -19,29 +19,29 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.core.google.shortcuts.ShortcutUtils.CAPABILITY_PARAM_SEPARATOR;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_DESCRIPTION_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_LABEL_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_URL_KEY;
import android.content.Context;
+import android.os.Build;
+import android.os.PersistableBundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.pm.ShortcutInfoChangeListener;
import androidx.core.content.pm.ShortcutInfoCompat;
-import androidx.core.graphics.drawable.IconCompat;
+import androidx.core.google.shortcuts.builders.CapabilityBuilder;
+import androidx.core.google.shortcuts.builders.ParameterBuilder;
+import androidx.core.google.shortcuts.builders.ShortcutBuilder;
import com.google.crypto.tink.KeysetHandle;
import com.google.firebase.appindexing.Action;
import com.google.firebase.appindexing.FirebaseAppIndex;
import com.google.firebase.appindexing.FirebaseUserActions;
import com.google.firebase.appindexing.Indexable;
-import com.google.firebase.appindexing.builders.IndexableBuilder;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -161,69 +161,79 @@
String url = ShortcutUtils.getIndexableUrl(mContext, shortcut.getId());
String shortcutUrl = ShortcutUtils.getIndexableShortcutUrl(mContext, shortcut.getIntent(),
mKeysetHandle);
+ String name = shortcut.getShortLabel().toString();
- Indexable.Builder builder = new Indexable.Builder()
+ ShortcutBuilder shortcutBuilder = new ShortcutBuilder()
.setId(shortcut.getId())
.setUrl(url)
- .setName(shortcut.getShortLabel().toString())
- .put(SHORTCUT_URL_KEY, shortcutUrl)
- .put(SHORTCUT_LABEL_KEY, shortcut.getShortLabel().toString());
-
+ .setShortcutLabel(name)
+ .setShortcutUrl(shortcutUrl);
if (shortcut.getLongLabel() != null) {
- builder.put(SHORTCUT_DESCRIPTION_KEY, shortcut.getLongLabel().toString());
- }
-
- if (shortcut.getIcon() != null && shortcut.getIcon().getType() == IconCompat.TYPE_URI) {
- builder.setImage(shortcut.getIcon().getUri().toString());
+ shortcutBuilder.setShortcutDescription(shortcut.getLongLabel().toString());
}
// Add capability binding
- if (shortcut.getCategories() != null) {
- List<Indexable.Builder> partOfList = new ArrayList<>();
- for (String capability : shortcut.getCategories()) {
- if (!ShortcutUtils.isAppActionCapability(capability)) {
- continue;
- }
-
- if (shortcut.getExtras() == null
- || shortcut.getExtras().getStringArray(capability) == null
- || shortcut.getExtras().getStringArray(capability).length == 0) {
- // Shortcut has a capability binding without any parameter binding.
- partOfList.add(buildPartOfIndexable(capability, null));
- } else {
- String[] params = shortcut.getExtras().getStringArray(capability);
- for (String param : params) {
- String capabilityParam = capability + CAPABILITY_PARAM_SEPARATOR + param;
- partOfList.add(buildPartOfIndexable(capabilityParam,
- shortcut.getExtras().getStringArray(capabilityParam)));
+ if (Build.VERSION.SDK_INT >= 21) {
+ if (shortcut.getCategories() != null) {
+ List<CapabilityBuilder> capabilityList = new ArrayList<>();
+ for (String capability : shortcut.getCategories()) {
+ if (!ShortcutUtils.isAppActionCapability(capability)) {
+ continue;
}
- }
- }
- if (!partOfList.isEmpty()) {
- builder.setIsPartOf(partOfList.toArray(new IndexableBuilder[0]));
+ capabilityList.add(Api21Impl.buildCapability(capability, shortcut.getExtras()));
+ }
+
+ if (!capabilityList.isEmpty()) {
+ shortcutBuilder
+ .setCapability(capabilityList.toArray(new CapabilityBuilder[0]));
+ }
}
}
// By default, the indexable will be saved only on-device.
- return builder.build();
+ return shortcutBuilder.build();
}
- @NonNull
- private Indexable.Builder buildPartOfIndexable(@NonNull String capabilityParam,
- @Nullable String[] values) {
- Indexable.Builder partOfBuilder = new Indexable.Builder()
- .setId(capabilityParam);
- if (values == null) {
- return partOfBuilder;
+ @RequiresApi(21)
+ private static class Api21Impl {
+ @NonNull
+ static CapabilityBuilder buildCapability(@NonNull String capability,
+ @Nullable PersistableBundle shortcutInfoExtras) {
+ CapabilityBuilder capabilityBuilder = new CapabilityBuilder()
+ .setName(capability);
+ if (shortcutInfoExtras == null) {
+ return capabilityBuilder;
+ }
+
+ String[] params = shortcutInfoExtras.getStringArray(capability);
+ if (params == null) {
+ return capabilityBuilder;
+ }
+
+ List<ParameterBuilder> parameterBuilders = new ArrayList<>();
+ for (String param : params) {
+ ParameterBuilder parameterBuilder =
+ new ParameterBuilder()
+ .setName(param);
+ String capabilityParamKey = capability + CAPABILITY_PARAM_SEPARATOR + param;
+ String[] values = shortcutInfoExtras.getStringArray(capabilityParamKey);
+ if (values == null || values.length == 0) {
+ // ignore this parameter since no values were given
+ continue;
+ }
+
+ parameterBuilder.setValue(values);
+ parameterBuilders.add(parameterBuilder);
+ }
+
+ if (parameterBuilders.size() > 0) {
+ capabilityBuilder
+ .setParameter(parameterBuilders.toArray(new ParameterBuilder[0]));
+ }
+ return capabilityBuilder;
}
- if (values.length > 0) {
- partOfBuilder.setName(values[0]);
- }
- if (values.length > 1) {
- partOfBuilder.setAlternateName(Arrays.copyOfRange(values, 1, values.length));
- }
- return partOfBuilder;
+ private Api21Impl() {}
}
}
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutUtils.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutUtils.java
index 5b11811..7f457d9 100644
--- a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutUtils.java
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutUtils.java
@@ -34,20 +34,18 @@
import com.google.crypto.tink.mac.MacConfig;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
+import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
/**
- * Utility methods and constants used by the google shortcuts library.
+ * Utility methods and constants used by the Google shortcuts library.
*
* @hide
*/
@RestrictTo(LIBRARY)
class ShortcutUtils {
- public static final String SHORTCUT_LABEL_KEY = "shortcutLabel";
- public static final String SHORTCUT_DESCRIPTION_KEY = "shortcutDescription";
- public static final String SHORTCUT_URL_KEY = "shortcutUrl";
public static final String ID_KEY = "id";
+ public static final String SHORTCUT_URL_KEY = "shortcutUrl";
public static final String CAPABILITY_PARAM_SEPARATOR = "/";
public static final String SHORTCUT_TAG_KEY = "shortcutTag";
public static final String SHORTCUT_LISTENER_INTENT_FILTER_ACTION = "androidx.core.content.pm"
@@ -88,7 +86,7 @@
*/
public static String getIndexableShortcutUrl(@NonNull Context context,
@NonNull Intent shortcutIntent, @Nullable KeysetHandle keysetHandle) {
- String shortcutUrl = shortcutIntent.toUri(0);
+ String shortcutUrl = shortcutIntent.toUri(Intent.URI_INTENT_SCHEME);
if (keysetHandle == null) {
// If keyset handle is null, then create the shortcut without using the Trampoline
// Activity. This means that only shortcuts with exported intent will work. Those with
@@ -104,7 +102,7 @@
// 2. only the shortcut that was indexed using this library can be opened. You cannot
// use the Trampoline Activity to open arbitrary shortcuts.
Mac mac = keysetHandle.getPrimitive(Mac.class);
- byte[] tag = mac.computeMac(shortcutUrl.getBytes(StandardCharsets.UTF_8));
+ byte[] tag = mac.computeMac(shortcutUrl.getBytes(Charset.forName("UTF-8")));
String tagString = Base64.encodeToString(tag, Base64.DEFAULT);
Intent trampolineIntent = new Intent(context, TrampolineActivity.class);
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/TrampolineActivity.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/TrampolineActivity.java
index 3dfe866..adf51d0 100644
--- a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/TrampolineActivity.java
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/TrampolineActivity.java
@@ -33,7 +33,7 @@
import com.google.crypto.tink.Mac;
import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
+import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
@@ -67,9 +67,9 @@
Mac mac = sKeysetHandle.getPrimitive(Mac.class);
// Will throw GeneralSecurityException when verifyMac fails.
mac.verifyMac(Base64.decode(tag, Base64.DEFAULT),
- shortcutUrl.getBytes(StandardCharsets.UTF_8));
+ shortcutUrl.getBytes(Charset.forName("UTF-8")));
- Intent shortcutIntent = Intent.parseUri(shortcutUrl, 0);
+ Intent shortcutIntent = Intent.parseUri(shortcutUrl, Intent.URI_INTENT_SCHEME);
startActivity(shortcutIntent);
} catch (GeneralSecurityException | URISyntaxException e) {
Log.w(TAG, "failed to open shortcut url", e);
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/CapabilityBuilder.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/CapabilityBuilder.java
new file mode 100644
index 0000000..d18753f
--- /dev/null
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/CapabilityBuilder.java
@@ -0,0 +1,45 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.core.google.shortcuts.builders.Constants.CAPABILITY_PARAMETER_KEY;
+import static androidx.core.google.shortcuts.builders.Constants.CAPABILITY_TYPE;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import com.google.firebase.appindexing.builders.IndexableBuilder;
+
+/**
+ * Builder for the Capability section in the Shortcut Corpus.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class CapabilityBuilder extends IndexableBuilder<CapabilityBuilder> {
+ public CapabilityBuilder() {
+ super(CAPABILITY_TYPE);
+ }
+
+ /** Sets one or more parameters for the given capability. */
+ @NonNull
+ public CapabilityBuilder setParameter(
+ @NonNull ParameterBuilder... parameter) {
+ return put(CAPABILITY_PARAMETER_KEY, parameter);
+ }
+}
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/Constants.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/Constants.java
new file mode 100644
index 0000000..35f8981
--- /dev/null
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/Constants.java
@@ -0,0 +1,44 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Constants for the Shortcut Corpus.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class Constants {
+ public static final String SHORTCUT_TYPE = "Shortcut";
+ public static final String CAPABILITY_TYPE = "Capability";
+ public static final String PARAMETER_TYPE = "Parameter";
+
+ public static final String SHORTCUT_LABEL_KEY = "shortcutLabel";
+ public static final String SHORTCUT_DESCRIPTION_KEY = "shortcutDescription";
+ public static final String SHORTCUT_URL_KEY = "shortcutUrl";
+ public static final String SHORTCUT_CAPABILITY_KEY = "capability";
+
+ public static final String CAPABILITY_PARAMETER_KEY = "parameter";
+
+ public static final String PARAMETER_VALUE_KEY = "value";
+
+ private Constants() {}
+}
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ParameterBuilder.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ParameterBuilder.java
new file mode 100644
index 0000000..4ea41e04
--- /dev/null
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ParameterBuilder.java
@@ -0,0 +1,45 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.core.google.shortcuts.builders.Constants.PARAMETER_TYPE;
+import static androidx.core.google.shortcuts.builders.Constants.PARAMETER_VALUE_KEY;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import com.google.firebase.appindexing.builders.IndexableBuilder;
+
+/**
+ * Builder for the Parameter section in the Shortcut Corpus.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ParameterBuilder
+ extends IndexableBuilder<ParameterBuilder> {
+ public ParameterBuilder() {
+ super(PARAMETER_TYPE);
+ }
+
+ /** Sets one or more values for given parameter. */
+ @NonNull
+ public ParameterBuilder setValue(@NonNull String... value) {
+ return put(PARAMETER_VALUE_KEY, value);
+ }
+}
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ShortcutBuilder.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ShortcutBuilder.java
new file mode 100644
index 0000000..37758b4
--- /dev/null
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ShortcutBuilder.java
@@ -0,0 +1,67 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_CAPABILITY_KEY;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_DESCRIPTION_KEY;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_LABEL_KEY;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_TYPE;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_URL_KEY;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import com.google.firebase.appindexing.builders.IndexableBuilder;
+
+/**
+ * Builder for the Shortcut Corpus.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ShortcutBuilder extends IndexableBuilder<ShortcutBuilder> {
+ public ShortcutBuilder() {
+ super(SHORTCUT_TYPE);
+ }
+
+ /** Sets the label for the shortcut. */
+ @NonNull
+ public ShortcutBuilder setShortcutLabel(@NonNull String shortcutLabel) {
+ setName(shortcutLabel);
+ return put(SHORTCUT_LABEL_KEY, shortcutLabel);
+ }
+
+ /** Sets the description for the shortcut. */
+ @NonNull
+ public ShortcutBuilder setShortcutDescription(@NonNull String shortcutDescription) {
+ setDescription(shortcutDescription);
+ return put(SHORTCUT_DESCRIPTION_KEY, shortcutDescription);
+ }
+
+ /** Sets the {@link android.content.Intent} url for the shortcut. */
+ @NonNull
+ public ShortcutBuilder setShortcutUrl(@NonNull String shortcutUrl) {
+ return put(SHORTCUT_URL_KEY, shortcutUrl);
+ }
+
+ /** Sets one or more capabilities for the shortcut. */
+ @NonNull
+ public ShortcutBuilder setCapability(@NonNull CapabilityBuilder... capability) {
+ return put(SHORTCUT_CAPABILITY_KEY, capability);
+ }
+}
diff --git a/core/core-role/lint-baseline.xml b/core/core-role/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/core/core-role/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
index b6767d5..9ecc7281 100644
--- a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
@@ -169,7 +169,7 @@
@Test
@SdkSuppress(minSdkVersion = 19, maxSdkVersion = 27)
- @FlakyTest
+ @FlakyTest(bugId = 187190911)
public void testAccessibilityPaneTitle_isntTrackedAsPaneWithoutTitle() {
// This test isn't to test the propagation up, just that the event is sent correctly
ViewCompat.setAccessibilityLiveRegion(mView,
diff --git a/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java b/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java
index 899e90e..a6db56b 100644
--- a/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java
+++ b/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java
@@ -831,7 +831,7 @@
private static List<ShortcutInfoChangeListener> getShortcutInfoListeners(Context context) {
if (sShortcutInfoChangeListeners == null) {
List<ShortcutInfoChangeListener> result = new ArrayList<>();
- if (Build.VERSION.SDK_INT >= 23) {
+ if (Build.VERSION.SDK_INT >= 21) {
PackageManager packageManager = context.getPackageManager();
Intent activityIntent = new Intent(SHORTCUT_LISTENER_INTENT_FILTER_ACTION);
activityIntent.setPackage(context.getPackageName());
diff --git a/datastore/datastore-core/lint-baseline.xml b/datastore/datastore-core/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/datastore/datastore-core/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences-core/datastore-preferences-proto/lint-baseline.xml b/datastore/datastore-preferences-core/datastore-preferences-proto/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/datastore/datastore-preferences-core/datastore-preferences-proto/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences-core/lint-baseline.xml b/datastore/datastore-preferences-core/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/datastore/datastore-preferences-core/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences-rxjava2/lint-baseline.xml b/datastore/datastore-preferences-rxjava2/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-preferences-rxjava2/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences-rxjava3/lint-baseline.xml b/datastore/datastore-preferences-rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-preferences-rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences/lint-baseline.xml b/datastore/datastore-preferences/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-preferences/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-proto/lint-baseline.xml b/datastore/datastore-proto/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-proto/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-rxjava2/lint-baseline.xml b/datastore/datastore-rxjava2/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-rxjava2/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-rxjava3/lint-baseline.xml b/datastore/datastore-rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 5d9bd20..3fe9a9d 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -633,6 +633,8 @@
Transforming pages\: *[0-9]+
Rendering\: *[0-9]+
WARNING\: unable to find what is referred to by
+@param maxSlotsToRetainForReuse
+in DClass SubcomposeLayoutState
@param initVal
in DClass AnimationVector[0-9]+D
@param a,
@@ -966,4 +968,4 @@
Execution optimizations have been disabled for task ':benchmark:benchmark\-macro:.*' to ensure correctness due to the following reasons:
\- Gradle detected a problem with the following location: '\$OUT_DIR/androidx/benchmark/benchmark\-macro/build/generated/source/wire'\. Reason: Task ':benchmark:benchmark\-macro:.*' uses this output of task ':benchmark:benchmark\-macro:.*' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
# > Task :support-emoji-demos:lintDebug
-Error processing .* broken class file\? \(This feature requires ASM[0-9]+\)
+Error processing .* broken class file\? \(This feature requires ASM[0-9]+\)
\ No newline at end of file
diff --git a/development/checkInvalidSuppress.py b/development/checkInvalidSuppress.py
deleted file mode 100755
index b4484fa7..0000000
--- a/development/checkInvalidSuppress.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Copyright 2019, 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.
-#
-
-"""Script used to find IDEA warning suppression that doesn't work for command line builds."""
-
-import os
-import sys
-
-MAIN_DIRECTORY = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
-
-# first level line filter - we ignore lines that don't contain the following for speed
-ROOT_MATCH = "noinspection"
-
-# Map of invalid pattern to correct replacement
-# These could be regex for fancy whitespace matching, but don't seem to need in practice
-MATCHERS = {
- '//noinspection deprecation' : '@SuppressWarnings("deprecation")',
- '//noinspection unchecked' : '@SuppressWarnings("unchecked")',
-}
-
-## Check a line for any invalid suppressions, and return it if found
-def getReportForLine(filename, i, line, lines):
- for bad, good in MATCHERS.items():
- if bad in line:
- context = "".join(lines[i:i+3])
- return "\n{}:{}:\nError: unsupported comment suppression\n{}Instead, use: {}\n".format(
- filename, i, context, good)
- return ""
-
-def main():
- report = ""
-
- for filename in sys.argv[1:]:
- # suppress comments ignored in kotlin, but may as well block there too
- if not filename.endswith(".java") and not filename.endswith(".kt"):
- continue
-
- filename = os.path.join(MAIN_DIRECTORY, filename)
-
- # check file exists
- if not os.path.isfile(filename):
- continue
-
- with open(filename, "r") as f:
- lines = f.readlines()
-
- linetuples = map(lambda i: [i, lines[i]], range(len(lines)))
- for i, line in linetuples:
- if ROOT_MATCH in line:
- report += getReportForLine(filename, i, line, lines)
-
- if not report:
- sys.exit(0)
-
- print("Invalid, IDEA-specific warning suppression found. These cause warnings during compilation.")
- print(report)
- sys.exit(1)
-
-if __name__ == '__main__':
- main()
diff --git a/development/check_os_prebuilts.py b/development/check_os_prebuilts.py
index 04977565..9b21fa0 100755
--- a/development/check_os_prebuilts.py
+++ b/development/check_os_prebuilts.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
#
# Copyright 2019, The Android Open Source Project
@@ -41,14 +41,14 @@
if not os_specific_files:
sys.exit(0)
- print "The following operating system specific files were found in your commit:\n\033[91m"
+ print("The following operating system specific files were found in your commit:\n\033[91m")
for filename in os_specific_files.values():
- print filename
- print """\033[0m\nPlease make sure to import the corresponding prebuilts for missing platforms.
+ print(filename)
+ print ("""\033[0m\nPlease make sure to import the corresponding prebuilts for missing platforms.
If you imported a prebuilt similar to foo:bar:linux, try foo:bar:osx and vice versa.
If there is no corresponding prebuilt, or only adding a prebuilt for one platform is intended, run:
\033[92mrepo upload --no-verify\033[0m
-to skip this warning."""
+to skip this warning.""")
sys.exit(1)
diff --git a/development/diagnose-build-failure/diagnose-build-failure.sh b/development/diagnose-build-failure/diagnose-build-failure.sh
index 89430cf..44b0cea 100755
--- a/development/diagnose-build-failure/diagnose-build-failure.sh
+++ b/development/diagnose-build-failure/diagnose-build-failure.sh
@@ -19,7 +19,7 @@
echo " Replaces the requirement for "'`'"./gradlew <tasks>"'`'" to fail with the requirement that it produces the given message"
echo
echo "SAMPLE USAGE"
- echo " $0 assembleDebug # or any other arguments you would normally give to ./gradlew"
+ echo " $0 assembleRelease # or any other arguments you would normally give to ./gradlew"
echo
echo "OUTPUT"
echo " diagnose-build-failure will conclude one of the following:"
@@ -122,7 +122,7 @@
function backupState() {
cd "$scriptPath"
backupDir="$1"
- ./impl/backup-state.sh "$backupDir" "$workingDir" "$@"
+ ./impl/backup-state.sh "$backupDir" "$workingDir" "$2"
}
function restoreState() {
diff --git a/development/diagnose-build-failure/impl/backup-state.sh b/development/diagnose-build-failure/impl/backup-state.sh
index 05708a2..df7ffa5 100755
--- a/development/diagnose-build-failure/impl/backup-state.sh
+++ b/development/diagnose-build-failure/impl/backup-state.sh
@@ -26,6 +26,12 @@
move=false
if [ "$moveArg" == "--move" ]; then
move=true
+ shift
+fi
+
+if [ "$3" != "" ]; then
+ echo "Unrecognized argument $3"
+ usage
fi
rm -rf "$stateDir"
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 1568593..b09e0a3 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -108,9 +108,9 @@
docs("androidx.enterprise:enterprise-feedback:1.1.0")
docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
docs("androidx.exifinterface:exifinterface:1.3.2")
- docs("androidx.fragment:fragment:1.3.3")
- docs("androidx.fragment:fragment-ktx:1.3.3")
- docs("androidx.fragment:fragment-testing:1.3.3")
+ docs("androidx.fragment:fragment:1.3.4")
+ docs("androidx.fragment:fragment-ktx:1.3.4")
+ docs("androidx.fragment:fragment-testing:1.3.4")
docs("androidx.gridlayout:gridlayout:1.0.0")
docs("androidx.heifwriter:heifwriter:1.1.0-alpha01")
docs("androidx.hilt:hilt-common:1.0.0-beta01")
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 4953d95..21ac59b 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -129,6 +129,7 @@
docs(project(":fragment:fragment-ktx"))
docs(project(":fragment:fragment-testing"))
docs(project(":gridlayout:gridlayout"))
+ docs(project(":health:health-services-client"))
docs(project(":heifwriter:heifwriter"))
docs(project(":hilt:hilt-common"))
docs(project(":hilt:hilt-navigation"))
diff --git a/docs/api_guidelines.md b/docs/api_guidelines.md
index f04be61..e2662a5 100644
--- a/docs/api_guidelines.md
+++ b/docs/api_guidelines.md
@@ -41,9 +41,8 @@
* `-core` for a low-level artifact that *may* contain public APIs but is
primarily intended for use by other libraries in the group
* `-ktx` for an Kotlin artifact that exposes idiomatic Kotlin APIs as an
- extension to a Java-only library
-* `-java8` for a Java 8 artifact that exposes idiomatic Java 8 APIs as an
- extension to a Java 7 library
+ extension to a Java-only library (see
+ [additional -ktx guidance](#module-ktx))
* `-<third-party>` for an artifact that integrates an optional third-party API
surface, e.g. `-proto` or `-rxjava2`. Note that a major version is included
in the sub-feature name for third-party API surfaces where the major version
@@ -177,6 +176,25 @@
external developers and should be considered a last-resort when backporting
behavior is not feasible.
+### Kotlin extension `-ktx` libraries {#module-ktx}
+
+New libraries should prefer Kotlin sources with built-in Java compatibility via
+`@JvmName` and other affordances of the Kotlin language; however, existing Java
+sourced libraries may benefit from extending their API surface with
+Kotlin-friendly APIs in a `-ktx` library.
+
+A Kotlin extension library **may only** provide extensions for a single base
+library's API surface and its name **must** match the base library exactly. For
+example, `work:work-ktx` may only provide extensions for APIs exposed by
+`work:work`.
+
+Additionally, an extension library **must** specify an `api`-type dependency on
+the base library and **must** be versioned and released identically to the base
+library.
+
+Kotlin extension libraries _should not_ expose new functionality; they should
+only provide Kotlin-friendly versions of existing Java-facing functionality.
+
## Platform compatibility API patterns {#platform-compatibility-apis}
NOTE For all library APIs that wrap or provide parity with platform APIs,
@@ -1767,7 +1785,7 @@
For library groups with strongly related samples that want to share code.
-Gradle project name: `:foo-library:samples`
+Gradle project name: `:foo-library:foo-library-samples`
```
foo-library/
@@ -1780,10 +1798,18 @@
For library groups with complex, relatively independent sub-libraries
-Gradle project name: `:foo-library:foo-module:samples`
+Gradle project name: `:foo-library:foo-module:foo-module-samples`
```
foo-library/
foo-module/
samples/
```
+
+**Samples module configuration**
+
+Samples modules are published to GMaven so that they are available to Android
+Studio, which displays code in @Sample annotations as hover-over pop-ups.
+
+To achieve this, samples modules must declare the same MavenGroup and `publish`
+as the library(s) they are samples for.
diff --git a/docs/benchmarking.md b/docs/benchmarking.md
index 5fc0276..a4fabb1 100644
--- a/docs/benchmarking.md
+++ b/docs/benchmarking.md
@@ -133,11 +133,11 @@
#### Device
-Get an API 28+ device (Or a rooted API 27 device). The rest of this section is
-about *why* those constraints exist, skip if not interested.
+Get an API 29+ device. The rest of this section is about *why* those constraints
+exist, skip if not interested.
Simpleperf has restrictions about where it can be used - Jetpack Benchmark will
-only support API 28+ for now, due to
+only support API 29+ for now, due to
[platform/simpleperf constraints](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md#prepare-an-android-application)
(see last subsection titled "If you want to profile Java code"). Summary is:
@@ -148,9 +148,13 @@
- 26 (O): Requires compiled Java code, and wrapper script. We haven't
investigated support.
-- 27 (P): Can profile all Java code, but requires `userdebug`/rooted device
+- 27 (O.1): Can profile all Java code, but requires `userdebug`/rooted device
-- \>=28 (Q): Can profile all Java code, requires profileable (or
+- 28 (P): Can profile all Java code, requires debuggable (or
+ `userdebug`/rooted device, but this appears to not be supported by scripts
+ currently)
+
+- \>=29 (Q): Can profile all Java code, requires profileable or debuggable (or
`userdebug`/rooted device)
We aren't planning to support profiling debuggable APK builds, since they're
diff --git a/docs/branching.md b/docs/branching.md
index 074a54c..dd23ec5 100644
--- a/docs/branching.md
+++ b/docs/branching.md
@@ -1,15 +1,20 @@
-# AndroidX Branch Workflow
+# Branch workflow
[TOC]
-## Single Development Branch [androidx-main]
+## Single Development Branch [`aosp/androidx-main`]
-All feature development occurs in the public AndroidX master dev branch of the
+All feature development occurs in the public main development branch of the
Android Open Source Project: `androidx-main`. This branch serves as the central
-location and source of truth for all AndroidX library source code. All alpha and
-beta version development, builds, and releases will be done ONLY in this branch.
+location and source of truth for all AndroidX library source code. All `alpha`
+and `beta` version work -- development, builds, and releases -- will be done
+ONLY in this branch.
-## Release Branches [androidx-\<feature\>-release]
+## Release Branches [`aosp/androidx-\<feature\>-release`]
+
+Release branches are used for stabilitization of a library and support of a
+previous stable release. With one development branch, this is how AndroidX
+provides support for the previous `rc` or stable version.
When a library updates to rc (release-candidate) or stable, that library version
will be snapped over to that library’s release branch. If that release branch
@@ -19,21 +24,13 @@
Release branches have the following properties:
-* A release branch will contain rc or stable versions of libraries.
-* Release branches are internal branches.
-* Release branches can **ONLY** be changed through
- cherry-picks
-* Bug-fixes and updates to that rc or stable version will need to be
- individually cherry-picked
-* No alpha or beta versions will exist in a release branch.
-* Toolchain and other library wide changes to androidx-main will be synced to
- each release branch.
+* A release branch will contain `rc` or `stable` versions of libraries
+* Release branches can **ONLY** be changed through cherry-picks
+* Bug fixes and updates to `rc` or stable versions must be cherry-picked
+* No `alpha` or `beta` versions will exist in a release branch.
+* Toolchain and other library wide changes to `androidx-main` will be synced
+ to each release branch.
* Release branches will have the naming format
`androidx-<feature-name>-release`
* Release branches will be re-snapped from `androidx-main` for each new minor
- version release (for example, releasing 2.2.0-rc01 after 2.1.0)
-
-## Platform Developement and AndroidX [androidx-platform-dev]
-
-Platform specific development is done using our INTERNAL platform development
-branch `androidx-platform-dev`.
+ version release (for example, releasing `2.2.0-rc01` after `2.1.0`)
diff --git a/docs/kdoc_guidelines.md b/docs/kdoc_guidelines.md
index ef33b04..72f91eb 100644
--- a/docs/kdoc_guidelines.md
+++ b/docs/kdoc_guidelines.md
@@ -179,6 +179,27 @@
*/
```
+### Don't use angle brackets for `@param`
+
+Instead of:
+
+```kotlin {.bad}
+/**
+ * @param <T> my cool param
+ */
+```
+
+Do this:
+
+```kotlin {.good}
+/**
+ * * @param T my cool param
+ */
+```
+
+This syntax is correct is Javadoc, but angle brackets aren't used in KDoc
+([@param reference guide](https://kotlinlang.org/docs/kotlin-doc.html#param-name)).
+
## Javadoc - KDoc differences
Some tags are shared between Javadoc and KDoc, such as `@param`, but there are
diff --git a/docs/onboarding.md b/docs/onboarding.md
index 5715972..f8dcafa 100644
--- a/docs/onboarding.md
+++ b/docs/onboarding.md
@@ -13,9 +13,10 @@
## Workstation setup {#setup}
-You will need to install the `repo` tool, which is used for Git branch and
-commit management. If you want to learn more about `repo`, see the
-[Repo Command Reference](https://source.android.com/setup/develop/repo).
+You will need to install the
+[`repo`](https://source.android.com/setup/develop#repo) tool, which is used for
+Git branch and commit management. If you want to learn more about `repo`, see
+the [Repo Command Reference](https://source.android.com/setup/develop/repo).
### Linux and MacOS {#setup-linux-mac}
@@ -210,7 +211,10 @@
```
The `--cbr` switch automatically picks the current repo branch for upload. The
-`-t` switch sets the Gerrit topic to the branch name, e.g. `my-branch-name`.
+`-t` switch sets the Gerrit topic to the branch name, e.g. `my-branch-name`. You
+can refer to the
+[Android documentation](https://source.android.com/setup/create/coding-tasks#workflow)
+for a high level overview of this basic workflow.
NOTE If you encounter issues with `repo upload`, consider running upload with
trace enabled, e.g. `GIT_DAPPER_TRACE=1 repo --trace upload . --cbr -y`. These
diff --git a/docs/policies.md b/docs/policies.md
index 35b7aaa..ffe656f 100644
--- a/docs/policies.md
+++ b/docs/policies.md
@@ -15,7 +15,7 @@
Libraries developed in AndroidX follow a consistent project naming and directory
structure.
-Library groups should organize their modules into directories and module names
+Library groups should organize their projects into directories and project names
(in brackets) as:
```
@@ -39,6 +39,27 @@
testapp/ [navigation:integration-tests:testapp]
```
+### Project creator script {#project-creator}
+
+Note: The terms _project_, _module_, and _library_ are often used
+interchangeably within AndroidX, with project being the technical term used by
+Gradle to describe a build target, e.g. a library that maps to a single AAR.
+
+New projects can be created using our
+[project creation script](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:development/project-creator/?q=project-creator&ss=androidx%2Fplatform%2Fframeworks%2Fsupport)
+available in our repo.
+
+It will create a new project with the proper structure and configuration based
+on your project needs!
+
+To use it:
+
+```sh
+cd ~/androidx-main/framework/support && \
+cd development/project-creator && \
+./create_project.py androidx.foo foo-bar
+```
+
## Terminology {#terminology}
**Artifact**
@@ -91,24 +112,6 @@
}
```
-#### Finalizing for release {#finalizing-for-release}
-
-When the artifact is ready for release, its versioned API file should be
-finalized to ensure that the subsequent versioned release conforms to the
-versioning policies.
-
-```
-./gradlew <module>:finalizeApi
-```
-
-This will prevent any subsequent changes to the API surface until the artifact
-version is updated. To update the artifact version and allow changes within the
-semantic versioning contract, simply update the version string in the artifact's
-`build.gradle` (see [Workflow](#workflow) introduction).
-
-To avoid breaking the development workflow, we recommend that API finalization
-and version string updates be submitted as a single CL.
-
## Dependencies {#dependencies}
Artifacts may depend on other artifacts within AndroidX as well as sanctioned
@@ -130,6 +133,9 @@
only have implementation-type dependencies, your artifact may carry either the
`alpha` or `beta` suffix.
+Note: This does not apply to test dependencies: suffixes of test dependencies do
+_not_ carry over to your artifact.
+
#### Pinned versions {#pinned-versions}
To avoid issues with dependency versioning, consider pinning your artifact's
diff --git a/docs/versioning.md b/docs/versioning.md
index 27efde7..3c1424a 100644
--- a/docs/versioning.md
+++ b/docs/versioning.md
@@ -239,13 +239,16 @@
granted for new features, non-trivial API changes, significant refactorings, or
any changes likely to introduce additional functional instability. Requests for
exceptions **must** be accompanied by a justification explaining why the change
-cannot be made in a future minor version.
+cannot be made in a future minor version. This policy does not apply to
+additions of `@Experimental` APIs or changes to `@Experimental` APIs.
#### Checklist for moving to `beta01` {#beta-checklist}
* API surface
* Entire API surface has been reviewed by API Council
* All APIs from alpha undergoing deprecate/remove cycle must be removed
+ * The final removal of a `@Deprecated` API must occur in alpha, not in
+ Beta.
* Testing
* All public APIs are tested
* All pre-submit and post-submit tests are enabled (e.g. all suppressions
@@ -264,10 +267,13 @@
* API surface
* May not add, remove, or change APIs unless granted an exception by API
Council following the beta API change exception request process
- * Must go through the full `@Deprecate` and hard-removal cycle in separate
- `beta` releases for any exception-approved API removals or changes
- * May not remove `@Experimental` from experimental APIs, see previous item
- regarding new APIs
+ * Must go through the full `@Deprecate` and hard-removal cycle in
+ separate `beta` releases for any exception-approved API removals or
+ changes
+ * May not remove `@Experimental` from experimental APIs, as this would
+ amount to an API addition
+ * **May** add new `@Experimental` APIs and change existing `@Experimental`
+ APIs
### RC {#rc}
diff --git a/dynamic-animation/dynamic-animation-ktx/lint-baseline.xml b/dynamic-animation/dynamic-animation-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/dynamic-animation/dynamic-animation-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/emoji/bundled/lint-baseline.xml b/emoji/bundled/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/emoji/bundled/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/emoji2/emoji2-benchmark/lint-baseline.xml b/emoji2/emoji2-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/emoji2/emoji2-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/emoji2/emoji2/api/current.txt b/emoji2/emoji2/api/current.txt
index f69bb70..f723bb9 100644
--- a/emoji2/emoji2/api/current.txt
+++ b/emoji2/emoji2/api/current.txt
@@ -2,7 +2,7 @@
package androidx.emoji2.text {
public final class DefaultEmojiCompatConfig {
- method public static androidx.emoji2.text.EmojiCompat.Config? create(android.content.Context);
+ method public static androidx.emoji2.text.FontRequestEmojiCompatConfig? create(android.content.Context);
}
@AnyThread public class EmojiCompat {
diff --git a/emoji2/emoji2/api/public_plus_experimental_current.txt b/emoji2/emoji2/api/public_plus_experimental_current.txt
index f69bb70..f723bb9 100644
--- a/emoji2/emoji2/api/public_plus_experimental_current.txt
+++ b/emoji2/emoji2/api/public_plus_experimental_current.txt
@@ -2,7 +2,7 @@
package androidx.emoji2.text {
public final class DefaultEmojiCompatConfig {
- method public static androidx.emoji2.text.EmojiCompat.Config? create(android.content.Context);
+ method public static androidx.emoji2.text.FontRequestEmojiCompatConfig? create(android.content.Context);
}
@AnyThread public class EmojiCompat {
diff --git a/emoji2/emoji2/api/restricted_current.txt b/emoji2/emoji2/api/restricted_current.txt
index f69bb70..f723bb9 100644
--- a/emoji2/emoji2/api/restricted_current.txt
+++ b/emoji2/emoji2/api/restricted_current.txt
@@ -2,7 +2,7 @@
package androidx.emoji2.text {
public final class DefaultEmojiCompatConfig {
- method public static androidx.emoji2.text.EmojiCompat.Config? create(android.content.Context);
+ method public static androidx.emoji2.text.FontRequestEmojiCompatConfig? create(android.content.Context);
}
@AnyThread public class EmojiCompat {
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java
index a03db5a..1c9db25 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java
@@ -93,8 +93,9 @@
* could be found.
*/
@Nullable
- public static EmojiCompat.Config create(@NonNull Context context) {
- return new DefaultEmojiCompatConfigFactory(null).create(context);
+ public static FontRequestEmojiCompatConfig create(@NonNull Context context) {
+ return (FontRequestEmojiCompatConfig) new DefaultEmojiCompatConfigFactory(null)
+ .create(context);
}
/**
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
index 462d1d4..69fd6904 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
@@ -17,8 +17,17 @@
package androidx.emoji2.text;
import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.WorkerThread;
+import androidx.core.os.TraceCompat;
import androidx.startup.Initializer;
import java.util.Collections;
@@ -27,16 +36,21 @@
/**
* Initializer for configuring EmojiCompat with the system installed downloadable font provider.
*
+ * <p>This initializer will initialize EmojiCompat immediately then defer loading the font for a
+ * short delay to avoid delaying application startup. Typically, the font will be loaded shortly
+ * after the first screen of your application loads, which means users may see system emoji
+ * briefly prior to the compat font loading.</p>
+ *
* <p>This is the recommended configuration for all apps that don't need specialized configuration,
- * and don't need to control the thread that initialization runs on. For more information see
- * {@link androidx.emoji2.text.DefaultEmojiCompatConfig}.</p>
+ * and don't need to control the background thread that initialization runs on. For more information
+ * see {@link androidx.emoji2.text.DefaultEmojiCompatConfig}.</p>
*
* <p>In addition to the reasons listed in {@code DefaultEmojiCompatConfig} you may wish to disable
* this automatic configuration if you intend to call initialization from an existing background
* thread pool in your application.</p>
*
- * <p></p>This is enabled by default by including the `:emoji2:emoji2` gradle artifact. To disable
- * the default configuration (and allow manual configuration) add this to your manifest:</p>
+ * <p>This is enabled by default by including the {@code :emoji2:emoji2} gradle artifact. To
+ * disable the default configuration (and allow manual configuration) add this to your manifest:</p>
*
* <pre>
* <provider
@@ -52,6 +66,8 @@
* @see androidx.emoji2.text.DefaultEmojiCompatConfig
*/
public class EmojiCompatInitializer implements Initializer<Boolean> {
+ private static final long STARTUP_THREAD_CREATION_DELAY_MS = 500L;
+ private static final String S_INITIALIZER_THREAD_NAME = "EmojiCompatInitializer";
/**
* Initialize EmojiCompat with the app's context.
@@ -62,8 +78,19 @@
@NonNull
@Override
public Boolean create(@NonNull Context context) {
- // note: super create requires this be non-null, share if the configuration was successful
- return EmojiCompat.init(context) != null;
+ if (Build.VERSION.SDK_INT >= 19) {
+ final Handler mainHandler;
+ if (Build.VERSION.SDK_INT >= 28) {
+ mainHandler = Handler28Impl.createAsync(Looper.getMainLooper());
+ } else {
+ mainHandler = new Handler(Looper.getMainLooper());
+ }
+ EmojiCompat.init(new BackgroundDefaultConfig(context));
+ mainHandler.postDelayed(new LoadEmojiCompatRunnable(),
+ STARTUP_THREAD_CREATION_DELAY_MS);
+ return true;
+ }
+ return false;
}
/**
@@ -74,4 +101,109 @@
public List<Class<? extends Initializer<?>>> dependencies() {
return Collections.emptyList();
}
+
+ static class LoadEmojiCompatRunnable implements Runnable {
+ @Override
+ public void run() {
+ try {
+ // this is main thread, so mark what we're doing (this trace includes thread
+ // start time in BackgroundLoadingLoader.load
+ TraceCompat.beginSection("EmojiCompat.EmojiCompatInitializer.run");
+ if (EmojiCompat.isConfigured()) {
+ EmojiCompat.get().load();
+ }
+ } finally {
+ TraceCompat.endSection();
+ }
+ }
+ }
+
+ @RequiresApi(19)
+ static class BackgroundDefaultConfig extends EmojiCompat.Config {
+ protected BackgroundDefaultConfig(Context context) {
+ super(new BackgroundDefaultLoader(context));
+ setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL);
+ }
+ }
+
+ @RequiresApi(19)
+ static class BackgroundDefaultLoader implements EmojiCompat.MetadataRepoLoader {
+ private final Context mContext;
+
+ BackgroundDefaultLoader(Context context) {
+ mContext = context.getApplicationContext();
+ }
+
+ @Nullable
+ private HandlerThread mThread;
+
+ @Override
+ public void load(@NonNull EmojiCompat.MetadataRepoLoaderCallback loaderCallback) {
+ Handler handler = getThreadHandler();
+ handler.post(() -> doLoad(loaderCallback, handler));
+ }
+
+ @WorkerThread
+ void doLoad(@NonNull EmojiCompat.MetadataRepoLoaderCallback loaderCallback,
+ @NonNull Handler handler) {
+ try {
+ FontRequestEmojiCompatConfig config = DefaultEmojiCompatConfig.create(mContext);
+ if (config == null) {
+ throw new RuntimeException("EmojiCompat font provider not available on this "
+ + "device.");
+ }
+ config.setHandler(handler);
+ config.getMetadataRepoLoader().load(new EmojiCompat.MetadataRepoLoaderCallback() {
+ @Override
+ public void onLoaded(@NonNull MetadataRepo metadataRepo) {
+ try {
+ // main thread is notified before returning, so we can quit now
+ loaderCallback.onLoaded(metadataRepo);
+ } finally {
+ quitHandlerThread();
+ }
+ }
+
+ @Override
+ public void onFailed(@Nullable Throwable throwable) {
+ try {
+ // main thread is notified before returning, so we can quit now
+ loaderCallback.onFailed(throwable);
+ } finally {
+ quitHandlerThread();
+ }
+ }
+ });
+ } catch (Throwable t) {
+ loaderCallback.onFailed(t);
+ quitHandlerThread();
+ }
+ }
+
+ void quitHandlerThread() {
+ if (mThread != null) {
+ mThread.quitSafely();
+ }
+ }
+
+ @NonNull
+ private Handler getThreadHandler() {
+ mThread = new HandlerThread(S_INITIALIZER_THREAD_NAME,
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mThread.start();
+ return new Handler(mThread.getLooper());
+ }
+ }
+
+ @RequiresApi(28)
+ private static class Handler28Impl {
+ private Handler28Impl() {
+ // Non-instantiable.
+ }
+
+ // avoid aligning with vsync when available (API 28+)
+ public static Handler createAsync(Looper looper) {
+ return Handler.createAsync(looper);
+ }
+ }
}
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
index 1e3006e..c261675 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
@@ -32,6 +32,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.graphics.TypefaceCompatUtil;
+import androidx.core.os.TraceCompat;
import androidx.core.provider.FontRequest;
import androidx.core.provider.FontsContractCompat;
import androidx.core.provider.FontsContractCompat.FontFamilyResult;
@@ -165,6 +166,10 @@
* given FontRequest.
*/
private static class FontRequestMetadataLoader implements EmojiCompat.MetadataRepoLoader {
+ private static final String S_TRACE_BUILD_TYPEFACE =
+ "EmojiCompat.FontRequestEmojiCompatConfig.buildTypeface";
+ private static final String S_TRACE_THREAD_CREATION =
+ "EmojiCompat.FontRequestEmojiCompatConfig.threadCreation";
private final @NonNull Context mContext;
private final @NonNull FontRequest mRequest;
private final @NonNull FontProviderHelper mFontProviderHelper;
@@ -209,11 +214,17 @@
public void load(@NonNull final EmojiCompat.MetadataRepoLoaderCallback loaderCallback) {
Preconditions.checkNotNull(loaderCallback, "LoaderCallback cannot be null");
synchronized (mLock) {
- if (mHandler == null) {
- // Developer didn't give a thread for fetching. Create our own one.
- mThread = new HandlerThread("emojiCompat", Process.THREAD_PRIORITY_BACKGROUND);
- mThread.start();
- mHandler = new Handler(mThread.getLooper());
+ try {
+ TraceCompat.beginSection(S_TRACE_THREAD_CREATION);
+ if (mHandler == null) {
+ // Developer didn't give a thread for fetching. Create our own one.
+ mThread = new HandlerThread("emojiCompat",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mThread.start();
+ mHandler = new Handler(mThread.getLooper());
+ }
+ } finally {
+ TraceCompat.endSection();
}
mHandler.post(new Runnable() {
@Override
@@ -312,13 +323,21 @@
throw new RuntimeException("fetchFonts result is not OK. (" + resultCode + ")");
}
- // TODO: Good to add new API to create Typeface from FD not to open FD twice.
- final Typeface typeface = mFontProviderHelper.buildTypeface(mContext, font);
- final ByteBuffer buffer = TypefaceCompatUtil.mmap(mContext, null, font.getUri());
- if (buffer == null) {
- throw new RuntimeException("Unable to open file.");
+ final MetadataRepo metadataRepo;
+ try {
+ TraceCompat.beginSection(S_TRACE_BUILD_TYPEFACE);
+ // TODO: Good to add new API to create Typeface from FD not to open FD twice.
+ final Typeface typeface = mFontProviderHelper.buildTypeface(mContext, font);
+ final ByteBuffer buffer = TypefaceCompatUtil.mmap(mContext, null,
+ font.getUri());
+ if (buffer == null) {
+ throw new RuntimeException("Unable to open file.");
+ }
+ metadataRepo = MetadataRepo.create(typeface, buffer);
+ } finally {
+ TraceCompat.endSection();
}
- mCallback.onLoaded(MetadataRepo.create(typeface, buffer));
+ mCallback.onLoaded(metadataRepo);
cleanUp();
} catch (Throwable t) {
mCallback.onFailed(t);
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
index bc31e26..a168667 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
@@ -24,6 +24,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
+import androidx.core.os.TraceCompat;
import androidx.core.util.Preconditions;
import androidx.emoji2.text.flatbuffer.MetadataList;
@@ -41,6 +42,7 @@
* The default children size of the root node.
*/
private static final int DEFAULT_ROOT_SIZE = 1024;
+ private static final String S_TRACE_CREATE_REPO = "EmojiCompat.MetadataRepo.create";
/**
* MetadataList that contains the emoji metadata.
@@ -87,7 +89,12 @@
@NonNull
@RestrictTo(RestrictTo.Scope.TESTS)
public static MetadataRepo create(@NonNull final Typeface typeface) {
- return new MetadataRepo(typeface, new MetadataList());
+ try {
+ TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+ return new MetadataRepo(typeface, new MetadataList());
+ } finally {
+ TraceCompat.endSection();
+ }
}
/**
@@ -100,7 +107,12 @@
@NonNull
public static MetadataRepo create(@NonNull final Typeface typeface,
@NonNull final InputStream inputStream) throws IOException {
- return new MetadataRepo(typeface, MetadataListReader.read(inputStream));
+ try {
+ TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+ return new MetadataRepo(typeface, MetadataListReader.read(inputStream));
+ } finally {
+ TraceCompat.endSection();
+ }
}
/**
@@ -113,7 +125,12 @@
@NonNull
public static MetadataRepo create(@NonNull final Typeface typeface,
@NonNull final ByteBuffer byteBuffer) throws IOException {
- return new MetadataRepo(typeface, MetadataListReader.read(byteBuffer));
+ try {
+ TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+ return new MetadataRepo(typeface, MetadataListReader.read(byteBuffer));
+ } finally {
+ TraceCompat.endSection();
+ }
}
/**
@@ -126,8 +143,14 @@
@NonNull
public static MetadataRepo create(@NonNull final AssetManager assetManager,
@NonNull final String assetPath) throws IOException {
- final Typeface typeface = Typeface.createFromAsset(assetManager, assetPath);
- return new MetadataRepo(typeface, MetadataListReader.read(assetManager, assetPath));
+ try {
+ TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+ final Typeface typeface = Typeface.createFromAsset(assetManager, assetPath);
+ return new MetadataRepo(typeface,
+ MetadataListReader.read(assetManager, assetPath));
+ } finally {
+ TraceCompat.endSection();
+ }
}
/**
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/README.md b/emoji2/integration-tests/init-disabled-macrobenchmark-target/README.md
new file mode 100644
index 0000000..95567d1
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/README.md
@@ -0,0 +1,2 @@
+A simple app with emoji2 startup initializer removed.
+
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/build.gradle b/emoji2/integration-tests/init-disabled-macrobenchmark-target/build.gradle
new file mode 100644
index 0000000..5f1098a
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("kotlin-android")
+}
+
+android {
+ buildTypes {
+ release {
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile("proguard-android-optimize.txt")
+ }
+ }
+}
+
+dependencies {
+ implementation(KOTLIN_STDLIB)
+ implementation(CONSTRAINT_LAYOUT, { transitive = true })
+ implementation(project(":arch:core:core-runtime"))
+ implementation(project(":appcompat:appcompat"))
+ implementation(project(":startup:startup-runtime"))
+ implementation(MATERIAL)
+}
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..cffda4d
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/AndroidManifest.xml
@@ -0,0 +1,57 @@
+<!--
+ ~ Copyright 2020 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.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.emoji2.integration.macrobenchmark.disabled.target">
+
+ <application
+ android:label="Emoji2 Init Enabled Macrobenchmark Target"
+ android:allowBackup="false"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.AppCompat"
+ tools:ignore="MissingApplicationIcon">
+
+ <!-- Profileable to enable macrobenchmark profiling -->
+ <!--suppress AndroidElementNotAllowed -->
+ <profileable android:shell="true"/>
+
+ <!--
+ Activities need to be exported so the macrobenchmark can discover them
+ under the new package visibility changes for Android 11.
+ -->
+ <activity
+ android:name=".SimpleTextActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="androidx.emoji2.integration.macrobenchmark.disabled.target.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ android:exported="false"
+ tools:node="merge">
+ <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
+ tools:node="remove" />
+ </provider>
+ </application>
+</manifest>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/disabled/target/SimpleTextActivity.kt b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/disabled/target/SimpleTextActivity.kt
new file mode 100644
index 0000000..a4ae6f5
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/disabled/target/SimpleTextActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.emoji2.integration.macrobenchmark.disabled.target
+
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+class SimpleTextActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ val notice = findViewById<TextView>(R.id.txtNotice)
+ notice.setText(R.string.app_notice)
+ }
+}
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/layout/activity_main.xml b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..6ec3889
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 2020 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.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/txtNotice"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:textSize="50sp"
+ tools:text="🐻❄️ (disabled)" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
new file mode 100644
index 0000000..207eaaa
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2020 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.
+ -->
+
+<resources>
+ <string name="app_notice">"🐻❄️" Appcompat emoji2 (disabled) test app.</string>
+</resources>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/build.gradle b/emoji2/integration-tests/init-disabled-macrobenchmark/build.gradle
new file mode 100644
index 0000000..1ef079d
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 28
+ }
+}
+
+dependencies {
+ androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+ androidTestImplementation(project(":internal-testutils-macrobenchmark"))
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+}
+
+def installReleaseTarget = tasks.getByPath(
+ ":emoji2:integration-tests:init-disabled-macrobenchmark-target:installRelease"
+)
+// Define a task dependency so the app is installed before we run macro benchmarks.
+tasks.getByPath(":emoji2:integration-tests:init-disabled-macrobenchmark:connectedCheck")
+ .dependsOn(installReleaseTarget)
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/AndroidManifest.xml b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..67818b1
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.disabled.test"/>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/disabled/EmojiStartupBenchmark.kt b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/disabled/EmojiStartupBenchmark.kt
new file mode 100644
index 0000000..488b9d8
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/disabled/EmojiStartupBenchmark.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.emoji2.integration.macrobenchmark.disabled
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.testutils.measureStartup
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class EmojiStartupBenchmark {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ fun disabledstartup() {
+ benchmarkRule.measureStartup(
+ compilationMode = CompilationMode.None,
+ startupMode = StartupMode.COLD,
+ packageName = "androidx.emoji2.integration.macrobenchmark.disabled.target"
+ ) {
+ action = "androidx.emoji2.integration.macrobenchmark.disabled.target.MAIN"
+ }
+ }
+}
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-disabled-macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0ca6a87
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 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.
+ -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.disabled" />
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/README.md b/emoji2/integration-tests/init-enabled-macrobenchmark-target/README.md
new file mode 100644
index 0000000..890837d
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/README.md
@@ -0,0 +1 @@
+A simple app with emoji2 startup initializer enabled.
\ No newline at end of file
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/build.gradle b/emoji2/integration-tests/init-enabled-macrobenchmark-target/build.gradle
new file mode 100644
index 0000000..9c0aa54
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("kotlin-android")
+}
+
+android {
+ buildTypes {
+ release {
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile("proguard-android-optimize.txt")
+ }
+ }
+}
+
+dependencies {
+ implementation(KOTLIN_STDLIB)
+ implementation(CONSTRAINT_LAYOUT, { transitive = true })
+ implementation(project(":arch:core:core-runtime"))
+ implementation(project(":appcompat:appcompat"))
+ implementation(MATERIAL)
+}
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..eff5f4c
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<!--
+ ~ Copyright 2020 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.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.emoji2.integration.macrobenchmark.enabled.target">
+
+ <application
+ android:label="Emoji2 Init Enabled Macrobenchmark Target"
+ android:allowBackup="false"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.AppCompat"
+ tools:ignore="MissingApplicationIcon">
+
+ <!-- Profileable to enable macrobenchmark profiling -->
+ <!--suppress AndroidElementNotAllowed -->
+ <profileable android:shell="true"/>
+
+ <!--
+ Activities need to be exported so the macrobenchmark can discover them
+ under the new package visibility changes for Android 11.
+ -->
+ <activity
+ android:name=".SimpleTextActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="androidx.emoji2.integration.macrobenchmark.enabled.target.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/enabled/target/SimpleTextActivity.kt b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/enabled/target/SimpleTextActivity.kt
new file mode 100644
index 0000000..e46d648
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/enabled/target/SimpleTextActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.emoji2.integration.macrobenchmark.enabled.target
+
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+class SimpleTextActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ val notice = findViewById<TextView>(R.id.txtNotice)
+ notice.setText(R.string.app_notice)
+ }
+}
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/layout/activity_main.xml b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..c81807d
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 2020 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.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/txtNotice"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:textSize="50sp"
+ tools:text="🐻❄️" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
new file mode 100644
index 0000000..11715d5
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2020 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.
+ -->
+
+<resources>
+ <string name="app_notice">"🐻❄️" Appcompat emoji2 (enabled) test app.</string>
+</resources>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/build.gradle b/emoji2/integration-tests/init-enabled-macrobenchmark/build.gradle
new file mode 100644
index 0000000..c9ebc39
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 28
+ }
+}
+
+dependencies {
+ androidTestImplementation(project(":emoji2:emoji2"))
+ androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+ androidTestImplementation(project(":internal-testutils-macrobenchmark"))
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+}
+
+def installReleaseTarget = tasks.getByPath(
+ ":emoji2:integration-tests:init-enabled-macrobenchmark-target:installRelease"
+)
+
+// Define a task dependency so the app is installed before we run macro benchmarks.
+tasks.getByPath(":emoji2:integration-tests:init-enabled-macrobenchmark:connectedCheck")
+ .dependsOn(installReleaseTarget)
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/AndroidManifest.xml b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..dbacc9c
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.enabled.test"/>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/enabled/EmojiStartupBenchmark.kt b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/enabled/EmojiStartupBenchmark.kt
new file mode 100644
index 0000000..61e06cb
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/enabled/EmojiStartupBenchmark.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.emoji2.integration.macrobenchmark.enabled
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.emoji2.text.DefaultEmojiCompatConfig
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.testutils.measureStartup
+import org.junit.Assume.assumeTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class EmojiStartupBenchmark {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ fun enabledstartup() {
+ // only run this test if the device can configure emoji2
+ assumeTrue(hasDiscoverableFontProviderOnDevice())
+ benchmarkRule.measureStartup(
+ compilationMode = CompilationMode.None,
+ startupMode = StartupMode.COLD,
+ packageName = "androidx.emoji2.integration.macrobenchmark.enabled.target"
+ ) {
+ action = "androidx.emoji2.integration.macrobenchmark.enabled.target.MAIN"
+ }
+ }
+
+ private fun hasDiscoverableFontProviderOnDevice(): Boolean {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ return DefaultEmojiCompatConfig.create(context) != null
+ }
+}
\ No newline at end of file
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-enabled-macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..040c742
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 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.
+ -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.enabled" />
diff --git a/enterprise/feedback/lint-baseline.xml b/enterprise/feedback/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/enterprise/feedback/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/enterprise/feedback/testing/lint-baseline.xml b/enterprise/feedback/testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/enterprise/feedback/testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-ktx/lint-baseline.xml b/fragment/fragment-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/fragment/fragment-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-lint/lint-baseline.xml b/fragment/fragment-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/fragment/fragment-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt
index 5384ce0..c0dc727 100644
--- a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt
@@ -31,6 +31,7 @@
UnsafeFragmentLifecycleObserverDetector.BACK_PRESSED_ISSUE,
UnsafeFragmentLifecycleObserverDetector.LIVEDATA_ISSUE,
UseRequireInsteadOfGet.ISSUE,
- UseGetLayoutInflater.ISSUE
+ UseGetLayoutInflater.ISSUE,
+ OnCreateDialogIncorrectCallbackDetector.ISSUE
)
}
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt
new file mode 100644
index 0000000..b462e88
--- /dev/null
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.fragment.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+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.isKotlin
+import com.intellij.psi.impl.source.PsiClassReferenceType
+import org.jetbrains.kotlin.psi.psiUtil.getSuperNames
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.kotlin.KotlinUClass
+import org.jetbrains.uast.visitor.AbstractUastVisitor
+
+/**
+ * When using a `DialogFragment`, the `setOnCancelListener` and `setOnDismissListener` callback
+ * functions within the `onCreateDialog` function __must not be used__
+ * because the `DialogFragment` owns these callbacks. Instead the respective `onCancel` and
+ * `onDismiss` functions can be used to achieve the desired effect.
+ */
+class OnCreateDialogIncorrectCallbackDetector : Detector(), SourceCodeScanner {
+
+ companion object Issues {
+ val ISSUE = Issue.create(
+ id = "DialogFragmentCallbacksDetector",
+ briefDescription = "Use onCancel() and onDismiss() instead of calling " +
+ "setOnCancelListener() and setOnDismissListener() from onCreateDialog()",
+ explanation = """When using a `DialogFragment`, the `setOnCancelListener` and \
+ `setOnDismissListener` callback functions within the `onCreateDialog` function \
+ __must not be used__ because the `DialogFragment` owns these callbacks. \
+ Instead the respective `onCancel` and `onDismiss` functions can be used to \
+ achieve the desired effect.""",
+ category = Category.CORRECTNESS,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ OnCreateDialogIncorrectCallbackDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ ),
+ androidSpecific = true
+ )
+ }
+
+ override fun getApplicableUastTypes(): List<Class<out UElement>>? {
+ return listOf(UClass::class.java)
+ }
+
+ override fun createUastHandler(context: JavaContext): UElementHandler? {
+ return UastHandler(context)
+ }
+
+ private inner class UastHandler(val context: JavaContext) : UElementHandler() {
+ override fun visitClass(node: UClass) {
+ if (isKotlin(context.psiFile) && (node as KotlinUClass).ktClass!!.getSuperNames()[0] !=
+ DIALOG_FRAGMENT_CLASS
+ ) {
+ return
+ }
+
+ if (!isKotlin(context.psiFile) &&
+ (node.uastSuperTypes[0].type as PsiClassReferenceType)
+ .className != DIALOG_FRAGMENT_CLASS
+ ) {
+ return
+ }
+
+ node.methods.forEach {
+ if (it.name == ENTRY_METHOD) {
+ val visitor = UastMethodsVisitor(context, it.name)
+ it.uastBody?.accept(visitor)
+ }
+ }
+ }
+ }
+
+ /**
+ * A UAST Visitor that explores all method calls within a
+ * [androidx.fragment.app.DialogFragment] callback to check for an incorrect method call.
+ *
+ * @param context The context of the lint request.
+ * @param containingMethodName The name of the originating Fragment lifecycle method.
+ */
+ private class UastMethodsVisitor(
+ private val context: JavaContext,
+ private val containingMethodName: String
+ ) : AbstractUastVisitor() {
+ private val visitedMethods = mutableSetOf<UCallExpression>()
+
+ override fun visitCallExpression(node: UCallExpression): Boolean {
+ if (visitedMethods.contains(node)) {
+ return super.visitCallExpression(node)
+ }
+
+ val methodName = node.methodIdentifier?.name ?: return super.visitCallExpression(node)
+
+ when (methodName) {
+ SET_ON_CANCEL_LISTENER -> {
+ report(
+ context = context,
+ node = node,
+ message = "Use onCancel() instead of calling setOnCancelListener() " +
+ "from onCreateDialog()"
+ )
+ visitedMethods.add(node)
+ }
+ SET_ON_DISMISS_LISTENER -> {
+ report(
+ context = context,
+ node = node,
+ message = "Use onDismiss() instead of calling setOnDismissListener() " +
+ "from onCreateDialog()"
+ )
+ visitedMethods.add(node)
+ }
+ }
+ return super.visitCallExpression(node)
+ }
+
+ private fun report(context: JavaContext, node: UCallExpression, message: String) {
+ context.report(
+ issue = ISSUE,
+ location = context.getLocation(node),
+ message = message,
+ quickfixData = null
+ )
+ }
+ }
+}
+
+private const val ENTRY_METHOD = "onCreateDialog"
+private const val DIALOG_FRAGMENT_CLASS = "DialogFragment"
+private const val SET_ON_CANCEL_LISTENER = "setOnCancelListener"
+private const val SET_ON_DISMISS_LISTENER = "setOnDismissListener"
diff --git a/fragment/fragment-lint/src/test/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetectorTest.kt b/fragment/fragment-lint/src/test/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetectorTest.kt
new file mode 100644
index 0000000..c67cd41
--- /dev/null
+++ b/fragment/fragment-lint/src/test/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetectorTest.kt
@@ -0,0 +1,271 @@
+/*
+ * 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.fragment.lint
+
+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
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+class OnCreateDialogIncorrectCallbackDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = OnCreateDialogIncorrectCallbackDetector()
+
+ override fun getIssues(): MutableList<Issue> {
+ return mutableListOf(OnCreateDialogIncorrectCallbackDetector.ISSUE)
+ }
+
+ private val dialogFragmentCorrectImplementationStubJava = java(
+ """
+ package foo;
+ import android.app.Dialog;
+ import android.content.DialogInterface;
+ import android.os.Bundle;
+ import androidx.appcompat.app.AlertDialog;
+ import androidx.fragment.app.DialogFragment;
+ public class TestFragment extends DialogFragment {
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ Dialog dialog = AlertDialog.Builder(requireActivity());
+ return dialog.create();
+ }
+ }
+ """
+ ).indented()
+
+ private val dialogFragmentCorrectImplementationStubKotlin = kotlin(
+ """
+ package foo
+ import android.app.Dialog
+ import android.content.DialogInterface
+ import android.os.Bundle
+ import androidx.appcompat.app.AlertDialog
+ import androidx.fragment.app.DialogFragment
+ class TestDialog : DialogFragment() {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val dialog = AlertDialog.Builder(requireActivity())
+ return dialog.create()
+ }
+ override fun onCancel(dialog: DialogInterface) {
+ super.onCancel(dialog)
+ }
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+ }
+ }
+ """
+ ).indented()
+
+ private val dialogFragmentStubJavaWithCancelListener = java(
+ """
+ package foo;
+ import android.app.Dialog;
+ import android.content.DialogInterface;
+ import android.os.Bundle;
+ import androidx.appcompat.app.AlertDialog;
+ import androidx.fragment.app.DialogFragment;
+ public class TestFragment extends DialogFragment {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = AlertDialog.Builder(requireActivity());
+ dialog.setOnCancelListener({ });
+ return dialog.create();
+ }
+ }
+ """
+ ).indented()
+
+ private val dialogFragmentStubJavaWithDismissListener = java(
+ """
+ package foo;
+ import android.app.Dialog;
+ import android.content.DialogInterface;
+ import android.os.Bundle;
+ import androidx.appcompat.app.AlertDialog;
+ import androidx.fragment.app.DialogFragment;
+ public class TestFragment extends DialogFragment {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = AlertDialog.Builder(requireActivity());
+ dialog.setOnDismissListener({ });
+ return dialog.create();
+ }
+ }
+ """
+ ).indented()
+
+ private val dialogFragmentStubKotlinWithCancelListener = kotlin(
+ """
+ package foo
+ import android.app.Dialog
+ import android.content.DialogInterface
+ import android.os.Bundle
+ import androidx.appcompat.app.AlertDialog
+ import androidx.fragment.app.DialogFragment
+ class TestDialog : DialogFragment() {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val dialog = AlertDialog.Builder(requireActivity())
+ dialog.setOnCancelListener { }
+ return dialog.create()
+ }
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+ }
+ }
+ """
+ ).indented()
+
+ private val dialogFragmentStubKotlinWithDismissListener = kotlin(
+ """
+ package foo
+ import android.app.Dialog
+ import android.content.DialogInterface
+ import android.os.Bundle
+ import androidx.appcompat.app.AlertDialog
+ import androidx.fragment.app.DialogFragment
+ class TestDialog : DialogFragment() {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val dialog = AlertDialog.Builder(requireActivity())
+ dialog.setOnDismissListener { }
+ return dialog.create()
+ }
+
+ override fun onCancel(dialog: DialogInterface) {
+ super.onCancel(dialog)
+ }
+ }
+ """
+ ).indented()
+
+ private val dialogFragmentStubKotlinWithDismissAndCancelListeners = kotlin(
+ """
+ package foo
+ import android.app.Dialog
+ import android.content.DialogInterface
+ import android.os.Bundle
+ import androidx.appcompat.app.AlertDialog
+ import androidx.fragment.app.DialogFragment
+ class TestDialog : DialogFragment() {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val dialog = AlertDialog.Builder(requireActivity())
+ dialog.setOnDismissListener { }
+ dialog.setOnCancelListener { }
+ return dialog.create()
+ }
+ }
+ """
+ ).indented()
+
+ @Test
+ fun `java expect fail dialog fragment with cancel listener`() {
+ lint().files(dialogFragmentStubJavaWithCancelListener)
+ .run()
+ .expect(
+ """
+src/foo/TestFragment.java:11: Warning: Use onCancel() instead of calling setOnCancelListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+ dialog.setOnCancelListener({ });
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ )
+ .expectWarningCount(1)
+ }
+
+ @Test
+ fun `java expect fail dialog fragment with dismiss listener`() {
+ lint().files(dialogFragmentStubJavaWithDismissListener)
+ .run()
+ .expect(
+ """
+src/foo/TestFragment.java:11: Warning: Use onDismiss() instead of calling setOnDismissListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+ dialog.setOnDismissListener({ });
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ )
+ .expectWarningCount(1)
+ }
+
+ @Test
+ fun `java expect clean dialog fragment`() {
+ lint().files(dialogFragmentCorrectImplementationStubJava)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun `kotlin expect fail dialog fragment with cancel listener`() {
+ lint().files(dialogFragmentStubKotlinWithCancelListener)
+ .run()
+ .expect(
+ """
+src/foo/TestDialog.kt:10: Warning: Use onCancel() instead of calling setOnCancelListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+ dialog.setOnCancelListener { }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ )
+ .expectWarningCount(1)
+ }
+
+ @Test
+ fun `kotlin expect fail dialog fragment with dismiss listener`() {
+ lint().files(dialogFragmentStubKotlinWithDismissListener)
+ .run()
+ .expect(
+ """
+src/foo/TestDialog.kt:10: Warning: Use onDismiss() instead of calling setOnDismissListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+ dialog.setOnDismissListener { }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ )
+ .expectWarningCount(1)
+ }
+
+ @Test
+ fun `kotlin expect fail dialog fragment with dismiss and cancel listeners`() {
+ lint().files(dialogFragmentStubKotlinWithDismissAndCancelListeners)
+ .run()
+ .expect(
+ """
+src/foo/TestDialog.kt:10: Warning: Use onDismiss() instead of calling setOnDismissListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+ dialog.setOnDismissListener { }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+src/foo/TestDialog.kt:11: Warning: Use onCancel() instead of calling setOnCancelListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+ dialog.setOnCancelListener { }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 2 warnings
+ """
+ )
+ .expectWarningCount(2)
+ }
+
+ @Test
+ fun `kotlin expect clean dialog fragment`() {
+ lint().files(dialogFragmentCorrectImplementationStubKotlin)
+ .run()
+ .expectClean()
+ }
+}
diff --git a/fragment/fragment-testing-lint/lint-baseline.xml b/fragment/fragment-testing-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/fragment/fragment-testing-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-testing/lint-baseline.xml b/fragment/fragment-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/fragment/fragment-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-truth/lint-baseline.xml b/fragment/fragment-truth/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/fragment/fragment-truth/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment/api/current.txt b/fragment/fragment/api/current.txt
index 38049f1..83ddaab 100644
--- a/fragment/fragment/api/current.txt
+++ b/fragment/fragment/api/current.txt
@@ -196,6 +196,7 @@
ctor public FragmentContainerView(android.content.Context);
ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?);
ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?, int);
+ method public <F extends androidx.fragment.app.Fragment> F? getFragment();
}
public class FragmentController {
diff --git a/fragment/fragment/api/public_plus_experimental_current.txt b/fragment/fragment/api/public_plus_experimental_current.txt
index c4a2d4d..6cb05d1 100644
--- a/fragment/fragment/api/public_plus_experimental_current.txt
+++ b/fragment/fragment/api/public_plus_experimental_current.txt
@@ -196,6 +196,7 @@
ctor public FragmentContainerView(android.content.Context);
ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?);
ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?, int);
+ method public <F extends androidx.fragment.app.Fragment> F? getFragment();
}
public class FragmentController {
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index 800e0f1..680d7b8 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -201,6 +201,7 @@
ctor public FragmentContainerView(android.content.Context);
ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?);
ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?, int);
+ method public <F extends androidx.fragment.app.Fragment> F? getFragment();
}
public class FragmentController {
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
index 775580f..5bc6cdf 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
@@ -27,6 +27,7 @@
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.animation.Animation
+import androidx.core.app.ActivityCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.test.FragmentTestActivity
import androidx.fragment.test.R
@@ -696,6 +697,66 @@
assertThat(drawnFirst!!).isEqualTo(frag2View)
}
+ @Test
+ fun getFragmentNoneAdded() {
+ val fragmentContainerView = ActivityCompat.requireViewById<FragmentContainerView>(
+ activityRule.activity,
+ R.id.fragment_container_view
+ )
+
+ assertThat(fragmentContainerView.getFragment<StrictViewFragment>()).isNull()
+ }
+
+ @Test
+ fun getFragmentTwoAdds() {
+ val fragmentContainerView = ActivityCompat.requireViewById<FragmentContainerView>(
+ activityRule.activity,
+ R.id.fragment_container_view
+ )
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment1 = StrictViewFragment()
+ val fragment2 = StrictViewFragment()
+
+ fm.beginTransaction()
+ .add(R.id.fragment_container_view, fragment1)
+ .commit()
+ activityRule.waitForExecution()
+
+ fm.beginTransaction()
+ .add(R.id.fragment_container_view, fragment2)
+ .commit()
+ activityRule.waitForExecution()
+
+ val topFragment = fragmentContainerView.getFragment<StrictViewFragment>()
+ assertThat(topFragment).isSameInstanceAs(fragment2)
+ }
+
+ @Test
+ fun getFragmentAddAndReplace() {
+ val fragmentContainerView = ActivityCompat.requireViewById<FragmentContainerView>(
+ activityRule.activity,
+ R.id.fragment_container_view
+ )
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment1 = StrictViewFragment(R.layout.fragment_container_view)
+ val fragment2 = StrictViewFragment(R.layout.fragment_container_view)
+
+ fm.beginTransaction()
+ .add(R.id.fragment_container_view, fragment1)
+ .commit()
+ activityRule.waitForExecution()
+
+ fm.beginTransaction()
+ .replace(R.id.fragment_container_view, fragment2)
+ .commit()
+ activityRule.waitForExecution()
+
+ val topFragment = fragmentContainerView.getFragment<StrictViewFragment>()
+ assertThat(topFragment).isSameInstanceAs(fragment2)
+ }
+
class ChildViewFragment(val viewTag: String? = null) : StrictViewFragment() {
override fun onCreateView(
inflater: LayoutInflater,
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt
index 4635777..3d6b8e2 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt
@@ -16,7 +16,10 @@
package androidx.fragment.app
+import android.app.Activity
import android.os.Bundle
+import android.os.Parcelable
+import androidx.activity.result.ActivityResult
import androidx.fragment.app.test.FragmentTestActivity
import androidx.fragment.test.R
import androidx.test.core.app.ActivityScenario
@@ -380,6 +383,54 @@
.isEqualTo("resultGood")
}
}
+
+ @Test
+ fun testReplaceResultWithParcelableOnRecreation() {
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ var fm = withActivity {
+ setContentView(R.layout.simple_container)
+ supportFragmentManager
+ }
+ var fragment1 = ParcelableResultFragment()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1, "fragment1")
+ .commit()
+ executePendingTransactions()
+
+ val fragment2 = StrictFragment()
+
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions()
+
+ val resultBundle = Bundle()
+ val expectedResult = ActivityResult(Activity.RESULT_OK, null)
+ resultBundle.putParcelable("bundleKey", expectedResult)
+
+ fm.setFragmentResult("requestKey", resultBundle)
+
+ assertWithMessage("The result is not set")
+ .that(fragment1.actualResult)
+ .isNull()
+
+ recreate()
+
+ fm = withActivity { supportFragmentManager }
+
+ withActivity {
+ fm.popBackStackImmediate()
+ }
+
+ fragment1 = fm.findFragmentByTag("fragment1") as ParcelableResultFragment
+
+ assertWithMessage("The result is incorrect")
+ .that(fragment1.actualResult)
+ .isEqualTo(expectedResult)
+ }
+ }
}
class ResultFragment : StrictFragment() {
@@ -434,4 +485,18 @@
}
parentFragmentManager.setFragmentResult("requestKey", resultBundle)
}
-}
\ No newline at end of file
+}
+
+class ParcelableResultFragment : StrictFragment() {
+ var actualResult: Parcelable? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ parentFragmentManager.setFragmentResultListener(
+ "requestKey", this,
+ FragmentResultListener
+ { _, bundle -> actualResult = bundle.getParcelable<ActivityResult>("bundleKey") }
+ )
+ }
+}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleOwnerTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleOwnerTest.kt
new file mode 100644
index 0000000..67ae872
--- /dev/null
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleOwnerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.fragment.app
+
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.lifecycle.HasDefaultViewModelProviderFactory
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class FragmentViewLifecycleOwnerTest {
+
+ /**
+ * Test representing a Non-Hilt case, in which the default factory is not overwritten at the
+ * Fragment level.
+ */
+ @Test
+ fun defaultFactoryNotOverwritten() {
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ val fm = withActivity {
+ setContentView(R.layout.simple_container)
+ supportFragmentManager
+ }
+ val fragment = StrictViewFragment()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .commit()
+ executePendingTransactions()
+
+ val defaultFactory1 = (
+ fragment.viewLifecycleOwner as HasDefaultViewModelProviderFactory
+ ).defaultViewModelProviderFactory
+ val defaultFactory2 = (
+ fragment.viewLifecycleOwner as HasDefaultViewModelProviderFactory
+ ).defaultViewModelProviderFactory
+
+ // Assure that multiple call return the same default factory
+ assertThat(defaultFactory1).isSameInstanceAs(defaultFactory2)
+ assertThat(defaultFactory1).isNotSameInstanceAs(
+ fragment.defaultViewModelProviderFactory
+ )
+ }
+ }
+
+ /**
+ * Test representing a Hilt case, in which the default factory is overwritten at the
+ * Fragment level.
+ */
+ @Test
+ fun defaultFactoryOverwritten() {
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ val fm = withActivity {
+ setContentView(R.layout.simple_container)
+ supportFragmentManager
+ }
+ val fragment = FragmentWithFactoryOverride()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .commit()
+ executePendingTransactions()
+
+ val defaultFactory = (
+ fragment.viewLifecycleOwner as HasDefaultViewModelProviderFactory
+ ).defaultViewModelProviderFactory
+
+ assertThat(defaultFactory).isInstanceOf(FakeViewModelProviderFactory::class.java)
+ }
+ }
+
+ private class TestViewModel : ViewModel()
+
+ class FakeViewModelProviderFactory : ViewModelProvider.Factory {
+ private var createCalled: Boolean = false
+ override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+ require(modelClass == TestViewModel::class.java)
+ createCalled = true
+ @Suppress("UNCHECKED_CAST")
+ return TestViewModel() as T
+ }
+ }
+
+ public class FragmentWithFactoryOverride : StrictViewFragment() {
+ public override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory =
+ FakeViewModelProviderFactory()
+ }
+}
\ No newline at end of file
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
index a66d652..42ad4dc 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
@@ -24,7 +24,7 @@
import androidx.lifecycle.ViewModelProvider
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
+import androidx.test.filters.LargeTest
import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -32,7 +32,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@MediumTest
+@LargeTest
@RunWith(AndroidJUnit4::class)
class SaveRestoreBackStackTest {
@@ -272,6 +272,7 @@
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
fm.saveBackStack("replacement")
executePendingTransactions()
@@ -279,6 +280,7 @@
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isTrue()
+ assertThat(fm.backStackEntryCount).isEqualTo(0)
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
@@ -303,6 +305,7 @@
assertThat(stateSavedReplacement.savedState).isEqualTo("saved")
assertThat(stateSavedReplacement.unsavedState).isNull()
assertThat(stateSavedReplacement.viewModel).isSameInstanceAs(originalViewModel)
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
@@ -331,6 +334,7 @@
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
fm.saveBackStack("replacement")
executePendingTransactions()
@@ -338,6 +342,7 @@
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isTrue()
+ assertThat(fm.backStackEntryCount).isEqualTo(0)
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
@@ -369,6 +374,63 @@
assertThat(stateSavedReplacement.savedState).isEqualTo("saved")
assertThat(stateSavedReplacement.unsavedState).isNull()
assertThat(stateSavedReplacement.viewModel).isSameInstanceAs(originalViewModel)
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun restoreBackStackWithoutExecutePendingTransactions() {
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ val fm = withActivity {
+ supportFragmentManager
+ }
+ val fragmentBase = StrictFragment()
+ val fragmentReplacement = StateSaveFragment("saved", "unsaved")
+
+ fm.beginTransaction()
+ .add(R.id.content, fragmentBase)
+ .commit()
+ executePendingTransactions()
+
+ fm.beginTransaction()
+ .setReorderingAllowed(true)
+ .replace(R.id.content, fragmentReplacement)
+ .addToBackStack("replacement")
+ .commit()
+ executePendingTransactions()
+
+ val originalViewModel = fragmentReplacement.viewModel
+ assertWithMessage("ViewModel should not be cleared after commit()")
+ .that(originalViewModel.cleared)
+ .isFalse()
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
+
+ withActivity {
+ fm.saveBackStack("replacement")
+ // Immediately restore the back stack without calling executePendingTransactions
+ fm.restoreBackStack("replacement")
+ }
+ executePendingTransactions()
+
+ assertWithMessage("Saved Fragments should not go through onSaveInstanceState")
+ .that(fragmentReplacement.calledOnSaveInstanceState)
+ .isFalse()
+ assertWithMessage("Saved Fragments should not have been destroyed")
+ .that(fragmentReplacement.calledOnDestroy)
+ .isFalse()
+ assertWithMessage("ViewModel should not be cleared after saveBackStack()")
+ .that(originalViewModel.cleared)
+ .isFalse()
+
+ assertWithMessage("Fragment should still be returned by FragmentManager")
+ .that(fm.findFragmentById(R.id.content))
+ .isSameInstanceAs(fragmentReplacement)
+
+ // Assert that restored fragment has its saved state restored
+ assertThat(fragmentReplacement.savedState).isEqualTo("saved")
+ assertThat(fragmentReplacement.unsavedState).isEqualTo("unsaved")
+ assertThat(fragmentReplacement.viewModel).isSameInstanceAs(originalViewModel)
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
index e691044..70ec336 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -142,6 +142,17 @@
mManager = manager;
}
+ BackStackRecord(@NonNull BackStackRecord bse) {
+ super(bse.mManager.getFragmentFactory(), bse.mManager.getHost() != null
+ ? bse.mManager.getHost().getContext().getClassLoader()
+ : null, bse);
+ mManager = bse.mManager;
+ mCommitted = bse.mCommitted;
+ mIndex = bse.mIndex;
+ mBeingSaved = bse.mBeingSaved;
+ }
+
+
@Override
public int getId() {
return mIndex;
@@ -281,15 +292,6 @@
}
}
- void runOnExecuteRunnables() {
- if (mExecuteRunnables != null) {
- for (int i = 0; i < mExecuteRunnables.size(); i++) {
- mExecuteRunnables.get(i).run();
- }
- mExecuteRunnables = null;
- }
- }
-
public void runOnCommitRunnables() {
if (mCommitRunnables != null) {
for (int i = 0; i < mCommitRunnables.size(); i++) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java
index c102873..06e1df3 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java
@@ -48,7 +48,14 @@
// These will populate the transactions we instantiate.
HashMap<String, Fragment> fragments = new HashMap<>(mFragments.size());
for (String fWho : mFragments) {
- // Retrieve any saved state, clearing it out for future calls
+ Fragment existingFragment = fm.getFragmentStore().findFragmentByWho(fWho);
+ if (existingFragment != null) {
+ // If the Fragment still exists, this means the saveBackStack()
+ // hasn't executed yet, so we can use the existing Fragment directly
+ fragments.put(existingFragment.mWho, existingFragment);
+ continue;
+ }
+ // Otherwise, retrieve any saved state, clearing it out for future calls
FragmentState fragmentState = fm.getFragmentStore().setSavedState(fWho, null);
if (fragmentState != null) {
Fragment fragment = fragmentState.instantiate(fm.getFragmentFactory(),
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index c33fffe..0a07f7c 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -289,7 +289,7 @@
@Nullable FragmentViewLifecycleOwner mViewLifecycleOwner;
MutableLiveData<LifecycleOwner> mViewLifecycleOwnerLiveData = new MutableLiveData<>();
- private ViewModelProvider.Factory mDefaultFactory;
+ ViewModelProvider.Factory mDefaultFactory;
SavedStateRegistryController mSavedStateRegistryController;
@@ -2939,7 +2939,7 @@
@Nullable Bundle savedInstanceState) {
mChildFragmentManager.noteStateNotSaved();
mPerformedCreateView = true;
- mViewLifecycleOwner = new FragmentViewLifecycleOwner(getViewModelStore());
+ mViewLifecycleOwner = new FragmentViewLifecycleOwner(this, getViewModelStore());
mView = onCreateView(inflater, container, savedInstanceState);
if (mView != null) {
// Initialize the view lifecycle
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
index 373a909..071cad3 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
@@ -399,4 +399,19 @@
mDisappearingFragmentChildren.add(v);
}
}
+
+ /**
+ * This method grabs the {@link Fragment} whose view was most recently
+ * added to the container. This may used as an alternative to calling
+ * {@link FragmentManager#findFragmentById(int)} and passing in the
+ * {@link FragmentContainerView}'s id.
+ *
+ * @return The fragment if any exist, null otherwise.
+ */
+ @Nullable
+ @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // a ClassCastException is
+ // automatically thrown if the given type of F is wrong
+ public <F extends Fragment> F getFragment() {
+ return (F) FragmentManager.findFragmentManager(this).findFragmentById(this.getId());
+ }
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 85a3a37..1c7ab0e 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1758,11 +1758,6 @@
}
executeOps(records, isRecordPop, startIndex, endIndex);
- for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
- final BackStackRecord record = records.get(recordNum);
- record.runOnExecuteRunnables();
- }
-
// The last operation determines the overall direction, this ensures that operations
// such as push, push, pop, push are correctly considered a push
boolean isPop = isRecordPop.get(endIndex - 1);
@@ -1997,11 +1992,11 @@
}
List<BackStackRecord> backStackRecords = backStackState.instantiate(this);
+ boolean added = false;
for (BackStackRecord record : backStackRecords) {
- records.add(record);
- isRecordPop.add(false);
+ added = record.generateOps(records, isRecordPop) || added;
}
- return true;
+ return added;
}
boolean saveBackStackState(@NonNull ArrayList<BackStackRecord> records,
@@ -2095,20 +2090,17 @@
final BackStackState backStackState = new BackStackState(
fragments, backStackRecordStates);
for (int i = mBackStack.size() - 1; i >= index; i--) {
- final BackStackRecord record = mBackStack.remove(i);
+ BackStackRecord record = mBackStack.remove(i);
+
+ // Create a copy of the record to save
+ BackStackRecord copy = new BackStackRecord(record);
+ copy.collapseOps();
+ BackStackRecordState state = new BackStackRecordState(copy);
+ backStackRecordStates.set(i - index, state);
+
+ // And now mark the record as being saved to ensure that each
+ // fragment saves its state properly
record.mBeingSaved = true;
- // Get a callback when the BackStackRecord is actually finished
- final int currentIndex = i;
- record.addOnExecuteRunnable(new Runnable() {
- @Override
- public void run() {
- // First collapse the record to remove expanded ops and get it ready to save
- record.collapseOps();
- // Then save the state
- BackStackRecordState state = new BackStackRecordState(record);
- backStackRecordStates.set(currentIndex - index, state);
- }
- });
records.add(record);
isRecordPop.add(true);
}
@@ -2381,7 +2373,9 @@
ArrayList<String> savedResultKeys = fms.mResultKeys;
if (savedResultKeys != null) {
for (int i = 0; i < savedResultKeys.size(); i++) {
- mResults.put(savedResultKeys.get(i), fms.mResults.get(i));
+ Bundle savedResult = fms.mResults.get(i);
+ savedResult.setClassLoader(mHost.getContext().getClassLoader());
+ mResults.put(savedResultKeys.get(i), savedResult);
}
}
mLaunchedFragments = new ArrayDeque<>(fms.mLaunchedFragments);
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
index 1ed008c..65a30e5 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
@@ -98,6 +98,18 @@
this.mOldMaxState = fragment.mMaxState;
this.mCurrentMaxState = state;
}
+
+ Op(Op op) {
+ this.mCmd = op.mCmd;
+ this.mFragment = op.mFragment;
+ this.mFromExpandedOp = op.mFromExpandedOp;
+ this.mEnterAnim = op.mEnterAnim;
+ this.mExitAnim = op.mExitAnim;
+ this.mPopEnterAnim = op.mPopEnterAnim;
+ this.mPopExitAnim = op.mPopExitAnim;
+ this.mOldMaxState = op.mOldMaxState;
+ this.mCurrentMaxState = op.mCurrentMaxState;
+ }
}
private final FragmentFactory mFragmentFactory;
@@ -122,7 +134,6 @@
ArrayList<String> mSharedElementTargetNames;
boolean mReorderingAllowed = false;
- ArrayList<Runnable> mExecuteRunnables;
ArrayList<Runnable> mCommitRunnables;
/**
@@ -141,6 +152,35 @@
mClassLoader = classLoader;
}
+ FragmentTransaction(@NonNull FragmentFactory fragmentFactory,
+ @Nullable ClassLoader classLoader, @NonNull FragmentTransaction ft) {
+ this(fragmentFactory, classLoader);
+ for (Op op : ft.mOps) {
+ mOps.add(new Op(op));
+ }
+ mEnterAnim = ft.mEnterAnim;
+ mExitAnim = ft.mExitAnim;
+ mPopEnterAnim = ft.mPopEnterAnim;
+ mPopExitAnim = ft.mPopExitAnim;
+ mTransition = ft.mTransition;
+ mAddToBackStack = ft.mAddToBackStack;
+ mAllowAddToBackStack = ft.mAllowAddToBackStack;
+ mName = ft.mName;
+ mBreadCrumbShortTitleRes = ft.mBreadCrumbShortTitleRes;
+ mBreadCrumbShortTitleText = ft.mBreadCrumbShortTitleText;
+ mBreadCrumbTitleRes = ft.mBreadCrumbTitleRes;
+ mBreadCrumbTitleText = ft.mBreadCrumbTitleText;
+ if (ft.mSharedElementSourceNames != null) {
+ mSharedElementSourceNames = new ArrayList<>();
+ mSharedElementSourceNames.addAll(ft.mSharedElementSourceNames);
+ }
+ if (ft.mSharedElementTargetNames != null) {
+ mSharedElementTargetNames = new ArrayList<>();
+ mSharedElementTargetNames.addAll(ft.mSharedElementTargetNames);
+ }
+ mReorderingAllowed = ft.mReorderingAllowed;
+ }
+
void addOp(Op op) {
mOps.add(op);
op.mEnterAnim = mEnterAnim;
@@ -826,18 +866,6 @@
}
/**
- * Add a runnable that is run immediately after the transaction is executed.
- * This differs from the commit runnables in that it happens before any
- * fragments move to their expected state.
- */
- void addOnExecuteRunnable(@NonNull Runnable runnable) {
- if (mExecuteRunnables == null) {
- mExecuteRunnables = new ArrayList<>();
- }
- mExecuteRunnables.add(runnable);
- }
-
- /**
* Add a Runnable to this transaction that will be run after this transaction has
* been committed. If fragment transactions are {@link #setReorderingAllowed(boolean) optimized}
* this may be after other subsequent fragment operations have also taken place, or operations
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java
index 65f16a4..ef96a00 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java
@@ -16,25 +16,36 @@
package androidx.fragment.app;
+import android.app.Application;
+import android.content.Context;
+import android.content.ContextWrapper;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.lifecycle.HasDefaultViewModelProviderFactory;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.SavedStateViewModelFactory;
+import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.savedstate.SavedStateRegistry;
import androidx.savedstate.SavedStateRegistryController;
import androidx.savedstate.SavedStateRegistryOwner;
-class FragmentViewLifecycleOwner implements SavedStateRegistryOwner, ViewModelStoreOwner {
+class FragmentViewLifecycleOwner implements HasDefaultViewModelProviderFactory,
+ SavedStateRegistryOwner, ViewModelStoreOwner {
+ private final Fragment mFragment;
private final ViewModelStore mViewModelStore;
+ private ViewModelProvider.Factory mDefaultFactory;
+
private LifecycleRegistry mLifecycleRegistry = null;
private SavedStateRegistryController mSavedStateRegistryController = null;
- FragmentViewLifecycleOwner(@NonNull ViewModelStore viewModelStore) {
+ FragmentViewLifecycleOwner(@NonNull Fragment fragment, @NonNull ViewModelStore viewModelStore) {
+ mFragment = fragment;
mViewModelStore = viewModelStore;
}
@@ -77,6 +88,44 @@
mLifecycleRegistry.handleLifecycleEvent(event);
}
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The {@link Fragment#getArguments() Fragment's arguments} when this is first called will
+ * be used as the defaults to any {@link androidx.lifecycle.SavedStateHandle} passed to a
+ * view model created using this factory.</p>
+ */
+ @NonNull
+ @Override
+ public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+ ViewModelProvider.Factory currentFactory =
+ mFragment.getDefaultViewModelProviderFactory();
+
+ if (!currentFactory.equals(mFragment.mDefaultFactory)) {
+ mDefaultFactory = currentFactory;
+ return currentFactory;
+ }
+
+ if (mDefaultFactory == null) {
+ Application application = null;
+ Context appContext = mFragment.requireContext().getApplicationContext();
+ while (appContext instanceof ContextWrapper) {
+ if (appContext instanceof Application) {
+ application = (Application) appContext;
+ break;
+ }
+ appContext = ((ContextWrapper) appContext).getBaseContext();
+ }
+
+ mDefaultFactory = new SavedStateViewModelFactory(
+ application,
+ this,
+ mFragment.getArguments());
+ }
+
+ return mDefaultFactory;
+ }
+
@NonNull
@Override
public SavedStateRegistry getSavedStateRegistry() {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 4d9b6bc..42c5121 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -59,7 +59,7 @@
dokkaGradlePlugin = { module = "org.jetbrains.dokka:dokka-android-gradle-plugin", version = "0.9.17-g014" }
espressoContrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "espresso" }
espressoCore = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
-espressoIdlingNet = { module = "androidx.test.espresso:espresso-idling-net", version.ref = "espresso" }
+espressoIdlingNet = { module = "androidx.test.espresso.idling:idling-net", version.ref = "espresso" }
espressoIdlingResource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "espresso" }
espressoIntents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso" }
espressoWeb = { module = "androidx.test.espresso:espresso-web", version.ref = "espresso" }
diff --git a/health/health-services-client/OWNERS b/health/health-services-client/OWNERS
new file mode 100644
index 0000000..99e9acf
--- /dev/null
+++ b/health/health-services-client/OWNERS
@@ -0,0 +1,3 @@
+aakanksha@google.com
+jlannin@google.com
+smskelley@google.com
diff --git a/health/health-services-client/api/api_lint.ignore b/health/health-services-client/api/api_lint.ignore
new file mode 100644
index 0000000..1a6bb40
--- /dev/null
+++ b/health/health-services-client/api/api_lint.ignore
@@ -0,0 +1,9 @@
+// Baseline format: 1.0
+ExecutorRegistration: androidx.health.services.client.ExerciseClient#clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener):
+ Registration methods should have overload that accepts delivery Executor: `clearUpdateListener`
+ExecutorRegistration: androidx.health.services.client.PassiveMonitoringClient#registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType>, android.app.PendingIntent, androidx.health.services.client.PassiveMonitoringCallback):
+ Registration methods should have overload that accepts delivery Executor: `registerDataCallback`
+
+
+MissingGetterMatchingBuilder: androidx.health.services.client.data.ExerciseConfig.Builder#setAutoPauseAndResume(boolean):
+ androidx.health.services.client.data.ExerciseConfig does not declare a `isAutoPauseAndResume()` method matching method androidx.health.services.client.data.ExerciseConfig.Builder.setAutoPauseAndResume(boolean)
diff --git a/health/health-services-client/api/current.txt b/health/health-services-client/api/current.txt
new file mode 100644
index 0000000..6abe37c
--- /dev/null
+++ b/health/health-services-client/api/current.txt
@@ -0,0 +1,869 @@
+// Signature format: 4.0
+package androidx.health.services.client {
+
+ public interface ExerciseClient {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExercise(androidx.health.services.client.data.ExerciseGoal exerciseGoal);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfo();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLap();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExercise(boolean enabled);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExercise(androidx.health.services.client.data.ExerciseConfig configuration);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> capabilities;
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> currentExerciseInfo;
+ }
+
+ public interface ExerciseUpdateListener {
+ method public void onExerciseUpdate(androidx.health.services.client.data.ExerciseUpdate update);
+ method public void onLapSummary(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+ }
+
+ public final class HealthServices {
+ method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+ field public static final androidx.health.services.client.HealthServices INSTANCE;
+ }
+
+ public interface HealthServicesClient {
+ method public androidx.health.services.client.ExerciseClient getExerciseClient();
+ method public androidx.health.services.client.MeasureClient getMeasureClient();
+ method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+ property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+ property public abstract androidx.health.services.client.MeasureClient measureClient;
+ property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+ }
+
+ public interface MeasureCallback {
+ method public void onAvailabilityChanged(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Availability availability);
+ method public void onData(java.util.List<androidx.health.services.client.data.DataPoint> data);
+ }
+
+ public interface MeasureClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> capabilities;
+ }
+
+ public interface PassiveMonitoringCallback {
+ method public void onPassiveActivityState(androidx.health.services.client.data.PassiveActivityState state);
+ }
+
+ public interface PassiveMonitoringClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent, androidx.health.services.client.PassiveMonitoringCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerEventCallback(androidx.health.services.client.data.event.Event event, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterDataCallback();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterEventCallback(androidx.health.services.client.data.event.Event event);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> capabilities;
+ }
+
+}
+
+package androidx.health.services.client.data {
+
+ public final class AchievedExerciseGoal implements android.os.Parcelable {
+ ctor public AchievedExerciseGoal(androidx.health.services.client.data.ExerciseGoal goal);
+ method public androidx.health.services.client.data.ExerciseGoal component1();
+ method public androidx.health.services.client.data.AchievedExerciseGoal copy(androidx.health.services.client.data.ExerciseGoal goal);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseGoal getGoal();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseGoal goal;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AchievedExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.AchievedExerciseGoal.Companion Companion;
+ }
+
+ public static final class AchievedExerciseGoal.Companion {
+ }
+
+ public final class AutoExerciseConfig implements android.os.Parcelable {
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent, optional android.os.Bundle exerciseParams);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect);
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> component1();
+ method public android.app.PendingIntent? component2();
+ method public android.os.Bundle component3();
+ method public androidx.health.services.client.data.AutoExerciseConfig copy(java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, android.app.PendingIntent? launchIntent, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public android.os.Bundle getExerciseParams();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getExercisesToDetect();
+ method public android.app.PendingIntent? getLaunchIntent();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final android.os.Bundle exerciseParams;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> exercisesToDetect;
+ property public final android.app.PendingIntent? launchIntent;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AutoExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.AutoExerciseConfig.Companion Companion;
+ }
+
+ public static final class AutoExerciseConfig.Companion {
+ }
+
+ public enum Availability {
+ method public static final androidx.health.services.client.data.Availability? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.Availability ACQUIRING;
+ enum_constant public static final androidx.health.services.client.data.Availability AVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNAVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNKNOWN;
+ field public static final androidx.health.services.client.data.Availability.Companion Companion;
+ }
+
+ public static final class Availability.Companion {
+ method public androidx.health.services.client.data.Availability? fromId(int id);
+ }
+
+ public final class Capabilities implements android.os.Parcelable {
+ ctor public Capabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> component1();
+ method public androidx.health.services.client.data.Capabilities copy(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+ method public androidx.health.services.client.data.ExerciseCapabilities getExerciseCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> getExerciseTypeToExerciseCapabilities();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+ property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Capabilities> CREATOR;
+ field public static final androidx.health.services.client.data.Capabilities.Companion Companion;
+ }
+
+ public static final class Capabilities.Companion {
+ }
+
+ public enum ComparisonType {
+ method public static final androidx.health.services.client.data.ComparisonType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+ field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+ }
+
+ public static final class ComparisonType.Companion {
+ method public androidx.health.services.client.data.ComparisonType? fromId(int id);
+ }
+
+ public final class DataPoint implements android.os.Parcelable {
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public java.time.Duration component3();
+ method public java.time.Duration component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.DataPoint copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public java.time.Duration getEndDurationFromBoot();
+ method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+ method public android.os.Bundle getMetadata();
+ method public java.time.Duration getStartDurationFromBoot();
+ method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+ method public androidx.health.services.client.data.Value getValue();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final java.time.Duration endDurationFromBoot;
+ property public final android.os.Bundle metadata;
+ property public final java.time.Duration startDurationFromBoot;
+ property public final androidx.health.services.client.data.Value value;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataPoint> CREATOR;
+ field public static final androidx.health.services.client.data.DataPoint.Companion Companion;
+ }
+
+ public static final class DataPoint.Companion {
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ }
+
+ @Keep public final class DataPoints {
+ method public static androidx.health.services.client.data.DataPoint aggregateCalories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateDistance(double distance, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSteps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSwimmingStrokes(long swimmingStrokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint altitude(double meters, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averagePace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averageSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint calories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint distance(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint elevation(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint floors(double floors, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method @Keep public static java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints(android.content.Intent intent);
+ method public static boolean getPermissionsGranted(android.content.Intent intent);
+ method public static androidx.health.services.client.data.DataPoint heartRate(double bpm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, double altitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint maxSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint pace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static void putDataPoints(android.content.Intent intent, java.util.Collection<androidx.health.services.client.data.DataPoint> dataPoints);
+ method public static void putPermissionsGranted(android.content.Intent intent, boolean granted);
+ method public static androidx.health.services.client.data.DataPoint speed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint spo2(double percent, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint steps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint stepsPerMinute(long stepsPerMinute, java.time.Duration startDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint swimmingStrokes(long strokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ field public static final androidx.health.services.client.data.DataPoints INSTANCE;
+ field public static final int LOCATION_DATA_POINT_ALTITUDE_INDEX = 2; // 0x2
+ field public static final int LOCATION_DATA_POINT_LATITUDE_INDEX = 0; // 0x0
+ field public static final int LOCATION_DATA_POINT_LONGITUDE_INDEX = 1; // 0x1
+ }
+
+ public final class DataType implements android.os.Parcelable {
+ ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public String component1();
+ method public androidx.health.services.client.data.DataType.TimeType component2();
+ method public int component3();
+ method public androidx.health.services.client.data.DataType copy(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public int describeContents();
+ method public int getFormat();
+ method public String getName();
+ method public androidx.health.services.client.data.DataType.TimeType getTimeType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final int format;
+ property public final String name;
+ property public final androidx.health.services.client.data.DataType.TimeType timeType;
+ field public static final androidx.health.services.client.data.DataType ABSOLUTE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType ACTIVE_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_CALORIES_EXPENDED;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLOORS;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_RUNNING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_SWIMMING_STROKE_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_WALKING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_PACE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_SPEED;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataType> CREATOR;
+ field public static final androidx.health.services.client.data.DataType.Companion Companion;
+ field public static final androidx.health.services.client.data.DataType DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType DISTANCE;
+ field public static final androidx.health.services.client.data.DataType ELEVATION;
+ field public static final androidx.health.services.client.data.DataType FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType FLOORS;
+ field public static final androidx.health.services.client.data.DataType HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType LOCATION;
+ field public static final androidx.health.services.client.data.DataType MAX_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType MAX_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType MAX_PACE;
+ field public static final androidx.health.services.client.data.DataType MAX_SPEED;
+ field public static final androidx.health.services.client.data.DataType MIN_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType PACE;
+ field public static final androidx.health.services.client.data.DataType REP_COUNT;
+ field public static final androidx.health.services.client.data.DataType RESTING_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType RUNNING_STEPS;
+ field public static final androidx.health.services.client.data.DataType SPEED;
+ field public static final androidx.health.services.client.data.DataType SPO2;
+ field public static final androidx.health.services.client.data.DataType STEPS;
+ field public static final androidx.health.services.client.data.DataType STEPS_PER_MINUTE;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_LAP_COUNT;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_STROKES;
+ field public static final androidx.health.services.client.data.DataType TOTAL_CALORIES;
+ field public static final androidx.health.services.client.data.DataType VO2;
+ field public static final androidx.health.services.client.data.DataType VO2_MAX;
+ field public static final androidx.health.services.client.data.DataType WALKING_STEPS;
+ }
+
+ public static final class DataType.Companion {
+ }
+
+ public enum DataType.TimeType {
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+ }
+
+ public final class DataTypeCondition implements android.os.Parcelable {
+ ctor public DataTypeCondition(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public androidx.health.services.client.data.ComparisonType component3();
+ method public androidx.health.services.client.data.DataTypeCondition copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ComparisonType getComparisonType();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public androidx.health.services.client.data.Value getThreshold();
+ method public boolean isSatisfied(androidx.health.services.client.data.DataPoint dataPoint);
+ method public boolean isThresholdSatisfied(androidx.health.services.client.data.Value value);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ComparisonType comparisonType;
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final androidx.health.services.client.data.Value threshold;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataTypeCondition> CREATOR;
+ field public static final androidx.health.services.client.data.DataTypeCondition.Companion Companion;
+ }
+
+ public static final class DataTypeCondition.Companion {
+ }
+
+ public final class DataTypes {
+ method public static androidx.health.services.client.data.DataType? getAggregateTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static java.util.Set<androidx.health.services.client.data.DataType> getAggregatedDataTypesFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getAverageTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getMaxTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAggregateType(androidx.health.services.client.data.DataType aggregateType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAverageType(androidx.health.services.client.data.DataType averageType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromMaxType(androidx.health.services.client.data.DataType maxType);
+ method public static boolean isAggregateDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isRawType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalAverageDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalMaxDataType(androidx.health.services.client.data.DataType dataType);
+ field public static final androidx.health.services.client.data.DataTypes INSTANCE;
+ }
+
+ public final class ExerciseCapabilities implements android.os.Parcelable {
+ ctor public ExerciseCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component2();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component3();
+ method public boolean component4();
+ method public boolean component5();
+ method public androidx.health.services.client.data.ExerciseCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypes();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+ method public boolean getSupportsAutoPauseAndResume();
+ method public boolean getSupportsLaps();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+ property public final boolean supportsAutoPauseAndResume;
+ property public final boolean supportsLaps;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseCapabilities.Companion Companion;
+ }
+
+ public static final class ExerciseCapabilities.Companion {
+ }
+
+ public final class ExerciseConfig implements android.os.Parcelable {
+ ctor protected ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public static androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ method public androidx.health.services.client.data.ExerciseType component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public boolean component3();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.ExerciseConfig copy(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public boolean getAutoPauseAndResume();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getDataTypes();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> getExerciseGoals();
+ method public android.os.Bundle getExerciseParams();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final boolean autoPauseAndResume;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> dataTypes;
+ property public final java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals;
+ property public final android.os.Bundle exerciseParams;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+ }
+
+ public static final class ExerciseConfig.Builder {
+ ctor public ExerciseConfig.Builder();
+ method public androidx.health.services.client.data.ExerciseConfig build();
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setAutoPauseAndResume(boolean autoPauseAndResume);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<androidx.health.services.client.data.DataType> dataTypes);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseType(androidx.health.services.client.data.ExerciseType exerciseType);
+ }
+
+ public static final class ExerciseConfig.Companion {
+ method public androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ }
+
+ public final class ExerciseGoal implements android.os.Parcelable {
+ ctor protected ExerciseGoal(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, optional androidx.health.services.client.data.Value? period);
+ method public androidx.health.services.client.data.ExerciseGoalType component1();
+ method public androidx.health.services.client.data.DataTypeCondition component2();
+ method public androidx.health.services.client.data.Value? component3();
+ method public androidx.health.services.client.data.ExerciseGoal copy(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.Value? period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public static androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+ method public androidx.health.services.client.data.Value? getPeriod();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+ property public final androidx.health.services.client.data.Value? period;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+ }
+
+ public static final class ExerciseGoal.Companion {
+ method public androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ }
+
+ public enum ExerciseGoalType {
+ method public static final androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+ field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+ }
+
+ public static final class ExerciseGoalType.Companion {
+ method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ }
+
+ public final class ExerciseInfo implements android.os.Parcelable {
+ ctor public ExerciseInfo(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public androidx.health.services.client.data.ExerciseTrackedStatus component1();
+ method public androidx.health.services.client.data.ExerciseType component2();
+ method public androidx.health.services.client.data.ExerciseInfo copy(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseTrackedStatus getExerciseTrackedStatus();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseInfo> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseInfo.Companion Companion;
+ }
+
+ public static final class ExerciseInfo.Companion {
+ }
+
+ public final class ExerciseLapSummary implements android.os.Parcelable {
+ ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int component1();
+ method public java.time.Instant component2();
+ method public java.time.Instant component3();
+ method public java.time.Duration component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.ExerciseLapSummary copy(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public int getLapCount();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getLapMetrics();
+ method public java.time.Instant getStartTime();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final int lapCount;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics;
+ property public final java.time.Instant startTime;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseLapSummary> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseLapSummary.Companion Companion;
+ }
+
+ public static final class ExerciseLapSummary.Companion {
+ }
+
+ public enum ExerciseState {
+ method public static final androidx.health.services.client.data.ExerciseState? fromId(int id);
+ method public final int getId();
+ method public final boolean isEnded();
+ method public final boolean isPaused();
+ method public final boolean isResuming();
+ property public final int id;
+ property public final boolean isEnded;
+ property public final boolean isPaused;
+ property public final boolean isResuming;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+ field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+ }
+
+ public static final class ExerciseState.Companion {
+ method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+ }
+
+ public enum ExerciseTrackedStatus {
+ method public static final androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus NO_EXERCISE_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OTHER_APP_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OWNED_EXERCISE_IN_PROGRESS;
+ field public static final androidx.health.services.client.data.ExerciseTrackedStatus.Companion Companion;
+ }
+
+ public static final class ExerciseTrackedStatus.Companion {
+ method public androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ }
+
+ public enum ExerciseType {
+ method public static final androidx.health.services.client.data.ExerciseType fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_SIT_UP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOXING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BURPEE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRICKET;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DANCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_FRONT_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_LATERAL_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_TWO_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FENCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GOLF;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GYNMASTICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LUNGE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PADDLING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PILATES;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PLANK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUGBY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SAILING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKIING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOCCER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUASH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUAT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SURFING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WALKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_INDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_OUTDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType YOGA;
+ field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+ }
+
+ public static final class ExerciseType.Companion {
+ method public androidx.health.services.client.data.ExerciseType fromId(int id);
+ }
+
+ public final class ExerciseUpdate implements android.os.Parcelable {
+ ctor public ExerciseUpdate(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public androidx.health.services.client.data.ExerciseState component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> component4();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> component5();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> component6();
+ method public androidx.health.services.client.data.ExerciseConfig? component7();
+ method public androidx.health.services.client.data.AutoExerciseConfig? component8();
+ method public androidx.health.services.client.data.ExerciseType? component9();
+ method public androidx.health.services.client.data.ExerciseUpdate copy(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public androidx.health.services.client.data.AutoExerciseConfig? getAutoExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseType? getAutoExerciseDetected();
+ method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType getExerciseSessionType();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> getLatestAchievedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> getLatestMetrics();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+ method public java.time.Instant getStartTime();
+ method public androidx.health.services.client.data.ExerciseState getState();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig;
+ property public final androidx.health.services.client.data.ExerciseType? autoExerciseDetected;
+ property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+ property public final java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics;
+ property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+ property public final java.time.Instant startTime;
+ property public final androidx.health.services.client.data.ExerciseState state;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseUpdate> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+ }
+
+ public static final class ExerciseUpdate.Companion {
+ }
+
+ public enum ExerciseUpdate.ExerciseSessionType {
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType AUTO_EXERCISE_DETECTION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType MANUALLY_STARTED_EXERCISE;
+ }
+
+ public final class MeasureCapabilities implements android.os.Parcelable {
+ ctor public MeasureCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public androidx.health.services.client.data.MeasureCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesMeasure();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MeasureCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.MeasureCapabilities.Companion Companion;
+ }
+
+ public static final class MeasureCapabilities.Companion {
+ }
+
+ public final class MilestoneMarkerSummary implements android.os.Parcelable {
+ ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public java.time.Instant component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public androidx.health.services.client.data.AchievedExerciseGoal component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.MilestoneMarkerSummary copy(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public int describeContents();
+ method public androidx.health.services.client.data.AchievedExerciseGoal getAchievedGoal();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public java.time.Instant getStartTime();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getSummaryMetrics();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.AchievedExerciseGoal achievedGoal;
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final java.time.Instant startTime;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MilestoneMarkerSummary> CREATOR;
+ field public static final androidx.health.services.client.data.MilestoneMarkerSummary.Companion Companion;
+ }
+
+ public static final class MilestoneMarkerSummary.Companion {
+ }
+
+ public final class PassiveActivityState implements android.os.Parcelable {
+ ctor public PassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> component1();
+ method public androidx.health.services.client.data.UserActivityState component2();
+ method public androidx.health.services.client.data.ExerciseType? component3();
+ method public java.time.Instant component4();
+ method public androidx.health.services.client.data.PassiveActivityState copy(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public int describeContents();
+ method public static androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints();
+ method public androidx.health.services.client.data.ExerciseType? getExerciseType();
+ method public java.time.Instant getStateChangeTime();
+ method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+ method public void putToIntent(android.content.Intent intent);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<androidx.health.services.client.data.DataPoint> dataPoints;
+ property public final androidx.health.services.client.data.ExerciseType? exerciseType;
+ property public final java.time.Instant stateChangeTime;
+ property public final androidx.health.services.client.data.UserActivityState userActivityState;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveActivityState> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveActivityState.Companion Companion;
+ }
+
+ public static final class PassiveActivityState.Companion {
+ method public androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ }
+
+ public final class PassiveMonitoringCapabilities implements android.os.Parcelable {
+ ctor public PassiveMonitoringCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public androidx.health.services.client.data.PassiveMonitoringCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesEvents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesPassiveMonitoring();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveMonitoringCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveMonitoringCapabilities.Companion Companion;
+ }
+
+ public static final class PassiveMonitoringCapabilities.Companion {
+ }
+
+ public enum UserActivityState {
+ method public static final androidx.health.services.client.data.UserActivityState? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_INACTIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+ field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+ }
+
+ public static final class UserActivityState.Companion {
+ method public androidx.health.services.client.data.UserActivityState? fromId(int id);
+ }
+
+ public final class Value implements android.os.Parcelable {
+ method public boolean asBoolean();
+ method public double asDouble();
+ method public double[] asDoubleArray();
+ method public long asLong();
+ method public int component1();
+ method public java.util.List<java.lang.Double> component2();
+ method public long component3();
+ method public androidx.health.services.client.data.Value copy(int format, java.util.List<java.lang.Double> doubleList, long longValue);
+ method public int describeContents();
+ method public java.util.List<java.lang.Double> getDoubleList();
+ method public int getFormat();
+ method public long getLongValue();
+ method public boolean isBoolean();
+ method public boolean isDouble();
+ method public boolean isDoubleArray();
+ method public boolean isLong();
+ method public static androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public static androidx.health.services.client.data.Value ofDouble(double value);
+ method public static androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public static androidx.health.services.client.data.Value ofLong(long value);
+ method public static androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<java.lang.Double> doubleList;
+ property public final int format;
+ property public final boolean isBoolean;
+ property public final boolean isDouble;
+ property public final boolean isDoubleArray;
+ property public final boolean isLong;
+ property public final long longValue;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Value> CREATOR;
+ field public static final androidx.health.services.client.data.Value.Companion Companion;
+ field public static final int FORMAT_BOOLEAN = 4; // 0x4
+ field public static final int FORMAT_DOUBLE = 1; // 0x1
+ field public static final int FORMAT_DOUBLE_ARRAY = 3; // 0x3
+ field public static final int FORMAT_LONG = 2; // 0x2
+ }
+
+ public static final class Value.Companion {
+ method public androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public androidx.health.services.client.data.Value ofDouble(double value);
+ method public androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public androidx.health.services.client.data.Value ofLong(long value);
+ method public androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ }
+
+}
+
+package androidx.health.services.client.data.event {
+
+ public final class Event implements android.os.Parcelable {
+ ctor public Event(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public androidx.health.services.client.data.DataTypeCondition component1();
+ method public androidx.health.services.client.data.event.Event.TriggerType component2();
+ method public androidx.health.services.client.data.event.Event copy(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.event.Event.TriggerType getTriggerType();
+ method public boolean isTriggered(androidx.health.services.client.data.DataPoint dataPoint);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.event.Event.TriggerType triggerType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.event.Event> CREATOR;
+ field public static final androidx.health.services.client.data.event.Event.Companion Companion;
+ }
+
+ public static final class Event.Companion {
+ }
+
+ public enum Event.TriggerType {
+ method public static final androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType ONCE;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType REPEATED;
+ field public static final androidx.health.services.client.data.event.Event.TriggerType.Companion Companion;
+ }
+
+ public static final class Event.TriggerType.Companion {
+ method public androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ }
+
+}
+
diff --git a/health/health-services-client/api/public_plus_experimental_current.txt b/health/health-services-client/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..6abe37c
--- /dev/null
+++ b/health/health-services-client/api/public_plus_experimental_current.txt
@@ -0,0 +1,869 @@
+// Signature format: 4.0
+package androidx.health.services.client {
+
+ public interface ExerciseClient {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExercise(androidx.health.services.client.data.ExerciseGoal exerciseGoal);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfo();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLap();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExercise(boolean enabled);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExercise(androidx.health.services.client.data.ExerciseConfig configuration);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> capabilities;
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> currentExerciseInfo;
+ }
+
+ public interface ExerciseUpdateListener {
+ method public void onExerciseUpdate(androidx.health.services.client.data.ExerciseUpdate update);
+ method public void onLapSummary(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+ }
+
+ public final class HealthServices {
+ method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+ field public static final androidx.health.services.client.HealthServices INSTANCE;
+ }
+
+ public interface HealthServicesClient {
+ method public androidx.health.services.client.ExerciseClient getExerciseClient();
+ method public androidx.health.services.client.MeasureClient getMeasureClient();
+ method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+ property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+ property public abstract androidx.health.services.client.MeasureClient measureClient;
+ property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+ }
+
+ public interface MeasureCallback {
+ method public void onAvailabilityChanged(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Availability availability);
+ method public void onData(java.util.List<androidx.health.services.client.data.DataPoint> data);
+ }
+
+ public interface MeasureClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> capabilities;
+ }
+
+ public interface PassiveMonitoringCallback {
+ method public void onPassiveActivityState(androidx.health.services.client.data.PassiveActivityState state);
+ }
+
+ public interface PassiveMonitoringClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent, androidx.health.services.client.PassiveMonitoringCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerEventCallback(androidx.health.services.client.data.event.Event event, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterDataCallback();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterEventCallback(androidx.health.services.client.data.event.Event event);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> capabilities;
+ }
+
+}
+
+package androidx.health.services.client.data {
+
+ public final class AchievedExerciseGoal implements android.os.Parcelable {
+ ctor public AchievedExerciseGoal(androidx.health.services.client.data.ExerciseGoal goal);
+ method public androidx.health.services.client.data.ExerciseGoal component1();
+ method public androidx.health.services.client.data.AchievedExerciseGoal copy(androidx.health.services.client.data.ExerciseGoal goal);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseGoal getGoal();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseGoal goal;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AchievedExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.AchievedExerciseGoal.Companion Companion;
+ }
+
+ public static final class AchievedExerciseGoal.Companion {
+ }
+
+ public final class AutoExerciseConfig implements android.os.Parcelable {
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent, optional android.os.Bundle exerciseParams);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect);
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> component1();
+ method public android.app.PendingIntent? component2();
+ method public android.os.Bundle component3();
+ method public androidx.health.services.client.data.AutoExerciseConfig copy(java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, android.app.PendingIntent? launchIntent, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public android.os.Bundle getExerciseParams();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getExercisesToDetect();
+ method public android.app.PendingIntent? getLaunchIntent();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final android.os.Bundle exerciseParams;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> exercisesToDetect;
+ property public final android.app.PendingIntent? launchIntent;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AutoExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.AutoExerciseConfig.Companion Companion;
+ }
+
+ public static final class AutoExerciseConfig.Companion {
+ }
+
+ public enum Availability {
+ method public static final androidx.health.services.client.data.Availability? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.Availability ACQUIRING;
+ enum_constant public static final androidx.health.services.client.data.Availability AVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNAVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNKNOWN;
+ field public static final androidx.health.services.client.data.Availability.Companion Companion;
+ }
+
+ public static final class Availability.Companion {
+ method public androidx.health.services.client.data.Availability? fromId(int id);
+ }
+
+ public final class Capabilities implements android.os.Parcelable {
+ ctor public Capabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> component1();
+ method public androidx.health.services.client.data.Capabilities copy(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+ method public androidx.health.services.client.data.ExerciseCapabilities getExerciseCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> getExerciseTypeToExerciseCapabilities();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+ property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Capabilities> CREATOR;
+ field public static final androidx.health.services.client.data.Capabilities.Companion Companion;
+ }
+
+ public static final class Capabilities.Companion {
+ }
+
+ public enum ComparisonType {
+ method public static final androidx.health.services.client.data.ComparisonType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+ field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+ }
+
+ public static final class ComparisonType.Companion {
+ method public androidx.health.services.client.data.ComparisonType? fromId(int id);
+ }
+
+ public final class DataPoint implements android.os.Parcelable {
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public java.time.Duration component3();
+ method public java.time.Duration component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.DataPoint copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public java.time.Duration getEndDurationFromBoot();
+ method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+ method public android.os.Bundle getMetadata();
+ method public java.time.Duration getStartDurationFromBoot();
+ method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+ method public androidx.health.services.client.data.Value getValue();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final java.time.Duration endDurationFromBoot;
+ property public final android.os.Bundle metadata;
+ property public final java.time.Duration startDurationFromBoot;
+ property public final androidx.health.services.client.data.Value value;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataPoint> CREATOR;
+ field public static final androidx.health.services.client.data.DataPoint.Companion Companion;
+ }
+
+ public static final class DataPoint.Companion {
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ }
+
+ @Keep public final class DataPoints {
+ method public static androidx.health.services.client.data.DataPoint aggregateCalories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateDistance(double distance, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSteps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSwimmingStrokes(long swimmingStrokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint altitude(double meters, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averagePace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averageSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint calories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint distance(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint elevation(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint floors(double floors, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method @Keep public static java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints(android.content.Intent intent);
+ method public static boolean getPermissionsGranted(android.content.Intent intent);
+ method public static androidx.health.services.client.data.DataPoint heartRate(double bpm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, double altitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint maxSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint pace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static void putDataPoints(android.content.Intent intent, java.util.Collection<androidx.health.services.client.data.DataPoint> dataPoints);
+ method public static void putPermissionsGranted(android.content.Intent intent, boolean granted);
+ method public static androidx.health.services.client.data.DataPoint speed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint spo2(double percent, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint steps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint stepsPerMinute(long stepsPerMinute, java.time.Duration startDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint swimmingStrokes(long strokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ field public static final androidx.health.services.client.data.DataPoints INSTANCE;
+ field public static final int LOCATION_DATA_POINT_ALTITUDE_INDEX = 2; // 0x2
+ field public static final int LOCATION_DATA_POINT_LATITUDE_INDEX = 0; // 0x0
+ field public static final int LOCATION_DATA_POINT_LONGITUDE_INDEX = 1; // 0x1
+ }
+
+ public final class DataType implements android.os.Parcelable {
+ ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public String component1();
+ method public androidx.health.services.client.data.DataType.TimeType component2();
+ method public int component3();
+ method public androidx.health.services.client.data.DataType copy(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public int describeContents();
+ method public int getFormat();
+ method public String getName();
+ method public androidx.health.services.client.data.DataType.TimeType getTimeType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final int format;
+ property public final String name;
+ property public final androidx.health.services.client.data.DataType.TimeType timeType;
+ field public static final androidx.health.services.client.data.DataType ABSOLUTE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType ACTIVE_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_CALORIES_EXPENDED;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLOORS;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_RUNNING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_SWIMMING_STROKE_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_WALKING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_PACE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_SPEED;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataType> CREATOR;
+ field public static final androidx.health.services.client.data.DataType.Companion Companion;
+ field public static final androidx.health.services.client.data.DataType DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType DISTANCE;
+ field public static final androidx.health.services.client.data.DataType ELEVATION;
+ field public static final androidx.health.services.client.data.DataType FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType FLOORS;
+ field public static final androidx.health.services.client.data.DataType HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType LOCATION;
+ field public static final androidx.health.services.client.data.DataType MAX_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType MAX_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType MAX_PACE;
+ field public static final androidx.health.services.client.data.DataType MAX_SPEED;
+ field public static final androidx.health.services.client.data.DataType MIN_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType PACE;
+ field public static final androidx.health.services.client.data.DataType REP_COUNT;
+ field public static final androidx.health.services.client.data.DataType RESTING_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType RUNNING_STEPS;
+ field public static final androidx.health.services.client.data.DataType SPEED;
+ field public static final androidx.health.services.client.data.DataType SPO2;
+ field public static final androidx.health.services.client.data.DataType STEPS;
+ field public static final androidx.health.services.client.data.DataType STEPS_PER_MINUTE;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_LAP_COUNT;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_STROKES;
+ field public static final androidx.health.services.client.data.DataType TOTAL_CALORIES;
+ field public static final androidx.health.services.client.data.DataType VO2;
+ field public static final androidx.health.services.client.data.DataType VO2_MAX;
+ field public static final androidx.health.services.client.data.DataType WALKING_STEPS;
+ }
+
+ public static final class DataType.Companion {
+ }
+
+ public enum DataType.TimeType {
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+ }
+
+ public final class DataTypeCondition implements android.os.Parcelable {
+ ctor public DataTypeCondition(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public androidx.health.services.client.data.ComparisonType component3();
+ method public androidx.health.services.client.data.DataTypeCondition copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ComparisonType getComparisonType();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public androidx.health.services.client.data.Value getThreshold();
+ method public boolean isSatisfied(androidx.health.services.client.data.DataPoint dataPoint);
+ method public boolean isThresholdSatisfied(androidx.health.services.client.data.Value value);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ComparisonType comparisonType;
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final androidx.health.services.client.data.Value threshold;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataTypeCondition> CREATOR;
+ field public static final androidx.health.services.client.data.DataTypeCondition.Companion Companion;
+ }
+
+ public static final class DataTypeCondition.Companion {
+ }
+
+ public final class DataTypes {
+ method public static androidx.health.services.client.data.DataType? getAggregateTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static java.util.Set<androidx.health.services.client.data.DataType> getAggregatedDataTypesFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getAverageTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getMaxTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAggregateType(androidx.health.services.client.data.DataType aggregateType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAverageType(androidx.health.services.client.data.DataType averageType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromMaxType(androidx.health.services.client.data.DataType maxType);
+ method public static boolean isAggregateDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isRawType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalAverageDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalMaxDataType(androidx.health.services.client.data.DataType dataType);
+ field public static final androidx.health.services.client.data.DataTypes INSTANCE;
+ }
+
+ public final class ExerciseCapabilities implements android.os.Parcelable {
+ ctor public ExerciseCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component2();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component3();
+ method public boolean component4();
+ method public boolean component5();
+ method public androidx.health.services.client.data.ExerciseCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypes();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+ method public boolean getSupportsAutoPauseAndResume();
+ method public boolean getSupportsLaps();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+ property public final boolean supportsAutoPauseAndResume;
+ property public final boolean supportsLaps;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseCapabilities.Companion Companion;
+ }
+
+ public static final class ExerciseCapabilities.Companion {
+ }
+
+ public final class ExerciseConfig implements android.os.Parcelable {
+ ctor protected ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public static androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ method public androidx.health.services.client.data.ExerciseType component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public boolean component3();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.ExerciseConfig copy(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public boolean getAutoPauseAndResume();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getDataTypes();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> getExerciseGoals();
+ method public android.os.Bundle getExerciseParams();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final boolean autoPauseAndResume;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> dataTypes;
+ property public final java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals;
+ property public final android.os.Bundle exerciseParams;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+ }
+
+ public static final class ExerciseConfig.Builder {
+ ctor public ExerciseConfig.Builder();
+ method public androidx.health.services.client.data.ExerciseConfig build();
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setAutoPauseAndResume(boolean autoPauseAndResume);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<androidx.health.services.client.data.DataType> dataTypes);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseType(androidx.health.services.client.data.ExerciseType exerciseType);
+ }
+
+ public static final class ExerciseConfig.Companion {
+ method public androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ }
+
+ public final class ExerciseGoal implements android.os.Parcelable {
+ ctor protected ExerciseGoal(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, optional androidx.health.services.client.data.Value? period);
+ method public androidx.health.services.client.data.ExerciseGoalType component1();
+ method public androidx.health.services.client.data.DataTypeCondition component2();
+ method public androidx.health.services.client.data.Value? component3();
+ method public androidx.health.services.client.data.ExerciseGoal copy(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.Value? period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public static androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+ method public androidx.health.services.client.data.Value? getPeriod();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+ property public final androidx.health.services.client.data.Value? period;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+ }
+
+ public static final class ExerciseGoal.Companion {
+ method public androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ }
+
+ public enum ExerciseGoalType {
+ method public static final androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+ field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+ }
+
+ public static final class ExerciseGoalType.Companion {
+ method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ }
+
+ public final class ExerciseInfo implements android.os.Parcelable {
+ ctor public ExerciseInfo(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public androidx.health.services.client.data.ExerciseTrackedStatus component1();
+ method public androidx.health.services.client.data.ExerciseType component2();
+ method public androidx.health.services.client.data.ExerciseInfo copy(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseTrackedStatus getExerciseTrackedStatus();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseInfo> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseInfo.Companion Companion;
+ }
+
+ public static final class ExerciseInfo.Companion {
+ }
+
+ public final class ExerciseLapSummary implements android.os.Parcelable {
+ ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int component1();
+ method public java.time.Instant component2();
+ method public java.time.Instant component3();
+ method public java.time.Duration component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.ExerciseLapSummary copy(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public int getLapCount();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getLapMetrics();
+ method public java.time.Instant getStartTime();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final int lapCount;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics;
+ property public final java.time.Instant startTime;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseLapSummary> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseLapSummary.Companion Companion;
+ }
+
+ public static final class ExerciseLapSummary.Companion {
+ }
+
+ public enum ExerciseState {
+ method public static final androidx.health.services.client.data.ExerciseState? fromId(int id);
+ method public final int getId();
+ method public final boolean isEnded();
+ method public final boolean isPaused();
+ method public final boolean isResuming();
+ property public final int id;
+ property public final boolean isEnded;
+ property public final boolean isPaused;
+ property public final boolean isResuming;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+ field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+ }
+
+ public static final class ExerciseState.Companion {
+ method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+ }
+
+ public enum ExerciseTrackedStatus {
+ method public static final androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus NO_EXERCISE_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OTHER_APP_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OWNED_EXERCISE_IN_PROGRESS;
+ field public static final androidx.health.services.client.data.ExerciseTrackedStatus.Companion Companion;
+ }
+
+ public static final class ExerciseTrackedStatus.Companion {
+ method public androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ }
+
+ public enum ExerciseType {
+ method public static final androidx.health.services.client.data.ExerciseType fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_SIT_UP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOXING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BURPEE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRICKET;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DANCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_FRONT_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_LATERAL_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_TWO_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FENCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GOLF;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GYNMASTICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LUNGE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PADDLING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PILATES;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PLANK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUGBY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SAILING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKIING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOCCER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUASH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUAT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SURFING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WALKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_INDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_OUTDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType YOGA;
+ field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+ }
+
+ public static final class ExerciseType.Companion {
+ method public androidx.health.services.client.data.ExerciseType fromId(int id);
+ }
+
+ public final class ExerciseUpdate implements android.os.Parcelable {
+ ctor public ExerciseUpdate(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public androidx.health.services.client.data.ExerciseState component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> component4();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> component5();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> component6();
+ method public androidx.health.services.client.data.ExerciseConfig? component7();
+ method public androidx.health.services.client.data.AutoExerciseConfig? component8();
+ method public androidx.health.services.client.data.ExerciseType? component9();
+ method public androidx.health.services.client.data.ExerciseUpdate copy(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public androidx.health.services.client.data.AutoExerciseConfig? getAutoExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseType? getAutoExerciseDetected();
+ method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType getExerciseSessionType();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> getLatestAchievedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> getLatestMetrics();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+ method public java.time.Instant getStartTime();
+ method public androidx.health.services.client.data.ExerciseState getState();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig;
+ property public final androidx.health.services.client.data.ExerciseType? autoExerciseDetected;
+ property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+ property public final java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics;
+ property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+ property public final java.time.Instant startTime;
+ property public final androidx.health.services.client.data.ExerciseState state;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseUpdate> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+ }
+
+ public static final class ExerciseUpdate.Companion {
+ }
+
+ public enum ExerciseUpdate.ExerciseSessionType {
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType AUTO_EXERCISE_DETECTION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType MANUALLY_STARTED_EXERCISE;
+ }
+
+ public final class MeasureCapabilities implements android.os.Parcelable {
+ ctor public MeasureCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public androidx.health.services.client.data.MeasureCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesMeasure();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MeasureCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.MeasureCapabilities.Companion Companion;
+ }
+
+ public static final class MeasureCapabilities.Companion {
+ }
+
+ public final class MilestoneMarkerSummary implements android.os.Parcelable {
+ ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public java.time.Instant component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public androidx.health.services.client.data.AchievedExerciseGoal component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.MilestoneMarkerSummary copy(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public int describeContents();
+ method public androidx.health.services.client.data.AchievedExerciseGoal getAchievedGoal();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public java.time.Instant getStartTime();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getSummaryMetrics();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.AchievedExerciseGoal achievedGoal;
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final java.time.Instant startTime;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MilestoneMarkerSummary> CREATOR;
+ field public static final androidx.health.services.client.data.MilestoneMarkerSummary.Companion Companion;
+ }
+
+ public static final class MilestoneMarkerSummary.Companion {
+ }
+
+ public final class PassiveActivityState implements android.os.Parcelable {
+ ctor public PassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> component1();
+ method public androidx.health.services.client.data.UserActivityState component2();
+ method public androidx.health.services.client.data.ExerciseType? component3();
+ method public java.time.Instant component4();
+ method public androidx.health.services.client.data.PassiveActivityState copy(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public int describeContents();
+ method public static androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints();
+ method public androidx.health.services.client.data.ExerciseType? getExerciseType();
+ method public java.time.Instant getStateChangeTime();
+ method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+ method public void putToIntent(android.content.Intent intent);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<androidx.health.services.client.data.DataPoint> dataPoints;
+ property public final androidx.health.services.client.data.ExerciseType? exerciseType;
+ property public final java.time.Instant stateChangeTime;
+ property public final androidx.health.services.client.data.UserActivityState userActivityState;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveActivityState> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveActivityState.Companion Companion;
+ }
+
+ public static final class PassiveActivityState.Companion {
+ method public androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ }
+
+ public final class PassiveMonitoringCapabilities implements android.os.Parcelable {
+ ctor public PassiveMonitoringCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public androidx.health.services.client.data.PassiveMonitoringCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesEvents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesPassiveMonitoring();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveMonitoringCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveMonitoringCapabilities.Companion Companion;
+ }
+
+ public static final class PassiveMonitoringCapabilities.Companion {
+ }
+
+ public enum UserActivityState {
+ method public static final androidx.health.services.client.data.UserActivityState? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_INACTIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+ field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+ }
+
+ public static final class UserActivityState.Companion {
+ method public androidx.health.services.client.data.UserActivityState? fromId(int id);
+ }
+
+ public final class Value implements android.os.Parcelable {
+ method public boolean asBoolean();
+ method public double asDouble();
+ method public double[] asDoubleArray();
+ method public long asLong();
+ method public int component1();
+ method public java.util.List<java.lang.Double> component2();
+ method public long component3();
+ method public androidx.health.services.client.data.Value copy(int format, java.util.List<java.lang.Double> doubleList, long longValue);
+ method public int describeContents();
+ method public java.util.List<java.lang.Double> getDoubleList();
+ method public int getFormat();
+ method public long getLongValue();
+ method public boolean isBoolean();
+ method public boolean isDouble();
+ method public boolean isDoubleArray();
+ method public boolean isLong();
+ method public static androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public static androidx.health.services.client.data.Value ofDouble(double value);
+ method public static androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public static androidx.health.services.client.data.Value ofLong(long value);
+ method public static androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<java.lang.Double> doubleList;
+ property public final int format;
+ property public final boolean isBoolean;
+ property public final boolean isDouble;
+ property public final boolean isDoubleArray;
+ property public final boolean isLong;
+ property public final long longValue;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Value> CREATOR;
+ field public static final androidx.health.services.client.data.Value.Companion Companion;
+ field public static final int FORMAT_BOOLEAN = 4; // 0x4
+ field public static final int FORMAT_DOUBLE = 1; // 0x1
+ field public static final int FORMAT_DOUBLE_ARRAY = 3; // 0x3
+ field public static final int FORMAT_LONG = 2; // 0x2
+ }
+
+ public static final class Value.Companion {
+ method public androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public androidx.health.services.client.data.Value ofDouble(double value);
+ method public androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public androidx.health.services.client.data.Value ofLong(long value);
+ method public androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ }
+
+}
+
+package androidx.health.services.client.data.event {
+
+ public final class Event implements android.os.Parcelable {
+ ctor public Event(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public androidx.health.services.client.data.DataTypeCondition component1();
+ method public androidx.health.services.client.data.event.Event.TriggerType component2();
+ method public androidx.health.services.client.data.event.Event copy(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.event.Event.TriggerType getTriggerType();
+ method public boolean isTriggered(androidx.health.services.client.data.DataPoint dataPoint);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.event.Event.TriggerType triggerType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.event.Event> CREATOR;
+ field public static final androidx.health.services.client.data.event.Event.Companion Companion;
+ }
+
+ public static final class Event.Companion {
+ }
+
+ public enum Event.TriggerType {
+ method public static final androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType ONCE;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType REPEATED;
+ field public static final androidx.health.services.client.data.event.Event.TriggerType.Companion Companion;
+ }
+
+ public static final class Event.TriggerType.Companion {
+ method public androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ }
+
+}
+
diff --git a/health/health-services-client/api/res-current.txt b/health/health-services-client/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/health/health-services-client/api/res-current.txt
diff --git a/health/health-services-client/api/restricted_current.txt b/health/health-services-client/api/restricted_current.txt
new file mode 100644
index 0000000..6abe37c
--- /dev/null
+++ b/health/health-services-client/api/restricted_current.txt
@@ -0,0 +1,869 @@
+// Signature format: 4.0
+package androidx.health.services.client {
+
+ public interface ExerciseClient {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExercise(androidx.health.services.client.data.ExerciseGoal exerciseGoal);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfo();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLap();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExercise(boolean enabled);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExercise(androidx.health.services.client.data.ExerciseConfig configuration);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> capabilities;
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> currentExerciseInfo;
+ }
+
+ public interface ExerciseUpdateListener {
+ method public void onExerciseUpdate(androidx.health.services.client.data.ExerciseUpdate update);
+ method public void onLapSummary(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+ }
+
+ public final class HealthServices {
+ method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+ field public static final androidx.health.services.client.HealthServices INSTANCE;
+ }
+
+ public interface HealthServicesClient {
+ method public androidx.health.services.client.ExerciseClient getExerciseClient();
+ method public androidx.health.services.client.MeasureClient getMeasureClient();
+ method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+ property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+ property public abstract androidx.health.services.client.MeasureClient measureClient;
+ property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+ }
+
+ public interface MeasureCallback {
+ method public void onAvailabilityChanged(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Availability availability);
+ method public void onData(java.util.List<androidx.health.services.client.data.DataPoint> data);
+ }
+
+ public interface MeasureClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> capabilities;
+ }
+
+ public interface PassiveMonitoringCallback {
+ method public void onPassiveActivityState(androidx.health.services.client.data.PassiveActivityState state);
+ }
+
+ public interface PassiveMonitoringClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent, androidx.health.services.client.PassiveMonitoringCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerEventCallback(androidx.health.services.client.data.event.Event event, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterDataCallback();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterEventCallback(androidx.health.services.client.data.event.Event event);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> capabilities;
+ }
+
+}
+
+package androidx.health.services.client.data {
+
+ public final class AchievedExerciseGoal implements android.os.Parcelable {
+ ctor public AchievedExerciseGoal(androidx.health.services.client.data.ExerciseGoal goal);
+ method public androidx.health.services.client.data.ExerciseGoal component1();
+ method public androidx.health.services.client.data.AchievedExerciseGoal copy(androidx.health.services.client.data.ExerciseGoal goal);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseGoal getGoal();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseGoal goal;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AchievedExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.AchievedExerciseGoal.Companion Companion;
+ }
+
+ public static final class AchievedExerciseGoal.Companion {
+ }
+
+ public final class AutoExerciseConfig implements android.os.Parcelable {
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent, optional android.os.Bundle exerciseParams);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect);
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> component1();
+ method public android.app.PendingIntent? component2();
+ method public android.os.Bundle component3();
+ method public androidx.health.services.client.data.AutoExerciseConfig copy(java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, android.app.PendingIntent? launchIntent, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public android.os.Bundle getExerciseParams();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getExercisesToDetect();
+ method public android.app.PendingIntent? getLaunchIntent();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final android.os.Bundle exerciseParams;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> exercisesToDetect;
+ property public final android.app.PendingIntent? launchIntent;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AutoExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.AutoExerciseConfig.Companion Companion;
+ }
+
+ public static final class AutoExerciseConfig.Companion {
+ }
+
+ public enum Availability {
+ method public static final androidx.health.services.client.data.Availability? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.Availability ACQUIRING;
+ enum_constant public static final androidx.health.services.client.data.Availability AVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNAVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNKNOWN;
+ field public static final androidx.health.services.client.data.Availability.Companion Companion;
+ }
+
+ public static final class Availability.Companion {
+ method public androidx.health.services.client.data.Availability? fromId(int id);
+ }
+
+ public final class Capabilities implements android.os.Parcelable {
+ ctor public Capabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> component1();
+ method public androidx.health.services.client.data.Capabilities copy(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+ method public androidx.health.services.client.data.ExerciseCapabilities getExerciseCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> getExerciseTypeToExerciseCapabilities();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+ property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Capabilities> CREATOR;
+ field public static final androidx.health.services.client.data.Capabilities.Companion Companion;
+ }
+
+ public static final class Capabilities.Companion {
+ }
+
+ public enum ComparisonType {
+ method public static final androidx.health.services.client.data.ComparisonType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+ field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+ }
+
+ public static final class ComparisonType.Companion {
+ method public androidx.health.services.client.data.ComparisonType? fromId(int id);
+ }
+
+ public final class DataPoint implements android.os.Parcelable {
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public java.time.Duration component3();
+ method public java.time.Duration component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.DataPoint copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public java.time.Duration getEndDurationFromBoot();
+ method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+ method public android.os.Bundle getMetadata();
+ method public java.time.Duration getStartDurationFromBoot();
+ method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+ method public androidx.health.services.client.data.Value getValue();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final java.time.Duration endDurationFromBoot;
+ property public final android.os.Bundle metadata;
+ property public final java.time.Duration startDurationFromBoot;
+ property public final androidx.health.services.client.data.Value value;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataPoint> CREATOR;
+ field public static final androidx.health.services.client.data.DataPoint.Companion Companion;
+ }
+
+ public static final class DataPoint.Companion {
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ }
+
+ @Keep public final class DataPoints {
+ method public static androidx.health.services.client.data.DataPoint aggregateCalories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateDistance(double distance, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSteps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSwimmingStrokes(long swimmingStrokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint altitude(double meters, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averagePace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averageSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint calories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint distance(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint elevation(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint floors(double floors, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method @Keep public static java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints(android.content.Intent intent);
+ method public static boolean getPermissionsGranted(android.content.Intent intent);
+ method public static androidx.health.services.client.data.DataPoint heartRate(double bpm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, double altitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint maxSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint pace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static void putDataPoints(android.content.Intent intent, java.util.Collection<androidx.health.services.client.data.DataPoint> dataPoints);
+ method public static void putPermissionsGranted(android.content.Intent intent, boolean granted);
+ method public static androidx.health.services.client.data.DataPoint speed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint spo2(double percent, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint steps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint stepsPerMinute(long stepsPerMinute, java.time.Duration startDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint swimmingStrokes(long strokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ field public static final androidx.health.services.client.data.DataPoints INSTANCE;
+ field public static final int LOCATION_DATA_POINT_ALTITUDE_INDEX = 2; // 0x2
+ field public static final int LOCATION_DATA_POINT_LATITUDE_INDEX = 0; // 0x0
+ field public static final int LOCATION_DATA_POINT_LONGITUDE_INDEX = 1; // 0x1
+ }
+
+ public final class DataType implements android.os.Parcelable {
+ ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public String component1();
+ method public androidx.health.services.client.data.DataType.TimeType component2();
+ method public int component3();
+ method public androidx.health.services.client.data.DataType copy(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public int describeContents();
+ method public int getFormat();
+ method public String getName();
+ method public androidx.health.services.client.data.DataType.TimeType getTimeType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final int format;
+ property public final String name;
+ property public final androidx.health.services.client.data.DataType.TimeType timeType;
+ field public static final androidx.health.services.client.data.DataType ABSOLUTE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType ACTIVE_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_CALORIES_EXPENDED;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLOORS;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_RUNNING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_SWIMMING_STROKE_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_WALKING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_PACE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_SPEED;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataType> CREATOR;
+ field public static final androidx.health.services.client.data.DataType.Companion Companion;
+ field public static final androidx.health.services.client.data.DataType DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType DISTANCE;
+ field public static final androidx.health.services.client.data.DataType ELEVATION;
+ field public static final androidx.health.services.client.data.DataType FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType FLOORS;
+ field public static final androidx.health.services.client.data.DataType HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType LOCATION;
+ field public static final androidx.health.services.client.data.DataType MAX_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType MAX_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType MAX_PACE;
+ field public static final androidx.health.services.client.data.DataType MAX_SPEED;
+ field public static final androidx.health.services.client.data.DataType MIN_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType PACE;
+ field public static final androidx.health.services.client.data.DataType REP_COUNT;
+ field public static final androidx.health.services.client.data.DataType RESTING_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType RUNNING_STEPS;
+ field public static final androidx.health.services.client.data.DataType SPEED;
+ field public static final androidx.health.services.client.data.DataType SPO2;
+ field public static final androidx.health.services.client.data.DataType STEPS;
+ field public static final androidx.health.services.client.data.DataType STEPS_PER_MINUTE;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_LAP_COUNT;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_STROKES;
+ field public static final androidx.health.services.client.data.DataType TOTAL_CALORIES;
+ field public static final androidx.health.services.client.data.DataType VO2;
+ field public static final androidx.health.services.client.data.DataType VO2_MAX;
+ field public static final androidx.health.services.client.data.DataType WALKING_STEPS;
+ }
+
+ public static final class DataType.Companion {
+ }
+
+ public enum DataType.TimeType {
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+ }
+
+ public final class DataTypeCondition implements android.os.Parcelable {
+ ctor public DataTypeCondition(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public androidx.health.services.client.data.ComparisonType component3();
+ method public androidx.health.services.client.data.DataTypeCondition copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ComparisonType getComparisonType();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public androidx.health.services.client.data.Value getThreshold();
+ method public boolean isSatisfied(androidx.health.services.client.data.DataPoint dataPoint);
+ method public boolean isThresholdSatisfied(androidx.health.services.client.data.Value value);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ComparisonType comparisonType;
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final androidx.health.services.client.data.Value threshold;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataTypeCondition> CREATOR;
+ field public static final androidx.health.services.client.data.DataTypeCondition.Companion Companion;
+ }
+
+ public static final class DataTypeCondition.Companion {
+ }
+
+ public final class DataTypes {
+ method public static androidx.health.services.client.data.DataType? getAggregateTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static java.util.Set<androidx.health.services.client.data.DataType> getAggregatedDataTypesFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getAverageTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getMaxTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAggregateType(androidx.health.services.client.data.DataType aggregateType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAverageType(androidx.health.services.client.data.DataType averageType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromMaxType(androidx.health.services.client.data.DataType maxType);
+ method public static boolean isAggregateDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isRawType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalAverageDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalMaxDataType(androidx.health.services.client.data.DataType dataType);
+ field public static final androidx.health.services.client.data.DataTypes INSTANCE;
+ }
+
+ public final class ExerciseCapabilities implements android.os.Parcelable {
+ ctor public ExerciseCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component2();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component3();
+ method public boolean component4();
+ method public boolean component5();
+ method public androidx.health.services.client.data.ExerciseCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypes();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+ method public boolean getSupportsAutoPauseAndResume();
+ method public boolean getSupportsLaps();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+ property public final boolean supportsAutoPauseAndResume;
+ property public final boolean supportsLaps;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseCapabilities.Companion Companion;
+ }
+
+ public static final class ExerciseCapabilities.Companion {
+ }
+
+ public final class ExerciseConfig implements android.os.Parcelable {
+ ctor protected ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public static androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ method public androidx.health.services.client.data.ExerciseType component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public boolean component3();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.ExerciseConfig copy(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public boolean getAutoPauseAndResume();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getDataTypes();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> getExerciseGoals();
+ method public android.os.Bundle getExerciseParams();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final boolean autoPauseAndResume;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> dataTypes;
+ property public final java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals;
+ property public final android.os.Bundle exerciseParams;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+ }
+
+ public static final class ExerciseConfig.Builder {
+ ctor public ExerciseConfig.Builder();
+ method public androidx.health.services.client.data.ExerciseConfig build();
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setAutoPauseAndResume(boolean autoPauseAndResume);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<androidx.health.services.client.data.DataType> dataTypes);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseType(androidx.health.services.client.data.ExerciseType exerciseType);
+ }
+
+ public static final class ExerciseConfig.Companion {
+ method public androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ }
+
+ public final class ExerciseGoal implements android.os.Parcelable {
+ ctor protected ExerciseGoal(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, optional androidx.health.services.client.data.Value? period);
+ method public androidx.health.services.client.data.ExerciseGoalType component1();
+ method public androidx.health.services.client.data.DataTypeCondition component2();
+ method public androidx.health.services.client.data.Value? component3();
+ method public androidx.health.services.client.data.ExerciseGoal copy(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.Value? period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public static androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+ method public androidx.health.services.client.data.Value? getPeriod();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+ property public final androidx.health.services.client.data.Value? period;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+ }
+
+ public static final class ExerciseGoal.Companion {
+ method public androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ }
+
+ public enum ExerciseGoalType {
+ method public static final androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+ field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+ }
+
+ public static final class ExerciseGoalType.Companion {
+ method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ }
+
+ public final class ExerciseInfo implements android.os.Parcelable {
+ ctor public ExerciseInfo(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public androidx.health.services.client.data.ExerciseTrackedStatus component1();
+ method public androidx.health.services.client.data.ExerciseType component2();
+ method public androidx.health.services.client.data.ExerciseInfo copy(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseTrackedStatus getExerciseTrackedStatus();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseInfo> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseInfo.Companion Companion;
+ }
+
+ public static final class ExerciseInfo.Companion {
+ }
+
+ public final class ExerciseLapSummary implements android.os.Parcelable {
+ ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int component1();
+ method public java.time.Instant component2();
+ method public java.time.Instant component3();
+ method public java.time.Duration component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.ExerciseLapSummary copy(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public int getLapCount();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getLapMetrics();
+ method public java.time.Instant getStartTime();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final int lapCount;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics;
+ property public final java.time.Instant startTime;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseLapSummary> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseLapSummary.Companion Companion;
+ }
+
+ public static final class ExerciseLapSummary.Companion {
+ }
+
+ public enum ExerciseState {
+ method public static final androidx.health.services.client.data.ExerciseState? fromId(int id);
+ method public final int getId();
+ method public final boolean isEnded();
+ method public final boolean isPaused();
+ method public final boolean isResuming();
+ property public final int id;
+ property public final boolean isEnded;
+ property public final boolean isPaused;
+ property public final boolean isResuming;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+ field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+ }
+
+ public static final class ExerciseState.Companion {
+ method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+ }
+
+ public enum ExerciseTrackedStatus {
+ method public static final androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus NO_EXERCISE_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OTHER_APP_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OWNED_EXERCISE_IN_PROGRESS;
+ field public static final androidx.health.services.client.data.ExerciseTrackedStatus.Companion Companion;
+ }
+
+ public static final class ExerciseTrackedStatus.Companion {
+ method public androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ }
+
+ public enum ExerciseType {
+ method public static final androidx.health.services.client.data.ExerciseType fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_SIT_UP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOXING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BURPEE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRICKET;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DANCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_FRONT_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_LATERAL_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_TWO_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FENCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GOLF;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GYNMASTICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LUNGE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PADDLING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PILATES;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PLANK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUGBY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SAILING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKIING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOCCER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUASH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUAT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SURFING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WALKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_INDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_OUTDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType YOGA;
+ field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+ }
+
+ public static final class ExerciseType.Companion {
+ method public androidx.health.services.client.data.ExerciseType fromId(int id);
+ }
+
+ public final class ExerciseUpdate implements android.os.Parcelable {
+ ctor public ExerciseUpdate(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public androidx.health.services.client.data.ExerciseState component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> component4();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> component5();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> component6();
+ method public androidx.health.services.client.data.ExerciseConfig? component7();
+ method public androidx.health.services.client.data.AutoExerciseConfig? component8();
+ method public androidx.health.services.client.data.ExerciseType? component9();
+ method public androidx.health.services.client.data.ExerciseUpdate copy(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public androidx.health.services.client.data.AutoExerciseConfig? getAutoExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseType? getAutoExerciseDetected();
+ method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType getExerciseSessionType();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> getLatestAchievedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> getLatestMetrics();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+ method public java.time.Instant getStartTime();
+ method public androidx.health.services.client.data.ExerciseState getState();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig;
+ property public final androidx.health.services.client.data.ExerciseType? autoExerciseDetected;
+ property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+ property public final java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics;
+ property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+ property public final java.time.Instant startTime;
+ property public final androidx.health.services.client.data.ExerciseState state;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseUpdate> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+ }
+
+ public static final class ExerciseUpdate.Companion {
+ }
+
+ public enum ExerciseUpdate.ExerciseSessionType {
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType AUTO_EXERCISE_DETECTION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType MANUALLY_STARTED_EXERCISE;
+ }
+
+ public final class MeasureCapabilities implements android.os.Parcelable {
+ ctor public MeasureCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public androidx.health.services.client.data.MeasureCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesMeasure();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MeasureCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.MeasureCapabilities.Companion Companion;
+ }
+
+ public static final class MeasureCapabilities.Companion {
+ }
+
+ public final class MilestoneMarkerSummary implements android.os.Parcelable {
+ ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public java.time.Instant component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public androidx.health.services.client.data.AchievedExerciseGoal component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.MilestoneMarkerSummary copy(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public int describeContents();
+ method public androidx.health.services.client.data.AchievedExerciseGoal getAchievedGoal();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public java.time.Instant getStartTime();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getSummaryMetrics();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.AchievedExerciseGoal achievedGoal;
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final java.time.Instant startTime;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MilestoneMarkerSummary> CREATOR;
+ field public static final androidx.health.services.client.data.MilestoneMarkerSummary.Companion Companion;
+ }
+
+ public static final class MilestoneMarkerSummary.Companion {
+ }
+
+ public final class PassiveActivityState implements android.os.Parcelable {
+ ctor public PassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> component1();
+ method public androidx.health.services.client.data.UserActivityState component2();
+ method public androidx.health.services.client.data.ExerciseType? component3();
+ method public java.time.Instant component4();
+ method public androidx.health.services.client.data.PassiveActivityState copy(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public int describeContents();
+ method public static androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints();
+ method public androidx.health.services.client.data.ExerciseType? getExerciseType();
+ method public java.time.Instant getStateChangeTime();
+ method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+ method public void putToIntent(android.content.Intent intent);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<androidx.health.services.client.data.DataPoint> dataPoints;
+ property public final androidx.health.services.client.data.ExerciseType? exerciseType;
+ property public final java.time.Instant stateChangeTime;
+ property public final androidx.health.services.client.data.UserActivityState userActivityState;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveActivityState> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveActivityState.Companion Companion;
+ }
+
+ public static final class PassiveActivityState.Companion {
+ method public androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ }
+
+ public final class PassiveMonitoringCapabilities implements android.os.Parcelable {
+ ctor public PassiveMonitoringCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public androidx.health.services.client.data.PassiveMonitoringCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesEvents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesPassiveMonitoring();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveMonitoringCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveMonitoringCapabilities.Companion Companion;
+ }
+
+ public static final class PassiveMonitoringCapabilities.Companion {
+ }
+
+ public enum UserActivityState {
+ method public static final androidx.health.services.client.data.UserActivityState? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_INACTIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+ field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+ }
+
+ public static final class UserActivityState.Companion {
+ method public androidx.health.services.client.data.UserActivityState? fromId(int id);
+ }
+
+ public final class Value implements android.os.Parcelable {
+ method public boolean asBoolean();
+ method public double asDouble();
+ method public double[] asDoubleArray();
+ method public long asLong();
+ method public int component1();
+ method public java.util.List<java.lang.Double> component2();
+ method public long component3();
+ method public androidx.health.services.client.data.Value copy(int format, java.util.List<java.lang.Double> doubleList, long longValue);
+ method public int describeContents();
+ method public java.util.List<java.lang.Double> getDoubleList();
+ method public int getFormat();
+ method public long getLongValue();
+ method public boolean isBoolean();
+ method public boolean isDouble();
+ method public boolean isDoubleArray();
+ method public boolean isLong();
+ method public static androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public static androidx.health.services.client.data.Value ofDouble(double value);
+ method public static androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public static androidx.health.services.client.data.Value ofLong(long value);
+ method public static androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<java.lang.Double> doubleList;
+ property public final int format;
+ property public final boolean isBoolean;
+ property public final boolean isDouble;
+ property public final boolean isDoubleArray;
+ property public final boolean isLong;
+ property public final long longValue;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Value> CREATOR;
+ field public static final androidx.health.services.client.data.Value.Companion Companion;
+ field public static final int FORMAT_BOOLEAN = 4; // 0x4
+ field public static final int FORMAT_DOUBLE = 1; // 0x1
+ field public static final int FORMAT_DOUBLE_ARRAY = 3; // 0x3
+ field public static final int FORMAT_LONG = 2; // 0x2
+ }
+
+ public static final class Value.Companion {
+ method public androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public androidx.health.services.client.data.Value ofDouble(double value);
+ method public androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public androidx.health.services.client.data.Value ofLong(long value);
+ method public androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ }
+
+}
+
+package androidx.health.services.client.data.event {
+
+ public final class Event implements android.os.Parcelable {
+ ctor public Event(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public androidx.health.services.client.data.DataTypeCondition component1();
+ method public androidx.health.services.client.data.event.Event.TriggerType component2();
+ method public androidx.health.services.client.data.event.Event copy(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.event.Event.TriggerType getTriggerType();
+ method public boolean isTriggered(androidx.health.services.client.data.DataPoint dataPoint);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.event.Event.TriggerType triggerType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.event.Event> CREATOR;
+ field public static final androidx.health.services.client.data.event.Event.Companion Companion;
+ }
+
+ public static final class Event.Companion {
+ }
+
+ public enum Event.TriggerType {
+ method public static final androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType ONCE;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType REPEATED;
+ field public static final androidx.health.services.client.data.event.Event.TriggerType.Companion Companion;
+ }
+
+ public static final class Event.TriggerType.Companion {
+ method public androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ }
+
+}
+
diff --git a/health/health-services-client/build.gradle b/health/health-services-client/build.gradle
new file mode 100644
index 0000000..248825c
--- /dev/null
+++ b/health/health-services-client/build.gradle
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 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.
+ */
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryType
+import androidx.build.LibraryVersions
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ api(KOTLIN_STDLIB)
+ api("androidx.annotation:annotation:1.1.0")
+ implementation(GUAVA_LISTENABLE_FUTURE)
+ implementation(GUAVA_ANDROID)
+ implementation("androidx.core:core-ktx:1.5.0-alpha04")
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 26
+ }
+ buildFeatures {
+ aidl = true
+ }
+}
+
+androidx {
+ name = "AndroidX Health Services Client Library"
+ type = LibraryType.PUBLISHED_LIBRARY
+ mavenVersion = LibraryVersions.HEALTH_SERVICES_CLIENT
+ mavenGroup = LibraryGroups.HEALTH
+ inceptionYear = "2021"
+ description = "This library helps developers create performant health applications in a platform agnostic way"
+}
diff --git a/health/health-services-client/lint-baseline.xml b/health/health-services-client/lint-baseline.xml
new file mode 100644
index 0000000..11eec21
--- /dev/null
+++ b/health/health-services-client/lint-baseline.xml
@@ -0,0 +1,1291 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
+
+ <issue
+ id="BanKeepAnnotation"
+ message="Uses @Keep annotation"
+ errorLine1="@Keep"
+ errorLine2="~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataPoints.kt"
+ line="27"
+ column="1"/>
+ </issue>
+
+ <issue
+ id="BanKeepAnnotation"
+ message="Uses @Keep annotation"
+ errorLine1=" @Keep"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataPoints.kt"
+ line="56"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class AchievedExerciseGoal("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt"
+ line="23"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class AutoExerciseConfig"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class AutoPauseAndResumeConfigRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt"
+ line="27"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class AvailabilityResponse("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt"
+ line="29"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class BackgroundRegistrationRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class BackgroundUnregistrationRequest(val packageName: String) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt"
+ line="27"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class Capabilities("
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Capabilities.kt"
+ line="24"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class CapabilitiesRequest(val packageName: String) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt"
+ line="27"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class CapabilitiesResponse("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class DataPoint"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataPoint.kt"
+ line="31"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class DataPointsResponse(val dataPoints: List<DataPoint>) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class DataType("
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataType.kt"
+ line="32"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class DataTypeCondition("
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataTypeCondition.kt"
+ line="23"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class Event("
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/event/Event.kt"
+ line="25"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class EventRequest(val packageName: String, val event: Event) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/EventRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseCapabilities("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="23"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseConfig"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseConfig.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseGoal"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseGoal.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseGoalRequest(val packageName: String, val exerciseGoal: ExerciseGoal) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseInfo("
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseInfo.kt"
+ line="23"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseInfoResponse(val exerciseInfo: ExerciseInfo) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseLapSummary("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt"
+ line="25"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseLapSummaryResponse(val exerciseLapSummary: ExerciseLapSummary) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseUpdate("
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseUpdateResponse(val exerciseUpdate: ExerciseUpdate) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MeasureCapabilities("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt"
+ line="25"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MeasureCapabilitiesResponse("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MeasureRegistrationRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MeasureUnregistrationRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MilestoneMarkerSummary("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt"
+ line="27"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class PassiveActivityState("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/PassiveActivityState.kt"
+ line="34"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class PassiveActivityStateResponse(val passiveActivityState: PassiveActivityState) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class PassiveMonitoringCapabilities("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class PassiveMonitoringCapabilitiesResponse("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class StartExerciseRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class Value"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Value.kt"
+ line="24"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" @Synchronized"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt"
+ line="55"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" @Synchronized"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt"
+ line="63"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" @Synchronized"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt"
+ line="63"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" @Synchronized"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt"
+ line="86"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" private synchronized void handleRetriableDisconnection(Throwable throwable) {"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="155"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getExerciseToExerciseCapabilityMap` of class `Companion` requires synthetic accessor"
+ errorLine1=" getExerciseToExerciseCapabilityMap(parcel)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Capabilities.kt"
+ line="79"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `createQueueOperation` of class `Client` requires synthetic accessor"
+ errorLine1=" createQueueOperation("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="133"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `writeSupportedDataTypes` of class `Companion` requires synthetic accessor"
+ errorLine1=" writeSupportedDataTypes(supportedGoals, dest, flags)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="36"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `writeSupportedDataTypes` of class `Companion` requires synthetic accessor"
+ errorLine1=" writeSupportedDataTypes(supportedMilestones, dest, flags)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="37"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `readSupportedDataTypes` of class `Companion` requires synthetic accessor"
+ errorLine1=" val supportedGoals = readSupportedDataTypes(source) ?: return null"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="51"
+ column="42"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `readSupportedDataTypes` of class `Companion` requires synthetic accessor"
+ errorLine1=" val supportedMilestones = readSupportedDataTypes(source) ?: return null"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="52"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `initialize` of class `Companion` requires synthetic accessor"
+ errorLine1=" private val IDS = initialize()"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseType.kt"
+ line="114"
+ column="27"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `ExerciseUpdateListenerStub` requires synthetic accessor"
+ errorLine1=" return listeners.getOrPut(listener) { ExerciseUpdateListenerStub(listener, executor) }"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt"
+ line="60"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `MeasureCallbackStub` requires synthetic accessor"
+ errorLine1=" measureCallbackStub = MeasureCallbackStub(callbackKey, measureCallback)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt"
+ line="79"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `Value` requires synthetic accessor"
+ errorLine1=" return Value(format, listOf(), parcel.readLong())"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Value.kt"
+ line="142"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `Value` requires synthetic accessor"
+ errorLine1=" return Value(format, listOf(parcel.readDouble()), /* longValue= */ 0)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Value.kt"
+ line="144"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `Value` requires synthetic accessor"
+ errorLine1=" return Value(format, doubleArray.toList(), /* longValue= */ 0)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Value.kt"
+ line="148"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="LambdaLast"
+ message="Functional interface parameters (such as parameter 1, "operation", in androidx.health.services.client.impl.ipc.Client.executeWithVersionCheck) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions"
+ errorLine1=" ServiceOperation<R> operation, int minApiVersion) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="115"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public BaseQueueOperation(ConnectionConfiguration connectionConfiguration) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="37"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void execute(IBinder binder) throws RemoteException {}"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="42"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void setException(Throwable exception) {}"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="45"
+ column="30"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public QueueOperation trackExecution(ExecutionTracker tracker) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="48"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public QueueOperation trackExecution(ExecutionTracker tracker) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="48"
+ column="42"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ConnectionConfiguration getConnectionConfiguration() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="54"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" Integer readVersion(IBinder binder) throws RemoteException;"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="53"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" Integer readVersion(IBinder binder) throws RemoteException;"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="53"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ClientConfiguration clientConfiguration,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="65"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ConnectionManager connectionManager,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="66"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" VersionGetter versionGetter) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="67"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> execute(ServiceOperation<R> operation) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="107"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> execute(ServiceOperation<R> operation) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="107"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> executeWithVersionCheck("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="114"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ServiceOperation<R> operation, int minApiVersion) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="115"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> registerListener("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="173"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ListenerKey listenerKey, ServiceOperation<R> registerListenerOperation) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="174"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ListenerKey listenerKey, ServiceOperation<R> registerListenerOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="174"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> unregisterListener("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="193"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ListenerKey listenerKey, ServiceOperation<R> unregisterListenerOperation) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="194"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ListenerKey listenerKey, ServiceOperation<R> unregisterListenerOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="194"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected Exception getApiVersionCheckFailureException(int currentVersion, int minApiVersion) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="203"
+ column="15"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="34"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="34"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="34"
+ column="81"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public String getServicePackageName() {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="41"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public String getBindAction() {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="46"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public String getApiClientName() {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="51"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" String packageName,"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+ line="38"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" String clientName,"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+ line="39"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" String bindAction,"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+ line="40"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" QueueOperation refreshVersionOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+ line="41"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ConnectionManager(Context context, Looper looper) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="52"
+ column="30"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ConnectionManager(Context context, Looper looper) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="52"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void scheduleForExecution(QueueOperation operation) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="62"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void registerListener(ListenerKey listenerKey, QueueOperation registerOperation) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="73"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void registerListener(ListenerKey listenerKey, QueueOperation registerOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="73"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterOperation) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="86"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="86"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onConnected(ServiceConnection connection) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="94"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onDisconnected(ServiceConnection connection, long reconnectDelayMs) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="99"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public boolean handleMessage(Message msg) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="110"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void track(SettableFuture<?> future) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java"
+ line="39"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void cancelPendingFutures(Throwable throwable) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java"
+ line="45"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void track(SettableFuture<?> future);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java"
+ line="33"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void cancelPendingFutures(Throwable throwable);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java"
+ line="36"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ListenerKey(Object listenerKey) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java"
+ line="32"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void execute(IBinder binder) throws RemoteException;"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="38"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void setException(Throwable exception);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="41"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" QueueOperation trackExecution(ExecutionTracker tracker);"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="48"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" QueueOperation trackExecution(ExecutionTracker tracker);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="48"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ConnectionConfiguration getConnectionConfiguration();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="51"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void onConnected(ServiceConnection connection);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="61"
+ column="26"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void onDisconnected(ServiceConnection connection, long reconnectDelayMs);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="69"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onServiceConnected(ComponentName componentName, IBinder binder) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="287"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onServiceConnected(ComponentName componentName, IBinder binder) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="287"
+ column="65"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onServiceDisconnected(ComponentName componentName) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="320"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onBindingDied(ComponentName name) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="326"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onNullBinding(ComponentName name) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="332"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void execute(IBinder binder, SettableFuture<R> resultFuture) throws RemoteException;"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java"
+ line="44"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void execute(IBinder binder, SettableFuture<R> resultFuture) throws RemoteException;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java"
+ line="44"
+ column="34"/>
+ </issue>
+
+</issues>
diff --git a/health/health-services-client/src/androidTest/AndroidManifest.xml b/health/health-services-client/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..d6673ca
--- /dev/null
+++ b/health/health-services-client/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.health.services.client.test">
+</manifest>
+
diff --git a/health/health-services-client/src/main/AndroidManifest.xml b/health/health-services-client/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..75a5742
--- /dev/null
+++ b/health/health-services-client/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.health.services.client">
+</manifest>
+
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl
new file mode 100644
index 0000000..6a08a9a
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.IExerciseUpdateListener;
+import androidx.health.services.client.impl.internal.IExerciseInfoCallback;
+import androidx.health.services.client.impl.internal.IStatusCallback;
+import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest;
+import androidx.health.services.client.impl.request.CapabilitiesRequest;
+import androidx.health.services.client.impl.request.ExerciseGoalRequest;
+import androidx.health.services.client.impl.request.StartExerciseRequest;
+import androidx.health.services.client.impl.response.CapabilitiesResponse;
+
+/**
+ * Interface to make ipc calls for health services exercise api.
+ *
+ * The next method added to the interface should use ID: 13
+ * (this id needs to be incremented for each added method)
+ *
+ * @hide
+ */
+interface IExerciseApiService {
+ /**
+ * API version of the AIDL interface. Should be incremented every time a new
+ * method is added.
+ *
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Handles a given request to start an exercise.
+ */
+ void startExercise(in StartExerciseRequest startExerciseRequest, IStatusCallback statusCallback) = 5;
+
+ /**
+ * Method to pause the active exercise for the calling app.
+ */
+ void pauseExercise(in String packageName, IStatusCallback statusCallback) = 0;
+
+ /**
+ * Method to resume the active exercise for the calling app.
+ */
+ void resumeExercise(in String packageName, IStatusCallback statusCallback) = 1;
+
+ /**
+ * Method to end the active exercise for the calling app.
+ */
+ void endExercise(in String packageName, IStatusCallback statusCallback) = 2;
+
+ /**
+ * Method to end the current lap in the active exercise for the calling app.
+ */
+ void markLap(in String packageName, IStatusCallback statusCallback) = 9;
+
+ /**
+ * Returns version of this AIDL interface.
+ *
+ * <p> Can be used by client to detect version of the API on the service
+ * side. Returned version should be always > 0.
+ */
+ int getApiVersion() = 3;
+
+ /**
+ * Returns the current exercise info.
+ */
+ void getCurrentExerciseInfo(in String packageName, IExerciseInfoCallback exerciseInfoCallback) = 12;
+
+ /**
+ * Sets the listener for the current exercise state.
+ */
+ void setUpdateListener(in String packageName, in IExerciseUpdateListener listener, IStatusCallback statusCallback) = 6;
+
+ /**
+ * Clears the listener set using {@link #setUpdateListener}.
+ */
+ void clearUpdateListener(in String packageName, in IExerciseUpdateListener listener, IStatusCallback statusCallback) = 7;
+
+ /**
+ * Adds an exercise goal for an active exercise.
+ *
+ * <p>An exercise goal is a one-time goal, such as achieving a target total step count.
+ *
+ * <p>Goals apply to only active exercises owned by the client, and will be invalidated once the
+ * exercise is complete. A goal can be added only after an exercise has been started.
+ */
+ void addGoalToActiveExercise(in ExerciseGoalRequest request, IStatusCallback statusCallback) = 8;
+
+ /**
+ * Sets whether auto-pause should be enabled
+ */
+ void overrideAutoPauseAndResumeForActiveExercise(in AutoPauseAndResumeConfigRequest request, IStatusCallback statusCallback) = 10;
+
+ /**
+ * Method to get capabilities.
+ */
+ CapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 11;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseUpdateListener.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseUpdateListener.aidl
new file mode 100644
index 0000000..df6420bd
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseUpdateListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.response.ExerciseLapSummaryResponse;
+import androidx.health.services.client.impl.response.ExerciseUpdateResponse;
+
+/**
+ * Interface to get exercise updates.
+ *
+ * @hide
+ */
+oneway interface IExerciseUpdateListener {
+ /** Called when there is an update of exercise state or metrics. */
+ void onExerciseUpdate(in ExerciseUpdateResponse update) = 0;
+
+ /** Called when a lap has been marked. */
+ void onLapSummary(in ExerciseLapSummaryResponse summaryResponse) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IHealthServicesApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IHealthServicesApiService.aidl
new file mode 100644
index 0000000..ddccc54
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IHealthServicesApiService.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+/**
+ * Interface to make ipc calls for health services api.
+ *
+ * @hide
+ */
+interface IHealthServicesApiService {
+ /**
+ * API version of the AIDL interface. Should be incremented every time a new
+ * method is added.
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Returns version of this AIDL interface.
+ *
+ * <p> Can be used by client to detect version of the API on the service
+ * side. Returned version should be always > 0.
+ */
+ int getApiVersion() = 1;
+}
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureApiService.aidl
new file mode 100644
index 0000000..2710467
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureApiService.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.IMeasureCallback;
+import androidx.health.services.client.impl.internal.IStatusCallback;
+import androidx.health.services.client.impl.request.CapabilitiesRequest;
+import androidx.health.services.client.impl.request.MeasureRegistrationRequest;
+import androidx.health.services.client.impl.request.MeasureUnregistrationRequest;
+import androidx.health.services.client.impl.response.MeasureCapabilitiesResponse;
+
+/**
+ * Interface to make ipc calls for health services api.
+ *
+ * @hide
+ */
+interface IMeasureApiService {
+ /**
+ * API version of the AIDL interface. Should be incremented every time a new
+ * method is added.
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Method to register measure listener.
+ */
+ void registerCallback(in MeasureRegistrationRequest request, in IMeasureCallback callback, in IStatusCallback statusCallback) = 0;
+
+ /**
+ * Method to unregister measure listener.
+ */
+ void unregisterCallback(in MeasureUnregistrationRequest request, in IMeasureCallback callback, in IStatusCallback statusCallback) = 1;
+
+ /**
+ * Returns version of this AIDL interface.
+ *
+ * <p> Can be used by client to detect version of the API on the service
+ * side. Returned version should be always > 0.
+ */
+ int getApiVersion() = 2;
+
+ /** Method to get capabilities. */
+ MeasureCapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 3;
+}
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureCallback.aidl
new file mode 100644
index 0000000..ff7f983
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.response.AvailabilityResponse;
+import androidx.health.services.client.impl.response.DataPointsResponse;
+
+/**
+ * Interface to get callback for measure api.
+ *
+ * @hide
+ */
+oneway interface IMeasureCallback {
+ void onAvailabilityChanged(in AvailabilityResponse response) = 0;
+
+ void onData(in DataPointsResponse response) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringApiService.aidl
new file mode 100644
index 0000000..d6be3f0
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringApiService.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import android.app.PendingIntent;
+import androidx.health.services.client.impl.IPassiveMonitoringCallback;
+import androidx.health.services.client.impl.internal.IStatusCallback;
+import androidx.health.services.client.impl.request.BackgroundRegistrationRequest;
+import androidx.health.services.client.impl.request.CapabilitiesRequest;
+import androidx.health.services.client.impl.request.EventRequest;
+import androidx.health.services.client.impl.response.PassiveMonitoringCapabilitiesResponse;
+
+/** @hide */
+interface IPassiveMonitoringApiService {
+ /**
+ * API version of the AIDL interface. Should be incremented every time a new
+ * method is added.
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Method to subscribe to an event with corresponding callback intent.
+ */
+ void registerEventCallback(in EventRequest request, in PendingIntent intent, in IStatusCallback statusCallback) = 0;
+
+ /**
+ * Method to subscribe to a set of data types with corresponding callback
+ * intent and an optional callback.
+ *
+ * <p>If a callback is present and is active, updates are provided via the callback. Otherwise,
+ * the provided PendingIntent gets the updates.
+ */
+ void registerDataCallback(in BackgroundRegistrationRequest request, in PendingIntent fallbackIntent, in IPassiveMonitoringCallback callback, in IStatusCallback statusCallback) = 1;
+
+ /**
+ * Method to subscribe to a set of data types with corresponding callback intent.
+ */
+ void unregisterDataCallback(in String packageName, in IStatusCallback statusCallback) = 3;
+
+ /**
+ * Method to subscribe to a set of data types with corresponding callback intent.
+ */
+ void unregisterEventCallback(in EventRequest request, in IStatusCallback statusCallback) = 4;
+
+ /**
+ * Returns version of this AIDL interface.
+ *
+ * <p> Can be used by client to detect version of the API on the service
+ * side. Returned version should be always > 0.
+ */
+ int getApiVersion() = 2;
+
+ /** Method to get capabilities. */
+ PassiveMonitoringCapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 5;
+}
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringCallback.aidl
new file mode 100644
index 0000000..2eafb48
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.response.PassiveActivityStateResponse;
+
+/**
+ * Interface to get passive monitoring updates.
+ *
+ * @hide
+ */
+oneway interface IPassiveMonitoringCallback {
+ /** Called when a new passive activity state response is available. */
+ void onPassiveActivityState(in PassiveActivityStateResponse response) = 0;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IExerciseInfoCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IExerciseInfoCallback.aidl
new file mode 100644
index 0000000..cfc56ca
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IExerciseInfoCallback.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal;
+
+import androidx.health.services.client.impl.response.ExerciseInfoResponse;
+
+/**
+ * Callback for an operation that returns an ExerciseInfo on successful
+ * completion.
+ *
+ * @hide
+ */
+oneway interface IExerciseInfoCallback {
+ /**
+ * Method invoked when the operation is a success and exercise info is
+ * successfully obtained.
+ */
+ void onExerciseInfo(in ExerciseInfoResponse response) = 0;
+
+ /**
+ * Method invoked when the operation is a failure.
+ */
+ void onFailure(String message) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IStatusCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IStatusCallback.aidl
new file mode 100644
index 0000000..2a58b96
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IStatusCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal;
+
+/**
+ * Generic callback for an operation that returns a status on completion.
+ *
+ * @hide
+ */
+oneway interface IStatusCallback {
+ /**
+ * Method invoked when the operation is a success.
+ */
+ void onSuccess() = 0;
+
+ /**
+ * Method invoked when the operation is a failure.
+ */
+ void onFailure(String msg) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.aidl
new file mode 100644
index 0000000..3f7984c
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable AutoPauseAndResumeConfigRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.aidl
new file mode 100644
index 0000000..7493c72
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable BackgroundRegistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.aidl
new file mode 100644
index 0000000..8264c63
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable BackgroundUnregistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/CapabilitiesRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/CapabilitiesRequest.aidl
new file mode 100644
index 0000000..b71b20a
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/CapabilitiesRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable CapabilitiesRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/EventRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/EventRequest.aidl
new file mode 100644
index 0000000..1b59985
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/EventRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable EventRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/ExerciseGoalRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/ExerciseGoalRequest.aidl
new file mode 100644
index 0000000..d8a9fbb
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/ExerciseGoalRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable ExerciseGoalRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureRegistrationRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureRegistrationRequest.aidl
new file mode 100644
index 0000000..96acaec
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureRegistrationRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable MeasureRegistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.aidl
new file mode 100644
index 0000000..fb2f060
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable MeasureUnregistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/StartExerciseRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/StartExerciseRequest.aidl
new file mode 100644
index 0000000..dd5e594
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/StartExerciseRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable StartExerciseRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseCapabilitiesResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseCapabilitiesResponse.aidl
new file mode 100644
index 0000000..2641359
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseCapabilitiesResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable AutoExerciseCapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseDetectionStateResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseDetectionStateResponse.aidl
new file mode 100644
index 0000000..4b38fe1
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseDetectionStateResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable AutoExerciseDetectionStateResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AvailabilityResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AvailabilityResponse.aidl
new file mode 100644
index 0000000..902c1ff
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AvailabilityResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable AvailabilityResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/CapabilitiesResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/CapabilitiesResponse.aidl
new file mode 100644
index 0000000..c48b0d0
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/CapabilitiesResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable CapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/DataPointsResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/DataPointsResponse.aidl
new file mode 100644
index 0000000..31ab81c
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/DataPointsResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable DataPointsResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseInfoResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseInfoResponse.aidl
new file mode 100644
index 0000000..8d429af
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseInfoResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable ExerciseInfoResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.aidl
new file mode 100644
index 0000000..55dd959
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable ExerciseLapSummaryResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseUpdateResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseUpdateResponse.aidl
new file mode 100644
index 0000000..d10ab6c
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseUpdateResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable ExerciseUpdateResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/HeartRateAlertParamsResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/HeartRateAlertParamsResponse.aidl
new file mode 100644
index 0000000..6464078
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/HeartRateAlertParamsResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable HeartRateAlertParamsResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.aidl
new file mode 100644
index 0000000..d9755f1
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable MeasureCapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveActivityStateResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveActivityStateResponse.aidl
new file mode 100644
index 0000000..5e3b130
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveActivityStateResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable PassiveActivityStateResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.aidl
new file mode 100644
index 0000000..8a5ab5f
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable PassiveMonitoringCapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
new file mode 100644
index 0000000..42a190e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.Capabilities
+import androidx.health.services.client.data.ExerciseConfig
+import androidx.health.services.client.data.ExerciseGoal
+import androidx.health.services.client.data.ExerciseInfo
+import androidx.health.services.client.data.ExerciseUpdate
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/** Client which provides a way to subscribe to the health data of a device during an exercise. */
+public interface ExerciseClient {
+ /**
+ * Starts a new exercise.
+ *
+ * Once started, WHS will begin collecting data associated with the exercise.
+ *
+ * Since WHS only allows a single active exercise at a time, this will terminate any active
+ * exercise currently in progress before starting the new one.
+ *
+ * @return a [ListenableFuture] that completes once the exercise has been started.
+ */
+ public fun startExercise(configuration: ExerciseConfig): ListenableFuture<Void>
+
+ /**
+ * Pauses the current exercise, if it is currently started.
+ *
+ * While the exercise is paused active time and cumulative metrics such as distance will not
+ * accumulate. Instantaneous measurements such as speed and heart rate will continue to update
+ * if requested in the [ExerciseConfig].
+ *
+ * If the exercise remains paused for a long period of time WHS will reduce or suspend access to
+ * sensors and GPS in order to conserve battery. Should this happen, access will automatically
+ * resume when the exercise is resumed.
+ *
+ * If the exercise is already paused then this method has no effect. If the exercise has ended
+ * then the returned future will fail.
+ *
+ * @return a [ListenableFuture] that completes once the exercise has been paused.
+ */
+ public fun pauseExercise(): ListenableFuture<Void>
+
+ /**
+ * Resumes the current exercise, if it is currently paused.
+ *
+ * Once resumed active time and cumulative metrics such as distance will resume accumulating.
+ *
+ * If the exercise has been started but is not currently paused this method has no effect. If
+ * the exercise has ended then the returned future will fail.
+ *
+ * @return a [ListenableFuture] that completes once the exercise has been resumed.
+ */
+ public fun resumeExercise(): ListenableFuture<Void>
+
+ /**
+ * Ends the current exercise, if it has been started. If the exercise has ended then this future
+ * will fail.
+ *
+ * No additional metrics will be produced for the exercise and any on device persisted data
+ * about the exercise will be deleted after the summary has been sent back.
+ */
+ public fun endExercise(): ListenableFuture<Void>
+
+ /**
+ * Ends the current lap, calls [ExerciseStateListener.onLapSummary] with data spanning the
+ * marked lap and starts a new lap. If the exercise supports laps this method can be called at
+ * any point after an exercise has been started and before it has been ended regardless of the
+ * exercise status.
+ *
+ * The metrics in the lap summary will start from either the start time of the exercise or the
+ * last time a lap was marked to the time this method is being called.
+ *
+ * If there's no exercise being tracked or if the exercise does not support laps then this
+ * future will fail.
+ */
+ public fun markLap(): ListenableFuture<Void>
+
+ /** Returns the [ExerciseInfo]. */
+ public val currentExerciseInfo: ListenableFuture<ExerciseInfo>
+
+ /**
+ * Sets the listener for the current [ExerciseUpdate].
+ *
+ * This listener won't be called until an exercise is in progress. It will also only receive
+ * updates from exercises tracked in this app.
+ *
+ * If an exercise is in progress, the [ExerciseUpdateListener] is immediately called with the
+ * associated [ExerciseUpdate], and subsequently whenever the state is updated or an event is
+ * triggered.
+ *
+ * Calls to the listener will be executed on the main application thread. To control where to
+ * execute the listener, see the overload taking an [Executor]. To remove the listener use
+ * [clearUpdateListener].
+ */
+ public fun setUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void>
+
+ /**
+ * Calls to the listener will be executed using the specified [Executor]. To execute the
+ * listener on the main application thread use the overload without the [Executor].
+ */
+ public fun setUpdateListener(
+ listener: ExerciseUpdateListener,
+ executor: Executor
+ ): ListenableFuture<Void>
+
+ /**
+ * Clears the listener set using [setUpdateListener].
+ *
+ * If the listener wasn't set, the returned [ListenableFuture] will fail.
+ */
+ public fun clearUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void>
+
+ /**
+ * Adds an [ExerciseGoal] for an active exercise.
+ *
+ * An [ExerciseGoal] is a one-time goal, such as achieving a target total step count.
+ *
+ * Goals apply to only active exercises owned by the client, and will be invalidated once the
+ * exercise is complete.
+ *
+ * @return a [ListenableFuture] that completes once the exercise goal has been added. This
+ * returned [ListenableFuture] fails if the exercise is not active.
+ */
+ public fun addGoalToActiveExercise(exerciseGoal: ExerciseGoal): ListenableFuture<Void>
+
+ /**
+ * Enables or disables the auto pause/resume for the current exercise.
+ *
+ * @param enabled a boolean to indicate if should be enabled or disabled
+ */
+ public fun overrideAutoPauseAndResumeForActiveExercise(enabled: Boolean): ListenableFuture<Void>
+
+ /** Returns the [Capabilities] of this client for the device. */
+ public val capabilities: ListenableFuture<Capabilities>
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateListener.kt b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateListener.kt
new file mode 100644
index 0000000..d622d43
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateListener.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.ExerciseLapSummary
+import androidx.health.services.client.data.ExerciseState
+import androidx.health.services.client.data.ExerciseUpdate
+
+/** Listener that is called when the state of the current exercise is updated. */
+// TODO(b/179756577): Add onExerciseEnd(ExerciseSummary) method.
+public interface ExerciseUpdateListener {
+ /** Called during an ACTIVE exercise or on any changes in [ExerciseState]. */
+ public fun onExerciseUpdate(update: ExerciseUpdate)
+
+ /** Called during an ACTIVE exercise once a lap has been marked. */
+ public fun onLapSummary(lapSummary: ExerciseLapSummary)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/HealthServices.kt b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServices.kt
new file mode 100644
index 0000000..8fba5b9
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServices.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import android.content.Context
+import androidx.health.services.client.impl.ServiceBackedHealthServicesClient
+
+/** Entry point for all Health Services APIs. */
+public object HealthServices {
+ /** Returns an instance of [HealthServicesClient]. */
+ @JvmStatic
+ public fun getClient(context: Context): HealthServicesClient {
+ return ServiceBackedHealthServicesClient(context)
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/HealthServicesClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServicesClient.kt
new file mode 100644
index 0000000..82c3d41
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServicesClient.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+/**
+ * Client which provides a way to subscribe to the health data of a device, in the background or in
+ * the foreground.
+ */
+public interface HealthServicesClient {
+ /** Returns a [ExerciseClient]. */
+ public val exerciseClient: ExerciseClient
+
+ /** Returns a [PassiveMonitoringClient]. */
+ public val passiveMonitoringClient: PassiveMonitoringClient
+
+ /** Returns a [MeasureClient]. */
+ public val measureClient: MeasureClient
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/MeasureCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureCallback.kt
new file mode 100644
index 0000000..7706b42
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureCallback.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.Availability
+import androidx.health.services.client.data.DataPoint
+import androidx.health.services.client.data.DataType
+
+/** Callback for [MeasureClient.registerCallback]. */
+public interface MeasureCallback {
+ /** Called when the availability of a [DataType] changes. */
+ public fun onAvailabilityChanged(dataType: DataType, availability: Availability)
+
+ /** Called when new data is available. Data can be batched in a list of [DataPoint]. */
+ public fun onData(data: List<@JvmSuppressWildcards DataPoint>)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/MeasureClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureClient.kt
new file mode 100644
index 0000000..782ae0d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureClient.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.MeasureCapabilities
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/**
+ * Client which provides a way to make measurements of health data on a device.
+ *
+ * This is optimized for apps to register live callbacks on data which may be sampled at a faster
+ * rate; this is not meant to be used for long-lived subscriptions to data (for this, consider using
+ * [ExerciseClient] or [PassiveMonitoringClient] depending on your use case).
+ *
+ * Existing subscriptions made with the [PassiveMonitoringClient] are also expected to get the data
+ * generated by this client.
+ */
+public interface MeasureClient {
+ /**
+ * Registers the app for live measurement of the specified [DataType].
+ *
+ * The callback will be called on the main application thread. To move calls to an alternative
+ * thread use [registerCallback].
+ *
+ * Even if data is registered for live capture, it can still be sent out in batches depending on
+ * the application processor state.
+ *
+ * Registering a [DataType] for live measurement capture is expected to increase the sample rate
+ * on the associated sensor(s); this is typically used for one-off measurements. Do not use this
+ * method for background capture or workout tracking.
+ *
+ * The callback will continue to be called until the app is killed or [unregisterCallback] is
+ * called.
+ *
+ * If the same [callback] is already registered for the given [DataType], this operation is a
+ * no-op.
+ */
+ public fun registerCallback(
+ dataType: DataType,
+ callback: MeasureCallback
+ ): ListenableFuture<Void>
+
+ /** Same as [registerCallback], except the [callback] is called on the given [Executor]. */
+ public fun registerCallback(
+ dataType: DataType,
+ callback: MeasureCallback,
+ executor: Executor
+ ): ListenableFuture<Void>
+
+ /** Unregisters the given [MeasureCallback] for updates of the given [DataType]. */
+ public fun unregisterCallback(
+ dataType: DataType,
+ callback: MeasureCallback
+ ): ListenableFuture<Void>
+
+ /** Returns the [MeasureCapabilities] of this client for the device. */
+ public val capabilities: ListenableFuture<MeasureCapabilities>
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringCallback.kt
new file mode 100644
index 0000000..2463188
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringCallback.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.PassiveActivityState
+
+/** A callback for receiving passive monitoring updates. */
+public interface PassiveMonitoringCallback {
+ /** Called when new state is available. */
+ public fun onPassiveActivityState(state: PassiveActivityState)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringClient.kt
new file mode 100644
index 0000000..23793f7
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringClient.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import android.app.PendingIntent
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.PassiveMonitoringCapabilities
+import androidx.health.services.client.data.event.Event
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Client which provides a means to passively monitor data without requiring an ongoing workout.
+ *
+ * The lifetimes of registrations made through this client are independent of the lifetime of the
+ * subscribing app. These registrations are therefore suitable for notifying of ongoing measurements
+ * or triggered events, regardless of whether or not the subscribing app is currently running, in
+ * the foreground or engaged in a workout.
+ */
+public interface PassiveMonitoringClient {
+ /**
+ * Subscribes for updates on a set of data types to be periodically delivered to the app.
+ *
+ * Data will be batched. Higher frequency updates are available through [ExerciseClient] or
+ * [MeasureClient].
+ *
+ * The provided [PendingIntent] will be invoked periodically with the collected data.
+ *
+ * Subscribing apps are responsible for ensuring they can receive the [callbackIntent] by e.g.
+ * declaring a suitable [android.content.BroadcastReceiver] in their app manifest.
+ *
+ * This registration is unique per subscribing app. Subsequent registrations will replace the
+ * previous registration, if one had been made.
+ */
+ public fun registerDataCallback(
+ dataTypes: Set<@JvmSuppressWildcards DataType>,
+ callbackIntent: PendingIntent
+ ): ListenableFuture<Void>
+
+ /**
+ * Subscribes an intent callback (the same way as [PassiveMonitoringClient.registerDataCallback]
+ * ) and a [PassiveMonitoringCallback] for updates on a set of data types periodically.
+ *
+ * The provided [callback] will take priority in receiving updates as long the app is alive and
+ * the callback can be successfully notified. Otherwise, updates will be delivered to the
+ * [callbackIntent].
+ *
+ * This registration is unique per subscribing app. Subsequent registrations will replace the
+ * previous registration, if one had been made.
+ */
+ public fun registerDataCallback(
+ dataTypes: Set<@JvmSuppressWildcards DataType>,
+ callbackIntent: PendingIntent,
+ callback: PassiveMonitoringCallback
+ ): ListenableFuture<Void>
+
+ /**
+ * Unregisters the subscription made by [PassiveMonitoringClient.registerDataCallback].
+ *
+ * The associated [PendingIntent] will be called one last time with any remaining buffered data.
+ */
+ public fun unregisterDataCallback(): ListenableFuture<Void>
+
+ /**
+ * Registers for notification of the [event] being triggered.
+ *
+ * The provided [PendingIntent] will be sent whenever [event] is triggered.
+ *
+ * Subscribing apps are responsible for ensuring they can receive the [callbackIntent] by e.g.
+ * declaring a suitable [android.content.BroadcastReceiver] in their app manifest.
+ *
+ * Registration of multiple events is possible except where there already exists an event that
+ * is equal, as per the definition of [Event.equals], in which case the existing registration
+ * for that event will be replaced.
+ */
+ public fun registerEventCallback(
+ event: Event,
+ callbackIntent: PendingIntent
+ ): ListenableFuture<Void>
+
+ /** Unregisters the subscription for the given [Event]. */
+ public fun unregisterEventCallback(event: Event): ListenableFuture<Void>
+
+ /** Returns the [PassiveMonitoringCapabilities] of this client for the device. */
+ public val capabilities: ListenableFuture<PassiveMonitoringCapabilities>
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt
new file mode 100644
index 0000000..6463d58
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** Defines an achieved [ExerciseGoal]. */
+public data class AchievedExerciseGoal(
+ /** [ExerciseGoal] that has been achieved. */
+ // TODO(b/181235444): do we need to deliver the DataPoint to the user again here, given
+ // that they will have already gotten it in the ExerciseState? And, what other data do we need
+ // to
+ // tag along an achieved ExerciseGoal?
+ val goal: ExerciseGoal,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(goal, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<AchievedExerciseGoal> =
+ object : Parcelable.Creator<AchievedExerciseGoal> {
+ override fun createFromParcel(source: Parcel): AchievedExerciseGoal? {
+ val goal =
+ source.readParcelable<ExerciseGoal>(ExerciseGoal::class.java.classLoader)
+ ?: return null
+ return AchievedExerciseGoal(goal)
+ }
+
+ override fun newArray(size: Int): Array<AchievedExerciseGoal?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt
new file mode 100644
index 0000000..710346f
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.app.PendingIntent
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import java.util.Objects
+
+/** Configs for the automatic exercise detection. */
+public data class AutoExerciseConfig
+@JvmOverloads
+constructor(
+ /**
+ * Set of [ExerciseType] for WHS to detect from. If left empty, all possible types are used].
+ */
+ val exercisesToDetect: Set<ExerciseType> = emptySet(),
+ /**
+ * A [PendingIntent] that WHS will use to post state / data changes to the app if the app has no
+ * active [androidx.health.services.client.ExerciseUpdateListener] registered at the moment. The
+ * launchIntent will be fed with the updated exercise states collected by the automatic exercise
+ * detection for the app to consume as soon as it re-starts.
+ */
+ val launchIntent: PendingIntent? = null,
+
+ /** See [ExerciseConfig.exerciseParams]. */
+ val exerciseParams: Bundle = Bundle(),
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(exercisesToDetect.size)
+ dest.writeIntArray(exercisesToDetect.map { it.id }.toIntArray())
+ dest.writeParcelable(launchIntent, flags)
+ dest.writeBundle(exerciseParams)
+ }
+
+ // TODO(b/180612514): Bundle doesn't have equals, so we need to override the data class default.
+ override fun equals(other: Any?): Boolean {
+ if (other === this) {
+ return true
+ }
+ if (other is AutoExerciseConfig) {
+ return (
+ launchIntent == other.launchIntent &&
+ BundlesUtil.equals(exerciseParams, other.exerciseParams) &&
+ exercisesToDetect == other.exercisesToDetect
+ )
+ }
+ return false
+ }
+
+ // TODO(b/180612514): Bundle doesn't have hashCode, so we need to override the data class
+ // default.
+ override fun hashCode(): Int {
+ return Objects.hash(launchIntent, BundlesUtil.hashCode(exerciseParams), exercisesToDetect)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<AutoExerciseConfig> =
+ object : Parcelable.Creator<AutoExerciseConfig> {
+ override fun createFromParcel(source: Parcel): AutoExerciseConfig? {
+ val exercisesIntArray = IntArray(source.readInt())
+ source.readIntArray(exercisesIntArray)
+ val launchIntent =
+ source.readParcelable<PendingIntent>(PendingIntent::class.java.classLoader)
+ val exerciseParams =
+ source.readBundle(AutoExerciseConfig::class.java.classLoader) ?: return null
+
+ return AutoExerciseConfig(
+ exercisesIntArray.map { ExerciseType.fromId(it) }.toSet(),
+ launchIntent,
+ exerciseParams
+ )
+ }
+
+ override fun newArray(size: Int): Array<AutoExerciseConfig?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
new file mode 100644
index 0000000..71a56d8
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Availability of a [DataType]. */
+public enum class Availability(public val id: Int) {
+ UNKNOWN(0),
+ AVAILABLE(1),
+ ACQUIRING(2),
+ UNAVAILABLE(3);
+
+ public companion object {
+ @JvmStatic public fun fromId(id: Int): Availability? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/BundlesUtil.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/BundlesUtil.kt
new file mode 100644
index 0000000..f20e608
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/BundlesUtil.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Bundle
+import java.util.Objects
+
+/** Utility methods for working with Bundles. */
+internal object BundlesUtil {
+
+ /**
+ * Compares two Bundles recursively and returns `true` if they are equal.
+ *
+ * Equality in this case means that both bundles contain the same set of keys and their
+ * corresponding values are all equal (using the [Object.equals] method).
+ */
+ @JvmStatic
+ fun equals(a: Bundle?, b: Bundle?): Boolean {
+ if (a == b) {
+ return true
+ } else if (a == null || b == null) {
+ return false
+ } else if (a.size() != b.size()) {
+ return false
+ }
+ for (key in a.keySet()) {
+ val aValue = a[key]
+ val bValue = b[key]
+ if (aValue is Bundle && bValue is Bundle) {
+ if (!equals(aValue as Bundle?, bValue as Bundle?)) {
+ return false
+ }
+ } else if (aValue == null) {
+ if (bValue != null || !b.containsKey(key)) {
+ return false
+ }
+ } else if (!Objects.deepEquals(aValue, bValue)) {
+ return false
+ }
+ }
+ return true
+ }
+
+ /** Calculates a hashCode for a Bundle, examining all keys and values. */
+ @JvmStatic
+ fun hashCode(b: Bundle?): Int {
+ if (b == null) {
+ return 0
+ }
+ val keySet = b.keySet()
+ val hashCodes = IntArray(keySet.size * 2)
+ var i = 0
+ for (key in keySet) {
+ hashCodes[i++] = Objects.hashCode(key)
+ val value = b[key]
+ val valueHashCode: Int =
+ if (value is Bundle) {
+ hashCode(value as Bundle?)
+ } else {
+ Objects.hashCode(value)
+ }
+ hashCodes[i++] = valueHashCode
+ }
+ return hashCodes.contentHashCode()
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/Capabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/Capabilities.kt
new file mode 100644
index 0000000..3c19682
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Capabilities.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseType.Companion.fromId
+
+/** A place holder class that represents the capabilities of WHS client on the device. */
+public data class Capabilities(
+ /** Mapping for each supported [ExerciseType] to its [ExerciseCapabilities] on this device. */
+ val exerciseTypeToExerciseCapabilities: Map<ExerciseType, ExerciseCapabilities>,
+) : Parcelable {
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ writeExerciseTypeToExerciseCapabilities(dest, flags)
+ }
+
+ /** Set of supported [ExerciseType] s on this device. */
+ public val supportedExerciseTypes: Set<ExerciseType>
+ get() = exerciseTypeToExerciseCapabilities.keys
+
+ /**
+ * Returns the supported [ExerciseCapabilities] for a requested [ExerciseType].
+ *
+ * @throws IllegalArgumentException if the supplied [exercise] isn't supported
+ */
+ public fun getExerciseCapabilities(exercise: ExerciseType): ExerciseCapabilities {
+ return exerciseTypeToExerciseCapabilities[exercise]
+ ?: throw IllegalArgumentException(
+ String.format("%s exercise type is not supported", exercise)
+ )
+ }
+
+ /** Returns the set of [ExerciseType] s that support auto pause and resume on this device. */
+ public val autoPauseAndResumeEnabledExercises: Set<ExerciseType>
+ get() {
+ return exerciseTypeToExerciseCapabilities
+ .entries
+ .filter { it.value.supportsAutoPauseAndResume }
+ .map { it.key }
+ .toSet()
+ }
+
+ private fun writeExerciseTypeToExerciseCapabilities(dest: Parcel, flags: Int) {
+ dest.writeInt(exerciseTypeToExerciseCapabilities.size)
+ for ((key1, value) in exerciseTypeToExerciseCapabilities) {
+ val key = key1.id
+ dest.writeInt(key)
+ dest.writeParcelable(value, flags)
+ }
+ }
+
+ public companion object {
+
+ @JvmField
+ public val CREATOR: Parcelable.Creator<Capabilities> =
+ object : Parcelable.Creator<Capabilities> {
+ override fun createFromParcel(parcel: Parcel): Capabilities {
+ val exerciseTypeToExerciseCapabilitiesFromParcel =
+ getExerciseToExerciseCapabilityMap(parcel)
+ return Capabilities(
+ exerciseTypeToExerciseCapabilitiesFromParcel,
+ )
+ }
+
+ override fun newArray(size: Int): Array<Capabilities?> = arrayOfNulls(size)
+ }
+
+ private fun readDataTypeSet(parcel: Parcel): Set<DataType> {
+ return parcel.createTypedArray(DataType.CREATOR)!!.toSet()
+ }
+
+ private fun writeDataTypeSet(out: Parcel, flags: Int, dataTypes: Set<DataType>) {
+ out.writeTypedArray(dataTypes.toTypedArray(), flags)
+ }
+
+ private fun getExerciseToExerciseCapabilityMap(
+ parcel: Parcel
+ ): Map<ExerciseType, ExerciseCapabilities> {
+ val map = HashMap<ExerciseType, ExerciseCapabilities>()
+ val mapSize = parcel.readInt()
+ for (i in 0 until mapSize) {
+ val key = fromId(parcel.readInt())
+ val value =
+ parcel.readParcelable<Parcelable>(
+ ExerciseCapabilities::class.java.classLoader
+ ) as
+ ExerciseCapabilities
+ map[key] = value
+ }
+ return map
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt
new file mode 100644
index 0000000..37e2288
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** For determining when a threshold has been met or exceeded in a [MetricCondition]. */
+public enum class ComparisonType(public val id: Int) {
+ // TODO(b/175064823): investigate adding EQUAL comparison type
+ GREATER_THAN(1),
+ GREATER_THAN_OR_EQUAL(2),
+ LESS_THAN(3),
+ LESS_THAN_OR_EQUAL(4);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): ComparisonType? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoint.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoint.kt
new file mode 100644
index 0000000..5e52737
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoint.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+import java.util.Objects
+
+/**
+ * A data point containing a [value] of type [dataType] from either a single point in time:
+ * [DataType.TimeType.SAMPLE], or a range in time: [DataType.TimeType.INTERVAL].
+ */
+@Suppress("DataClassPrivateConstructor")
+public data class DataPoint
+internal constructor(
+ val dataType: DataType,
+ val value: Value,
+
+ /**
+ * Elapsed start time of this [DataPoint].
+ *
+ * This represents the time at which this [DataPoint] originated, as a [Duration] since boot
+ * time. This is not exposed as a timestamp as the clock may drift between when the data is
+ * generated and when it is read out. Use [getStartInstant] to get the start time of this
+ * [DataPoint] as an [Instant].
+ */
+ val startDurationFromBoot: Duration,
+
+ /**
+ * Elapsed end time of this [DataPoint].
+ *
+ * This represents the time at which this [DataPoint] ends, as a [Duration] since boot time.
+ * This is not exposed as a timestamp as the clock may drift between when the data is generated
+ * and when it is read out. Use [getStartInstant] to get the start time of this [DataPoint] as
+ * an [Instant].
+ *
+ * For instantaneous data points, this is equal to [startDurationFromBoot].
+ */
+ val endDurationFromBoot: Duration = startDurationFromBoot,
+
+ /** Returns any provided metadata of this [DataPoint]. */
+ val metadata: Bundle = Bundle(),
+) : Parcelable {
+
+ init {
+ require(dataType.format == value.format) {
+ "DataType and Value format must match, but got ${dataType.format} and ${value.format}"
+ }
+ }
+
+ /**
+ * Returns the start [Instant] of this [DataPoint], knowing the time at which the system booted.
+ *
+ * @param bootInstant the [Instant] at which the system booted, this can be computed by
+ * `Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime()) `
+ */
+ public fun getStartInstant(bootInstant: Instant): Instant {
+ return bootInstant.plus(startDurationFromBoot)
+ }
+
+ /**
+ * Returns the end [Instant] of this [DataPoint], knowing the time at which the system booted.
+ *
+ * @param bootInstant the [Instant] at which the system booted, this can be computed by
+ * `Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())`
+ */
+ public fun getEndInstant(bootInstant: Instant): Instant {
+ return bootInstant.plus(endDurationFromBoot)
+ }
+
+ // TODO(b/180612514): Bundle doesn't have equals, so we need to override the data class default.
+ override fun equals(other: Any?): Boolean {
+ if (other === this) {
+ return true
+ }
+ if (other is DataPoint) {
+ return dataType == other.dataType &&
+ value == other.value &&
+ startDurationFromBoot == other.startDurationFromBoot &&
+ endDurationFromBoot == other.endDurationFromBoot &&
+ BundlesUtil.equals(metadata, other.metadata)
+ }
+ return false
+ }
+
+ // TODO(b/180612514): Bundle doesn't have hashCode, so we need to override the data class
+ // default.
+ override fun hashCode(): Int {
+ return Objects.hash(
+ dataType,
+ value,
+ startDurationFromBoot,
+ endDurationFromBoot,
+ BundlesUtil.hashCode(metadata)
+ )
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeParcelable(value, flags)
+ dest.writeLong(startDurationFromBoot.toNanos())
+ dest.writeLong(endDurationFromBoot.toNanos())
+ dest.writeBundle(metadata)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<DataPoint> =
+ object : Parcelable.Creator<DataPoint> {
+ override fun createFromParcel(parcel: Parcel): DataPoint? {
+ val dataType: DataType =
+ parcel.readParcelable(DataType::class.java.classLoader) ?: return null
+ val value: Value =
+ parcel.readParcelable(Value::class.java.classLoader) ?: return null
+ val startDurationFromBoot = Duration.ofNanos(parcel.readLong())
+ val endDurationFromBoot = Duration.ofNanos(parcel.readLong())
+ val metadata: Bundle? = parcel.readBundle(Bundle::class.java.classLoader)
+
+ return when (dataType.timeType) {
+ DataType.TimeType.INTERVAL ->
+ createInterval(
+ dataType,
+ value,
+ startDurationFromBoot,
+ endDurationFromBoot,
+ metadata ?: Bundle()
+ )
+ DataType.TimeType.SAMPLE -> {
+ require(endDurationFromBoot.compareTo(startDurationFromBoot) == 0) {
+ "DataType [$dataType] has SAMPLE type, but" +
+ " start[$startDurationFromBoot]/end[$endDurationFromBoot]" +
+ " duration from boot are not the same"
+ }
+ createSample(
+ dataType,
+ value,
+ startDurationFromBoot,
+ metadata ?: Bundle()
+ )
+ }
+ }
+ }
+
+ override fun newArray(size: Int): Array<DataPoint?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /**
+ * Returns a [DataPoint] representing the [value] of type [dataType] from
+ * [startDurationFromBoot] to [endDurationFromBoot].
+ *
+ * @throws IllegalArgumentException if the [DataType.TimeType] of the associated [DataType]
+ * is not [DataType.TimeType.INTERVAL], or if data is malformed
+ */
+ @JvmStatic
+ @JvmOverloads
+ public fun createInterval(
+ dataType: DataType,
+ value: Value,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration,
+ metadata: Bundle = Bundle()
+ ): DataPoint {
+ require(DataType.TimeType.INTERVAL == dataType.timeType) {
+ "DataType $dataType must be of interval type to be created with an interval"
+ }
+
+ require(endDurationFromBoot >= startDurationFromBoot) {
+ "End timestamp mustn't be earlier than start timestamp, but got" +
+ " $startDurationFromBoot and $endDurationFromBoot"
+ }
+
+ return DataPoint(dataType, value, startDurationFromBoot, endDurationFromBoot, metadata)
+ }
+
+ /**
+ * Returns a [DataPoint] representing the [value] of type [dataType] at [durationFromBoot].
+ *
+ * @throws IllegalArgumentException if the [DataType.TimeType] of the associated [DataType]
+ * is not [DataType.TimeType.SAMPLE], or if data is malformed
+ */
+ @JvmStatic
+ @JvmOverloads
+ public fun createSample(
+ dataType: DataType,
+ value: Value,
+ durationFromBoot: Duration,
+ metadata: Bundle = Bundle()
+ ): DataPoint {
+ require(DataType.TimeType.SAMPLE == dataType.timeType) {
+ "DataType $dataType must be of sample type to be created with a single timestamp"
+ }
+
+ return DataPoint(
+ dataType,
+ value,
+ durationFromBoot,
+ endDurationFromBoot = durationFromBoot,
+ metadata
+ )
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoints.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoints.kt
new file mode 100644
index 0000000..1168035
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoints.kt
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.content.Intent
+import androidx.annotation.Keep
+import java.time.Duration
+import java.util.ArrayList
+
+/** Helper class to facilitate working with [DataPoint] s. */
+// TODO(b/177504986): Remove all @Keep annotations once we figure out why this class gets stripped
+// away by proguard.
+@Keep
+public object DataPoints {
+ /**
+ * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
+ * this index represents the latitude.
+ */
+ public const val LOCATION_DATA_POINT_LATITUDE_INDEX: Int = 0
+
+ /**
+ * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
+ * this index represents the longitude.
+ */
+ public const val LOCATION_DATA_POINT_LONGITUDE_INDEX: Int = 1
+
+ /**
+ * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
+ * this index represents the altitude. This is an optional index and there is no guarantee that
+ * this index will be present.
+ */
+ public const val LOCATION_DATA_POINT_ALTITUDE_INDEX: Int = 2
+
+ /** Name of intent extra containing the data points set on pending intent. */
+ private const val EXTRA_DATA_POINTS: String = "whs.data_points_list"
+
+ /** Name of intent extra containing whether permissions are granted or not. */
+ private const val EXTRA_PERMISSIONS_GRANTED: String = "whs.data_points_has_permissions"
+
+ /** Retrieves the [DataPoint] s that are contained in the given [Intent], if any. */
+ @JvmStatic
+ @Keep
+ public fun getDataPoints(intent: Intent): List<DataPoint> =
+ intent.getParcelableArrayListExtra(EXTRA_DATA_POINTS) ?: listOf()
+
+ /** Puts the given [DataPoint] s in the given [Intent]. */
+ @JvmStatic
+ public fun putDataPoints(intent: Intent, dataPoints: Collection<DataPoint>) {
+ val copy = ArrayList(dataPoints)
+ intent.putParcelableArrayListExtra(EXTRA_DATA_POINTS, copy)
+ }
+
+ /** Sets whether [DataPoint] permissions are `granted` in the given [Intent]. */
+ @JvmStatic
+ public fun putPermissionsGranted(intent: Intent, granted: Boolean) {
+ intent.putExtra(EXTRA_PERMISSIONS_GRANTED, granted)
+ }
+
+ /** Retrieves whether permissions are granted in this [Intent]. */
+ @JvmStatic
+ public fun getPermissionsGranted(intent: Intent): Boolean =
+ intent.getBooleanExtra(EXTRA_PERMISSIONS_GRANTED, true)
+
+ /** Creates a new [DataPoint] of type [DataType.STEPS] with the given `steps`. */
+ @JvmStatic
+ public fun steps(
+ steps: Long,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.STEPS,
+ Value.ofLong(steps),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.STEPS_PER_MINUTE] with the given
+ * `stepsPerMinute`.
+ */
+ @JvmStatic
+ public fun stepsPerMinute(stepsPerMinute: Long, startDurationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(
+ DataType.STEPS_PER_MINUTE,
+ Value.ofLong(stepsPerMinute),
+ startDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.DISTANCE] with the given `meters`. */
+ @JvmStatic
+ public fun distance(
+ meters: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.DISTANCE,
+ Value.ofDouble(meters),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.ELEVATION] with the given `meters`. */
+ @JvmStatic
+ public fun elevation(
+ meters: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.ELEVATION,
+ Value.ofDouble(meters),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.ALTITUDE] with the given `meters`. */
+ @JvmStatic
+ public fun altitude(meters: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.ALTITUDE, Value.ofDouble(meters), durationFromBoot)
+
+ /** Creates a new [DataPoint] of type [DataType.FLOORS] with the given `floors`. */
+ @JvmStatic
+ public fun floors(
+ floors: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.FLOORS,
+ Value.ofDouble(floors),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.TOTAL_CALORIES] with the given `kcalories`. */
+ @JvmStatic
+ public fun calories(
+ kcalories: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.TOTAL_CALORIES,
+ Value.ofDouble(kcalories),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.SWIMMING_STROKES] with the given `kcalories`. */
+ @JvmStatic
+ public fun swimmingStrokes(
+ strokes: Long,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.SWIMMING_STROKES,
+ Value.ofLong(strokes),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.LOCATION] with the given `latitude` and
+ * `longitude`.
+ */
+ @JvmStatic
+ public fun location(
+ latitude: Double,
+ longitude: Double,
+ durationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createSample(
+ DataType.LOCATION,
+ Value.ofDoubleArray(latitude, longitude),
+ durationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.LOCATION] with the given `latitude`, `longitude`
+ * and `altitude`.
+ */
+ @JvmStatic
+ public fun location(
+ latitude: Double,
+ longitude: Double,
+ altitude: Double,
+ durationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createSample(
+ DataType.LOCATION,
+ Value.ofDoubleArray(latitude, longitude, altitude),
+ durationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.SPEED] with the given `metersPerSecond`. */
+ @JvmStatic
+ public fun speed(metersPerSecond: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.SPEED, Value.ofDouble(metersPerSecond), durationFromBoot)
+
+ /** Creates a new [DataPoint] of type [DataType.PACE] with the given `millisPerKm`. */
+ @JvmStatic
+ public fun pace(millisPerKm: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.PACE, Value.ofDouble(millisPerKm), durationFromBoot)
+
+ /** Creates a new [DataPoint] of type [DataType.HEART_RATE_BPM] with the given `bpm`. */
+ @JvmStatic
+ public fun heartRate(bpm: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.HEART_RATE_BPM, Value.ofDouble(bpm), durationFromBoot)
+
+ /** Creates a new [DataPoint] of type [DataType.SPO2] with the given `percent`. */
+ @JvmStatic
+ public fun spo2(percent: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.SPO2, Value.ofDouble(percent), durationFromBoot)
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.AGGREGATE_DISTANCE] with the given `distance`.
+ */
+ @JvmStatic
+ public fun aggregateDistance(
+ distance: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.AGGREGATE_DISTANCE,
+ Value.ofDouble(distance),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.AGGREGATE_STEP_COUNT] with the given `steps`. */
+ @JvmStatic
+ public fun aggregateSteps(
+ steps: Long,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.AGGREGATE_STEP_COUNT,
+ Value.ofLong(steps),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.AGGREGATE_CALORIES_EXPENDED] with the given
+ * `kcalories`.
+ */
+ @JvmStatic
+ public fun aggregateCalories(
+ kcalories: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.AGGREGATE_CALORIES_EXPENDED,
+ Value.ofDouble(kcalories),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.AGGREGATE_SWIMMING_STROKE_COUNT] with the given
+ * `swimmingStrokes`.
+ */
+ @JvmStatic
+ public fun aggregateSwimmingStrokes(
+ swimmingStrokes: Long,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.AGGREGATE_SWIMMING_STROKE_COUNT,
+ Value.ofLong(swimmingStrokes),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.AVERAGE_PACE] with the given `millisPerKm`. */
+ @JvmStatic
+ public fun averagePace(millisPerKm: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.AVERAGE_PACE, Value.ofDouble(millisPerKm), durationFromBoot)
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.AVERAGE_SPEED] with the given `metersPerSecond`.
+ */
+ @JvmStatic
+ public fun averageSpeed(metersPerSecond: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(
+ DataType.AVERAGE_SPEED,
+ Value.ofDouble(metersPerSecond),
+ durationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.MAX_SPEED] with the given `metersPerSecond`. */
+ @JvmStatic
+ public fun maxSpeed(metersPerSecond: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(
+ DataType.MAX_SPEED,
+ Value.ofDouble(metersPerSecond),
+ durationFromBoot
+ )
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
new file mode 100644
index 0000000..d3a0ac6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A data type is a representation of health data managed by Health Services.
+ *
+ * A [DataType] specifies the format of the values inside a [DataPoint]. WHS defines data types for
+ * instantaneous observations [TimeType.SAMPLE](e.g. heart rate) and data types for change between
+ * readings [TimeType.INTERVAL](e.g. distance).
+ *
+ * Note: the data type defines only the representation and format of the data, and not how it's
+ * being collected, the sensor being used, or the parameters of the collection.
+ */
+public data class DataType(
+ /** Returns the name of this [DataType], e.g. `"Steps"`. */
+ val name: String,
+ /** Returns the [TimeType] of this [DataType]. */
+ val timeType: TimeType,
+ /** Returns the expected format for a [Value] of this [DataType]. */
+ val format: Int,
+) : Parcelable {
+
+ /**
+ * Whether the `DataType` corresponds to a measurement spanning an interval, or a sample at a
+ * single point in time.
+ */
+ public enum class TimeType {
+ INTERVAL,
+ SAMPLE
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ with(dest) {
+ writeString(name)
+ writeString(timeType.name)
+ writeInt(format)
+ }
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<DataType> =
+ object : Parcelable.Creator<DataType> {
+ override fun createFromParcel(parcel: Parcel): DataType? {
+ return DataType(
+ parcel.readString() ?: return null,
+ TimeType.valueOf(parcel.readString() ?: return null),
+ parcel.readInt()
+ )
+ }
+
+ override fun newArray(size: Int): Array<DataType?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /** Current altitude expressed in meters in `double` format. */
+ @JvmField
+ public val ALTITUDE: DataType = DataType("Altitude", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** A distance delta between each reading expressed in meters in `double` format. */
+ @JvmField
+ public val DISTANCE: DataType = DataType("Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * A duration delta during an exercise over which the user was traveling down a decline,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val DECLINE_TIME: DataType =
+ DataType("Decline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * A distance delta traveled over declining ground between each reading expressed in meters
+ * in `double` format.
+ */
+ @JvmField
+ public val DECLINE_DISTANCE: DataType =
+ DataType("Decline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * A duration delta during an exercise over which the user was traveling across flat ground,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val FLAT_TIME: DataType = DataType("Flat Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * A distance delta traveled over flat ground between each reading expressed in meters in
+ * `double` format.
+ */
+ @JvmField
+ public val FLAT_DISTANCE: DataType =
+ DataType("Flat Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * A duration delta during an exercise over which the user was traveling up an incline,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val INCLINE_TIME: DataType =
+ DataType("Incline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * A distance delta traveled over inclining ground between each reading expressed in meters
+ * in `double` format.
+ */
+ @JvmField
+ public val INCLINE_DISTANCE: DataType =
+ DataType("Incline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** An elevation delta between each reading expressed in meters in `double` format. */
+ @JvmField
+ public val ELEVATION: DataType =
+ DataType("Elevation", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** Absolute elevation between each reading expressed in meters in `double` format. */
+ @JvmField
+ public val ABSOLUTE_ELEVATION: DataType =
+ DataType("Absolute Elevation", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** Number of floors climbed between each reading in `double` format */
+ @JvmField
+ public val FLOORS: DataType = DataType("Floors", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** Current heart rate, in beats per minute in `double` format. */
+ @JvmField
+ public val HEART_RATE_BPM: DataType =
+ DataType("HeartRate", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /**
+ * Current latitude, longitude and optionally, altitude in `double[]` format. Latitude at
+ * index [DataPoints.LOCATION_DATA_POINT_LATITUDE_INDEX], longitude at index
+ * [DataPoints.LOCATION_DATA_POINT_LONGITUDE_INDEX] and if available, altitude at index
+ * [DataPoints.LOCATION_DATA_POINT_ALTITUDE_INDEX]
+ */
+ @JvmField
+ public val LOCATION: DataType =
+ DataType("Location", TimeType.SAMPLE, Value.FORMAT_DOUBLE_ARRAY)
+
+ /** Current speed over time. In meters/second in `double` format. */
+ @JvmField
+ public val SPEED: DataType = DataType("Speed", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** Percentage of oxygen in the blood in `double` format. Valid range `0f` - `100f`. */
+ @JvmField public val SPO2: DataType = DataType("SpO2", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** Rate of oxygen consumption in `double` format. Valid range `0f` - `100f`. */
+ @JvmField public val VO2: DataType = DataType("VO2", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /**
+ * Maximum rate of oxygen consumption measured during incremental exercise in `double`
+ * format. Valid range `0f` - `100f`.
+ */
+ @JvmField
+ public val VO2_MAX: DataType = DataType("VO2 Max", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** Delta of steps between each reading in `long` format. */
+ @JvmField
+ public val STEPS: DataType = DataType("Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** Delta of walking steps between each reading in `long` format. */
+ @JvmField
+ public val WALKING_STEPS: DataType =
+ DataType("Walking Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** Delta of running steps between each reading in `long` format. */
+ @JvmField
+ public val RUNNING_STEPS: DataType =
+ DataType("Running Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** Current step rate in steps/minute in `long` format. */
+ @JvmField
+ public val STEPS_PER_MINUTE: DataType =
+ DataType("Step per minute", TimeType.SAMPLE, Value.FORMAT_LONG)
+
+ /** Delta of strokes between each reading of swimming strokes in `long` format. */
+ @JvmField
+ public val SWIMMING_STROKES: DataType =
+ DataType("Swimming Strokes", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * Delta of total calories (including basal rate and activity) between each reading in
+ * `double` format.
+ */
+ @JvmField
+ public val TOTAL_CALORIES: DataType =
+ DataType("Calories", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** Current pace. In millisec/km in `double` format. */
+ @JvmField public val PACE: DataType = DataType("Pace", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The aggregate distance over a period of time expressed in meters in `double` format. */
+ @JvmField
+ public val AGGREGATE_DISTANCE: DataType =
+ DataType("Aggregate Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * The aggregate flat distance over a period of time expressed in meters in `double` format.
+ */
+ @JvmField
+ public val AGGREGATE_FLAT_DISTANCE: DataType =
+ DataType("Aggregate Flat Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * The aggregate incline distance over a period of time expressed in meters in `double`
+ * format.
+ */
+ @JvmField
+ public val AGGREGATE_INCLINE_DISTANCE: DataType =
+ DataType("Aggregate Incline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * The aggregate incline distance over a period of time expressed in meters in `double`
+ * format.
+ */
+ @JvmField
+ public val AGGREGATE_DECLINE_DISTANCE: DataType =
+ DataType("Aggregate Decline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * The aggregate duration for an exercise when the user was traveling on flat ground,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val AGGREGATE_FLAT_TIME: DataType =
+ DataType("Aggregate Flat Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * The aggregate duration for an exercise when the user was traveling up an incline,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val AGGREGATE_INCLINE_TIME: DataType =
+ DataType("Aggregate Incline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * The aggregate duration for an exercise when the user was traveling down a decline,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val AGGREGATE_DECLINE_TIME: DataType =
+ DataType("Aggregate Decline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * The aggregate total calories (including basal rate and activity) expended over a period
+ * of time in `double` format.
+ */
+ @JvmField
+ public val AGGREGATE_CALORIES_EXPENDED: DataType =
+ DataType("Aggregate Calories", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** The aggregate step count over a period of time in `long` format. */
+ @JvmField
+ public val AGGREGATE_STEP_COUNT: DataType =
+ DataType("Aggregate Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The aggregate walking step count over a period of time in `long` format. */
+ @JvmField
+ public val AGGREGATE_WALKING_STEP_COUNT: DataType =
+ DataType("Aggregate Walking Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The aggregate running step count over a period of time in `long` format. */
+ @JvmField
+ public val AGGREGATE_RUNNING_STEP_COUNT: DataType =
+ DataType("Aggregate Running Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The aggregate swimming stroke count over a period of time in `long` format. */
+ @JvmField
+ public val AGGREGATE_SWIMMING_STROKE_COUNT: DataType =
+ DataType("Aggregate Swimming Strokes", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The aggregate elevation over a period of time in meters in `double` format. */
+ @JvmField
+ public val AGGREGATE_ELEVATION: DataType =
+ DataType("Aggregate Elevation", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** The number of floors climbed over a period of time in `double` format */
+ @JvmField
+ public val AGGREGATE_FLOORS: DataType =
+ DataType("Aggregate Floors", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** The average pace over a period of time in millisec/km in `double` format. */
+ @JvmField
+ public val AVERAGE_PACE: DataType =
+ DataType("Average Pace", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The average speed over a period of time in meters/second in `double` format. */
+ @JvmField
+ public val AVERAGE_SPEED: DataType =
+ DataType("Average Speed", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The average heart rate over a period of time in beats/minute in `double` format. */
+ @JvmField
+ public val AVERAGE_HEART_RATE_BPM: DataType =
+ DataType("Average Heart Rate BPM", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The maximum altitude over a period of time in meters in `double` format. */
+ @JvmField
+ public val MAX_ALTITUDE: DataType =
+ DataType("Max Altitude", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The minimum altitude over a period of time in meters in `double` format. */
+ @JvmField
+ public val MIN_ALTITUDE: DataType =
+ DataType("Min Altitude", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The maximum pace over a period of time in millisec/km in `double` format. */
+ @JvmField
+ public val MAX_PACE: DataType = DataType("Max Pace", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The maximum speed over a period of time in meters/second in `double` format. */
+ @JvmField
+ public val MAX_SPEED: DataType = DataType("Max Speed", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The maximum instantaneous heart rate in beats/minute in `double` format. */
+ @JvmField
+ public val MAX_HEART_RATE_BPM: DataType =
+ DataType("Max Heart Rate", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /**
+ * The duration during which the user was resting during an Exercise in seconds in `long`
+ * format.
+ */
+ @JvmField
+ public val RESTING_EXERCISE_DURATION: DataType =
+ DataType("Resting Exercise Duration", TimeType.SAMPLE, Value.FORMAT_LONG)
+
+ /** The duration of the time the Exercise was ACTIVE in seconds in `long` format. */
+ @JvmField
+ public val ACTIVE_EXERCISE_DURATION: DataType =
+ DataType("Active Exercise Duration", TimeType.SAMPLE, Value.FORMAT_LONG)
+
+ /** Count of swimming laps ins `long` format. */
+ @JvmField
+ public val SWIMMING_LAP_COUNT: DataType =
+ DataType("Swim Lap Count", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The current rep count of the exercise in `long` format. */
+ @JvmField
+ public val REP_COUNT: DataType = DataType("Rep Count", TimeType.INTERVAL, Value.FORMAT_LONG)
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeCondition.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeCondition.kt
new file mode 100644
index 0000000..152c9df
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeCondition.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** A condition which is considered met when a data type value passes a defined threshold. */
+public data class DataTypeCondition(
+ val dataType: DataType,
+ val threshold: Value,
+ val comparisonType: ComparisonType,
+) : Parcelable {
+ init {
+ require(dataType.format == threshold.format) {
+ "provided data type must have sample time type."
+ }
+ }
+
+ /** Checks whether or not the condition is satisfied by a given [DataPoint]. */
+ public fun isSatisfied(dataPoint: DataPoint): Boolean {
+ require(dataType == dataPoint.dataType) {
+ "attempted to evaluate data type condition with incorrect data type. Expected " +
+ "${dataType.name} but was ${dataPoint.dataType.name}"
+ }
+ return isThresholdSatisfied(dataPoint.value)
+ }
+
+ /** Checks whether or not the value of the condition is satisfied by a given [Value]. */
+ public fun isThresholdSatisfied(value: Value): Boolean {
+ val comparison = Value.compare(value, threshold)
+ return when (comparisonType) {
+ ComparisonType.LESS_THAN -> comparison < 0
+ ComparisonType.GREATER_THAN -> comparison > 0
+ ComparisonType.LESS_THAN_OR_EQUAL -> comparison <= 0
+ ComparisonType.GREATER_THAN_OR_EQUAL -> comparison >= 0
+ }
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeParcelable(threshold, flags)
+ dest.writeInt(comparisonType.id)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<DataTypeCondition> =
+ object : Parcelable.Creator<DataTypeCondition> {
+ override fun createFromParcel(source: Parcel): DataTypeCondition? {
+ val dataType =
+ source.readParcelable<DataType>(DataType::class.java.classLoader)
+ ?: return null
+ val threshold =
+ source.readParcelable<Value>(Value::class.java.classLoader) ?: return null
+ val comparisonType = ComparisonType.fromId(source.readInt()) ?: return null
+ return DataTypeCondition(dataType, threshold, comparisonType)
+ }
+
+ override fun newArray(size: Int): Array<DataTypeCondition?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypes.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypes.kt
new file mode 100644
index 0000000..b821de8
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypes.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Helper class to facilitate working with [DataTypes] [DataType]. */
+public object DataTypes {
+ private val AGGREGATE_TYPE_TO_RAW_TYPE =
+ DataTypeBiMap(
+ DataType.AGGREGATE_DISTANCE to DataType.DISTANCE,
+ DataType.AGGREGATE_CALORIES_EXPENDED to DataType.TOTAL_CALORIES,
+ DataType.AGGREGATE_STEP_COUNT to DataType.STEPS,
+ DataType.AGGREGATE_ELEVATION to DataType.ELEVATION,
+ DataType.AGGREGATE_FLOORS to DataType.FLOORS,
+ DataType.AGGREGATE_SWIMMING_STROKE_COUNT to DataType.SWIMMING_STROKES
+ )
+
+ private val MAX_TYPE_TO_RAW_TYPE =
+ DataTypeBiMap(
+ DataType.MAX_HEART_RATE_BPM to DataType.HEART_RATE_BPM,
+ DataType.MAX_PACE to DataType.PACE,
+ DataType.MAX_SPEED to DataType.SPEED
+ )
+
+ private val AVERAGE_TYPE_TO_RAW_TYPE =
+ DataTypeBiMap(
+ DataType.AVERAGE_HEART_RATE_BPM to DataType.HEART_RATE_BPM,
+ DataType.AVERAGE_PACE to DataType.PACE,
+ DataType.AVERAGE_SPEED to DataType.SPEED
+ )
+
+ /** Check if a [DataType] represents aggregate value of a collection of non-aggregate data. */
+ @JvmStatic
+ public fun isAggregateDataType(dataType: DataType): Boolean =
+ AGGREGATE_TYPE_TO_RAW_TYPE.map.containsKey(dataType)
+
+ /** Check if a [DataType] represents the maximum value of a collection of non-aggregate data. */
+ @JvmStatic
+ public fun isStatisticalMaxDataType(dataType: DataType): Boolean =
+ MAX_TYPE_TO_RAW_TYPE.map.containsKey(dataType)
+
+ /** Check if a [DataType] represents the average value of a collection of non-aggregate data. */
+ @JvmStatic
+ public fun isStatisticalAverageDataType(dataType: DataType): Boolean =
+ AVERAGE_TYPE_TO_RAW_TYPE.map.containsKey(dataType)
+
+ /**
+ * Check if a [DataType] represents raw data value, i.e., neither aggregate value nor
+ * statistical value.
+ */
+ @JvmStatic
+ public fun isRawType(dataType: DataType): Boolean =
+ !isAggregateDataType(dataType) &&
+ !isStatisticalMaxDataType(dataType) &&
+ !isStatisticalAverageDataType(dataType)
+
+ /** Get the aggregate [DataType] from a raw [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getAggregateTypeFromRawType(rawType: DataType): DataType? =
+ AGGREGATE_TYPE_TO_RAW_TYPE.inverse[rawType]
+
+ /** Get the raw [DataType] from an aggregate [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getRawTypeFromAggregateType(aggregateType: DataType): DataType? =
+ AGGREGATE_TYPE_TO_RAW_TYPE.map[aggregateType]
+
+ /** Get the max [DataType] from a raw [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getMaxTypeFromRawType(rawType: DataType): DataType? =
+ MAX_TYPE_TO_RAW_TYPE.inverse[rawType]
+
+ /** Get the raw [DataType] from a max [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getRawTypeFromMaxType(maxType: DataType): DataType? =
+ MAX_TYPE_TO_RAW_TYPE.map[maxType]
+
+ /** Get the average [DataType] from a raw [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getAverageTypeFromRawType(rawType: DataType): DataType? =
+ AVERAGE_TYPE_TO_RAW_TYPE.inverse[rawType]
+
+ /** Get the raw [DataType] from an average [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getRawTypeFromAverageType(averageType: DataType): DataType? =
+ AVERAGE_TYPE_TO_RAW_TYPE.map[averageType]
+
+ /** Get the aggregate, average, and max [DataType] from a raw [DataType] if they exist. */
+ @JvmStatic
+ public fun getAggregatedDataTypesFromRawType(rawType: DataType): Set<DataType> {
+ val allDataTypes = HashSet<DataType>()
+
+ getAggregateTypeFromRawType(rawType)?.let { allDataTypes.add(it) }
+ getMaxTypeFromRawType(rawType)?.let { allDataTypes.add(it) }
+ getAverageTypeFromRawType(rawType)?.let { allDataTypes.add(it) }
+
+ return allDataTypes
+ }
+
+ private class DataTypeBiMap(vararg pairs: Pair<DataType, DataType>) {
+ val map = mapOf(*pairs)
+ val inverse = pairs.map { it.second to it.first }.toMap()
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt
new file mode 100644
index 0000000..6564f06
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** Provides exercise specific capabilities data. */
+public data class ExerciseCapabilities(
+ val supportedDataTypes: Set<DataType>,
+ val supportedGoals: Map<DataType, Set<ComparisonType>>,
+ val supportedMilestones: Map<DataType, Set<ComparisonType>>,
+ val supportsAutoPauseAndResume: Boolean,
+ val supportsLaps: Boolean,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(supportedDataTypes.size)
+ dest.writeTypedArray(supportedDataTypes.toTypedArray(), flags)
+
+ writeSupportedDataTypes(supportedGoals, dest, flags)
+ writeSupportedDataTypes(supportedMilestones, dest, flags)
+
+ dest.writeInt(if (supportsAutoPauseAndResume) 1 else 0)
+ dest.writeInt(if (supportsLaps) 1 else 0)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseCapabilities> =
+ object : Parcelable.Creator<ExerciseCapabilities> {
+ override fun createFromParcel(source: Parcel): ExerciseCapabilities? {
+ val supportedDataTypesArray = Array<DataType?>(source.readInt()) { null }
+ source.readTypedArray(supportedDataTypesArray, DataType.CREATOR)
+
+ val supportedGoals = readSupportedDataTypes(source) ?: return null
+ val supportedMilestones = readSupportedDataTypes(source) ?: return null
+ val supportsAutoPauseAndResume = source.readInt() == 1
+ val supportsLaps = source.readInt() == 1
+
+ return ExerciseCapabilities(
+ supportedDataTypesArray.filterNotNull().toSet(),
+ supportedGoals,
+ supportedMilestones,
+ supportsAutoPauseAndResume,
+ supportsLaps
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseCapabilities?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ private fun writeSupportedDataTypes(
+ supportedDataTypes: Map<DataType, Set<ComparisonType>>,
+ dest: Parcel,
+ flags: Int
+ ) {
+ dest.writeInt(supportedDataTypes.size)
+ for ((dataType, comparisonTypeSet) in supportedDataTypes) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeInt(comparisonTypeSet.size)
+ dest.writeIntArray(comparisonTypeSet.map { it.id }.toIntArray())
+ }
+ }
+
+ private fun readSupportedDataTypes(source: Parcel): Map<DataType, Set<ComparisonType>>? {
+ val supportedDataTypes = HashMap<DataType, Set<ComparisonType>>()
+
+ val numSupportedDataTypes = source.readInt()
+ repeat(numSupportedDataTypes) {
+ val dataType: DataType =
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+
+ val comparisonTypeIntArray = IntArray(source.readInt())
+ source.readIntArray(comparisonTypeIntArray)
+ val comparisonTypeSet =
+ comparisonTypeIntArray.map { ComparisonType.fromId(it) }.filterNotNull().toSet()
+
+ supportedDataTypes[dataType] = comparisonTypeSet
+ }
+
+ return supportedDataTypes
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
new file mode 100644
index 0000000..9531eea
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import java.util.Objects
+
+/** Defines configuration for an exercise tracked using WHS. */
+@Suppress("DataClassPrivateConstructor")
+public data class ExerciseConfig
+protected constructor(
+ /**
+ * [ExerciseType] the user is performing for this exercise.
+ *
+ * This information can be used to tune sensors, e.g. the calories estimate can take the MET
+ * value into account.
+ */
+ val exerciseType: ExerciseType,
+ val dataTypes: Set<DataType>,
+ val autoPauseAndResume: Boolean,
+ val exerciseGoals: List<ExerciseGoal>,
+ val exerciseParams: Bundle,
+) : Parcelable {
+ init {
+ require(dataTypes.isNotEmpty()) { "Must specify the desired data types." }
+ require(exerciseType != ExerciseType.UNKNOWN) { "Must specify a valid exercise type." }
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(exerciseType.id)
+ dest.writeInt(dataTypes.size)
+ dest.writeTypedArray(dataTypes.toTypedArray(), flags)
+ dest.writeInt(if (autoPauseAndResume) 1 else 0)
+ dest.writeInt(exerciseGoals.size)
+ dest.writeTypedArray(exerciseGoals.toTypedArray(), flags)
+ dest.writeBundle(exerciseParams)
+ }
+
+ /** Builder for [ExerciseConfig] instances. */
+ public class Builder {
+ private var exerciseType: ExerciseType? = null
+ private var dataTypes: Set<DataType>? = null
+ private var autoPauseAndResume: Boolean = false
+ private var exerciseGoals: List<ExerciseGoal> = emptyList()
+ private var exerciseParams: Bundle = Bundle.EMPTY
+
+ /**
+ * Sets the active [ExerciseType] the user is performing for this exercise.
+ *
+ * Provide this parameter when tracking a workout to provide more accurate data. This
+ * information can be used to tune sensors, e.g. the calories estimate can take the MET
+ * value into account.
+ */
+ public fun setExerciseType(exerciseType: ExerciseType): Builder {
+ this.exerciseType = exerciseType
+ return this
+ }
+
+ /**
+ * Sets the requested [DataType] s that should be tracked during this exercise. If not
+ * explicitly called, a default set of [DataType] will be chosen based on the [ ].
+ */
+ public fun setDataTypes(dataTypes: Set<DataType>): Builder {
+ this.dataTypes = dataTypes.toSet()
+ return this
+ }
+
+ /**
+ * Sets whether auto pause and auto resume are enabled for this exercise. If not set,
+ * they're disabled by default.
+ */
+ public fun setAutoPauseAndResume(autoPauseAndResume: Boolean): Builder {
+ this.autoPauseAndResume = autoPauseAndResume
+ return this
+ }
+
+ /**
+ * Sets [ExerciseGoal] s specified for this exercise.
+ *
+ * This is useful to have goals specified before the start of an exercise.
+ */
+ public fun setExerciseGoals(exerciseGoals: List<ExerciseGoal>): Builder {
+ this.exerciseGoals = exerciseGoals.toList()
+ return this
+ }
+
+ /**
+ * Sets additional parameters for current exercise. Supported keys can be found in
+ * [ExerciseConfig].
+ */
+ // TODO(b/180612514) expose keys on a per-OEM basis.
+ public fun setExerciseParams(exerciseParams: Bundle): Builder {
+ this.exerciseParams = exerciseParams
+ return this
+ }
+
+ /** Returns the built `ExerciseConfig`. */
+ public fun build(): ExerciseConfig {
+ return ExerciseConfig(
+ checkNotNull(exerciseType) { "No exercise type specified" },
+ checkNotNull(dataTypes) { "No data types specified" },
+ autoPauseAndResume,
+ exerciseGoals,
+ exerciseParams
+ )
+ }
+ }
+
+ // TODO(b/180612514): Bundle doesn't have equals, so we need to override the data class default.
+ override fun equals(other: Any?): Boolean {
+ if (other === this) {
+ return true
+ }
+ if (other is ExerciseConfig) {
+ return exerciseType == other.exerciseType &&
+ dataTypes == other.dataTypes &&
+ autoPauseAndResume == other.autoPauseAndResume &&
+ exerciseGoals == other.exerciseGoals &&
+ BundlesUtil.equals(exerciseParams, other.exerciseParams)
+ }
+ return false
+ }
+
+ // TODO(b/180612514): Bundle doesn't have hashCode, so we need to override the data class
+ // default.
+ override fun hashCode(): Int {
+ return Objects.hash(
+ exerciseType,
+ dataTypes,
+ autoPauseAndResume,
+ exerciseGoals,
+ BundlesUtil.hashCode(exerciseParams)
+ )
+ }
+
+ public companion object {
+ @JvmStatic public fun builder(): Builder = Builder()
+
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseConfig> =
+ object : Parcelable.Creator<ExerciseConfig> {
+ override fun createFromParcel(source: Parcel): ExerciseConfig? {
+ val exerciseType = ExerciseType.fromId(source.readInt())
+
+ val dataTypesArray = Array<DataType?>(source.readInt()) { null }
+ source.readTypedArray(dataTypesArray, DataType.CREATOR)
+
+ val autoPauseAndResume = source.readInt() == 1
+
+ val exerciseGoals = Array<ExerciseGoal?>(source.readInt()) { null }
+ source.readTypedArray(exerciseGoals, ExerciseGoal.CREATOR)
+
+ val exerciseParams =
+ source.readBundle(ExerciseConfig::class.java.classLoader) ?: Bundle()
+
+ return ExerciseConfig(
+ exerciseType,
+ dataTypesArray.filterNotNull().toSet(),
+ autoPauseAndResume,
+ exerciseGoals.filterNotNull().toList(),
+ exerciseParams
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseConfig?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoal.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoal.kt
new file mode 100644
index 0000000..39c2ab1
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoal.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.util.Objects
+
+// TODO(yeabkal): as we support more types of goals, we may want to rename the class.
+/** Defines a goal for an exercise. */
+@Suppress("DataClassPrivateConstructor")
+public data class ExerciseGoal
+protected constructor(
+ val exerciseGoalType: ExerciseGoalType,
+ val dataTypeCondition: DataTypeCondition,
+ // TODO(yeabkal): shall we rename to "getMilestonePeriod"? Currently "getPeriod" is used to be
+ // flexible in case we support other kinds of goals. Recheck when design is fully locked.
+ val period: Value? = null,
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(exerciseGoalType.id)
+ dest.writeParcelable(dataTypeCondition, flags)
+ dest.writeParcelable(period, flags)
+ }
+
+ // TODO(yeabkal): try to unify equality logic across goal types.
+ // TODO(b/186899729): We need a better way to match on achieved goals.
+ override fun equals(other: Any?): Boolean {
+ if (other === this) {
+ return true
+ }
+ if (other !is ExerciseGoal) {
+ return false
+ }
+
+ if (this.exerciseGoalType != other.exerciseGoalType) {
+ return false
+ }
+
+ return when (exerciseGoalType) {
+ ExerciseGoalType.ONE_TIME_GOAL -> dataTypeCondition == other.dataTypeCondition
+ // The threshold of a milestone is not included in the equality calculation to let apps
+ // easily map back an achieved milestone to the milestone they requested for tracking.
+ ExerciseGoalType.MILESTONE ->
+ dataTypeCondition.dataType == other.dataTypeCondition.dataType &&
+ dataTypeCondition.comparisonType == other.dataTypeCondition.comparisonType &&
+ period == other.period
+ }
+ }
+
+ override fun hashCode(): Int {
+ return if (exerciseGoalType == ExerciseGoalType.ONE_TIME_GOAL) {
+ Objects.hash(exerciseGoalType, dataTypeCondition)
+ } else {
+ Objects.hash(
+ exerciseGoalType,
+ dataTypeCondition.dataType,
+ dataTypeCondition.comparisonType,
+ period
+ )
+ }
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseGoal> =
+ object : Parcelable.Creator<ExerciseGoal> {
+ override fun createFromParcel(source: Parcel): ExerciseGoal? {
+ val exerciseGoalType = ExerciseGoalType.fromId(source.readInt()) ?: return null
+ val dataTypeCondition: DataTypeCondition =
+ source.readParcelable(DataTypeCondition::class.java.classLoader)
+ ?: return null
+ val period: Value? = source.readParcelable(Value::class.java.classLoader)
+
+ return ExerciseGoal(exerciseGoalType, dataTypeCondition, period)
+ }
+
+ override fun newArray(size: Int): Array<ExerciseGoal?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /**
+ * Creates an [ExerciseGoal] that is achieved once when the given [DataTypeCondition] is
+ * satisfied.
+ */
+ @JvmStatic
+ public fun createOneTimeGoal(condition: DataTypeCondition): ExerciseGoal {
+ return ExerciseGoal(ExerciseGoalType.ONE_TIME_GOAL, condition)
+ }
+
+ /**
+ * Creates an [ExerciseGoal] that is achieved multiple times with its threshold being
+ * updated by a `period` value each time it is achieved. For instance, a milestone could be
+ * one for every 2km. This goal will there be triggered at distances = 2km, 4km, 6km, ...
+ */
+ @JvmStatic
+ public fun createMilestone(condition: DataTypeCondition, period: Value): ExerciseGoal {
+ require(period.format == condition.threshold.format) {
+ "The condition's threshold and the period should have the same types of values."
+ }
+ return ExerciseGoal(ExerciseGoalType.MILESTONE, condition, period)
+ }
+
+ /** Creates a new goal that is the same as a given goal but with a new threshold value. */
+ @JvmStatic
+ public fun createMilestoneGoalWithUpdatedThreshold(
+ goal: ExerciseGoal,
+ newThreshold: Value
+ ): ExerciseGoal {
+ require(ExerciseGoalType.MILESTONE == goal.exerciseGoalType) {
+ "The goal to update should be of MILESTONE type."
+ }
+ require(goal.period != null) { "The milestone goal's period should not be null." }
+ val (dataType, oldThreshold, comparisonType) = goal.dataTypeCondition
+ require(oldThreshold.format == newThreshold.format) {
+ "The old and new thresholds should have the same types of values."
+ }
+ return ExerciseGoal(
+ ExerciseGoalType.MILESTONE,
+ DataTypeCondition(dataType, newThreshold, comparisonType),
+ goal.period
+ )
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
new file mode 100644
index 0000000..0790057
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Exercise goal types. */
+public enum class ExerciseGoalType(public val id: Int) {
+ ONE_TIME_GOAL(1),
+ MILESTONE(2);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): ExerciseGoalType? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseInfo.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseInfo.kt
new file mode 100644
index 0000000..7c43643
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseInfo.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** High-level info about the exercise. */
+public data class ExerciseInfo(
+ /** Returns the [ExerciseTrackedStatus]. */
+ val exerciseTrackedStatus: ExerciseTrackedStatus,
+
+ /**
+ * Returns the [ExerciseType] of the active exercise, or [ExerciseType.UNKNOWN] if there is no
+ * active exercise.
+ */
+ val exerciseType: ExerciseType,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(exerciseTrackedStatus.id)
+ dest.writeInt(exerciseType.id)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseInfo> =
+ object : Parcelable.Creator<ExerciseInfo> {
+ override fun createFromParcel(source: Parcel): ExerciseInfo? {
+ return ExerciseInfo(
+ ExerciseTrackedStatus.fromId(source.readInt()) ?: return null,
+ ExerciseType.fromId(source.readInt())
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseInfo?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt
new file mode 100644
index 0000000..cd17ab1
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+
+/** Describes a completed exercise lap. */
+public data class ExerciseLapSummary(
+ /** Returns the lap count of this summary. Lap count starts at 1 for the first lap. */
+ val lapCount: Int,
+
+ /** Returns the time at which the lap has started. */
+ val startTime: Instant,
+
+ /** Returns the time at which the lap has ended. */
+ val endTime: Instant,
+
+ /**
+ * Returns the total elapsed time for which the exercise has been active during this lap, i.e.
+ * started but not paused.
+ */
+ val activeDuration: Duration,
+
+ /**
+ * Returns the [DataPoint] s for each metric keyed by [DataType] tracked between [startTime] and
+ * [endTime] i.e. during the duration of this lap. This will only contain aggregated [DataType]
+ * s calculated over the duration of the lap.
+ */
+ val lapMetrics: Map<DataType, DataPoint>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(lapCount)
+ dest.writeLong(startTime.toEpochMilli())
+ dest.writeLong(endTime.toEpochMilli())
+ dest.writeLong(activeDuration.toMillis())
+
+ dest.writeInt(lapMetrics.size)
+ for ((dataType, dataPoint) in lapMetrics) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeParcelable(dataPoint, flags)
+ }
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseLapSummary> =
+ object : Parcelable.Creator<ExerciseLapSummary> {
+ override fun createFromParcel(source: Parcel): ExerciseLapSummary? {
+ val lapCount = source.readInt()
+ val startTime = Instant.ofEpochMilli(source.readLong())
+ val endTime = Instant.ofEpochMilli(source.readLong())
+ val activeDuration = Duration.ofMillis(source.readLong())
+
+ val lapMetrics = HashMap<DataType, DataPoint>()
+ val numMetrics = source.readInt()
+ repeat(numMetrics) {
+ val dataType: DataType =
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+ lapMetrics[dataType] =
+ source.readParcelable(DataPoint::class.java.classLoader) ?: return null
+ }
+
+ return ExerciseLapSummary(
+ lapCount,
+ startTime,
+ endTime,
+ activeDuration,
+ lapMetrics
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseLapSummary?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt
new file mode 100644
index 0000000..964e271
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Enumerates the state of an exercise. */
+public enum class ExerciseState(public val id: Int) {
+ /**
+ * The exercise is actively being started, but we don't yet have sensor stability or GPS fix.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_STARTING(1),
+
+ /**
+ * The exercise is actively in-progress.
+ *
+ * Used in both of the manually started exercise and the automatic exercise detection. It's also
+ * the state when the automatic exercise detection has detected an exercise and the exercise is
+ * actively in-progress.
+ */
+ ACTIVE(2),
+
+ /**
+ * The session is being paused by the user. Sensors are actively being flushed.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_PAUSING(3),
+
+ /**
+ * The session has been paused by the user. Sensors have completed flushing.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_PAUSED(4),
+
+ /**
+ * The session is being paused by auto-pause. Sensors are actively being flushed.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_PAUSING(5),
+
+ /**
+ * The session has been automatically paused. Sensors have completed flushing.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_PAUSED(6),
+
+ /**
+ * The session is being resumed by the user.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_RESUMING(7),
+
+ /**
+ * The session is being automatically resumed.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_RESUMING(8),
+
+ /**
+ * The exercise is being ended by the user. Sensors are actively being flushed.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_ENDING(9),
+
+ /**
+ * The exercise has been ended by the user. No new metrics will be exported and a final summary
+ * should be provided to the client.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_ENDED(10),
+
+ /**
+ * The exercise is being automatically ended due to a lack of exercise updates being received by
+ * the user. Sensors are actively being flushed.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_ENDING(11),
+
+ /**
+ * The exercise has been automatically ended due to a lack of exercise updates being received by
+ * the user. No new metrics will be exported and a final summary should be provided to the
+ * client.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_ENDED(12),
+
+ /**
+ * The exercise is being ended because it has been superseded by a new exercise being started by
+ * another client. Sensors are actively being flushed.
+ *
+ * Used in both of the manually started exercise and the automatic exercise detection.
+ */
+ TERMINATING(13),
+
+ /**
+ * The exercise has been ended because it was superseded by a new exercise being started by
+ * another client. No new metrics will be exported and a final summary should be provided to the
+ * client.
+ *
+ * Used in both of the manually started exercise and the automatic exercise detection.
+ */
+ TERMINATED(14);
+
+ /**
+ * Returns true if this [ExerciseState] corresponds to one of the paused states and false
+ * otherwise. This method returns false if the exercise has ended, to check whether it has ended
+ * call [isEnded].
+ */
+ public val isPaused: Boolean
+ get() = PAUSED_STATES.contains(this)
+
+ /**
+ * Returns true if this [ExerciseState] corresponds to one of the resuming states and false
+ * otherwise. This method returns false if the exercise has ended, to check whether it has ended
+ * call [isEnded].
+ */
+ public val isResuming: Boolean
+ get() = RESUMING_STATES.contains(this)
+
+ /**
+ * Returns true if this [ExerciseState] corresponds to one of the ended states and false
+ * otherwise. This method returns false if the exercise has been paused, to check whether it is
+ * currently paused call [isPaused].
+ */
+ public val isEnded: Boolean
+ get() = ENDED_STATES.contains(this)
+
+ public companion object {
+ private val RESUMING_STATES = setOf(USER_RESUMING, AUTO_RESUMING)
+ private val PAUSED_STATES = setOf(USER_PAUSED, AUTO_PAUSED)
+ private val ENDED_STATES = setOf(USER_ENDED, AUTO_ENDED, TERMINATED)
+
+ @JvmStatic public fun fromId(id: Int): ExerciseState? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt
new file mode 100644
index 0000000..2a1279b
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Status representing if an exercise is being tracked and which app owns the exercise. */
+public enum class ExerciseTrackedStatus(public val id: Int) {
+ OTHER_APP_IN_PROGRESS(1),
+ OWNED_EXERCISE_IN_PROGRESS(2),
+ NO_EXERCISE_IN_PROGRESS(3);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): ExerciseTrackedStatus? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt
new file mode 100644
index 0000000..d385c67
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+// TODO(b/185276729): Keep track of values separately to maintain alphabetical order
+// once values are locked in
+/** Exercise type used to configure sensors and algorithms. */
+public enum class ExerciseType(
+ /** Returns a unique identifier of for the [ExerciseType], as an `int`. */
+ public val id: Int
+) {
+ /** The current exercise type of the user is unknown or not set. */
+ UNKNOWN(0),
+ BACK_EXTENSION(1),
+ BADMINTON(2),
+ BARBELL_SHOULDER_PRESS(3),
+ BASEBALL(4),
+ BASKETBALL(5),
+ BENCH_PRESS(6),
+ BENCH_SIT_UP(7),
+ BIKING(8),
+ BIKING_STATIONARY(9),
+ BOOT_CAMP(10),
+ BOXING(11),
+ BURPEE(12),
+
+ /** (E.g., push ups, sit ups, pull-ups, jumping jacks). */
+ CALISTHENICS(13),
+ CRICKET(14),
+ CRUNCH(15),
+ DANCING(16),
+ DEADLIFT(17),
+ DUMBBELL_CURL_RIGHT_ARM(18),
+ DUMBBELL_CURL_LEFT_ARM(19),
+ DUMBBELL_FRONT_RAISE(20),
+ DUMBBELL_LATERAL_RAISE(21),
+ DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM(22),
+ DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM(23),
+ DUMBBELL_TRICEPS_EXTENSION_TWO_ARM(24),
+ ELLIPTICAL(25),
+ EXERCISE_CLASS(26),
+ FENCING(27),
+ FRISBEE_DISC(28),
+ FOOTBALL_AMERICAN(29),
+ FOOTBALL_AUSTRALIAN(30),
+ GOLF(31),
+ GUIDED_BREATHING(32),
+ GYNMASTICS(33),
+ HANDBALL(34),
+ HIGH_INTENSITY_INTERVAL_TRAINING(35),
+ HIKING(36),
+ ICE_HOCKEY(37),
+ ICE_SKATING(38),
+ JUMP_ROPE(39),
+ JUMPING_JACK(40),
+ LAT_PULL_DOWN(41),
+ LUNGE(42),
+ MARTIAL_ARTS(43),
+ MEDITATION(44),
+ PADDLING(45),
+ PARA_GLIDING(46),
+ PILATES(47),
+ PLANK(48),
+ RACQUETBALL(49),
+ ROCK_CLIMBING(50),
+ ROLLER_HOCKEY(51),
+ ROWING(52),
+ ROWING_MACHINE(53),
+ RUNNING(54),
+ RUNNING_TREADMILL(55),
+ RUGBY(56),
+ SAILING(57),
+ SCUBA_DIVING(58),
+ SKATING(59),
+ SKIING(60),
+ SNOWBOARDING(61),
+ SNOWSHOEING(62),
+ SOCCER(63),
+ SOFTBALL(64),
+ SQUASH(65),
+ SQUAT(66),
+ STAIR_CLIMBING(67),
+ STAIR_CLIMBING_MACHINE(68),
+ STRENGTH_TRAINING(69),
+ STRETCHING(70),
+ SURFING(71),
+ SWIMMING_OPEN_WATER(72),
+ SWIMMING_POOL(73),
+ TABLE_TENNIS(74),
+ TENNIS(75),
+ VOLLEYBALL(76),
+ WALKING(77),
+ WATER_POLO(78),
+ WEIGHTLIFTING(79),
+ WORKOUT_INDOOR(80),
+ WORKOUT_OUTDOOR(81),
+ YOGA(82);
+
+ public companion object {
+ private val IDS = initialize()
+ private fun initialize(): Map<Int, ExerciseType> {
+ val map = mutableMapOf<Int, ExerciseType>()
+ for (exerciseType in values()) {
+ map.put(exerciseType.id, exerciseType)
+ }
+ return map
+ }
+
+ /**
+ * Returns the [ExerciseType] based on its unique `id`.
+ *
+ * If the `id` doesn't map to an particular [ExerciseType], then [ExerciseType.UNKNOWN] is
+ * returned by default.
+ */
+ @JvmStatic
+ public fun fromId(id: Int): ExerciseType {
+ val exerciseType = IDS[id]
+ return exerciseType ?: UNKNOWN
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt
new file mode 100644
index 0000000..915d490a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+
+// TODO(b/179756269): add aggregated metrics.
+/** Contains the latest updated state and metrics for the current exercise. */
+public data class ExerciseUpdate(
+ /** Returns the current status of the exercise. */
+ val state: ExerciseState,
+
+ /** Returns the time at which the exercise was started. */
+ val startTime: Instant,
+
+ /**
+ * Returns the total elapsed time for which the exercise has been active, i.e. started but not
+ * paused.
+ */
+ val activeDuration: Duration,
+
+ /**
+ * Returns the list of latest [DataPoint] for each metric keyed by data type name. This allows a
+ * client to easily query for the "current" values of each metric since last call. There will
+ * only be one value for an Aggregated DataType.
+ */
+ val latestMetrics: Map<DataType, List<DataPoint>>,
+
+ /**
+ * Returns the latest `#ONE_TIME_GOAL` [ExerciseGoal] s that have been achieved. `#MILESTONE`
+ * [ExerciseGoal] s will be returned via `#getLatestMilestoneMarkerSummaries` below.
+ */
+ val latestAchievedGoals: Set<AchievedExerciseGoal>,
+
+ /** Returns the latest [MilestoneMarkerSummary] s. */
+ val latestMilestoneMarkerSummaries: Set<MilestoneMarkerSummary>,
+
+ /**
+ * Returns the [ExerciseConfig] used by the exercise when the [ExerciseUpdate] was dispatched,
+ * or `null` if there isn't any manually started exercise.
+ */
+ val exerciseConfig: ExerciseConfig?,
+
+ /**
+ * Returns the [AutoExerciseConfig] associated for the automatic exercise detection or `null` if
+ * the automatic exercise detection is stopped.
+ */
+ val autoExerciseConfig: AutoExerciseConfig?,
+
+ /**
+ * Returns the [ExerciseType] instance detected by the automatic exercise detection, otherwise
+ * [ExerciseType.UNKNOWN].
+ *
+ * It's only relevant when the automatic exercise detection is on.
+ */
+ val autoExerciseDetected: ExerciseType?,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(state.id)
+ dest.writeLong(startTime.toEpochMilli())
+ dest.writeLong(activeDuration.toMillis())
+
+ dest.writeInt(latestMetrics.size)
+ for ((dataType, dataPoints) in latestMetrics) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeInt(dataPoints.size)
+ dest.writeTypedArray(dataPoints.toTypedArray(), flags)
+ }
+
+ dest.writeInt(latestAchievedGoals.size)
+ dest.writeTypedArray(latestAchievedGoals.toTypedArray(), flags)
+
+ dest.writeInt(latestMilestoneMarkerSummaries.size)
+ dest.writeTypedArray(latestMilestoneMarkerSummaries.toTypedArray(), flags)
+
+ dest.writeParcelable(exerciseConfig, flags)
+ dest.writeParcelable(autoExerciseConfig, flags)
+ dest.writeInt(autoExerciseDetected?.id ?: -1)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseUpdate> =
+ object : Parcelable.Creator<ExerciseUpdate> {
+ override fun createFromParcel(source: Parcel): ExerciseUpdate? {
+ val exerciseState = ExerciseState.fromId(source.readInt()) ?: return null
+ val startTime = Instant.ofEpochMilli(source.readLong())
+ val activeDuration = Duration.ofMillis(source.readLong())
+
+ val numMetrics = source.readInt()
+ val latestMetrics = HashMap<DataType, List<DataPoint>>()
+ repeat(numMetrics) {
+ val dataType: DataType =
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+ val dataPointsArray = Array<DataPoint?>(source.readInt()) { null }
+ source.readTypedArray(dataPointsArray, DataPoint.CREATOR)
+ latestMetrics[dataType] = dataPointsArray.filterNotNull().toList()
+ }
+
+ val latestAchievedGoalsArray =
+ Array<AchievedExerciseGoal?>(source.readInt()) { null }
+ source.readTypedArray(latestAchievedGoalsArray, AchievedExerciseGoal.CREATOR)
+
+ val latestMilestoneMarkerSummariesArray =
+ Array<MilestoneMarkerSummary?>(source.readInt()) { null }
+ source.readTypedArray(
+ latestMilestoneMarkerSummariesArray,
+ MilestoneMarkerSummary.CREATOR
+ )
+
+ val exerciseConfig: ExerciseConfig? =
+ source.readParcelable(ExerciseConfig::class.java.classLoader)
+ val autoExerciseConfig: AutoExerciseConfig? =
+ source.readParcelable(AutoExerciseConfig::class.java.classLoader)
+ val autoExerciseDetectedId = source.readInt()
+ val autoExerciseDetected =
+ if (autoExerciseDetectedId == -1) null
+ else ExerciseType.fromId(autoExerciseDetectedId)
+
+ return ExerciseUpdate(
+ exerciseState,
+ startTime,
+ activeDuration,
+ latestMetrics,
+ latestAchievedGoalsArray.filterNotNull().toSet(),
+ latestMilestoneMarkerSummariesArray.filterNotNull().toSet(),
+ exerciseConfig,
+ autoExerciseConfig,
+ autoExerciseDetected
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseUpdate?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+
+ /** Supported Exercise session types. */
+ public enum class ExerciseSessionType {
+ /**
+ * The exercise is a manually started exercise session.
+ *
+ * [ExerciseUpdate.getExerciseConfig] returns non-null config with this type.
+ */
+ MANUALLY_STARTED_EXERCISE,
+
+ /**
+ * The exercise is an automatic exercise detection session.
+ *
+ * [ExerciseUpdate.getAutoExerciseConfig] returns non-null config with this type.
+ */
+ AUTO_EXERCISE_DETECTION,
+ }
+
+ /** Returns the current [ExerciseSessionType] WHS is carrying out. */
+ public fun getExerciseSessionType(): ExerciseSessionType {
+ return if (autoExerciseConfig != null) {
+ ExerciseSessionType.AUTO_EXERCISE_DETECTION
+ } else {
+ ExerciseSessionType.MANUALLY_STARTED_EXERCISE
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt
new file mode 100644
index 0000000..bffc1ec
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A place holder class that represents the capabilities of the WHS measuring client on the device.
+ */
+public data class MeasureCapabilities(
+ /**
+ * Set of supported [DataType] s for measure capture on this device.
+ *
+ * Some data types are not available for measurement; this is typically used to measure health
+ * data (e.g. HR).
+ */
+ val supportedDataTypesMeasure: Set<DataType>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeTypedList(supportedDataTypesMeasure.toList())
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MeasureCapabilities> =
+ object : Parcelable.Creator<MeasureCapabilities> {
+ override fun createFromParcel(source: Parcel): MeasureCapabilities? {
+ val measureDataTypes = ArrayList<DataType>()
+ source.readTypedList(measureDataTypes, DataType.CREATOR)
+ return MeasureCapabilities(
+ measureDataTypes.toSet(),
+ )
+ }
+
+ override fun newArray(size: Int): Array<MeasureCapabilities?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt
new file mode 100644
index 0000000..98a340c
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+
+/**
+ * The summary of metrics and state from the previously achieved milestone marker [ExerciseGoal].
+ */
+public data class MilestoneMarkerSummary(
+ /** Returns the time at which this milestone marker started being tracked. */
+ val startTime: Instant,
+
+ /** Returns the time at which this milestone marker was reached. */
+ val endTime: Instant,
+
+ /**
+ * Returns the total elapsed time for which the exercise was active during this milestone, i.e.
+ * started but not paused.
+ */
+ val activeDuration: Duration,
+
+ /** The [AchievedExerciseGoal] that triggered this milestone summary. */
+ val achievedGoal: AchievedExerciseGoal,
+
+ /**
+ * Returns the [DataPoint] for each aggregated metric keyed by [DataType] tracked between
+ * [startTime] and [endTime] i.e. during the duration of this milestone.
+ */
+ val summaryMetrics: Map<DataType, DataPoint>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeLong(startTime.toEpochMilli())
+ dest.writeLong(endTime.toEpochMilli())
+ dest.writeLong(activeDuration.toMillis())
+ dest.writeParcelable(achievedGoal, flags)
+
+ dest.writeInt(summaryMetrics.size)
+ for ((dataType, dataPoint) in summaryMetrics) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeParcelable(dataPoint, flags)
+ }
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MilestoneMarkerSummary> =
+ object : Parcelable.Creator<MilestoneMarkerSummary> {
+ override fun createFromParcel(source: Parcel): MilestoneMarkerSummary? {
+ val startTime = Instant.ofEpochMilli(source.readLong())
+ val endTime = Instant.ofEpochMilli(source.readLong())
+ val activeDuration = Duration.ofMillis(source.readLong())
+ val achievedGoal: AchievedExerciseGoal =
+ source.readParcelable(AchievedExerciseGoal::class.java.classLoader)
+ ?: return null
+
+ val summaryMetrics = HashMap<DataType, DataPoint>()
+ repeat(source.readInt()) {
+ val dataType: DataType =
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+ val dataPoint: DataPoint =
+ source.readParcelable(DataPoint::class.java.classLoader) ?: return null
+ summaryMetrics[dataType] = dataPoint
+ }
+
+ return MilestoneMarkerSummary(
+ startTime = startTime,
+ endTime = endTime,
+ activeDuration = activeDuration,
+ achievedGoal = achievedGoal,
+ summaryMetrics = summaryMetrics
+ )
+ }
+
+ override fun newArray(size: Int): Array<MilestoneMarkerSummary?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveActivityState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveActivityState.kt
new file mode 100644
index 0000000..0013973
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveActivityState.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.content.Intent
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_EXERCISE
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_INACTIVE
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_PASSIVE
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_UNKNOWN
+import java.time.Instant
+
+/**
+ * Represents state from Passive tracking.
+ *
+ * Provides [DataPoint] s associated with the Passive tracking, in addition to data related to the
+ * user's [UserActivityState].
+ */
+public data class PassiveActivityState(
+ /** List of [DataPoint] s from Passive tracking. */
+ val dataPoints: List<DataPoint>,
+
+ /** The [UserActivityState] of the user from Passive tracking. */
+ val userActivityState: UserActivityState,
+
+ /**
+ * The [ExerciseType] of the user for a [UserActivityState.USER_ACTIVITY_EXERCISE] state, and
+ * `null` for other [UserActivityState] s.
+ */
+ val exerciseType: ExerciseType?,
+
+ /** The time at which the current state took effect. */
+ val stateChangeTime: Instant,
+) : Parcelable {
+
+ /**
+ * Puts the state as an extra into a given [Intent]. The state can then be obtained from the
+ * intent via [PassiveActivityState.fromIntent].
+ */
+ public fun putToIntent(intent: Intent) {
+ intent.putExtra(EXTRA_KEY, this)
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(dataPoints.size)
+ dest.writeTypedArray(dataPoints.toTypedArray(), flags)
+
+ dest.writeInt(userActivityState.id)
+ dest.writeInt(exerciseType?.id ?: -1)
+ dest.writeLong(stateChangeTime.toEpochMilli())
+ }
+
+ public companion object {
+ private const val EXTRA_KEY = "whs.passive_activity_state"
+
+ @JvmField
+ public val CREATOR: Parcelable.Creator<PassiveActivityState> =
+ object : Parcelable.Creator<PassiveActivityState> {
+ override fun createFromParcel(source: Parcel): PassiveActivityState? {
+ val dataPointsArray: Array<DataPoint?> = arrayOfNulls(source.readInt())
+ source.readTypedArray(dataPointsArray, DataPoint.CREATOR)
+
+ val activityState = UserActivityState.fromId(source.readInt()) ?: return null
+ val exerciseTypeId = source.readInt()
+ val exerciseType =
+ if (exerciseTypeId == -1) null else ExerciseType.fromId(exerciseTypeId)
+ val time = Instant.ofEpochMilli(source.readLong())
+
+ return PassiveActivityState(
+ dataPointsArray.filterNotNull().toList(),
+ activityState,
+ exerciseType,
+ time
+ )
+ }
+
+ override fun newArray(size: Int): Array<PassiveActivityState?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /** Creates a [PassiveActivityState] for [USER_ACTIVITY_UNKNOWN]. */
+ @JvmStatic
+ public fun createUnknownTypeState(
+ dataPoints: List<DataPoint>,
+ stateChangeTime: Instant
+ ): PassiveActivityState =
+ PassiveActivityState(
+ dataPoints,
+ USER_ACTIVITY_UNKNOWN,
+ exerciseType = null,
+ stateChangeTime
+ )
+
+ /** Creates a [PassiveActivityState] for [USER_ACTIVITY_EXERCISE]. */
+ @JvmStatic
+ public fun createActiveExerciseState(
+ dataPoints: List<DataPoint>,
+ exerciseType: ExerciseType,
+ stateChangeTime: Instant
+ ): PassiveActivityState =
+ PassiveActivityState(dataPoints, USER_ACTIVITY_EXERCISE, exerciseType, stateChangeTime)
+
+ /** Creates a [PassiveActivityState] for [USER_ACTIVITY_PASSIVE]. */
+ @JvmStatic
+ public fun createPassiveActivityState(
+ dataPoints: List<DataPoint>,
+ stateChangeTime: Instant
+ ): PassiveActivityState =
+ PassiveActivityState(
+ dataPoints,
+ USER_ACTIVITY_PASSIVE,
+ exerciseType = null,
+ stateChangeTime
+ )
+
+ /**
+ * Creates a [PassiveActivityState] from an [Intent]. Returns null if no
+ * [PassiveActivityState] is stored in the given intent.
+ */
+ @JvmStatic
+ public fun fromIntent(intent: Intent): PassiveActivityState? =
+ intent.getParcelableExtra(EXTRA_KEY)
+
+ /** Creates a [PassiveActivityState] for [USER_ACTIVITY_INACTIVE]. */
+ @JvmStatic
+ public fun createInactiveState(
+ dataPoints: List<DataPoint>,
+ stateChangeTime: Instant
+ ): PassiveActivityState =
+ PassiveActivityState(
+ dataPoints,
+ USER_ACTIVITY_INACTIVE,
+ exerciseType = null,
+ stateChangeTime
+ )
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt
new file mode 100644
index 0000000..b715fda
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A place holder class that represents the capabilities of the WHS passive monitoring client on the
+ * device.
+ */
+public data class PassiveMonitoringCapabilities(
+
+ /**
+ * Set of supported [DataType] s for background capture on this device.
+ *
+ * Some data types are only available during exercise (e.g. location) or for measurements.
+ */
+ val supportedDataTypesPassiveMonitoring: Set<DataType>,
+
+ /** Set of supported [DataType] s for event callbacks on this device. */
+ val supportedDataTypesEvents: Set<DataType>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeTypedList(supportedDataTypesPassiveMonitoring.toList())
+ dest.writeTypedList(supportedDataTypesEvents.toList())
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<PassiveMonitoringCapabilities> =
+ object : Parcelable.Creator<PassiveMonitoringCapabilities> {
+ override fun createFromParcel(source: Parcel): PassiveMonitoringCapabilities? {
+ val passiveMonitoringDataTypes = ArrayList<DataType>()
+ source.readTypedList(passiveMonitoringDataTypes, DataType.CREATOR)
+ val eventDataTypes = ArrayList<DataType>()
+ source.readTypedList(eventDataTypes, DataType.CREATOR)
+ return PassiveMonitoringCapabilities(
+ passiveMonitoringDataTypes.toSet(),
+ eventDataTypes.toSet()
+ )
+ }
+
+ override fun newArray(size: Int): Array<PassiveMonitoringCapabilities?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt
new file mode 100644
index 0000000..5ba779e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Types of user activity states. */
+public enum class UserActivityState(public val id: Int) {
+ USER_ACTIVITY_UNKNOWN(0),
+ USER_ACTIVITY_EXERCISE(1),
+ USER_ACTIVITY_PASSIVE(2),
+ USER_ACTIVITY_INACTIVE(3);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): UserActivityState? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/Value.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/Value.kt
new file mode 100644
index 0000000..5ae780d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Value.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** A [Parcelable] wrapper that can hold a value of a specified type. */
+@Suppress("DataClassPrivateConstructor")
+public data class Value
+private constructor(
+ // TODO(b/175054913): Investigate using a AutoOneOf instead.
+ val format: Int,
+ val doubleList: List<Double>,
+ val longValue: Long,
+) : Parcelable {
+
+ /**
+ * Returns this [Value] represented as an `long`.
+ *
+ * @throws IllegalStateException if [isLong] is `false`
+ */
+ public fun asLong(): Long {
+ check(isLong) { "Attempted to read value as long, but value is not of type long" }
+ return longValue
+ }
+
+ /**
+ * Returns this [Value] represented as an `boolean`.
+ *
+ * @throws IllegalStateException if [isBoolean] is `false`
+ */
+ public fun asBoolean(): Boolean {
+ check(isBoolean) { "Attempted to read value as boolean, but value is not of type boolean" }
+ return longValue != 0L
+ }
+
+ /**
+ * Returns this [Value] represented as a `double`.
+ *
+ * @throws IllegalStateException if [isDouble] is `false`
+ */
+ public fun asDouble(): Double {
+ check(isDouble) { "Attempted to read value as double, but value is not of type double" }
+ return doubleList[0]
+ }
+
+ /**
+ * Returns this [Value] represented as a `double[]`.
+ *
+ * @throws IllegalStateException if [isDoubleArray] is `false`
+ */
+ public fun asDoubleArray(): DoubleArray {
+ check(isDoubleArray) {
+ "Attempted to read value as double array, but value is not correct type"
+ }
+ return doubleList.toDoubleArray()
+ }
+
+ /** Whether or not this [Value] can be represented as an `long`. */
+ public val isLong: Boolean
+ get() = format == FORMAT_LONG
+
+ /** Whether or not this [Value] can be represented as an `boolean`. */
+ public val isBoolean: Boolean
+ get() = format == FORMAT_BOOLEAN
+
+ /** Whether or not this [Value] can be represented as a `double`. */
+ public val isDouble: Boolean
+ get() = format == FORMAT_DOUBLE
+
+ /** Whether or not this [Value] can be represented as a `double[]`. */
+ public val isDoubleArray: Boolean
+ get() = format == FORMAT_DOUBLE_ARRAY
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ /**
+ * Writes the value of this object to [dest].
+ *
+ * @throws IllegalStateException if [format] is invalid
+ */
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ val format = format
+ dest.writeInt(format)
+ when (format) {
+ FORMAT_BOOLEAN, FORMAT_LONG -> {
+ dest.writeLong(longValue)
+ return
+ }
+ FORMAT_DOUBLE -> {
+ dest.writeDouble(asDouble())
+ return
+ }
+ FORMAT_DOUBLE_ARRAY -> {
+ val doubleArray = asDoubleArray()
+ dest.writeInt(doubleArray.size)
+ dest.writeDoubleArray(doubleArray)
+ return
+ }
+ else -> {}
+ }
+ throw IllegalStateException(String.format("Unexpected format: %s", format))
+ }
+
+ public companion object {
+ /** The format used for a [Value] represented as a `double`. */
+ public const val FORMAT_DOUBLE: Int = 1
+
+ /** The format used for a [Value] represented as an `long`. */
+ public const val FORMAT_LONG: Int = 2
+
+ /** The format used for a [Value] represented as an `boolean`. */
+ public const val FORMAT_BOOLEAN: Int = 4
+
+ /** The format used for a [Value] represented as a `double[]`. */
+ public const val FORMAT_DOUBLE_ARRAY: Int = 3
+
+ @JvmField
+ public val CREATOR: Parcelable.Creator<Value> =
+ object : Parcelable.Creator<Value> {
+ override fun createFromParcel(parcel: Parcel): Value {
+ val format = parcel.readInt()
+ when (format) {
+ FORMAT_BOOLEAN, FORMAT_LONG ->
+ return Value(format, listOf(), parcel.readLong())
+ FORMAT_DOUBLE ->
+ return Value(format, listOf(parcel.readDouble()), /* longValue= */ 0)
+ FORMAT_DOUBLE_ARRAY -> {
+ val doubleArray = DoubleArray(parcel.readInt())
+ parcel.readDoubleArray(doubleArray)
+ return Value(format, doubleArray.toList(), /* longValue= */ 0)
+ }
+ else -> {}
+ }
+ throw IllegalStateException(String.format("Unexpected format: %s", format))
+ }
+
+ override fun newArray(size: Int): Array<Value?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /** Creates a [Value] that represents a `long`. */
+ @JvmStatic
+ public fun ofLong(value: Long): Value {
+ return Value(FORMAT_LONG, listOf(), value)
+ }
+
+ /** Creates a [Value] that represents an `boolean`. */
+ @JvmStatic
+ public fun ofBoolean(value: Boolean): Value {
+ return Value(FORMAT_BOOLEAN, listOf(), if (value) 1 else 0)
+ }
+
+ /** Creates a [Value] that represents a `double`. */
+ @JvmStatic
+ public fun ofDouble(value: Double): Value {
+ return Value(FORMAT_DOUBLE, listOf(value), longValue = 0)
+ }
+
+ /** Creates a [Value] that represents a `double[]`. */
+ @JvmStatic
+ public fun ofDoubleArray(vararg doubleArray: Double): Value {
+ return Value(FORMAT_DOUBLE_ARRAY, doubleArray.toList(), longValue = 0)
+ }
+
+ /**
+ * Compares two [Value] s based on their representation.
+ *
+ * @throws IllegalStateException if `first` and `second` do not share the same format or are
+ * represented as a `double[]`
+ */
+ internal fun compare(first: Value, second: Value): Int {
+ check(first.format == second.format) {
+ "Attempted to compare Values with different formats"
+ }
+ when (first.format) {
+ FORMAT_LONG -> return first.longValue.compareTo(second.longValue)
+ FORMAT_BOOLEAN -> return first.asBoolean().compareTo(second.asBoolean())
+ FORMAT_DOUBLE -> return first.doubleList[0].compareTo(second.doubleList[0])
+ FORMAT_DOUBLE_ARRAY ->
+ throw IllegalStateException(
+ "Attempted to compare Values with invalid format (double array)"
+ )
+ else -> {}
+ }
+ throw IllegalStateException(String.format("Unexpected format: %s", first.format))
+ }
+
+ /**
+ * Adds two [Value] s based on their representation.
+ *
+ * @throws IllegalStateException if `first` and `second` do not share the same format or are
+ * represented as a `double[]` or `boolean`
+ */
+ @JvmStatic
+ public fun sum(first: Value, second: Value): Value {
+ require(first.format == second.format) {
+ "Attempted to add Values with different formats"
+ }
+ when (first.format) {
+ FORMAT_LONG -> return ofLong(first.asLong() + second.asLong())
+ FORMAT_DOUBLE -> return ofDouble(first.asDouble() + second.asDouble())
+ FORMAT_BOOLEAN ->
+ throw IllegalStateException(
+ "Attempted to add Values with invalid format (boolean)"
+ )
+ FORMAT_DOUBLE_ARRAY ->
+ throw IllegalStateException(
+ "Attempted to add Values with invalid format (double array)"
+ )
+ else -> {}
+ }
+ throw IllegalStateException(String.format("Unexpected format: %s", first.format))
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/event/Event.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/event/Event.kt
new file mode 100644
index 0000000..5e28beb
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/event/Event.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 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.health.services.client.data.event
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataPoint
+import androidx.health.services.client.data.DataTypeCondition
+
+/** Defines an event that will be triggered when the specified condition is met. */
+public data class Event(
+ /** [DataTypeCondition] which must be met for the event to be triggered. */
+ val dataTypeCondition: DataTypeCondition,
+ val triggerType: TriggerType,
+) : Parcelable {
+
+ /** Whether or not repeated events should be triggered. */
+ public enum class TriggerType(public val id: Int) {
+ /** The event will trigger the first time the specified conditions are met. */
+ ONCE(1),
+
+ /** The event will trigger each time the specified conditions *become* met. */
+ REPEATED(2);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): TriggerType? = values().firstOrNull { it.id == id }
+ }
+ }
+
+ /**
+ * Does the provided [DataPoint] satisfy the event condition.
+ *
+ * @throws IllegalArgumentException if the provided data point is not of the same data type as
+ * the condition itself.
+ */
+ public fun isTriggered(dataPoint: DataPoint): Boolean {
+ return dataTypeCondition.isSatisfied(dataPoint)
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(dataTypeCondition, flags)
+ dest.writeInt(triggerType.id)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<Event> =
+ object : Parcelable.Creator<Event> {
+ override fun createFromParcel(source: Parcel): Event? {
+ return Event(
+ source.readParcelable(DataTypeCondition::class.java.classLoader)
+ ?: return null,
+ TriggerType.fromId(source.readInt()) ?: return null
+ )
+ }
+
+ override fun newArray(size: Int): Array<Event?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseIpcClient.kt
new file mode 100644
index 0000000..7ab0e8a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseIpcClient.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service to make Exercise API calls.
+ *
+ * @hide
+ */
+public class ExerciseIpcClient internal constructor(connectionManager: ConnectionManager) :
+ Client(
+ CLIENT_CONFIGURATION,
+ connectionManager,
+ VersionGetter { binder: IBinder -> getServiceInterface(binder).apiVersion }
+ ) {
+
+ public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+ return super.execute(operation)
+ }
+
+ public override fun <T> registerListener(
+ listenerKey: ListenerKey,
+ registerListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.registerListener(listenerKey, registerListenerOperation)
+ }
+
+ public override fun <T> unregisterListener(
+ listenerKey: ListenerKey,
+ unregisterListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.unregisterListener(listenerKey, unregisterListenerOperation)
+ }
+
+ public companion object {
+ public const val SERVICE_BIND_ACTION: String =
+ "com.google.android.wearable.healthservices.ExerciseClient"
+ private const val CLIENT = "HealthServicesExerciseClient"
+ private const val SERVICE_PACKAGE_NAME = "com.google.android.wearable.healthservices"
+ private val CLIENT_CONFIGURATION =
+ ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+ @JvmStatic
+ internal fun getServiceInterface(binder: IBinder): IExerciseApiService {
+ return IExerciseApiService.Stub.asInterface(binder)
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
new file mode 100644
index 0000000..d48bee2
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import androidx.annotation.GuardedBy
+import androidx.health.services.client.ExerciseUpdateListener
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import androidx.health.services.client.impl.response.ExerciseLapSummaryResponse
+import androidx.health.services.client.impl.response.ExerciseUpdateResponse
+import java.util.HashMap
+import java.util.concurrent.Executor
+
+/**
+ * A stub implementation for IExerciseUpdateListener.
+ *
+ * @hide
+ */
+public class ExerciseUpdateListenerStub
+private constructor(private val listener: ExerciseUpdateListener, private val executor: Executor) :
+ IExerciseUpdateListener.Stub() {
+
+ public val listenerKey: ListenerKey = ListenerKey(listener)
+
+ override fun onExerciseUpdate(response: ExerciseUpdateResponse) {
+ executor.execute { listener.onExerciseUpdate(response.exerciseUpdate) }
+ }
+
+ override fun onLapSummary(response: ExerciseLapSummaryResponse) {
+ executor.execute { listener.onLapSummary(response.exerciseLapSummary) }
+ }
+
+ /**
+ * A class that stores unique active instances of [ExerciseUpdateListener] to ensure same binder
+ * object is passed by framework to service side of the IPC.
+ */
+ public class ExerciseUpdateListenerCache private constructor() {
+ @GuardedBy("this")
+ private val listeners: MutableMap<ExerciseUpdateListener, ExerciseUpdateListenerStub> =
+ HashMap()
+
+ @Synchronized
+ public fun getOrCreate(
+ listener: ExerciseUpdateListener,
+ executor: Executor
+ ): ExerciseUpdateListenerStub {
+ return listeners.getOrPut(listener) { ExerciseUpdateListenerStub(listener, executor) }
+ }
+
+ @Synchronized
+ public fun remove(
+ exerciseUpdateListener: ExerciseUpdateListener
+ ): ExerciseUpdateListenerStub? {
+ return listeners.remove(exerciseUpdateListener)
+ }
+
+ public companion object {
+ @JvmField
+ public val INSTANCE: ExerciseUpdateListenerCache = ExerciseUpdateListenerCache()
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/HealthServicesIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/HealthServicesIpcClient.kt
new file mode 100644
index 0000000..f46fda7
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/HealthServicesIpcClient.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service.
+ *
+ * @hide
+ */
+public class HealthServicesIpcClient internal constructor(connectionManager: ConnectionManager) :
+ Client(
+ CLIENT_CONFIGURATION,
+ connectionManager,
+ VersionGetter { binder -> getServiceInterface(binder).apiVersion }
+ ) {
+
+ public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+ return super.execute(operation)
+ }
+
+ public override fun <T> registerListener(
+ listenerKey: ListenerKey,
+ registerListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.registerListener(listenerKey, registerListenerOperation)
+ }
+
+ public override fun <T> unregisterListener(
+ listenerKey: ListenerKey,
+ unregisterListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.unregisterListener(listenerKey, unregisterListenerOperation)
+ }
+
+ public companion object {
+ private const val CLIENT = "HealthServicesClient"
+ private const val SERVICE_PACKAGE_NAME = "com.google.android.wearable.healthservices"
+ public const val SERVICE_BIND_ACTION: String =
+ "com.google.android.wearable.healthservices.HealthServicesClient"
+ private val CLIENT_CONFIGURATION =
+ ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+ @JvmStatic
+ internal fun getServiceInterface(binder: IBinder): IHealthServicesApiService {
+ return IHealthServicesApiService.Stub.asInterface(binder)
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt
new file mode 100644
index 0000000..476c6f6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
+import androidx.health.services.client.MeasureCallback
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import androidx.health.services.client.impl.response.AvailabilityResponse
+import androidx.health.services.client.impl.response.DataPointsResponse
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.HashMap
+import java.util.concurrent.Executor
+
+/**
+ * A stub implementation for IMeasureCallback.
+ *
+ * @hide
+ */
+public class MeasureCallbackStub
+private constructor(callbackKey: MeasureCallbackKey, private val callback: MeasureCallback) :
+ IMeasureCallback.Stub() {
+
+ public val listenerKey: ListenerKey = ListenerKey(callbackKey)
+
+ @get:VisibleForTesting
+ public var executor: Executor = MoreExecutors.directExecutor()
+ private set
+
+ override fun onAvailabilityChanged(response: AvailabilityResponse) {
+ executor.execute {
+ callback.onAvailabilityChanged(response.dataType, response.availability)
+ }
+ }
+
+ override fun onData(response: DataPointsResponse) {
+ executor.execute { callback.onData(response.dataPoints) }
+ }
+
+ /**
+ * Its important to use the same stub for registration and un-registration, to ensure same
+ * binder object is passed by framework to service side of the IPC.
+ */
+ public class MeasureCallbackCache private constructor() {
+ @GuardedBy("this")
+ private val listeners: MutableMap<MeasureCallbackKey, MeasureCallbackStub> = HashMap()
+
+ @Synchronized
+ public fun getOrCreate(
+ dataType: DataType,
+ measureCallback: MeasureCallback,
+ executor: Executor
+ ): MeasureCallbackStub {
+ val callbackKey = MeasureCallbackKey(dataType, measureCallback)
+
+ // If a measure callback happens for the same datatype with same callback, pass the same
+ // stub instance, but update the executor, as executor might have changed. Its ok to
+ // register
+ // the callback once again, as on our service implementation re-registering for same
+ // datatype
+ // with same callback is a no-op.
+ var measureCallbackStub = listeners[callbackKey]
+ if (measureCallbackStub == null) {
+ measureCallbackStub = MeasureCallbackStub(callbackKey, measureCallback)
+ listeners[callbackKey] = measureCallbackStub
+ }
+ measureCallbackStub.executor = executor
+ return measureCallbackStub
+ }
+
+ @Synchronized
+ public fun remove(
+ dataType: DataType,
+ measureCallback: MeasureCallback
+ ): MeasureCallbackStub? {
+ val callbackKey = MeasureCallbackKey(dataType, measureCallback)
+ return listeners.remove(callbackKey)
+ }
+
+ public companion object {
+ @JvmField public val INSTANCE: MeasureCallbackCache = MeasureCallbackCache()
+ }
+ }
+
+ private data class MeasureCallbackKey(
+ private val dataType: DataType,
+ private val measureCallback: MeasureCallback
+ )
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureIpcClient.kt
new file mode 100644
index 0000000..e816a0a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureIpcClient.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service to make Measure API calls.
+ *
+ * @hide
+ */
+public class MeasureIpcClient internal constructor(connectionManager: ConnectionManager) :
+ Client(
+ CLIENT_CONFIGURATION,
+ connectionManager,
+ VersionGetter { binder -> getServiceInterface(binder).apiVersion }
+ ) {
+
+ public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+ return super.execute(operation)
+ }
+
+ public override fun <T> registerListener(
+ listenerKey: ListenerKey,
+ registerListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.registerListener(listenerKey, registerListenerOperation)
+ }
+
+ public override fun <T> unregisterListener(
+ listenerKey: ListenerKey,
+ unregisterListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.unregisterListener(listenerKey, unregisterListenerOperation)
+ }
+
+ public companion object {
+ public const val SERVICE_BIND_ACTION: String =
+ "com.google.android.wearable.healthservices.MeasureClient"
+ public const val CLIENT: String = "HealthServicesMeasureClient"
+ public const val SERVICE_PACKAGE_NAME: String = "com.google.android.wearable.healthservices"
+ private val CLIENT_CONFIGURATION =
+ ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+ @JvmStatic
+ internal fun getServiceInterface(binder: IBinder): IMeasureApiService {
+ return IMeasureApiService.Stub.asInterface(binder)
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringCallbackStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringCallbackStub.kt
new file mode 100644
index 0000000..700f1af
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringCallbackStub.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import androidx.health.services.client.PassiveMonitoringCallback
+import androidx.health.services.client.impl.response.PassiveActivityStateResponse
+
+/**
+ * A stub implementation for IPassiveMonitoringCallback.
+ *
+ * @hide
+ */
+internal class PassiveMonitoringCallbackStub
+internal constructor(private val callback: PassiveMonitoringCallback) :
+ IPassiveMonitoringCallback.Stub() {
+
+ override fun onPassiveActivityState(response: PassiveActivityStateResponse) {
+ callback.onPassiveActivityState(response.passiveActivityState)
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringIpcClient.kt
new file mode 100644
index 0000000..acbe468
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringIpcClient.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service to make passive monitoring
+ * API calls.
+ *
+ * @hide
+ */
+public class PassiveMonitoringIpcClient(connectionManager: ConnectionManager) :
+ Client(
+ CLIENT_CONFIGURATION,
+ connectionManager,
+ VersionGetter { binder -> getServiceInterface(binder).apiVersion }
+ ) {
+
+ public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+ return super.execute(operation)
+ }
+
+ public companion object {
+ public const val SERVICE_BIND_ACTION: String =
+ "com.google.android.wearable.healthservices.PassiveMonitoringClient"
+ private const val CLIENT = "HealthServicesPassiveMonitoringClient"
+ private const val SERVICE_PACKAGE_NAME = "com.google.android.wearable.healthservices"
+ private val CLIENT_CONFIGURATION =
+ ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+ @JvmStatic
+ internal fun getServiceInterface(binder: IBinder): IPassiveMonitoringApiService {
+ return IPassiveMonitoringApiService.Stub.asInterface(binder)
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
new file mode 100644
index 0000000..adff9c2
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.content.Context
+import androidx.core.content.ContextCompat
+import androidx.health.services.client.ExerciseClient
+import androidx.health.services.client.ExerciseUpdateListener
+import androidx.health.services.client.data.Capabilities
+import androidx.health.services.client.data.ExerciseConfig
+import androidx.health.services.client.data.ExerciseGoal
+import androidx.health.services.client.data.ExerciseInfo
+import androidx.health.services.client.impl.ExerciseIpcClient.Companion.getServiceInterface
+import androidx.health.services.client.impl.internal.ExerciseInfoCallback
+import androidx.health.services.client.impl.internal.StatusCallback
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest
+import androidx.health.services.client.impl.request.CapabilitiesRequest
+import androidx.health.services.client.impl.request.ExerciseGoalRequest
+import androidx.health.services.client.impl.request.StartExerciseRequest
+import androidx.health.services.client.impl.response.CapabilitiesResponse
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/**
+ * [ExerciseClient] API implementation that receives data from the WHS Service.
+ *
+ * @hide
+ */
+internal class ServiceBackedExerciseClient
+private constructor(private val context: Context, connectionManager: ConnectionManager) :
+ ExerciseClient {
+
+ private val ipcClient: ExerciseIpcClient = ExerciseIpcClient(connectionManager)
+
+ override fun startExercise(configuration: ExerciseConfig): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .startExercise(
+ StartExerciseRequest(context.packageName, configuration),
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun pauseExercise(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .pauseExercise(context.packageName, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun resumeExercise(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .resumeExercise(context.packageName, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun endExercise(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .endExercise(context.packageName, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun markLap(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .markLap(context.packageName, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override val currentExerciseInfo: ListenableFuture<ExerciseInfo>
+ get() {
+ val serviceOperation =
+ ServiceOperation<ExerciseInfo> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .getCurrentExerciseInfo(
+ context.packageName,
+ ExerciseInfoCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun setUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void> {
+ return setUpdateListener(listener, ContextCompat.getMainExecutor(context))
+ }
+
+ override fun setUpdateListener(
+ listener: ExerciseUpdateListener,
+ executor: Executor
+ ): ListenableFuture<Void> {
+ val listenerStub =
+ ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.getOrCreate(
+ listener,
+ executor
+ )
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .setUpdateListener(
+ context.packageName,
+ listenerStub,
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.registerListener(listenerStub.listenerKey, serviceOperation)
+ }
+
+ override fun clearUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void> {
+ val listenerStub =
+ ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.remove(listener)
+ ?: return Futures.immediateFailedFuture(
+ IllegalArgumentException("Given listener was not added.")
+ )
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .clearUpdateListener(
+ context.packageName,
+ listenerStub,
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.unregisterListener(listenerStub.listenerKey, serviceOperation)
+ }
+
+ override fun addGoalToActiveExercise(exerciseGoal: ExerciseGoal): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .addGoalToActiveExercise(
+ ExerciseGoalRequest(context.packageName, exerciseGoal),
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun overrideAutoPauseAndResumeForActiveExercise(
+ enabled: Boolean
+ ): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .overrideAutoPauseAndResumeForActiveExercise(
+ AutoPauseAndResumeConfigRequest(context.packageName, enabled),
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override val capabilities: ListenableFuture<Capabilities>
+ get() {
+ val request = CapabilitiesRequest(context.packageName)
+ val serviceOperation =
+ ServiceOperation<CapabilitiesResponse> { binder, resultFuture ->
+ resultFuture.set(getServiceInterface(binder).getCapabilities(request))
+ }
+ return Futures.transform(
+ ipcClient.execute(serviceOperation),
+ { response -> response?.capabilities },
+ ContextCompat.getMainExecutor(context)
+ )
+ }
+
+ internal companion object {
+ @JvmStatic
+ fun getClient(context: Context): ServiceBackedExerciseClient {
+ return ServiceBackedExerciseClient(context, WhsConnectionManager.getInstance(context))
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedHealthServicesClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedHealthServicesClient.kt
new file mode 100644
index 0000000..ef052fb
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedHealthServicesClient.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.content.Context
+import androidx.health.services.client.ExerciseClient
+import androidx.health.services.client.HealthServicesClient
+import androidx.health.services.client.MeasureClient
+import androidx.health.services.client.PassiveMonitoringClient
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+
+/**
+ * A [HealthServicesClient] implementation.
+ *
+ * @hide
+ */
+public class ServiceBackedHealthServicesClient
+internal constructor(context: Context, connectionManager: ConnectionManager) :
+ HealthServicesClient {
+
+ private val applicationContext: Context = context.applicationContext
+ private val ipcClient: HealthServicesIpcClient = HealthServicesIpcClient(connectionManager)
+
+ public constructor(context: Context) : this(context, WhsConnectionManager.getInstance(context))
+
+ override val exerciseClient: ExerciseClient
+ get() = ServiceBackedExerciseClient.getClient(applicationContext)
+ override val passiveMonitoringClient: PassiveMonitoringClient
+ get() = ServiceBackedPassiveMonitoringClient(applicationContext)
+ override val measureClient: MeasureClient
+ get() = ServiceBackedMeasureClient.getClient(applicationContext)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt
new file mode 100644
index 0000000..51e21ed
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.ContextCompat
+import androidx.health.services.client.MeasureCallback
+import androidx.health.services.client.MeasureClient
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.MeasureCapabilities
+import androidx.health.services.client.impl.MeasureCallbackStub.MeasureCallbackCache
+import androidx.health.services.client.impl.MeasureIpcClient.Companion.getServiceInterface
+import androidx.health.services.client.impl.internal.StatusCallback
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.request.CapabilitiesRequest
+import androidx.health.services.client.impl.request.MeasureRegistrationRequest
+import androidx.health.services.client.impl.request.MeasureUnregistrationRequest
+import androidx.health.services.client.impl.response.MeasureCapabilitiesResponse
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/**
+ * [MeasureClient] that interacts with WHS via IPC.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class ServiceBackedMeasureClient(
+ private val context: Context,
+ connectionManager: ConnectionManager
+) : MeasureClient {
+
+ private val ipcClient: MeasureIpcClient = MeasureIpcClient(connectionManager)
+
+ override fun registerCallback(
+ dataType: DataType,
+ callback: MeasureCallback
+ ): ListenableFuture<Void> {
+ return registerCallback(dataType, callback, ContextCompat.getMainExecutor(context))
+ }
+
+ override fun registerCallback(
+ dataType: DataType,
+ callback: MeasureCallback,
+ executor: Executor
+ ): ListenableFuture<Void> {
+ val request = MeasureRegistrationRequest(context.packageName, dataType)
+ val callbackStub = MeasureCallbackCache.INSTANCE.getOrCreate(dataType, callback, executor)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .registerCallback(request, callbackStub, StatusCallback(resultFuture))
+ }
+ return ipcClient.registerListener(callbackStub.listenerKey, serviceOperation)
+ }
+
+ override fun unregisterCallback(
+ dataType: DataType,
+ callback: MeasureCallback
+ ): ListenableFuture<Void> {
+ val callbackStub =
+ MeasureCallbackCache.INSTANCE.remove(dataType, callback)
+ ?: return Futures.immediateFailedFuture(
+ IllegalArgumentException("Given callback was not registered.")
+ )
+ val request = MeasureUnregistrationRequest(context.packageName, dataType)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .unregisterCallback(request, callbackStub, StatusCallback(resultFuture))
+ }
+ return ipcClient.unregisterListener(callbackStub.listenerKey, serviceOperation)
+ }
+
+ override val capabilities: ListenableFuture<MeasureCapabilities>
+ get() {
+ val request = CapabilitiesRequest(context.packageName)
+ val serviceOperation =
+ ServiceOperation<MeasureCapabilitiesResponse> { binder, resultFuture ->
+ resultFuture.set(getServiceInterface(binder).getCapabilities(request))
+ }
+ return Futures.transform(
+ ipcClient.execute(serviceOperation),
+ { response -> response?.measureCapabilities },
+ ContextCompat.getMainExecutor(context)
+ )
+ }
+
+ internal companion object {
+ @JvmStatic
+ fun getClient(context: Context): ServiceBackedMeasureClient {
+ return ServiceBackedMeasureClient(context, WhsConnectionManager.getInstance(context))
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
new file mode 100644
index 0000000..f156c38
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.app.PendingIntent
+import android.content.Context
+import androidx.core.content.ContextCompat
+import androidx.health.services.client.PassiveMonitoringCallback
+import androidx.health.services.client.PassiveMonitoringClient
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.PassiveMonitoringCapabilities
+import androidx.health.services.client.data.event.Event
+import androidx.health.services.client.impl.PassiveMonitoringIpcClient.Companion.getServiceInterface
+import androidx.health.services.client.impl.internal.StatusCallback
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.request.BackgroundRegistrationRequest
+import androidx.health.services.client.impl.request.CapabilitiesRequest
+import androidx.health.services.client.impl.request.EventRequest
+import androidx.health.services.client.impl.response.PassiveMonitoringCapabilitiesResponse
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Passive monitoring client that interacts with WHS via IPC.
+ *
+ * @hide
+ */
+internal class ServiceBackedPassiveMonitoringClient(private val applicationContext: Context) :
+ PassiveMonitoringClient {
+
+ private val ipcClient: PassiveMonitoringIpcClient =
+ PassiveMonitoringIpcClient(WhsConnectionManager.getInstance(applicationContext))
+
+ override fun registerDataCallback(
+ dataTypes: Set<DataType>,
+ callbackIntent: PendingIntent
+ ): ListenableFuture<Void> {
+ return registerDataCallbackInternal(dataTypes, callbackIntent, callback = null)
+ }
+
+ override fun registerDataCallback(
+ dataTypes: Set<DataType>,
+ callbackIntent: PendingIntent,
+ callback: PassiveMonitoringCallback
+ ): ListenableFuture<Void> {
+ return registerDataCallbackInternal(dataTypes, callbackIntent, callback)
+ }
+
+ override fun unregisterDataCallback(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .unregisterDataCallback(
+ applicationContext.packageName,
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun registerEventCallback(
+ event: Event,
+ callbackIntent: PendingIntent
+ ): ListenableFuture<Void> {
+ val request = EventRequest(applicationContext.packageName, event)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .registerEventCallback(request, callbackIntent, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun unregisterEventCallback(event: Event): ListenableFuture<Void> {
+ val request = EventRequest(applicationContext.packageName, event)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .unregisterEventCallback(request, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override val capabilities: ListenableFuture<PassiveMonitoringCapabilities>
+ get() {
+ val request = CapabilitiesRequest(applicationContext.packageName)
+ val serviceOperation =
+ ServiceOperation<PassiveMonitoringCapabilitiesResponse> { binder, resultFuture ->
+ resultFuture.set(getServiceInterface(binder).getCapabilities(request))
+ }
+ return Futures.transform(
+ ipcClient.execute(serviceOperation),
+ { response -> response?.passiveMonitoringCapabilities },
+ ContextCompat.getMainExecutor(applicationContext)
+ )
+ }
+
+ private fun registerDataCallbackInternal(
+ dataTypes: Set<DataType>,
+ callbackIntent: PendingIntent,
+ callback: PassiveMonitoringCallback?
+ ): ListenableFuture<Void> {
+ val request = BackgroundRegistrationRequest(applicationContext.packageName, dataTypes)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .registerDataCallback(
+ request,
+ callbackIntent,
+ callback?.let { PassiveMonitoringCallbackStub(it) },
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt
new file mode 100644
index 0000000..078cc154
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal
+
+import android.os.RemoteException
+import androidx.health.services.client.data.ExerciseInfo
+import androidx.health.services.client.impl.response.ExerciseInfoResponse
+import com.google.common.util.concurrent.SettableFuture
+
+/**
+ * A callback for ipc invocations dealing with [ExerciseInfo].
+ *
+ * @hide
+ */
+public class ExerciseInfoCallback(private val resultFuture: SettableFuture<ExerciseInfo>) :
+ IExerciseInfoCallback.Stub() {
+
+ @Throws(RemoteException::class)
+ override fun onExerciseInfo(response: ExerciseInfoResponse) {
+ resultFuture.set(response.exerciseInfo)
+ }
+
+ @Throws(RemoteException::class)
+ override fun onFailure(message: String) {
+ resultFuture.setException(Exception(message))
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt
new file mode 100644
index 0000000..89789e6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal
+
+import android.os.RemoteException
+import com.google.common.util.concurrent.SettableFuture
+
+/**
+ * A generic callback for ipc invocations.
+ *
+ * @hide
+ */
+public class StatusCallback(private val resultFuture: SettableFuture<Void>) :
+ IStatusCallback.Stub() {
+
+ @Throws(RemoteException::class)
+ override fun onSuccess() {
+ resultFuture.set(null)
+ }
+
+ @Throws(RemoteException::class)
+ override fun onFailure(msg: String) {
+ resultFuture.setException(Exception(msg))
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/WhsConnectionManager.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/WhsConnectionManager.kt
new file mode 100644
index 0000000..eb701b6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/WhsConnectionManager.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
+import androidx.annotation.GuardedBy
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+
+/**
+ * Utility to return an instance of connection manager.
+ *
+ * @hide
+ */
+public object WhsConnectionManager {
+
+ private val lock = Any()
+
+ // Suppress StaticFieldLeak; we're only storing application Context.
+ @SuppressLint("StaticFieldLeak")
+ @GuardedBy("lock")
+ private lateinit var instance: ConnectionManager
+
+ @JvmStatic
+ public fun getInstance(context: Context): ConnectionManager {
+ synchronized(lock) {
+ if (!::instance.isInitialized) {
+ val looper = startHandlerThread()
+ instance = ConnectionManager(context.applicationContext, looper)
+ }
+
+ return instance
+ }
+ }
+
+ private fun startHandlerThread(): Looper {
+ val handlerThread =
+ HandlerThread(
+ "WhsConnectionManager",
+ Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE
+ )
+ handlerThread.start()
+ return handlerThread.looper
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ApiVersionException.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ApiVersionException.java
new file mode 100644
index 0000000..134ffa3
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ApiVersionException.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Exception that is thrown when API version requirements are not met.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class ApiVersionException extends ExecutionException {
+
+ private final int mRemoteVersion;
+ private final int mMinVersion;
+
+ public ApiVersionException(int remoteVersion, int minVersion) {
+ super(
+ "Version requirements for calling the method was not met, remoteVersion: "
+ + remoteVersion
+ + ", minVersion: "
+ + minVersion);
+ this.mRemoteVersion = remoteVersion;
+ this.mMinVersion = minVersion;
+ }
+
+ public int getRemoteVersion() {
+ return mRemoteVersion;
+ }
+
+ public int getMinVersion() {
+ return mMinVersion;
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/Client.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/Client.java
new file mode 100644
index 0000000..b417620
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/Client.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.VisibleForTesting;
+import androidx.health.services.client.impl.ipc.internal.BaseQueueOperation;
+import androidx.health.services.client.impl.ipc.internal.ConnectionConfiguration;
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager;
+import androidx.health.services.client.impl.ipc.internal.ExecutionTracker;
+import androidx.health.services.client.impl.ipc.internal.ListenerKey;
+import androidx.health.services.client.impl.ipc.internal.QueueOperation;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * SDK client for establishing connection to a cross process service.
+ *
+ * <p>Extend this class to create a new client. Each client should represent one connection to AIDL
+ * interface. For user instruction see: go/wear-dd-wcs-sdk
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public abstract class Client {
+
+ /** Interface abstracting extraction of the API version from the binder. */
+ public interface VersionGetter {
+ /** Returns the API version. */
+ Integer readVersion(IBinder binder) throws RemoteException;
+ }
+
+ private static final int UNKNOWN_VERSION = -1;
+
+ private final ConnectionConfiguration mConnectionConfiguration;
+ private final ConnectionManager mConnectionManager;
+ private final ServiceOperation<Integer> mApiVersionOperation;
+
+ @VisibleForTesting volatile int mCurrentVersion = UNKNOWN_VERSION;
+
+ public Client(
+ ClientConfiguration clientConfiguration,
+ ConnectionManager connectionManager,
+ VersionGetter versionGetter) {
+ QueueOperation versionOperation =
+ new QueueOperation() {
+ @Override
+ public void execute(IBinder binder) throws RemoteException {
+ mCurrentVersion = versionGetter.readVersion(binder);
+ }
+
+ @Override
+ public void setException(Throwable exception) {}
+
+ @Override
+ public QueueOperation trackExecution(ExecutionTracker tracker) {
+ return this;
+ }
+
+ @Override
+ public ConnectionConfiguration getConnectionConfiguration() {
+ return Client.this.getConnectionConfiguration();
+ }
+ };
+ this.mConnectionConfiguration =
+ new ConnectionConfiguration(
+ clientConfiguration.getServicePackageName(),
+ clientConfiguration.getApiClientName(),
+ clientConfiguration.getBindAction(),
+ versionOperation);
+ this.mConnectionManager = connectionManager;
+ this.mApiVersionOperation =
+ (binder, resultFuture) -> resultFuture.set(versionGetter.readVersion(binder));
+ }
+
+ /**
+ * Executes given operation against a IPC service defined by {@code clientConfiguration}.
+ *
+ * @param operation Operation that will be executed against the service
+ * @param <R> Type of returned variable
+ * @return {@link ListenableFuture<R>} with the result of the operation or an exception if the
+ * execution fails.
+ */
+ protected <R> ListenableFuture<R> execute(ServiceOperation<R> operation) {
+ SettableFuture<R> settableFuture = SettableFuture.create();
+ mConnectionManager.scheduleForExecution(
+ createQueueOperation(operation, mConnectionConfiguration, settableFuture));
+ return settableFuture;
+ }
+
+ protected <R> ListenableFuture<R> executeWithVersionCheck(
+ ServiceOperation<R> operation, int minApiVersion) {
+ if (mCurrentVersion == UNKNOWN_VERSION) {
+ SettableFuture<R> settableFuture = SettableFuture.create();
+ ListenableFuture<Integer> versionFuture = execute(mApiVersionOperation);
+ Futures.addCallback(
+ versionFuture,
+ new FutureCallback<Integer>() {
+ @Override
+ public void onSuccess(@Nullable Integer remoteVersion) {
+ mCurrentVersion =
+ remoteVersion == null ? UNKNOWN_VERSION : remoteVersion;
+ if (mCurrentVersion < minApiVersion) {
+ settableFuture.setException(
+ getApiVersionCheckFailureException(
+ mCurrentVersion, minApiVersion));
+ } else {
+ getConnectionManager()
+ .scheduleForExecution(
+ createQueueOperation(
+ operation,
+ getConnectionConfiguration(),
+ settableFuture));
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable throwable) {
+ settableFuture.setException(throwable);
+ }
+ },
+ MoreExecutors.directExecutor());
+ return settableFuture;
+ } else if (mCurrentVersion >= minApiVersion) {
+ return execute(operation);
+ } else {
+ // This empty operation is executed just to connect to the service. If we didn't connect
+ // it
+ // could happen that we won't detect change in the API version.
+ mConnectionManager.scheduleForExecution(
+ new BaseQueueOperation(mConnectionConfiguration));
+ return Futures.immediateFailedFuture(
+ getApiVersionCheckFailureException(mCurrentVersion, minApiVersion));
+ }
+ }
+
+ /**
+ * Registers a listener by executing the provided {@link ServiceOperation}.
+ *
+ * <p>The provided {@code registerListenerOperation} will be stored for every unique {@code
+ * listenerKey} and re-executed when connection is lost.
+ *
+ * @param listenerKey Key based on which listeners will be distinguished.
+ * @param registerListenerOperation Method that registers the listener, can by any {@link
+ * ServiceOperation}.
+ * @param <R> Type of return value returned in the future.
+ * @return {@link ListenableFuture<R>} with the result of the operation or an exception if the
+ * execution fails.
+ */
+ protected <R> ListenableFuture<R> registerListener(
+ ListenerKey listenerKey, ServiceOperation<R> registerListenerOperation) {
+ SettableFuture<R> settableFuture = SettableFuture.create();
+ mConnectionManager.registerListener(
+ listenerKey,
+ createQueueOperation(
+ registerListenerOperation, mConnectionConfiguration, settableFuture));
+ return settableFuture;
+ }
+
+ /**
+ * Unregisters a listener by executing the provided {@link ServiceOperation}.
+ *
+ * @param listenerKey Key based on which listeners will be distinguished.
+ * @param unregisterListenerOperation Method that unregisters the listener, can by any {@link
+ * ServiceOperation}.
+ * @param <R> Type of return value returned in the future.
+ * @return {@link ListenableFuture<R>} with the result of the operation or an exception if the
+ * execution fails.
+ */
+ protected <R> ListenableFuture<R> unregisterListener(
+ ListenerKey listenerKey, ServiceOperation<R> unregisterListenerOperation) {
+ SettableFuture<R> settableFuture = SettableFuture.create();
+ mConnectionManager.unregisterListener(
+ listenerKey,
+ createQueueOperation(
+ unregisterListenerOperation, getConnectionConfiguration(), settableFuture));
+ return settableFuture;
+ }
+
+ protected Exception getApiVersionCheckFailureException(int currentVersion, int minApiVersion) {
+ return new ApiVersionException(currentVersion, minApiVersion);
+ }
+
+ ConnectionConfiguration getConnectionConfiguration() {
+ return mConnectionConfiguration;
+ }
+
+ ConnectionManager getConnectionManager() {
+ return mConnectionManager;
+ }
+
+ private static <R> QueueOperation createQueueOperation(
+ ServiceOperation<R> operation,
+ ConnectionConfiguration connectionConfiguration,
+ SettableFuture<R> settableFuture) {
+ return new BaseQueueOperation(connectionConfiguration) {
+ @Override
+ public void execute(IBinder binder) throws RemoteException {
+ operation.execute(binder, settableFuture);
+ }
+
+ @Override
+ public void setException(Throwable exception) {
+ settableFuture.setException(exception);
+ }
+
+ @Override
+ public QueueOperation trackExecution(ExecutionTracker tracker) {
+ tracker.track(settableFuture);
+ return this;
+ }
+ };
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java
new file mode 100644
index 0000000..cf824d1
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * An interface that provides basic information about the IPC service. This is required for building
+ * the service in {@link Client}.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class ClientConfiguration {
+ private final String mServicePackageName;
+ private final String mBindAction;
+ private final String mApiClientName;
+
+ public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {
+ this.mServicePackageName = servicePackageName;
+ this.mBindAction = bindAction;
+ this.mApiClientName = apiClientName;
+ }
+
+ /** Returns the application package of the remote service. */
+ public String getServicePackageName() {
+ return mServicePackageName;
+ }
+
+ /** Returns the action used to bind to the remote service. */
+ public String getBindAction() {
+ return mBindAction;
+ }
+
+ /** Returns name of the service, use for logging and debugging only. */
+ public String getApiClientName() {
+ return mApiClientName;
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java
new file mode 100644
index 0000000..c15503e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * General operation that will be executed against given service. A new operation can be created by
+ * implementing this interface. User is then responsible for setting the result Future with the
+ * result value.
+ *
+ * @param <R> Type of the returned value.
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public interface ServiceOperation<R> {
+
+ /**
+ * Method executed against the service.
+ *
+ * @param binder Already connected binder to the target service.
+ * @param resultFuture A {@link SettableFuture} that should be set with the result.
+ */
+ void execute(IBinder binder, SettableFuture<R> resultFuture) throws RemoteException;
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java
new file mode 100644
index 0000000..079d509
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * Abstract implementation of QueueOperation that accepts {@link ConnectionConfiguration} describing
+ * the service where it will be executed.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class BaseQueueOperation implements QueueOperation {
+ private final ConnectionConfiguration mConnectionConfiguration;
+
+ public BaseQueueOperation(ConnectionConfiguration connectionConfiguration) {
+ this.mConnectionConfiguration = checkNotNull(connectionConfiguration);
+ }
+
+ @Override
+ public void execute(IBinder binder) throws RemoteException {}
+
+ @Override
+ public void setException(Throwable exception) {}
+
+ @Override
+ public QueueOperation trackExecution(ExecutionTracker tracker) {
+ return this;
+ }
+
+ /** Configuration of the service connection on which the operation will be executed. */
+ @Override
+ public ConnectionConfiguration getConnectionConfiguration() {
+ return mConnectionConfiguration;
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java
new file mode 100644
index 0000000..9b6c42b
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.health.services.client.impl.ipc.ClientConfiguration;
+
+/**
+ * Internal representation of configuration of IPC service connection.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public final class ConnectionConfiguration {
+ private final String mPackageName;
+ private final String mClientName;
+ private final String mBindAction;
+ private final QueueOperation mRefreshVersionOperation;
+
+ public ConnectionConfiguration(
+ String packageName,
+ String clientName,
+ String bindAction,
+ QueueOperation refreshVersionOperation) {
+ this.mPackageName = checkNotNull(packageName);
+ this.mClientName = checkNotNull(clientName);
+ this.mBindAction = checkNotNull(bindAction);
+ this.mRefreshVersionOperation = checkNotNull(refreshVersionOperation);
+ }
+
+ /** A key that defines the connection among other IPC connections. It should be unique. */
+ String getKey() {
+ return String.format("%s#%s#%s", mClientName, mPackageName, mBindAction);
+ }
+
+ /** API Client name defined in the {@link ClientConfiguration#getApiClientName()}. */
+ String getClientName() {
+ return mClientName;
+ }
+
+ /**
+ * An action used to bind to the remote service. Taken from {@link
+ * ClientConfiguration#getBindAction()}.
+ */
+ String getBindAction() {
+ return mBindAction;
+ }
+
+ /**
+ * Package name of remote service. Taken from {@link
+ * ClientConfiguration#getServicePackageName()}.
+ */
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ QueueOperation getRefreshVersionOperation() {
+ return mRefreshVersionOperation;
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java
new file mode 100644
index 0000000..739a531
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages connections to a service in a different process.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public final class ConnectionManager implements Handler.Callback, ServiceConnection.Callback {
+ private static final String TAG = "ConnectionManager";
+
+ private static final int MSG_CONNECTED = 1;
+ private static final int MSG_DISCONNECTED = 2;
+ private static final int MSG_EXECUTE = 3;
+ private static final int MSG_REGISTER_LISTENER = 4;
+ private static final int MSG_UNREGISTER_LISTENER = 5;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Map<String, ServiceConnection> mServiceConnectionMap = new HashMap<>();
+
+ private boolean mBindToSelfEnabled;
+
+ public ConnectionManager(Context context, Looper looper) {
+ this.mContext = context;
+ this.mHandler = new Handler(looper, this);
+ }
+
+ /**
+ * Schedules operation for execution
+ *
+ * @param operation Operation prepared for scheduling on the connection queue.
+ */
+ public void scheduleForExecution(QueueOperation operation) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_EXECUTE, operation));
+ }
+
+ /**
+ * Registers a listener by executing an operation represented by the {@link QueueOperation}.
+ *
+ * @param listenerKey Key based on which listeners will be distinguished.
+ * @param registerOperation Queue operation executed against the corresponding connection to
+ * register the listener. Will be used to re-register when connection is lost.
+ */
+ public void registerListener(ListenerKey listenerKey, QueueOperation registerOperation) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(
+ MSG_REGISTER_LISTENER, new ListenerHolder(listenerKey, registerOperation)));
+ }
+
+ /**
+ * Unregisters a listener by executing an operation represented by the {@link QueueOperation}.
+ *
+ * @param listenerKey Key based on which listeners will be distinguished.
+ * @param unregisterOperation Queue operation executed against the corresponding connection to
+ * unregister the listener.
+ */
+ public void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterOperation) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(
+ MSG_UNREGISTER_LISTENER,
+ new ListenerHolder(listenerKey, unregisterOperation)));
+ }
+
+ @Override
+ public void onConnected(ServiceConnection connection) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTED, connection));
+ }
+
+ @Override
+ public void onDisconnected(ServiceConnection connection, long reconnectDelayMs) {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_DISCONNECTED, connection), reconnectDelayMs);
+ }
+
+ @Override
+ public boolean isBindToSelfEnabled() {
+ return mBindToSelfEnabled;
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CONNECTED:
+ ServiceConnection serviceConnection = ((ServiceConnection) msg.obj);
+ serviceConnection.reRegisterAllListeners();
+ serviceConnection.refreshServiceVersion();
+ serviceConnection.flushQueue();
+ return true;
+ case MSG_DISCONNECTED:
+ ((ServiceConnection) msg.obj).maybeReconnect();
+ return true;
+ case MSG_EXECUTE:
+ QueueOperation queueOperation = (QueueOperation) msg.obj;
+ getConnection(queueOperation.getConnectionConfiguration()).enqueue(queueOperation);
+ return true;
+ case MSG_REGISTER_LISTENER:
+ ListenerHolder registerListenerHolder = (ListenerHolder) msg.obj;
+ getConnection(
+ registerListenerHolder
+ .getListenerOperation()
+ .getConnectionConfiguration())
+ .registerListener(
+ registerListenerHolder.getListenerKey(),
+ registerListenerHolder.getListenerOperation());
+ return true;
+ case MSG_UNREGISTER_LISTENER:
+ ListenerHolder unregisterListenerHolder = (ListenerHolder) msg.obj;
+ getConnection(
+ unregisterListenerHolder
+ .getListenerOperation()
+ .getConnectionConfiguration())
+ .unregisterListener(
+ unregisterListenerHolder.getListenerKey(),
+ unregisterListenerHolder.getListenerOperation());
+ return true;
+ default:
+ Log.e(TAG, "Received unknown message: " + msg.what);
+ return false;
+ }
+ }
+
+ public void setBindToSelf(boolean bindToSelfEnabled) {
+ this.mBindToSelfEnabled = bindToSelfEnabled;
+ }
+
+ private ServiceConnection getConnection(ConnectionConfiguration connectionConfiguration) {
+ String connectionKey = connectionConfiguration.getKey();
+ ServiceConnection serviceConnection = mServiceConnectionMap.get(connectionKey);
+ if (serviceConnection == null) {
+ serviceConnection =
+ new ServiceConnection(
+ mContext, connectionConfiguration, new DefaultExecutionTracker(), this);
+ mServiceConnectionMap.put(connectionKey, serviceConnection);
+ }
+ return serviceConnection;
+ }
+
+ private static class ListenerHolder {
+ private final ListenerKey mListenerKey;
+ private final QueueOperation mListenerOperation;
+
+ ListenerHolder(ListenerKey listenerKey, QueueOperation listenerOperation) {
+ this.mListenerKey = listenerKey;
+ this.mListenerOperation = listenerOperation;
+ }
+
+ ListenerKey getListenerKey() {
+ return mListenerKey;
+ }
+
+ QueueOperation getListenerOperation() {
+ return mListenerOperation;
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java
new file mode 100644
index 0000000..c8b527e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Default implementation of {@link ExecutionTracker}.
+ *
+ * @hide
+ */
+@SuppressWarnings("ExecutorTaskName")
+@RestrictTo(Scope.LIBRARY)
+public class DefaultExecutionTracker implements ExecutionTracker {
+ private final Set<SettableFuture<?>> mFuturesInProgress = new HashSet<>();
+
+ @Override
+ public void track(SettableFuture<?> future) {
+ mFuturesInProgress.add(future);
+ future.addListener(() -> mFuturesInProgress.remove(future), MoreExecutors.directExecutor());
+ }
+
+ @Override
+ public void cancelPendingFutures(Throwable throwable) {
+ for (SettableFuture<?> future : mFuturesInProgress) {
+ future.setException(throwable);
+ }
+ mFuturesInProgress.clear();
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java
new file mode 100644
index 0000000..eb9e81d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * Tracker for tracking operations that are currently in progress.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public interface ExecutionTracker {
+
+ /** Track given future as in progress. */
+ void track(SettableFuture<?> future);
+
+ /** Cancel all tracked futures with given exception. */
+ void cancelPendingFutures(Throwable throwable);
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java
new file mode 100644
index 0000000..f2960e3
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * Unique key to hold listener reference.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public final class ListenerKey {
+ private final Object mListenerKey;
+
+ public ListenerKey(Object listenerKey) {
+ this.mListenerKey = listenerKey;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ListenerKey)) {
+ return false;
+ }
+
+ ListenerKey that = (ListenerKey) o;
+ return mListenerKey.equals(that);
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(mListenerKey);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(mListenerKey);
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java
new file mode 100644
index 0000000..6021bdf
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * A wrapper for SDK operation that will be executed on a connected binder. It is intended for
+ * scheduling in execution queue.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public interface QueueOperation {
+ /**
+ * Method executed against the service.
+ *
+ * @param binder already connected to the target service.
+ */
+ void execute(IBinder binder) throws RemoteException;
+
+ /** Sets exception as the result of the operation. */
+ void setException(Throwable exception);
+
+ /**
+ * Tracks the operation execution with an {@link ExecutionTracker}.
+ *
+ * @param tracker To track the execution as in progress.
+ */
+ QueueOperation trackExecution(ExecutionTracker tracker);
+
+ /** Returns configuration of the service connection on which the operation will be executed. */
+ ConnectionConfiguration getConnectionConfiguration();
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java
new file mode 100644
index 0000000..fa432bc
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * A class that maintains a connection to IPC backend service. If connection is not available it
+ * uses a queue to store service requests until connection is renewed. One {@link ServiceConnection}
+ * is associated with one AIDL file .
+ *
+ * <p>Note: this class is not thread safe and should be called always from the same thread.
+ *
+ * @hide
+ */
+@NotThreadSafe
+@RestrictTo(Scope.LIBRARY)
+public class ServiceConnection implements android.content.ServiceConnection {
+ private static final String TAG = "ServiceConnection";
+
+ /** Callback for reporting back to the manager. */
+ public interface Callback {
+
+ /** Called when the connection to the server was successfully established. */
+ void onConnected(ServiceConnection connection);
+
+ /**
+ * Called when the connection to the server was lost.
+ *
+ * @param connection Represents this connection to a service.
+ * @param reconnectDelayMs Delay before the caller should try to reconnect this connection.
+ */
+ void onDisconnected(ServiceConnection connection, long reconnectDelayMs);
+
+ /**
+ * Return true if the {@link ServiceConnection} should bind to the service in the same
+ * application for testing reason.
+ */
+ boolean isBindToSelfEnabled();
+ }
+
+ private static final int MAX_RETRIES = 10;
+
+ private final Context mContext;
+ private final Queue<QueueOperation> mOperationQueue = new ConcurrentLinkedQueue<>();
+ private final ConnectionConfiguration mConnectionConfiguration;
+ private final ExecutionTracker mExecutionTracker;
+ private final Map<ListenerKey, QueueOperation> mRegisteredListeners = new HashMap<>();
+ private final Callback mCallback;
+
+ @VisibleForTesting @Nullable IBinder mBinder;
+ private volatile boolean mIsServiceBound;
+ /** Denotes how many times connection to the service failed and we retried. */
+ private int mServiceConnectionRetry;
+
+ ServiceConnection(
+ Context context,
+ ConnectionConfiguration connectionConfiguration,
+ ExecutionTracker executionTracker,
+ Callback callback) {
+ this.mContext = checkNotNull(context);
+ this.mConnectionConfiguration = checkNotNull(connectionConfiguration);
+ this.mExecutionTracker = checkNotNull(executionTracker);
+ this.mCallback = checkNotNull(callback);
+ }
+
+ private String getBindPackageName() {
+ if (mCallback.isBindToSelfEnabled()) {
+ return mContext.getPackageName();
+ } else {
+ return mConnectionConfiguration.getPackageName();
+ }
+ }
+
+ /** Connects to the service. */
+ public void connect() {
+ if (mIsServiceBound) {
+ return;
+ }
+ try {
+ mIsServiceBound =
+ mContext.bindService(
+ new Intent()
+ .setPackage(getBindPackageName())
+ .setAction(mConnectionConfiguration.getBindAction()),
+ this,
+ Context.BIND_AUTO_CREATE | Context.BIND_ADJUST_WITH_ACTIVITY);
+ } catch (SecurityException exception) {
+ Log.w(
+ TAG,
+ "Failed to bind connection '"
+ + mConnectionConfiguration.getKey()
+ + "', no permission or service not found.",
+ exception);
+ mIsServiceBound = false;
+ mBinder = null;
+ throw exception;
+ }
+
+ if (!mIsServiceBound) {
+ // Service not found or we don't have permission to call it.
+ Log.e(
+ TAG,
+ "Connection to service is not available for package '"
+ + mConnectionConfiguration.getPackageName()
+ + "' and action '"
+ + mConnectionConfiguration.getBindAction()
+ + "'.");
+ handleNonRetriableDisconnection(new CancellationException("Service not available"));
+ }
+ }
+
+ private void handleNonRetriableDisconnection(Throwable throwable) {
+ // Set retry count to maximum to prevent retries
+ mServiceConnectionRetry = MAX_RETRIES;
+ handleRetriableDisconnection(throwable);
+ }
+
+ private synchronized void handleRetriableDisconnection(Throwable throwable) {
+ if (isConnected()) {
+ // Connection is already re-established. So just return.
+ Log.w(TAG, "Connection is already re-established. No need to reconnect again");
+ return;
+ }
+
+ clearConnection(throwable);
+
+ if (mServiceConnectionRetry < MAX_RETRIES) {
+ Log.w(
+ TAG,
+ "WCS SDK Client '"
+ + mConnectionConfiguration.getClientName()
+ + "' disconnected, retrying connection. Retry attempt: "
+ + mServiceConnectionRetry,
+ throwable);
+ mCallback.onDisconnected(this, getRetryDelayMs(mServiceConnectionRetry));
+ } else {
+ Log.e(TAG, "Connection disconnected and maximum number of retries reached.", throwable);
+ }
+ }
+
+ private static int getRetryDelayMs(int retryNumber) {
+ // Exponential retry delay starting on 200ms.
+ return (200 << retryNumber);
+ }
+
+ private void clearConnection(Throwable throwable) {
+ if (mIsServiceBound) {
+ try {
+ mContext.unbindService(this);
+ mIsServiceBound = false;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to unbind the service. Ignoring and continuing", e);
+ }
+ }
+
+ mBinder = null;
+ mExecutionTracker.cancelPendingFutures(throwable);
+ cancelAllOperationsInQueue(throwable);
+ }
+
+ void enqueue(QueueOperation operation) {
+ if (isConnected()) {
+ execute(operation);
+ } else {
+ mOperationQueue.add(operation);
+ connect();
+ }
+ }
+
+ void registerListener(ListenerKey listenerKey, QueueOperation registerListenerOperation) {
+ mRegisteredListeners.put(listenerKey, registerListenerOperation);
+ if (isConnected()) {
+ enqueue(registerListenerOperation);
+ } else {
+ connect();
+ }
+ }
+
+ void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterListenerOperation) {
+ mRegisteredListeners.remove(listenerKey);
+ enqueue(unregisterListenerOperation);
+ }
+
+ void maybeReconnect() {
+ if (mRegisteredListeners.isEmpty()) {
+ Log.d(
+ TAG,
+ "No listeners registered, service "
+ + mConnectionConfiguration.getClientName()
+ + " is not automatically reconnected.");
+ } else {
+ mServiceConnectionRetry++;
+ Log.d(
+ TAG,
+ "Listeners for service "
+ + mConnectionConfiguration.getClientName()
+ + " are registered, reconnecting.");
+ connect();
+ }
+ }
+
+ @VisibleForTesting
+ void execute(QueueOperation operation) {
+ try {
+ operation.trackExecution(mExecutionTracker);
+ operation.execute(checkNotNull(mBinder));
+ } catch (DeadObjectException exception) {
+ handleRetriableDisconnection(exception);
+ // TODO(b/152024821): Consider possible TransactionTooLargeException failure.
+ } catch (RemoteException | RuntimeException exception) {
+ operation.setException(exception);
+ }
+ }
+
+ void reRegisterAllListeners() {
+ for (Map.Entry<ListenerKey, QueueOperation> entry : mRegisteredListeners.entrySet()) {
+ Log.d(TAG, "Re-registering listener: " + entry.getKey());
+ execute(entry.getValue());
+ }
+ }
+
+ void refreshServiceVersion() {
+ mOperationQueue.add(mConnectionConfiguration.getRefreshVersionOperation());
+ }
+
+ void flushQueue() {
+ for (QueueOperation operation : new ArrayList<>(mOperationQueue)) {
+ boolean removed = mOperationQueue.remove(operation);
+ if (removed) {
+ execute(operation);
+ }
+ }
+ }
+
+ private void cancelAllOperationsInQueue(Throwable throwable) {
+ for (QueueOperation operation : new ArrayList<>(mOperationQueue)) {
+ boolean removed = mOperationQueue.remove(operation);
+ if (removed) {
+ operation.setException(throwable);
+ execute(operation);
+ }
+ }
+ }
+
+ private boolean isConnected() {
+ return mBinder != null && mBinder.isBinderAlive();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder binder) {
+ Log.d(TAG, "onServiceConnected(), componentName = " + componentName);
+ if (binder == null) {
+ Log.e(TAG, "Service connected but binder is null.");
+ return;
+ }
+ mServiceConnectionRetry = 0;
+ cleanOnDeath(binder);
+ this.mBinder = binder;
+ mCallback.onConnected(this);
+ }
+
+ private void cleanOnDeath(IBinder binder) {
+ try {
+ binder.linkToDeath(
+ () -> {
+ Log.w(
+ TAG,
+ "Binder died for client:"
+ + mConnectionConfiguration.getClientName());
+ handleRetriableDisconnection(new CancellationException());
+ },
+ /* flags= */ 0);
+ } catch (RemoteException exception) {
+ Log.w(
+ TAG,
+ "Cannot link to death, binder already died. Cleaning operations.",
+ exception);
+ handleRetriableDisconnection(exception);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ Log.d(TAG, "onServiceDisconnected(), componentName = " + componentName);
+ // Service disconnected but binding still exists so it should reconnect automatically.
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ Log.e(TAG, "Binding died for client '" + mConnectionConfiguration.getClientName() + "'.");
+ handleRetriableDisconnection(new CancellationException());
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ Log.e(
+ TAG,
+ "Cannot bind client '"
+ + mConnectionConfiguration.getClientName()
+ + "', binder is null");
+ // This connection will never be usable, don't bother with retries.
+ handleNonRetriableDisconnection(new CancellationException("Null binding"));
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
new file mode 100644
index 0000000..6fc9161
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Request for enabling/disabling auto pause/resume.
+ *
+ * @hide
+ */
+public data class AutoPauseAndResumeConfigRequest(
+ val packageName: String,
+ val shouldEnable: Boolean,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeInt(if (shouldEnable) 1 else 0)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<AutoPauseAndResumeConfigRequest> =
+ object : Parcelable.Creator<AutoPauseAndResumeConfigRequest> {
+ override fun createFromParcel(source: Parcel): AutoPauseAndResumeConfigRequest? {
+ return AutoPauseAndResumeConfigRequest(
+ source.readString() ?: return null,
+ source.readInt() == 1,
+ )
+ }
+
+ override fun newArray(size: Int): Array<AutoPauseAndResumeConfigRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt
new file mode 100644
index 0000000..2c0296a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataType
+
+/**
+ * Request for background registration.
+ *
+ * @hide
+ */
+public data class BackgroundRegistrationRequest(
+ val packageName: String,
+ val dataTypes: Set<DataType>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeTypedList(dataTypes.toList())
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<BackgroundRegistrationRequest> =
+ object : Parcelable.Creator<BackgroundRegistrationRequest> {
+ override fun createFromParcel(source: Parcel): BackgroundRegistrationRequest? {
+ val packageName = source.readString() ?: return null
+ val list = ArrayList<DataType>()
+ source.readTypedList(list, DataType.CREATOR)
+ return BackgroundRegistrationRequest(packageName, list.toSet())
+ }
+
+ override fun newArray(size: Int): Array<BackgroundRegistrationRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt
new file mode 100644
index 0000000..67fcaa8
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Request for background unregistration.
+ *
+ * @hide
+ */
+public data class BackgroundUnregistrationRequest(val packageName: String) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<BackgroundUnregistrationRequest> =
+ object : Parcelable.Creator<BackgroundUnregistrationRequest> {
+ override fun createFromParcel(source: Parcel): BackgroundUnregistrationRequest? {
+ val packageName = source.readString() ?: return null
+ return BackgroundUnregistrationRequest(packageName)
+ }
+
+ override fun newArray(size: Int): Array<BackgroundUnregistrationRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
new file mode 100644
index 0000000..ba837e4
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Request for capabilities.
+ *
+ * @hide
+ */
+public data class CapabilitiesRequest(val packageName: String) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<CapabilitiesRequest> =
+ object : Parcelable.Creator<CapabilitiesRequest> {
+ override fun createFromParcel(source: Parcel): CapabilitiesRequest? {
+ val packageName = source.readString() ?: return null
+ return CapabilitiesRequest(packageName)
+ }
+
+ override fun newArray(size: Int): Array<CapabilitiesRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/EventRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/EventRequest.kt
new file mode 100644
index 0000000..ddfce39
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/EventRequest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.event.Event
+
+/**
+ * Request for event registration.
+ *
+ * @hide
+ */
+public data class EventRequest(val packageName: String, val event: Event) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(event, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<EventRequest> =
+ object : Parcelable.Creator<EventRequest> {
+ override fun createFromParcel(source: Parcel): EventRequest? {
+ return EventRequest(
+ source.readString() ?: return null,
+ source.readParcelable(Event::class.java.classLoader) ?: return null,
+ )
+ }
+
+ override fun newArray(size: Int): Array<EventRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt
new file mode 100644
index 0000000..a427c67
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseGoal
+
+/**
+ * Request for adding a [ExerciseGoal] to an exercise.
+ *
+ * @hide
+ */
+public data class ExerciseGoalRequest(val packageName: String, val exerciseGoal: ExerciseGoal) :
+ Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(exerciseGoal, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseGoalRequest> =
+ object : Parcelable.Creator<ExerciseGoalRequest> {
+ override fun createFromParcel(source: Parcel): ExerciseGoalRequest? {
+ return ExerciseGoalRequest(
+ source.readString() ?: return null,
+ source.readParcelable(ExerciseGoal::class.java.classLoader) ?: return null,
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseGoalRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
new file mode 100644
index 0000000..2d88026
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataType
+
+/**
+ * Request for measure registration.
+ *
+ * @hide
+ */
+public data class MeasureRegistrationRequest(
+ val packageName: String,
+ val dataType: DataType,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(dataType, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MeasureRegistrationRequest> =
+ object : Parcelable.Creator<MeasureRegistrationRequest> {
+ override fun createFromParcel(source: Parcel): MeasureRegistrationRequest? {
+ return MeasureRegistrationRequest(
+ source.readString() ?: return null,
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+ )
+ }
+
+ override fun newArray(size: Int): Array<MeasureRegistrationRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
new file mode 100644
index 0000000..55e8054
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataType
+
+/**
+ * Request for measure unregistration.
+ *
+ * @hide
+ */
+public data class MeasureUnregistrationRequest(
+ val packageName: String,
+ val dataType: DataType,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(dataType, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MeasureUnregistrationRequest> =
+ object : Parcelable.Creator<MeasureUnregistrationRequest> {
+ override fun createFromParcel(source: Parcel): MeasureUnregistrationRequest? {
+ val packageName = source.readString() ?: return null
+ val dataType =
+ source.readParcelable<DataType>(DataType::class.java.classLoader)
+ ?: return null
+ return MeasureUnregistrationRequest(packageName, dataType)
+ }
+
+ override fun newArray(size: Int): Array<MeasureUnregistrationRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
new file mode 100644
index 0000000..d67b359
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseConfig
+
+/**
+ * Request for starting an exercise.
+ *
+ * @hide
+ */
+public data class StartExerciseRequest(
+ val packageName: String,
+ val exerciseConfig: ExerciseConfig,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(exerciseConfig, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<StartExerciseRequest> =
+ object : Parcelable.Creator<StartExerciseRequest> {
+ override fun createFromParcel(source: Parcel): StartExerciseRequest? {
+ val packageName = source.readString() ?: return null
+ val parcelable =
+ source.readParcelable<ExerciseConfig>(
+ ExerciseConfig::class.java.classLoader
+ )
+ ?: return null
+ return StartExerciseRequest(packageName, parcelable)
+ }
+
+ override fun newArray(size: Int): Array<StartExerciseRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt
new file mode 100644
index 0000000..3a3ccf6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.Availability
+import androidx.health.services.client.data.DataType
+
+/**
+ * Response sent on MeasureCallback with a [DataType] and its associated [Availability] status.
+ *
+ * @hide
+ */
+public data class AvailabilityResponse(
+ /** [DataType] of the [AvailabilityResponse]. */
+ val dataType: DataType,
+ /** [Availability] of the [AvailabilityResponse]. */
+ val availability: Availability,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeInt(availability.id)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<AvailabilityResponse> =
+ object : Parcelable.Creator<AvailabilityResponse> {
+ override fun createFromParcel(source: Parcel): AvailabilityResponse? {
+ val parcelable =
+ source.readParcelable<DataType>(DataType::class.java.classLoader)
+ ?: return null
+ val availability = Availability.fromId(source.readInt()) ?: return null
+ return AvailabilityResponse(parcelable, availability)
+ }
+
+ override fun newArray(size: Int): Array<AvailabilityResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt
new file mode 100644
index 0000000..213a060
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.Capabilities
+
+/**
+ * Response containing the capabilities of WHS client on the device.
+ *
+ * @hide
+ */
+public data class CapabilitiesResponse(
+ /** [Capabilities] supported by this device. */
+ val capabilities: Capabilities,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(capabilities, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<CapabilitiesResponse> =
+ object : Parcelable.Creator<CapabilitiesResponse> {
+ override fun createFromParcel(source: Parcel): CapabilitiesResponse? {
+ val parcelable =
+ source.readParcelable<Capabilities>(Capabilities::class.java.classLoader)
+ ?: return null
+ return CapabilitiesResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<CapabilitiesResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt
new file mode 100644
index 0000000..0b30f6d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataPoint
+
+/**
+ * Response sent on MeasureCallback when new [DataPoints] [DataPoint] are available.
+ *
+ * @hide
+ */
+public data class DataPointsResponse(val dataPoints: List<DataPoint>) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeTypedList(dataPoints)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<DataPointsResponse> =
+ object : Parcelable.Creator<DataPointsResponse> {
+ override fun createFromParcel(source: Parcel): DataPointsResponse? {
+ val list = ArrayList<DataPoint>()
+ source.readTypedList(list, DataPoint.CREATOR)
+ return DataPointsResponse(list)
+ }
+
+ override fun newArray(size: Int): Array<DataPointsResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
new file mode 100644
index 0000000..7a44767
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseInfo
+
+/**
+ * Response containing [ExerciseInfo] when changed.
+ *
+ * @hide
+ */
+public data class ExerciseInfoResponse(val exerciseInfo: ExerciseInfo) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(exerciseInfo, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseInfoResponse> =
+ object : Parcelable.Creator<ExerciseInfoResponse> {
+ override fun createFromParcel(source: Parcel): ExerciseInfoResponse? {
+ val parcelable: ExerciseInfo =
+ source.readParcelable(ExerciseInfo::class.java.classLoader) ?: return null
+ return ExerciseInfoResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<ExerciseInfoResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
new file mode 100644
index 0000000..769346d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseLapSummary
+
+/**
+ * Response containing [ExerciseLapSummary] when it's updated.
+ *
+ * @hide
+ */
+public data class ExerciseLapSummaryResponse(val exerciseLapSummary: ExerciseLapSummary) :
+ Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(exerciseLapSummary, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseLapSummaryResponse> =
+ object : Parcelable.Creator<ExerciseLapSummaryResponse> {
+ override fun createFromParcel(source: Parcel): ExerciseLapSummaryResponse? {
+ val parcelable =
+ source.readParcelable<ExerciseLapSummary>(
+ ExerciseLapSummary::class.java.classLoader
+ )
+ ?: return null
+ return ExerciseLapSummaryResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<ExerciseLapSummaryResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
new file mode 100644
index 0000000..6c800d4
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseUpdate
+
+/**
+ * Response containing [ExerciseUpdate] when it's updated.
+ *
+ * @hide
+ */
+public data class ExerciseUpdateResponse(val exerciseUpdate: ExerciseUpdate) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(exerciseUpdate, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseUpdateResponse> =
+ object : Parcelable.Creator<ExerciseUpdateResponse> {
+ override fun createFromParcel(source: Parcel): ExerciseUpdateResponse? {
+ val parcelable =
+ source.readParcelable<ExerciseUpdate>(
+ ExerciseUpdate::class.java.classLoader
+ )
+ ?: return null
+ return ExerciseUpdateResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<ExerciseUpdateResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
new file mode 100644
index 0000000..12f80e3
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.MeasureCapabilities
+
+/**
+ * Response containing the capabilities of WHS client on the device.
+ *
+ * @hide
+ */
+public data class MeasureCapabilitiesResponse(
+ /** [MeasureCapabilities] supported by this device. */
+ val measureCapabilities: MeasureCapabilities,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(measureCapabilities, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MeasureCapabilitiesResponse> =
+ object : Parcelable.Creator<MeasureCapabilitiesResponse> {
+ override fun createFromParcel(source: Parcel): MeasureCapabilitiesResponse? {
+ val parcelable =
+ source.readParcelable<MeasureCapabilities>(
+ MeasureCapabilities::class.java.classLoader
+ )
+ ?: return null
+ return MeasureCapabilitiesResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<MeasureCapabilitiesResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt
new file mode 100644
index 0000000..77e98b5
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.PassiveActivityState
+
+/**
+ * Response containing [PassiveActivityState].
+ *
+ * @hide
+ */
+public data class PassiveActivityStateResponse(val passiveActivityState: PassiveActivityState) :
+ Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(passiveActivityState, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<PassiveActivityStateResponse> =
+ object : Parcelable.Creator<PassiveActivityStateResponse> {
+ override fun createFromParcel(source: Parcel): PassiveActivityStateResponse? {
+ val parcelable =
+ source.readParcelable<PassiveActivityState>(
+ PassiveActivityState::class.java.classLoader
+ )
+ ?: return null
+ return PassiveActivityStateResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<PassiveActivityStateResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
new file mode 100644
index 0000000..753735e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.PassiveMonitoringCapabilities
+
+/**
+ * Response containing the capabilities of WHS client on the device.
+ *
+ * @hide
+ */
+public data class PassiveMonitoringCapabilitiesResponse(
+ /** [PassiveMonitoringCapabilities] supported by this device. */
+ val passiveMonitoringCapabilities: PassiveMonitoringCapabilities,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(passiveMonitoringCapabilities, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<PassiveMonitoringCapabilitiesResponse> =
+ object : Parcelable.Creator<PassiveMonitoringCapabilitiesResponse> {
+ override fun createFromParcel(
+ source: Parcel
+ ): PassiveMonitoringCapabilitiesResponse? {
+ val parcelable =
+ source.readParcelable<PassiveMonitoringCapabilities>(
+ PassiveMonitoringCapabilities::class.java.classLoader
+ )
+ ?: return null
+ return PassiveMonitoringCapabilitiesResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<PassiveMonitoringCapabilitiesResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/package-info.java b/health/health-services-client/src/main/java/androidx/health/services/client/package-info.java
new file mode 100644
index 0000000..09f9b13
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package contains top level interfaces for the Health Services client.
+ */
+package androidx.health.services.client;
+
diff --git a/hilt/hilt-common/lint-baseline.xml b/hilt/hilt-common/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/hilt/hilt-common/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/hilt-compiler/build.gradle b/hilt/hilt-compiler/build.gradle
index 2151577..d14a347 100644
--- a/hilt/hilt-compiler/build.gradle
+++ b/hilt/hilt-compiler/build.gradle
@@ -51,7 +51,7 @@
}
tasks.named("compileKotlin").configure {
- dependsOn(":hilt:hilt-work:jarDebug")
+ dependsOn(":hilt:hilt-work:jarRelease")
}
androidx {
diff --git a/hilt/hilt-compiler/lint-baseline.xml b/hilt/hilt-compiler/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/hilt/hilt-compiler/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/hilt-navigation-compose/build.gradle b/hilt/hilt-navigation-compose/build.gradle
index 8ef423c..b41c57c 100644
--- a/hilt/hilt-navigation-compose/build.gradle
+++ b/hilt/hilt-navigation-compose/build.gradle
@@ -41,7 +41,7 @@
kotlinPlugin projectOrArtifact(":compose:compiler:compiler")
implementation(KOTLIN_STDLIB)
- api(project(":hilt:hilt-navigation"))
+ api("androidx.hilt:hilt-navigation:1.0.0")
api(projectOrArtifact(":compose:runtime:runtime"))
api(projectOrArtifact(":compose:ui:ui"))
api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-compose"))
diff --git a/hilt/hilt-navigation-compose/samples/src/main/java/androidx/hilt/navigation/compose/samples/HiltViewModelSamples.kt b/hilt/hilt-navigation-compose/samples/src/main/java/androidx/hilt/navigation/compose/samples/HiltViewModelSamples.kt
index 2a13592..e52fb79 100644
--- a/hilt/hilt-navigation-compose/samples/src/main/java/androidx/hilt/navigation/compose/samples/HiltViewModelSamples.kt
+++ b/hilt/hilt-navigation-compose/samples/src/main/java/androidx/hilt/navigation/compose/samples/HiltViewModelSamples.kt
@@ -24,9 +24,8 @@
import androidx.lifecycle.ViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
-import androidx.navigation.compose.getBackStackEntry
-import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navigation
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
diff --git a/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt b/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
index e45832f..8c51e0e 100644
--- a/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
+++ b/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
@@ -20,19 +20,17 @@
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.testutils.runBlockingWithManualClock
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
-import androidx.compose.testutils.runBlockingWithManualClock
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
-import androidx.navigation.compose.getBackStackEntry
-import androidx.navigation.compose.navigate
-import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navigation
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.google.common.truth.Truth.assertThat
diff --git a/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt b/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
index d6ce28c..7a1210c 100644
--- a/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
+++ b/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
@@ -25,7 +25,6 @@
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
-import androidx.navigation.compose.getBackStackEntry
import kotlin.reflect.KClass
/**
diff --git a/hilt/hilt-navigation-fragment/lint-baseline.xml b/hilt/hilt-navigation-fragment/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/hilt/hilt-navigation-fragment/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/hilt-work/lint-baseline.xml b/hilt/hilt-work/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/hilt/hilt-work/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/integration-tests/viewmodelapp/lint-baseline.xml b/hilt/integration-tests/viewmodelapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/hilt/integration-tests/viewmodelapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/integration-tests/workerapp/lint-baseline.xml b/hilt/integration-tests/workerapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/hilt/integration-tests/workerapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/inspection/inspection-gradle-plugin/lint-baseline.xml b/inspection/inspection-gradle-plugin/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/inspection/inspection-gradle-plugin/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/interpolator/interpolator/lint-baseline.xml b/interpolator/interpolator/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/interpolator/interpolator/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/jetifier/jetifier/core/lint-baseline.xml b/jetifier/jetifier/core/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/jetifier/jetifier/core/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/jetifier/jetifier/preprocessor/lint-baseline.xml b/jetifier/jetifier/preprocessor/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/jetifier/jetifier/preprocessor/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/jetifier/jetifier/processor/lint-baseline.xml b/jetifier/jetifier/processor/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/jetifier/jetifier/processor/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/jetifier/jetifier/standalone/lint-baseline.xml b/jetifier/jetifier/standalone/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/jetifier/jetifier/standalone/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/leanback/leanback-paging/lint-baseline.xml b/leanback/leanback-paging/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/leanback/leanback-paging/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/leanback/leanback-tab/lint-baseline.xml b/leanback/leanback-tab/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/leanback/leanback-tab/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
index b6bae78..ce23b2f 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
@@ -4567,7 +4567,7 @@
assertEquals(0, mGridView.getSelectedPosition());
}
- @FlakyTest
+ @FlakyTest(bugId = 187191618)
@Test
public void testExtraLayoutSpace() throws Throwable {
Intent intent = new Intent();
diff --git a/lifecycle/integration-tests/incrementality/lint-baseline.xml b/lifecycle/integration-tests/incrementality/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/integration-tests/incrementality/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/integration-tests/kotlintestapp/lint-baseline.xml b/lifecycle/integration-tests/kotlintestapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/integration-tests/kotlintestapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-common-java8/lint-baseline.xml b/lifecycle/lifecycle-common-java8/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/lifecycle-common-java8/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-compiler/lint-baseline.xml b/lifecycle/lifecycle-compiler/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/lifecycle-compiler/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/lint-baseline.xml b/lifecycle/lifecycle-livedata-core-ktx-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt b/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
index 098de7c..9b801e1 100644
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
+++ b/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
@@ -30,6 +30,8 @@
import com.android.tools.lint.detector.api.isKotlin
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiVariable
+import com.intellij.psi.impl.source.PsiImmediateClassType
+import org.jetbrains.kotlin.asJava.elements.KtLightTypeParameter
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtNullableType
import org.jetbrains.kotlin.psi.KtTypeReference
@@ -94,15 +96,26 @@
}
private fun getFieldTypeReference(element: KotlinUField): KtTypeReference? {
- // We need to extract type from the expression
+ // If field has type reference, we need to use type reference
+ // Given the field `val liveDataField: MutableLiveData<Boolean> = MutableLiveData()`
+ // reference: `MutableLiveData<Boolean>`
+ // argument: `Boolean`
+ val typeReference = element.sourcePsi
+ ?.children
+ ?.firstOrNull { it is KtTypeReference } as? KtTypeReference
+ val typeArgument = typeReference?.typeElement?.typeArgumentsAsTypes?.singleOrNull()
+ if (typeArgument != null) {
+ return typeArgument
+ }
+
+ // We need to extract type from the call expression
// Given the field `val liveDataField = MutableLiveData<Boolean>()`
// expression: `MutableLiveData<Boolean>()`
// argument: `Boolean`
val expression = element.sourcePsi
?.children
?.firstOrNull { it is KtCallExpression } as? KtCallExpression
- val argument = expression?.typeArguments?.singleOrNull()
- return argument?.typeReference
+ return expression?.typeArguments?.singleOrNull()?.typeReference
}
override fun visitCallExpression(node: UCallExpression) {
@@ -170,6 +183,9 @@
context: JavaContext,
node: UCallExpression
) {
+ // ignore generic types
+ if (node.isGenericTypeDefinition()) return
+
if (liveDataType.typeElement !is KtNullableType) {
val fixes = mutableListOf<LintFix>()
if (context.getLocation(liveDataType).file == context.file) {
@@ -198,6 +214,12 @@
}
}
+ private fun UCallExpression.isGenericTypeDefinition(): Boolean {
+ val classType = typeArguments.singleOrNull() as? PsiImmediateClassType
+ val resolveGenerics = classType?.resolveGenerics()
+ return resolveGenerics?.element is KtLightTypeParameter
+ }
+
/**
* Reports a lint error at [element]'s location with message and quick fixes.
*
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt b/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
index 095d9f6..591e6a9 100644
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
+++ b/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
@@ -199,23 +199,28 @@
import androidx.lifecycle.MutableLiveData
val liveDataField = MutableLiveData<Boolean>()
- val secondLiveDataField = MutableLiveData<String>()
+ val secondLiveDataField: MutableLiveData<String> = MutableLiveData()
+ val thirdLiveDataField: MutableLiveData<String> = MutableLiveData<String>("Value")
fun foo() {
liveDataField.value = null
secondLiveDataField.value = null
+ thirdLiveDataField.value = null
}
"""
).indented()
).expect(
"""
-src/com/example/test.kt:9: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+src/com/example/test.kt:10: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
liveDataField.value = null
~~~~
-src/com/example/test.kt:10: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+src/com/example/test.kt:11: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
secondLiveDataField.value = null
~~~~
-2 errors, 0 warnings
+src/com/example/test.kt:12: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+ thirdLiveDataField.value = null
+ ~~~~
+3 errors, 0 warnings
"""
)
}
@@ -760,4 +765,39 @@
).indented()
).expectClean()
}
+
+ @Test
+ fun justKotlinObject() {
+ check(
+ kotlin(
+ """
+ package com.example
+
+ object Foo
+ """
+ ).indented()
+ ).expectClean()
+ }
+
+ @Test
+ fun genericParameterDefinition() {
+ check(
+ kotlin(
+ """
+ package com.example
+
+ import androidx.lifecycle.MutableLiveData
+
+ class Foo<T>(
+ var target: MutableLiveData<T>
+ ) {
+
+ fun foo(value: T) {
+ target.value = null
+ }
+ }
+ """
+ ).indented()
+ ).expectClean()
+ }
}
diff --git a/lifecycle/lifecycle-livedata-core-ktx/lint-baseline.xml b/lifecycle/lifecycle-livedata-core-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-livedata-core-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-livedata-core-truth/lint-baseline.xml b/lifecycle/lifecycle-livedata-core-truth/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-livedata-core-truth/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-reactivestreams-ktx/lint-baseline.xml b/lifecycle/lifecycle-reactivestreams-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-reactivestreams-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-runtime-ktx-lint/lint-baseline.xml b/lifecycle/lifecycle-runtime-ktx-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/lifecycle-runtime-ktx-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-runtime-ktx/lint-baseline.xml b/lifecycle/lifecycle-runtime-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-runtime-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-runtime-testing/lint-baseline.xml b/lifecycle/lifecycle-runtime-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-runtime-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-service/lint-baseline.xml b/lifecycle/lifecycle-service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-viewmodel-ktx/lint-baseline.xml b/lifecycle/lifecycle-viewmodel-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-viewmodel-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-viewmodel/lint-baseline.xml b/lifecycle/lifecycle-viewmodel/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-viewmodel/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lint-checks/integration-tests/build.gradle b/lint-checks/integration-tests/build.gradle
index a01e2e6..c7f4c61 100644
--- a/lint-checks/integration-tests/build.gradle
+++ b/lint-checks/integration-tests/build.gradle
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-import androidx.build.BuildOnServerKt
-import androidx.build.dependencyTracker.AffectedModuleDetector
-import androidx.build.uptodatedness.EnableCachingKt
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import static androidx.build.dependencies.DependenciesKt.KOTLIN_STDLIB
@@ -82,28 +79,3 @@
}
}
}
-
-def lintOutputFile = project.file("${buildDir}/reports/lint-results-debug.xml")
-def lintOutputFileNormalized = project.file("${buildDir}/lint-results-normalized/lint-results-debug.xml.normalized")
-
-def normalizeLintOutput = tasks.register("normalizeLintOutput", Copy) {
- def supportRootFolder = project.rootProject.ext.supportRootFolder
- from(lintOutputFile) {
- filter { line ->
- return line.replace(supportRootFolder.getAbsolutePath(), "\$SUPPORT")
- }
- }
- into(lintOutputFileNormalized.parentFile)
- rename(".*", lintOutputFileNormalized.name)
- dependsOn("lintDebug")
-}
-
-def validateLint = tasks.register("validateLint", CompareFilesTask) { task ->
- task.actualFile = lintOutputFileNormalized
- task.expectedFile = project.file("expected-lint-results.xml")
- EnableCachingKt.cacheEvenIfNoOutputs(task)
- dependsOn(normalizeLintOutput)
- AffectedModuleDetector.configureTaskGuard(task)
-}
-
-BuildOnServerKt.addToBuildOnServer(project, validateLint)
diff --git a/lint-checks/integration-tests/expected-lint-results.xml b/lint-checks/integration-tests/expected-lint-results.xml
index 79b7190..698b8cd 100644
--- a/lint-checks/integration-tests/expected-lint-results.xml
+++ b/lint-checks/integration-tests/expected-lint-results.xml
@@ -20,6 +20,22 @@
<issue
id="ClassVerificationFailure"
severity="Error"
+ message="This call references a method added in API level 30; however, the containing class androidx.AutofixUnsafeConstructorReferenceJava is reachable from earlier API levels and will fail run-time class verification."
+ category="Correctness"
+ priority="5"
+ summary="Even in cases where references to new APIs are gated on SDK_INT checks, run-time class verification will still fail on references to APIs that may not be available at run time, including platform APIs introduced after a library's minSdkVersion."
+ explanation="The Java language requires a virtual machine to verify the class files it
loads and executes. A class may fail verification for a wide variety of
reasons, but in practice it‘s usually because the class’s code refers to
unknown classes or methods.

References to APIs added after a library's minSdkVersion -- regardless of
any surrounding version checks -- will fail run-time class verification if
the API does not exist on the device, leading to reduced run-time
performance.

Gating references on SDK checks alone DOES NOT address class verification
failures.

To prevent class verification failures, references to new APIs must be
moved to inner classes that are only initialized inside of an appropriate
SDK check.

For example, if our minimum SDK is 14 and platform method a.x(params...)
was added in SDK 16, the method call must be moved to an inner class like:

@RequiresApi(16)
private static class Api16Impl{
 @DoNotInline
 static void callX(params...) {
 a.x(params...);
 }
}

The call site is changed from a.x(params...) to Api16Impl.callX(params).

Since ART will only try to optimize Api16Impl when it's on the execution
path, we are guaranteed to have a.x(...) available.

In addition, optimizers like R8 or Proguard may inline the method in the
separate class and replace the wrapper call with the actual call, so you
must disable inlining using the @DoNotInline annotation.

Failure to do the above may result in overall performance degradation."
+ errorLine1=" AccessibilityNodeInfo node = new AccessibilityNodeInfo(new View(context), 1);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="$SUPPORT/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeConstructorReferenceJava.java"
+ line="35"
+ column="42"/>
+ </issue>
+
+ <issue
+ id="ClassVerificationFailure"
+ severity="Error"
message="This call references a method added in API level 23; however, the containing class androidx.AutofixUnsafeGenericMethodReferenceJava is reachable from earlier API levels and will fail run-time class verification."
category="Correctness"
priority="5"
diff --git a/lint-checks/tests/lint-baseline.xml b/lint-checks/tests/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lint-checks/tests/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/loader/loader-ktx/lint-baseline.xml b/loader/loader-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/loader/loader-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/loader/loader/lint-baseline.xml b/loader/loader/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/loader/loader/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/localbroadcastmanager/localbroadcastmanager/lint-baseline.xml b/localbroadcastmanager/localbroadcastmanager/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/localbroadcastmanager/localbroadcastmanager/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media/media/api/current.txt b/media/media/api/current.txt
index ab43c37..e82dbe2 100644
--- a/media/media/api/current.txt
+++ b/media/media/api/current.txt
@@ -729,6 +729,7 @@
field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
field public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
+ field public static final String METADATA_KEY_SERIES_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
field public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L; // 0x1L
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
diff --git a/media/media/api/public_plus_experimental_current.txt b/media/media/api/public_plus_experimental_current.txt
index cf5874c..d9037ca 100644
--- a/media/media/api/public_plus_experimental_current.txt
+++ b/media/media/api/public_plus_experimental_current.txt
@@ -729,6 +729,7 @@
field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
field public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
+ field public static final String METADATA_KEY_SERIES_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
field public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L; // 0x1L
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
diff --git a/media/media/api/restricted_current.txt b/media/media/api/restricted_current.txt
index 3f5c9d9..bd39a46 100644
--- a/media/media/api/restricted_current.txt
+++ b/media/media/api/restricted_current.txt
@@ -761,6 +761,7 @@
field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
field public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
+ field public static final String METADATA_KEY_SERIES_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
field public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L; // 0x1L
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
diff --git a/media/media/src/main/java/androidx/media/utils/MediaConstants.java b/media/media/src/main/java/androidx/media/utils/MediaConstants.java
index 8d81004..bd04425 100644
--- a/media/media/src/main/java/androidx/media/utils/MediaConstants.java
+++ b/media/media/src/main/java/androidx/media/utils/MediaConstants.java
@@ -113,7 +113,7 @@
/**
* Bundle key used for media content id in {@link MediaMetadataCompat metadata}, should contain
* the same ID provided to Media Actions Catalog in reference to this title (e.g., episode,
- * movie). Google uses this information to allow users to resume watching this title on your app
+ * movie). This information can be used to allow users to resume watching this title on your app
* across the supported surfaces (e.g., Android TV's Play Next row)
*
* <p>TYPE: String
@@ -126,12 +126,10 @@
/**
* Bundle key used for next episode's media content ID in {@link MediaMetadataCompat metadata},
- * following the same ID and format provided to
- * <a href="https://developers.google.com/actions/media">Media Actions Catalog</a> in reference
- * to the next episode of the current title episode. Google uses this information to allow users
- * to resume watching the next episode of this title on your app once the current episode ends
- * across the supported surfaces (e.g., Android TV's Play Next row). This can be left blank for
- * movies.
+ * following the same ID and format provided to Media Actions Catalog in reference to the next
+ * episode of the current title episode. This information can be used to allow users to resume
+ * watching the next episode of this title on your app once the current episode ends across the
+ * supported surfaces (e.g., Android TV's Play Next row). This can be left blank for movies.
*
* <p>TYPE: String
*
@@ -142,6 +140,22 @@
"androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
/**
+ * Bundle key used for the TV series's media content ID in {@link MediaMetadataCompat metadata},
+ * following the same ID and format provided to Media Actions Catalog</a> in reference to the
+ * TV series of the title episode. This information can be used to allow users to resume
+ * watching the current episode or next episode of this title on your app across the
+ * supported surfaces (e.g., Android TV's Play Next row). This value is only valid for TV
+ * Episode content type.
+ *
+ * <p>TYPE: String
+ *
+ * @see MediaMetadataCompat
+ */
+ @SuppressLint("IntentName")
+ public static final String METADATA_KEY_SERIES_CONTENT_ID =
+ "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
+
+ /**
* Key sent through a key-value mapping in {@link MediaMetadataCompat#getLong(String)} or in the
* {@link MediaDescriptionCompat#getExtras()} bundle to the hosting {@link MediaBrowserCompat}
* to indicate that the corresponding {@link MediaMetadataCompat} or {@link
diff --git a/media/version-compat-tests/current/client/lint-baseline.xml b/media/version-compat-tests/current/client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media/version-compat-tests/current/client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java
index a509209..8bde7c9 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java
@@ -50,7 +50,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -138,7 +137,6 @@
}
@Test
- @Ignore("b/185140325")
public void testSendCommand() throws InterruptedException {
mMediaControllerCallback.setExpectedCallbackMethodName("onCommand");
mMediaController.sendCommand("anyCommand", /* extras= */ null, /* cb= */ null);
diff --git a/media/version-compat-tests/current/service/lint-baseline.xml b/media/version-compat-tests/current/service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media/version-compat-tests/current/service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media/version-compat-tests/previous/client/lint-baseline.xml b/media/version-compat-tests/previous/client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media/version-compat-tests/previous/client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media/version-compat-tests/previous/service/lint-baseline.xml b/media/version-compat-tests/previous/service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media/version-compat-tests/previous/service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-exoplayer/lint-baseline.xml b/media2/media2-exoplayer/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-exoplayer/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
index e51f2fa..d39c2ad 100644
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
+++ b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
@@ -50,6 +50,7 @@
import androidx.media2.player.TestUtils.Monitor;
import androidx.media2.player.test.R;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
@@ -1089,6 +1090,7 @@
}
*/
+ @FlakyTest(bugId = 187340262)
@Test
@LargeTest
public void playbackRate() throws Exception {
diff --git a/media2/media2-session/version-compat-tests/current/client/lint-baseline.xml b/media2/media2-session/version-compat-tests/current/client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-session/version-compat-tests/current/client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
index b7d0f5e..e7b4a70 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
@@ -46,6 +46,7 @@
import androidx.media2.test.common.PollingCheck;
import androidx.media2.test.common.TestUtils;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import org.junit.After;
@@ -597,6 +598,7 @@
assertEquals(testSeekPosition, mControllerCompat.getPlaybackState().getPosition());
}
+ @FlakyTest(bugId = 187338985)
@Test
public void currentMediaItemChange() throws Exception {
int testItemIndex = 3;
diff --git a/media2/media2-session/version-compat-tests/current/service/lint-baseline.xml b/media2/media2-session/version-compat-tests/current/service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-session/version-compat-tests/current/service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-session/version-compat-tests/previous/client/lint-baseline.xml b/media2/media2-session/version-compat-tests/previous/client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-session/version-compat-tests/previous/service/lint-baseline.xml b/media2/media2-session/version-compat-tests/previous/service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/benchmark/lint-baseline.xml b/navigation/benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-common-ktx/lint-baseline.xml b/navigation/navigation-common-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-common-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-common/api/api_lint.ignore b/navigation/navigation-common/api/api_lint.ignore
index aefa4c8..8610f6c 100644
--- a/navigation/navigation-common/api/api_lint.ignore
+++ b/navigation/navigation-common/api/api_lint.ignore
@@ -37,6 +37,8 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.navigation.NavDestinationBuilder.deepLink(kotlin.jvm.functions.Function1<? super androidx.navigation.NavDeepLinkDslBuilder,kotlin.Unit>)
BuilderSetStyle: androidx.navigation.NavOptionsBuilder#anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit>):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.navigation.NavOptionsBuilder.anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit>)
+BuilderSetStyle: androidx.navigation.NavOptionsBuilder#popUpTo(String, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>):
+ Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.navigation.NavOptionsBuilder.popUpTo(String,kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>)
BuilderSetStyle: androidx.navigation.NavOptionsBuilder#popUpTo(int, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.navigation.NavOptionsBuilder.popUpTo(int,kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>)
@@ -81,8 +83,10 @@
Getter should be on the built object, not the builder: method androidx.navigation.NavDestinationBuilder.getRoute()
GetterOnBuilder: androidx.navigation.NavOptionsBuilder#getLaunchSingleTop():
Getter should be on the built object, not the builder: method androidx.navigation.NavOptionsBuilder.getLaunchSingleTop()
-GetterOnBuilder: androidx.navigation.NavOptionsBuilder#getPopUpTo():
- Getter should be on the built object, not the builder: method androidx.navigation.NavOptionsBuilder.getPopUpTo()
+GetterOnBuilder: androidx.navigation.NavOptionsBuilder#getPopUpToId():
+ Getter should be on the built object, not the builder: method androidx.navigation.NavOptionsBuilder.getPopUpToId()
+GetterOnBuilder: androidx.navigation.NavOptionsBuilder#getPopUpToRoute():
+ Getter should be on the built object, not the builder: method androidx.navigation.NavOptionsBuilder.getPopUpToRoute()
GetterOnBuilder: androidx.navigation.PopUpToBuilder#getInclusive():
Getter should be on the built object, not the builder: method androidx.navigation.PopUpToBuilder.getInclusive()
@@ -115,6 +119,12 @@
androidx.navigation.NavOptions does not declare a `isLaunchSingleTop()` method matching method androidx.navigation.NavOptions.Builder.setLaunchSingleTop(boolean)
+MissingJvmstatic: androidx.navigation.NavOptionsBuilder#popUpTo(String, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>):
+ A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults
+MissingJvmstatic: androidx.navigation.NavOptionsBuilder#popUpTo(int, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>):
+ A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults
+
+
NullableCollection: androidx.navigation.NavAction#getDefaultArguments():
Return type of method androidx.navigation.NavAction.getDefaultArguments() is a nullable collection (`android.os.Bundle`); must be non-null
NullableCollection: androidx.navigation.NavBackStackEntry#getArguments():
@@ -123,6 +133,10 @@
Return type of method androidx.navigation.Navigator.onSaveState() is a nullable collection (`android.os.Bundle`); must be non-null
+OptionalBuilderConstructorArgument: androidx.navigation.NavDestinationBuilder#NavDestinationBuilder(androidx.navigation.Navigator<? extends D>, String) parameter #1:
+ Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter route in androidx.navigation.NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String route)
+
+
SetterReturnsThis: androidx.navigation.AnimBuilder#setEnter(int):
Methods must return the builder object (return type androidx.navigation.AnimBuilder instead of void): method androidx.navigation.AnimBuilder.setEnter(int)
SetterReturnsThis: androidx.navigation.AnimBuilder#setExit(int):
@@ -149,8 +163,6 @@
Methods must return the builder object (return type androidx.navigation.NavDestinationBuilder<D> instead of void): method androidx.navigation.NavDestinationBuilder.setLabel(CharSequence)
SetterReturnsThis: androidx.navigation.NavOptionsBuilder#setLaunchSingleTop(boolean):
Methods must return the builder object (return type androidx.navigation.NavOptionsBuilder instead of void): method androidx.navigation.NavOptionsBuilder.setLaunchSingleTop(boolean)
-SetterReturnsThis: androidx.navigation.NavOptionsBuilder#setPopUpTo(int):
- Methods must return the builder object (return type androidx.navigation.NavOptionsBuilder instead of void): method androidx.navigation.NavOptionsBuilder.setPopUpTo(int)
SetterReturnsThis: androidx.navigation.PopUpToBuilder#setInclusive(boolean):
Methods must return the builder object (return type androidx.navigation.PopUpToBuilder instead of void): method androidx.navigation.PopUpToBuilder.setInclusive(boolean)
diff --git a/navigation/navigation-common/api/current.txt b/navigation/navigation-common/api/current.txt
index d136e44..5f8904b 100644
--- a/navigation/navigation-common/api/current.txt
+++ b/navigation/navigation-common/api/current.txt
@@ -104,12 +104,14 @@
method public android.os.Bundle? getArguments();
method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method public androidx.navigation.NavDestination getDestination();
+ method public String getId();
method public androidx.lifecycle.Lifecycle getLifecycle();
method public androidx.lifecycle.SavedStateHandle getSavedStateHandle();
method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
method public androidx.lifecycle.ViewModelStore getViewModelStore();
property public final android.os.Bundle? arguments;
property public final androidx.navigation.NavDestination destination;
+ property public final String id;
property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
}
@@ -222,7 +224,7 @@
@androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
- ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String route);
+ ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
method public D build();
@@ -257,15 +259,21 @@
method public final void addDestinations(androidx.navigation.NavDestination... nodes);
method public final void clear();
method public final androidx.navigation.NavDestination? findNode(@IdRes int resid);
- method @IdRes public final int getStartDestination();
+ method public final androidx.navigation.NavDestination? findNode(String? route);
+ method @Deprecated @IdRes public final int getStartDestination();
+ method @IdRes public final int getStartDestinationId();
+ method public final String? getStartDestinationRoute();
method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
method public final void remove(androidx.navigation.NavDestination node);
method public final void setStartDestination(int startDestId);
- property @IdRes public final int startDestination;
+ method public final void setStartDestination(String startDestRoute);
+ property @IdRes public final int startDestinationId;
+ property public final String? startDestinationRoute;
}
@androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+ ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
method public final void addDestination(androidx.navigation.NavDestination destination);
method public androidx.navigation.NavGraph build();
method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
@@ -276,12 +284,16 @@
public final class NavGraphBuilderKt {
method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavGraphKt {
method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+ method public static operator boolean contains(androidx.navigation.NavGraph, String route);
method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+ method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
@@ -290,7 +302,6 @@
@androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider);
method public androidx.navigation.NavGraph createDestination();
- method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
}
public final class NavOptions {
@@ -298,7 +309,9 @@
method @AnimRes @AnimatorRes public int getExitAnim();
method @AnimRes @AnimatorRes public int getPopEnterAnim();
method @AnimRes @AnimatorRes public int getPopExitAnim();
- method @IdRes public int getPopUpTo();
+ method @Deprecated @IdRes public int getPopUpTo();
+ method @IdRes public int getPopUpToId();
+ method public String? getPopUpToRoute();
method public boolean isPopUpToInclusive();
method public boolean shouldLaunchSingleTop();
method public boolean shouldPopUpToSaveState();
@@ -307,7 +320,8 @@
property @AnimRes @AnimatorRes public final int exitAnim;
property @AnimRes @AnimatorRes public final int popEnterAnim;
property @AnimRes @AnimatorRes public final int popExitAnim;
- property @IdRes public final int popUpTo;
+ property @IdRes public final int popUpToId;
+ property public final String? popUpToRoute;
}
public static final class NavOptions.Builder {
@@ -320,6 +334,8 @@
method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int popExitAnim);
method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive, optional boolean saveState);
method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive);
+ method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive, optional boolean saveState);
+ method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive);
method public androidx.navigation.NavOptions.Builder setRestoreState(boolean restoreState);
}
@@ -327,14 +343,19 @@
ctor public NavOptionsBuilder();
method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
method public boolean getLaunchSingleTop();
- method public int getPopUpTo();
+ method @Deprecated public int getPopUpTo();
+ method public int getPopUpToId();
+ method public String? getPopUpToRoute();
method public boolean getRestoreState();
- method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+ method public void popUpTo(@IdRes int id, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+ method public void popUpTo(String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
method public void setLaunchSingleTop(boolean p);
- method public void setPopUpTo(int value);
+ method @Deprecated public void setPopUpTo(int value);
method public void setRestoreState(boolean p);
property public final boolean launchSingleTop;
- property public final int popUpTo;
+ property @Deprecated public final int popUpTo;
+ property public final int popUpToId;
+ property public final String? popUpToRoute;
property public final boolean restoreState;
}
diff --git a/navigation/navigation-common/api/public_plus_experimental_current.txt b/navigation/navigation-common/api/public_plus_experimental_current.txt
index 2eb2397..5159fc4 100644
--- a/navigation/navigation-common/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-common/api/public_plus_experimental_current.txt
@@ -106,7 +106,7 @@
method public android.os.Bundle? getArguments();
method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method public androidx.navigation.NavDestination getDestination();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public java.util.UUID getId();
+ method public String getId();
method public androidx.lifecycle.Lifecycle getLifecycle();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public androidx.lifecycle.Lifecycle.State getMaxLifecycle();
method public androidx.lifecycle.SavedStateHandle getSavedStateHandle();
@@ -120,7 +120,7 @@
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void updateState();
property public final android.os.Bundle? arguments;
property public final androidx.navigation.NavDestination destination;
- property @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final java.util.UUID id;
+ property public final String id;
property @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.lifecycle.Lifecycle.State maxLifecycle;
property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
}
@@ -261,7 +261,7 @@
@androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
- ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String route);
+ ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
method public D build();
@@ -296,20 +296,25 @@
method public final void addDestinations(androidx.navigation.NavDestination... nodes);
method public final void clear();
method public final androidx.navigation.NavDestination? findNode(@IdRes int resid);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.navigation.NavDestination? findNode(@IdRes int resid, boolean searchParents);
+ method public final androidx.navigation.NavDestination? findNode(String? route);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.collection.SparseArrayCompat<androidx.navigation.NavDestination> getNodes();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final String getStartDestDisplayName();
- method @IdRes public final int getStartDestination();
+ method @Deprecated @IdRes public final int getStartDestination();
+ method @IdRes public final int getStartDestinationId();
+ method public final String? getStartDestinationRoute();
method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
method public final void remove(androidx.navigation.NavDestination node);
method public final void setStartDestination(int startDestId);
+ method public final void setStartDestination(String startDestRoute);
property @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.collection.SparseArrayCompat<androidx.navigation.NavDestination> nodes;
property @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final String startDestDisplayName;
- property @IdRes public final int startDestination;
+ property @IdRes public final int startDestinationId;
+ property public final String? startDestinationRoute;
}
@androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+ ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
method public final void addDestination(androidx.navigation.NavDestination destination);
method public androidx.navigation.NavGraph build();
method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
@@ -320,12 +325,16 @@
public final class NavGraphBuilderKt {
method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavGraphKt {
method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+ method public static operator boolean contains(androidx.navigation.NavGraph, String route);
method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+ method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
@@ -334,7 +343,6 @@
@androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider);
method public androidx.navigation.NavGraph createDestination();
- method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
}
public final class NavOptions {
@@ -342,7 +350,9 @@
method @AnimRes @AnimatorRes public int getExitAnim();
method @AnimRes @AnimatorRes public int getPopEnterAnim();
method @AnimRes @AnimatorRes public int getPopExitAnim();
- method @IdRes public int getPopUpTo();
+ method @Deprecated @IdRes public int getPopUpTo();
+ method @IdRes public int getPopUpToId();
+ method public String? getPopUpToRoute();
method public boolean isPopUpToInclusive();
method public boolean shouldLaunchSingleTop();
method public boolean shouldPopUpToSaveState();
@@ -351,7 +361,8 @@
property @AnimRes @AnimatorRes public final int exitAnim;
property @AnimRes @AnimatorRes public final int popEnterAnim;
property @AnimRes @AnimatorRes public final int popExitAnim;
- property @IdRes public final int popUpTo;
+ property @IdRes public final int popUpToId;
+ property public final String? popUpToRoute;
}
public static final class NavOptions.Builder {
@@ -364,6 +375,8 @@
method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int popExitAnim);
method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive, optional boolean saveState);
method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive);
+ method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive, optional boolean saveState);
+ method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive);
method public androidx.navigation.NavOptions.Builder setRestoreState(boolean restoreState);
}
@@ -371,14 +384,19 @@
ctor public NavOptionsBuilder();
method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
method public boolean getLaunchSingleTop();
- method public int getPopUpTo();
+ method @Deprecated public int getPopUpTo();
+ method public int getPopUpToId();
+ method public String? getPopUpToRoute();
method public boolean getRestoreState();
- method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+ method public void popUpTo(@IdRes int id, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+ method public void popUpTo(String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
method public void setLaunchSingleTop(boolean p);
- method public void setPopUpTo(int value);
+ method @Deprecated public void setPopUpTo(int value);
method public void setRestoreState(boolean p);
property public final boolean launchSingleTop;
- property public final int popUpTo;
+ property @Deprecated public final int popUpTo;
+ property public final int popUpToId;
+ property public final String? popUpToRoute;
property public final boolean restoreState;
}
diff --git a/navigation/navigation-common/api/restricted_current.txt b/navigation/navigation-common/api/restricted_current.txt
index d136e44..5f8904b 100644
--- a/navigation/navigation-common/api/restricted_current.txt
+++ b/navigation/navigation-common/api/restricted_current.txt
@@ -104,12 +104,14 @@
method public android.os.Bundle? getArguments();
method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method public androidx.navigation.NavDestination getDestination();
+ method public String getId();
method public androidx.lifecycle.Lifecycle getLifecycle();
method public androidx.lifecycle.SavedStateHandle getSavedStateHandle();
method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
method public androidx.lifecycle.ViewModelStore getViewModelStore();
property public final android.os.Bundle? arguments;
property public final androidx.navigation.NavDestination destination;
+ property public final String id;
property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
}
@@ -222,7 +224,7 @@
@androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
- ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String route);
+ ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
method public D build();
@@ -257,15 +259,21 @@
method public final void addDestinations(androidx.navigation.NavDestination... nodes);
method public final void clear();
method public final androidx.navigation.NavDestination? findNode(@IdRes int resid);
- method @IdRes public final int getStartDestination();
+ method public final androidx.navigation.NavDestination? findNode(String? route);
+ method @Deprecated @IdRes public final int getStartDestination();
+ method @IdRes public final int getStartDestinationId();
+ method public final String? getStartDestinationRoute();
method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
method public final void remove(androidx.navigation.NavDestination node);
method public final void setStartDestination(int startDestId);
- property @IdRes public final int startDestination;
+ method public final void setStartDestination(String startDestRoute);
+ property @IdRes public final int startDestinationId;
+ property public final String? startDestinationRoute;
}
@androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+ ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
method public final void addDestination(androidx.navigation.NavDestination destination);
method public androidx.navigation.NavGraph build();
method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
@@ -276,12 +284,16 @@
public final class NavGraphBuilderKt {
method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavGraphKt {
method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+ method public static operator boolean contains(androidx.navigation.NavGraph, String route);
method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+ method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
@@ -290,7 +302,6 @@
@androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider);
method public androidx.navigation.NavGraph createDestination();
- method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
}
public final class NavOptions {
@@ -298,7 +309,9 @@
method @AnimRes @AnimatorRes public int getExitAnim();
method @AnimRes @AnimatorRes public int getPopEnterAnim();
method @AnimRes @AnimatorRes public int getPopExitAnim();
- method @IdRes public int getPopUpTo();
+ method @Deprecated @IdRes public int getPopUpTo();
+ method @IdRes public int getPopUpToId();
+ method public String? getPopUpToRoute();
method public boolean isPopUpToInclusive();
method public boolean shouldLaunchSingleTop();
method public boolean shouldPopUpToSaveState();
@@ -307,7 +320,8 @@
property @AnimRes @AnimatorRes public final int exitAnim;
property @AnimRes @AnimatorRes public final int popEnterAnim;
property @AnimRes @AnimatorRes public final int popExitAnim;
- property @IdRes public final int popUpTo;
+ property @IdRes public final int popUpToId;
+ property public final String? popUpToRoute;
}
public static final class NavOptions.Builder {
@@ -320,6 +334,8 @@
method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int popExitAnim);
method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive, optional boolean saveState);
method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive);
+ method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive, optional boolean saveState);
+ method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive);
method public androidx.navigation.NavOptions.Builder setRestoreState(boolean restoreState);
}
@@ -327,14 +343,19 @@
ctor public NavOptionsBuilder();
method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
method public boolean getLaunchSingleTop();
- method public int getPopUpTo();
+ method @Deprecated public int getPopUpTo();
+ method public int getPopUpToId();
+ method public String? getPopUpToRoute();
method public boolean getRestoreState();
- method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+ method public void popUpTo(@IdRes int id, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+ method public void popUpTo(String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
method public void setLaunchSingleTop(boolean p);
- method public void setPopUpTo(int value);
+ method @Deprecated public void setPopUpTo(int value);
method public void setRestoreState(boolean p);
property public final boolean launchSingleTop;
- property public final int popUpTo;
+ property @Deprecated public final int popUpTo;
+ property public final int popUpToId;
+ property public final String? popUpToRoute;
property public final boolean restoreState;
}
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index 5ecb6d0..fe1af8a 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -36,6 +36,8 @@
implementation("androidx.collection:collection-ktx:1.1.0")
api(KOTLIN_STDLIB)
+ testImplementation(project(":navigation:navigation-testing"))
+ testImplementation("androidx.arch.core:core-testing:2.1.0")
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
testImplementation(TRUTH)
diff --git a/navigation/navigation-common/lint-baseline.xml b/navigation/navigation-common/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-common/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
index fd6d922..68fa76a 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
@@ -94,7 +94,7 @@
action(ACTION_ID) {
destinationId = DESTINATION_ID
navOptions {
- popUpTo = DESTINATION_ID
+ popUpTo(DESTINATION_ID)
}
defaultArguments[ACTION_ARGUMENT_KEY] = ACTION_ARGUMENT_VALUE
}
@@ -104,7 +104,7 @@
.that(action)
.isNotNull()
assertWithMessage("NavAction should have NavOptions set")
- .that(action?.navOptions?.popUpTo)
+ .that(action?.navOptions?.popUpToId)
.isEqualTo(DESTINATION_ID)
assertWithMessage("NavAction should have its default argument set")
.that(action?.defaultArguments?.getString(ACTION_ARGUMENT_KEY))
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphAndroidTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphAndroidTest.kt
index a0cdb66..d47d5f2 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphAndroidTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphAndroidTest.kt
@@ -26,8 +26,10 @@
class NavGraphAndroidTest {
companion object {
const val DESTINATION_ID = 1
+ const val DESTINATION_ROUTE = "destination_route"
const val DESTINATION_LABEL = "test_label"
const val GRAPH_ID = 2
+ const val GRAPH_ROUTE = "graph_route"
const val GRAPH_LABEL = "graph_label"
}
@@ -240,7 +242,7 @@
.createDestination().apply {
id = GRAPH_ID
label = GRAPH_LABEL
- startDestination = DESTINATION_ID
+ setStartDestination(DESTINATION_ID)
}
val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) label=$GRAPH_LABEL " +
"startDestination=0x${DESTINATION_ID.toString(16)}"
@@ -248,6 +250,24 @@
}
@Test
+ fun toStringStartDestRouteOnly() {
+ val navigatorProvider = NavigatorProvider().apply {
+ addNavigator(NavGraphNavigator(this))
+ addNavigator(NoOpNavigator())
+ }
+ val graph = navigatorProvider.getNavigator(NavGraphNavigator::class.java)
+ .createDestination().apply {
+ route = GRAPH_ROUTE
+ id = GRAPH_ID
+ label = GRAPH_LABEL
+ setStartDestination(DESTINATION_ROUTE)
+ }
+ val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " +
+ "label=$GRAPH_LABEL startDestination=$DESTINATION_ROUTE"
+ assertThat(graph.toString()).isEqualTo(expected)
+ }
+
+ @Test
fun toStringStartDestInNodes() {
val navigatorProvider = NavigatorProvider().apply {
addNavigator(NavGraphNavigator(this))
@@ -262,7 +282,7 @@
.createDestination().apply {
id = GRAPH_ID
label = GRAPH_LABEL
- startDestination = DESTINATION_ID
+ setStartDestination(DESTINATION_ID)
addDestination(destination)
}
val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) label=$GRAPH_LABEL " +
@@ -270,4 +290,82 @@
"label=$DESTINATION_LABEL}"
assertThat(graph.toString()).isEqualTo(expected)
}
+
+ @Test
+ fun toStringStartDestInNodesRoute() {
+ val navigatorProvider = NavigatorProvider().apply {
+ addNavigator(NavGraphNavigator(this))
+ addNavigator(NoOpNavigator())
+ }
+ val destination = navigatorProvider.getNavigator(NoOpNavigator::class.java)
+ .createDestination().apply {
+ route = DESTINATION_ROUTE
+ id = DESTINATION_ID
+ label = DESTINATION_LABEL
+ }
+ val graph = navigatorProvider.getNavigator(NavGraphNavigator::class.java)
+ .createDestination().apply {
+ route = GRAPH_ROUTE
+ id = GRAPH_ID
+ label = GRAPH_LABEL
+ setStartDestination(DESTINATION_ROUTE)
+ setStartDestination(DESTINATION_ID)
+ addDestination(destination)
+ }
+ val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " +
+ "label=$GRAPH_LABEL " +
+ "startDestination={NavDestination(0x${DESTINATION_ID.toString(16)}) " +
+ "route=$DESTINATION_ROUTE label=$DESTINATION_LABEL}"
+ assertThat(graph.toString()).isEqualTo(expected)
+ }
+
+ @Test
+ fun toStringStartDestInNodesRouteWithStartDestID() {
+ val navigatorProvider = NavigatorProvider().apply {
+ addNavigator(NavGraphNavigator(this))
+ addNavigator(NoOpNavigator())
+ }
+ val destination = navigatorProvider.getNavigator(NoOpNavigator::class.java)
+ .createDestination().apply {
+ route = DESTINATION_ROUTE
+ label = DESTINATION_LABEL
+ }
+ val graph = navigatorProvider.getNavigator(NavGraphNavigator::class.java)
+ .createDestination().apply {
+ route = GRAPH_ROUTE
+ id = GRAPH_ID
+ label = GRAPH_LABEL
+ setStartDestination(DESTINATION_ROUTE)
+ setStartDestination(DESTINATION_ID)
+ addDestination(destination)
+ }
+ val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " +
+ "label=$GRAPH_LABEL startDestination=0x${DESTINATION_ID.toString(16)}"
+ assertThat(graph.toString()).isEqualTo(expected)
+ }
+
+ @Test
+ fun toStringStartDestInNodesRouteWithID() {
+ val navigatorProvider = NavigatorProvider().apply {
+ addNavigator(NavGraphNavigator(this))
+ addNavigator(NoOpNavigator())
+ }
+ val destination = navigatorProvider.getNavigator(NoOpNavigator::class.java)
+ .createDestination().apply {
+ route = DESTINATION_ROUTE
+ id = DESTINATION_ID
+ label = DESTINATION_LABEL
+ }
+ val graph = navigatorProvider.getNavigator(NavGraphNavigator::class.java)
+ .createDestination().apply {
+ route = GRAPH_ROUTE
+ id = GRAPH_ID
+ label = GRAPH_LABEL
+ setStartDestination(DESTINATION_ROUTE)
+ addDestination(destination)
+ }
+ val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " +
+ "label=$GRAPH_LABEL startDestination=$DESTINATION_ROUTE"
+ assertThat(graph.toString()).isEqualTo(expected)
+ }
}
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
index cef5597..cb75a08 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
@@ -43,6 +43,18 @@
}
@Test
+ fun navigationRoute() {
+ val graph = provider.navigation(
+ startDestination = DESTINATION_ROUTE
+ ) {
+ navDestination(DESTINATION_ROUTE) {}
+ }
+ assertWithMessage("Destination should be added to the graph")
+ .that(DESTINATION_ROUTE in graph)
+ .isTrue()
+ }
+
+ @Test
fun navigationUnaryPlus() {
val graph = provider.navigation(startDestination = DESTINATION_ID) {
+provider[NoOpNavigator::class].createDestination().apply {
@@ -55,6 +67,20 @@
}
@Test
+ fun navigationUnaryPlusRoute() {
+ val graph = provider.navigation(
+ startDestination = DESTINATION_ROUTE
+ ) {
+ +provider[NoOpNavigator::class].createDestination().apply {
+ route = DESTINATION_ROUTE
+ }
+ }
+ assertWithMessage("Destination should be added to the graph")
+ .that(DESTINATION_ROUTE in graph)
+ .isTrue()
+ }
+
+ @Test
fun navigationAddDestination() {
val graph = provider.navigation(startDestination = DESTINATION_ID) {
val destination = provider[NoOpNavigator::class].createDestination().apply {
@@ -67,6 +93,21 @@
.isTrue()
}
+ @Test
+ fun navigationAddDestinationRoute() {
+ val graph = provider.navigation(
+ startDestination = DESTINATION_ROUTE
+ ) {
+ val destination = provider[NoOpNavigator::class].createDestination().apply {
+ route = DESTINATION_ROUTE
+ }
+ addDestination(destination)
+ }
+ assertWithMessage("Destination should be added to the graph")
+ .that(DESTINATION_ROUTE in graph)
+ .isTrue()
+ }
+
@Test(expected = IllegalStateException::class)
fun navigationMissingStartDestination() {
provider.navigation(startDestination = 0) {
@@ -75,6 +116,14 @@
fail("NavGraph should throw IllegalStateException if startDestination is zero")
}
+ @Test(expected = IllegalArgumentException::class)
+ fun navigationMissingStartDestinationRoute() {
+ provider.navigation(startDestination = "") {
+ navDestination(DESTINATION_ROUTE) {}
+ }
+ fail("NavGraph should throw IllegalStateException if no startDestinationRoute is set")
+ }
+
@Test
fun navigationNested() {
val graph = provider.navigation(startDestination = DESTINATION_ID) {
@@ -86,10 +135,24 @@
.that(DESTINATION_ID in graph)
.isTrue()
}
+
+ @Test
+ fun navigationNestedRoute() {
+ val graph = provider.navigation(startDestination = DESTINATION_ROUTE) {
+ navigation(startDestination = SECOND_DESTINATION_ROUTE, route = DESTINATION_ROUTE) {
+ navDestination(SECOND_DESTINATION_ROUTE) {}
+ }
+ }
+ assertWithMessage("Destination should be added to the graph")
+ .that(DESTINATION_ROUTE in graph)
+ .isTrue()
+ }
}
private const val DESTINATION_ID = 1
private const val SECOND_DESTINATION_ID = 2
+private const val DESTINATION_ROUTE = "first"
+private const val SECOND_DESTINATION_ROUTE = "second"
/**
* Create a base NavDestination. Generally, only subtypes of NavDestination should be
@@ -99,3 +162,12 @@
@IdRes id: Int,
builder: NavDestinationBuilder<NavDestination>.() -> Unit
) = destination(NavDestinationBuilder(provider[NoOpNavigator::class], id).apply(builder))
+
+/**
+ * Create a base NavDestination. Generally, only subtypes of NavDestination should be
+ * added to a NavGraph (hence why this is not in the common-ktx library)
+ */
+fun NavGraphBuilder.navDestination(
+ route: String,
+ builder: NavDestinationBuilder<NavDestination>.() -> Unit
+) = destination(NavDestinationBuilder(provider[NoOpNavigator::class], route).apply(builder))
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
index 6eb1a90c..c0b072a 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
@@ -75,6 +75,28 @@
.isFalse()
}
+ @Test
+ fun plusAssignGraphRoute() {
+ val graph = NavGraph(navGraphNavigator)
+ val other = NavGraph(navGraphNavigator)
+ other += navigator.createDestination().apply { route = DESTINATION_ROUTE }
+ other += navigator.createDestination().apply { route = SECOND_DESTINATION_ROUTE }
+ graph += other
+ assertWithMessage("NavGraph should have destination1 from other")
+ .that(DESTINATION_ROUTE in graph)
+ .isTrue()
+ assertWithMessage("other nav graph should not have destination1")
+ .that(DESTINATION_ROUTE in other)
+ .isFalse()
+
+ assertWithMessage("NavGraph should have destination2 from other")
+ .that(SECOND_DESTINATION_ROUTE in graph)
+ .isTrue()
+ assertWithMessage("other nav graph should not have destination2")
+ .that(SECOND_DESTINATION_ROUTE in other)
+ .isFalse()
+ }
+
@Test(expected = IllegalArgumentException::class)
fun getIllegalArgumentException() {
val graph = NavGraph(navGraphNavigator)
@@ -84,3 +106,5 @@
private const val DESTINATION_ID = 1
private const val SECOND_DESTINATION_ID = 2
+private const val DESTINATION_ROUTE = "first"
+private const val SECOND_DESTINATION_ROUTE = "second"
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
index f62b37e..bf40bfd 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
@@ -49,10 +49,10 @@
@Test
fun popUpTo() {
val navOptions = navOptions {
- popUpTo = DESTINATION_ID
+ popUpTo(DESTINATION_ID)
}
assertWithMessage("NavOptions should have popUpTo destination id set")
- .that(navOptions.popUpTo)
+ .that(navOptions.popUpToId)
.isEqualTo(DESTINATION_ID)
assertWithMessage("NavOptions should have isPopUpToInclusive false by default")
.that(navOptions.isPopUpToInclusive())
@@ -63,6 +63,19 @@
}
@Test
+ fun popUpToRoute() {
+ val navOptions = navOptions {
+ popUpTo(DESTINATION_ROUTE)
+ }
+ assertWithMessage("NavOptions should have popUpTo destination route set")
+ .that(navOptions.popUpToRoute)
+ .isEqualTo(DESTINATION_ROUTE)
+ assertWithMessage("NavOptions should have isPopUpToInclusive false by default")
+ .that(navOptions.isPopUpToInclusive())
+ .isFalse()
+ }
+
+ @Test
fun popUpToBuilder() {
val navOptions = navOptions {
popUpTo(DESTINATION_ID) {
@@ -71,7 +84,7 @@
}
}
assertWithMessage("NavOptions should have popUpTo destination id set")
- .that(navOptions.popUpTo)
+ .that(navOptions.popUpToId)
.isEqualTo(DESTINATION_ID)
assertWithMessage("NavOptions should have isPopUpToInclusive set")
.that(navOptions.isPopUpToInclusive())
@@ -82,6 +95,21 @@
}
@Test
+ fun popUpToRouteInclusive() {
+ val navOptions = navOptions {
+ popUpTo(DESTINATION_ROUTE) {
+ inclusive = true
+ }
+ }
+ assertWithMessage("NavOptions should have popUpTo destination id set")
+ .that(navOptions.popUpToRoute)
+ .isEqualTo(DESTINATION_ROUTE)
+ assertWithMessage("NavOptions should have isPopUpToInclusive set")
+ .that(navOptions.isPopUpToInclusive())
+ .isTrue()
+ }
+
+ @Test
fun anim() {
val navOptions = navOptions {
anim {
@@ -107,6 +135,7 @@
}
private const val DESTINATION_ID = 1
+private const val DESTINATION_ROUTE = "destination_route"
private const val ENTER_ANIM_ID = 10
private const val EXIT_ANIM_ID = 11
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
index a0e13a8..59e3987 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
@@ -57,9 +57,11 @@
public var arguments: Bundle? = null,
navControllerLifecycleOwner: LifecycleOwner? = null,
private val viewModelStoreProvider: NavViewModelStoreProvider? = null,
- // Internal unique name for this navBackStackEntry;
- @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public val id: UUID = UUID.randomUUID(),
+ /**
+ * Gets the unique ID that serves as the identity of this entry
+ * @return the unique ID of this entry
+ */
+ public val id: String = UUID.randomUUID().toString(),
private val savedState: Bundle? = null
) : LifecycleOwner,
ViewModelStoreOwner,
@@ -81,7 +83,7 @@
arguments: Bundle? = null,
navControllerLifecycleOwner: LifecycleOwner? = null,
viewModelStoreProvider: NavViewModelStoreProvider? = null,
- id: UUID = UUID.randomUUID(),
+ id: String = UUID.randomUUID().toString(),
savedState: Bundle? = null
): NavBackStackEntry = NavBackStackEntry(
context, destination, arguments,
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
index 12b750b..963c1f2 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
@@ -389,7 +389,7 @@
hierarchy.addFirst(current)
break
}
- if (parent == null || parent.startDestination != current.id) {
+ if (parent == null || parent.startDestinationId != current.id) {
hierarchy.addFirst(current)
}
current = parent
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
index 909f425..ceab39f 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
@@ -70,7 +70,7 @@
*
* @return the newly constructed [NavDestination]
*/
- public constructor(navigator: Navigator<out D>, route: String) :
+ public constructor(navigator: Navigator<out D>, route: String?) :
this(navigator, -1, route)
/**
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
index c826aa0..7932446 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
@@ -55,7 +55,7 @@
attrs,
R.styleable.NavGraphNavigator
).use {
- startDestination = it.getResourceId(R.styleable.NavGraphNavigator_startDestination, 0)
+ startDestinationId = it.getResourceId(R.styleable.NavGraphNavigator_startDestination, 0)
startDestIdName = getDisplayName(context, startDestId)
}
}
@@ -86,9 +86,15 @@
*/
public fun addDestination(node: NavDestination) {
val id = node.id
- require(id != 0) {
- "Destinations must have an id. Call setId() or include an android:id in your " +
- "navigation XML."
+ val innerRoute = node.route
+ require(id != 0 || innerRoute != null) {
+ "Destinations must have an id or route. Call setId(), setRoute(), or include an " +
+ "android:id or app:route in your navigation XML."
+ }
+ if (route != null) {
+ require(innerRoute != route) {
+ "Destination $node cannot have the same route as graph $this"
+ }
}
require(id != this.id) { "Destination $node cannot have the same id as graph $this" }
val existingDestination = nodes[id]
@@ -152,6 +158,20 @@
return findNode(resid, true)
}
+ /**
+ * Finds a destination in the collection by route. This will recursively check the
+ * [parent][getParent] of this navigation graph if node is not found in this navigation graph.
+ *
+ * @param route Route to locate
+ * @return the node with route
+ */
+ public fun findNode(route: String?): NavDestination? {
+ return if (!route.isNullOrBlank()) findNode(route, true) else null
+ }
+
+ /**
+ * @hide
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun findNode(@IdRes resid: Int, searchParents: Boolean): NavDestination? {
val destination = nodes[resid]
@@ -162,6 +182,19 @@
}
/**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public fun findNode(route: String, searchParents: Boolean): NavDestination? {
+ val id = createRoute(route).hashCode()
+ val destination = nodes[id]
+ // Search the parent for the NavDestination if it is not a child of this navigation graph
+ // and searchParents is true
+ return destination
+ ?: if (searchParents && parent != null) parent!!.findNode(route) else null
+ }
+
+ /**
* @throws NoSuchElementException if there no more elements
*/
public final override fun iterator(): MutableIterator<NavDestination> {
@@ -239,8 +272,18 @@
*/
get() = if (id != 0) super.displayName else "the root navigation"
+ /**
+ * Returns the starting destination for this NavGraph. When navigating to the NavGraph, this
+ * destination is the one the user will initially see.
+ *
+ * @return the start destination
+ */
+ @IdRes
+ @Deprecated("Use getStartDestinationId instead.", ReplaceWith("startDestinationId"))
+ public fun getStartDestination(): Int = startDestinationId
+
@get:IdRes
- public var startDestination: Int
+ public var startDestinationId: Int
/**
* Returns the starting destination for this NavGraph. When navigating to the NavGraph, this
* destination is the one the user will initially see.
@@ -248,20 +291,64 @@
* @return the start destination
*/
get() = startDestId
- /**
- * Sets the starting destination for this NavGraph.
- *
- * @param startDestId The id of the destination to be shown when navigating to this
- * NavGraph.
- */
- set(startDestId) {
+ private set(startDestId) {
require(startDestId != id) {
"Start destination $startDestId cannot use the same id as the graph $this"
}
+ if (startDestinationRoute != null) {
+ startDestinationRoute = null
+ }
this.startDestId = startDestId
startDestIdName = null
}
+ /**
+ * Sets the starting destination for this NavGraph.
+ *
+ * This will clear any previously set [startDestinationRoute]
+ *
+ * @param startDestId The id of the destination to be shown when navigating to this
+ * NavGraph.
+ */
+ public fun setStartDestination(startDestId: Int) {
+ startDestinationId = startDestId
+ }
+
+ /**
+ * Sets the starting destination for this NavGraph.
+ *
+ * This will override any previously set [startDestinationId]
+ *
+ * @param startDestRoute The route of the destination to be shown when navigating to this
+ * NavGraph.
+ */
+ public fun setStartDestination(startDestRoute: String) {
+ startDestinationRoute = startDestRoute
+ }
+
+ /**
+ * Returns the route for the starting destination for this NavGraph. When navigating to the
+ * NavGraph, this destination is the one the user will initially see.
+ *
+ * @return the start destination
+ */
+ public var startDestinationRoute: String? = null
+ private set(startDestRoute) {
+ startDestId = if (startDestRoute == null) {
+ 0
+ } else {
+ require(startDestRoute != route) {
+ "Start destination $startDestRoute cannot use the same route as the graph $this"
+ }
+ require(startDestRoute.isNotBlank()) {
+ "Cannot have an empty start destination route"
+ }
+ val internalRoute = createRoute(startDestRoute)
+ internalRoute.hashCode()
+ }
+ field = startDestRoute
+ }
+
public val startDestDisplayName: String
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
get() {
@@ -274,14 +361,13 @@
public override fun toString(): String {
val sb = StringBuilder()
sb.append(super.toString())
+ val startDestination = findNode(startDestinationRoute) ?: findNode(startDestinationId)
sb.append(" startDestination=")
- val startDestination = findNode(startDestination)
if (startDestination == null) {
- if (startDestIdName == null) {
- sb.append("0x")
- sb.append(Integer.toHexString(startDestId))
- } else {
- sb.append(startDestIdName)
+ when {
+ startDestinationRoute != null -> sb.append(startDestinationRoute)
+ startDestIdName != null -> sb.append(startDestIdName)
+ else -> sb.append("0x${Integer.toHexString(startDestId)}")
}
} else {
sb.append("{")
@@ -301,9 +387,22 @@
public inline operator fun NavGraph.get(@IdRes id: Int): NavDestination =
findNode(id) ?: throw IllegalArgumentException("No destination for $id was found in $this")
+/**
+ * Returns the destination with `route`.
+ *
+ * @throws IllegalArgumentException if no destination is found with that route.
+ */
+@Suppress("NOTHING_TO_INLINE")
+public inline operator fun NavGraph.get(route: String): NavDestination =
+ findNode(route)
+ ?: throw IllegalArgumentException("No destination for $route was found in $this")
+
/** Returns `true` if a destination with `id` is found in this navigation graph. */
public operator fun NavGraph.contains(@IdRes id: Int): Boolean = findNode(id) != null
+/** Returns `true` if a destination with `route` is found in this navigation graph. */
+public operator fun NavGraph.contains(route: String): Boolean = findNode(route) != null
+
/**
* Adds a destination to this NavGraph. The destination must have an
* [id][NavDestination.getId] set.
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
index e357290..cfe6d65 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
@@ -20,6 +20,12 @@
/**
* Construct a new [NavGraph]
+ *
+ * @param id the destination's unique id
+ * @param startDestination the starting destination for this NavGraph
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed NavGraph
*/
public inline fun NavigatorProvider.navigation(
@IdRes id: Int = 0,
@@ -28,7 +34,29 @@
): NavGraph = NavGraphBuilder(this, id, startDestination).apply(builder).build()
/**
+ * Construct a new [NavGraph]
+ *
+ * @param startDestination the starting destination's route for this NavGraph
+ * @param route the destination's unique route
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed NavGraph
+ */
+public inline fun NavigatorProvider.navigation(
+ startDestination: String,
+ route: String? = null,
+ builder: NavGraphBuilder.() -> Unit
+): NavGraph = NavGraphBuilder(this, startDestination, route).apply(builder)
+ .build()
+
+/**
* Construct a nested [NavGraph]
+ *
+ * @param id the destination's unique id
+ * @param startDestination the starting destination for this NavGraph
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
*/
public inline fun NavGraphBuilder.navigation(
@IdRes id: Int,
@@ -37,14 +65,70 @@
): Unit = destination(NavGraphBuilder(provider, id, startDestination).apply(builder))
/**
+ * Construct a nested [NavGraph]
+ *
+ * @param startDestination the starting destination's route for this NavGraph
+ * @param route the destination's unique route
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
+ */
+public inline fun NavGraphBuilder.navigation(
+ startDestination: String,
+ route: String,
+ builder: NavGraphBuilder.() -> Unit
+): Unit = destination(NavGraphBuilder(provider, startDestination, route).apply(builder))
+
+/**
* DSL for constructing a new [NavGraph]
*/
@NavDestinationDsl
-public open class NavGraphBuilder(
- public val provider: NavigatorProvider,
- @IdRes id: Int,
- @IdRes private var startDestination: Int
-) : NavDestinationBuilder<NavGraph>(provider[NavGraphNavigator::class], id) {
+public open class NavGraphBuilder : NavDestinationBuilder<NavGraph> {
+ /**
+ * Retrieve the [NavGraphBuilder]'s [NavigatorProvider].
+ *
+ * @return The [NavigatorProvider] used by this [NavGraphBuilder].
+ */
+ public val provider: NavigatorProvider
+ @IdRes private var startDestinationId: Int = 0
+ private var startDestinationRoute: String? = null
+
+ /**
+ * DSL for constructing a new [NavGraph]
+ *
+ * @param provider navigator used to create the destination
+ * @param id the graph's unique id
+ * @param startDestination the starting destination for this NavGraph
+ *
+ * @return the newly created NavGraph
+ */
+ public constructor(
+ provider: NavigatorProvider,
+ @IdRes id: Int,
+ @IdRes startDestination: Int
+ ) : super(provider[NavGraphNavigator::class], id) {
+ this.provider = provider
+ this.startDestinationId = startDestination
+ }
+
+ /**
+ * DSL for constructing a new [NavGraph]
+ *
+ * @param provider navigator used to create the destination
+ * @param startDestination the starting destination's route for this NavGraph
+ * @param route the graph's unique route
+ *
+ * @return the newly created NavGraph
+ */
+ public constructor(
+ provider: NavigatorProvider,
+ startDestination: String,
+ route: String?
+ ) : super(provider[NavGraphNavigator::class], route) {
+ this.provider = provider
+ this.startDestinationRoute = startDestination
+ }
+
private val destinations = mutableListOf<NavDestination>()
/**
@@ -70,9 +154,17 @@
override fun build(): NavGraph = super.build().also { navGraph ->
navGraph.addDestinations(destinations)
- if (startDestination == 0) {
- throw IllegalStateException("You must set a startDestination")
+ if (startDestinationId == 0 && startDestinationRoute == null) {
+ if (route != null) {
+ throw IllegalStateException("You must set a start destination route")
+ } else {
+ throw IllegalStateException("You must set a start destination id")
+ }
}
- navGraph.startDestination = startDestination
+ if (startDestinationRoute != null) {
+ navGraph.setStartDestination(startDestinationRoute!!)
+ } else {
+ navGraph.setStartDestination(startDestinationId)
+ }
}
}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphNavigator.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphNavigator.kt
index 748bb59..4b58555 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphNavigator.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphNavigator.kt
@@ -15,9 +15,6 @@
*/
package androidx.navigation
-import android.os.Bundle
-import java.lang.IllegalArgumentException
-
/**
* A Navigator built specifically for [NavGraph] elements. Handles navigating to the
* correct destination when the NavGraph is the target of navigation actions.
@@ -44,16 +41,32 @@
* @throws IllegalArgumentException if given destination is not a child of the current navgraph
*/
override fun navigate(
- destination: NavGraph,
- args: Bundle?,
+ entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Extras?
- ): NavDestination? {
- val startId = destination.startDestination
- check(startId != 0) {
+ ) {
+ for (entry in entries) {
+ navigate(entry, navOptions, navigatorExtras)
+ }
+ }
+
+ private fun navigate(
+ entry: NavBackStackEntry,
+ navOptions: NavOptions?,
+ navigatorExtras: Extras?
+ ) {
+ val destination = entry.destination as NavGraph
+ val args = entry.arguments
+ val startId = destination.startDestinationId
+ val startRoute = destination.startDestinationRoute
+ check(startId != 0 || startRoute != null) {
("no start destination defined via app:startDestination for ${destination.displayName}")
}
- val startDestination = destination.findNode(startId, false)
+ val startDestination = if (startRoute != null) {
+ destination.findNode(startRoute, false)
+ } else {
+ destination.findNode(startId, false)
+ }
requireNotNull(startDestination) {
val dest = destination.startDestDisplayName
throw IllegalArgumentException(
@@ -63,15 +76,10 @@
val navigator = navigatorProvider.getNavigator<Navigator<NavDestination>>(
startDestination.navigatorName
)
- return navigator.navigate(
+ val startDestinationEntry = state.createBackStackEntry(
startDestination,
- startDestination.addInDefaultArgs(args),
- navOptions,
- navigatorExtras
+ startDestination.addInDefaultArgs(args)
)
- }
-
- override fun popBackStack(): Boolean {
- return true
+ navigator.navigate(listOf(startDestinationEntry), navOptions, navigatorExtras)
}
}
\ No newline at end of file
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavOptions.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavOptions.kt
index 8e54db5..38e8fa3 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavOptions.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavOptions.kt
@@ -18,6 +18,7 @@
import androidx.annotation.AnimRes
import androidx.annotation.AnimatorRes
import androidx.annotation.IdRes
+import androidx.navigation.NavDestination.Companion.createRoute
/**
* NavOptions stores special options for navigate actions
@@ -35,7 +36,7 @@
* @see shouldPopUpToSaveState
*/
@field:IdRes @get:IdRes @param:IdRes
- public val popUpTo: Int,
+ public val popUpToId: Int,
private val popUpToInclusive: Boolean,
private val popUpToSaveState: Boolean,
/**
@@ -66,6 +67,58 @@
public val popExitAnim: Int
) {
/**
+ * The destination to pop up to before navigating. When set, all non-matching destinations
+ * should be popped from the back stack.
+ * @return the destinationId to pop up to, clearing all intervening destinations
+ * @see Builder.setPopUpTo
+ *
+ * @see isPopUpToInclusive
+ * @see shouldPopUpToSaveState
+ */
+ @IdRes
+ @Deprecated("Use popUpToId instead.", ReplaceWith("popUpToId"))
+ public fun getPopUpTo(): Int = popUpToId
+
+ /**
+ * Route for the destination to pop up to before navigating. When set, all non-matching
+ * destinations should be popped from the back stack.
+ * @return the destination route to pop up to, clearing all intervening destinations
+ * @see Builder.setPopUpTo
+ *
+ * @see isPopUpToInclusive
+ * @see shouldPopUpToSaveState
+ */
+ public var popUpToRoute: String? = null
+ private set
+
+ /**
+ * NavOptions stores special options for navigate actions
+ */
+ internal constructor(
+ singleTop: Boolean,
+ restoreState: Boolean,
+ popUpToRoute: String?,
+ popUpToInclusive: Boolean,
+ popUpToSaveState: Boolean,
+ enterAnim: Int,
+ exitAnim: Int,
+ popEnterAnim: Int,
+ popExitAnim: Int
+ ) : this(
+ singleTop,
+ restoreState,
+ createRoute(popUpToRoute).hashCode(),
+ popUpToInclusive,
+ popUpToSaveState,
+ enterAnim,
+ exitAnim,
+ popEnterAnim,
+ popExitAnim
+ ) {
+ this.popUpToRoute = popUpToRoute
+ }
+
+ /**
* Whether this navigation action should launch as single-top (i.e., there will be at most
* one copy of a given destination on the top of the back stack).
*
@@ -97,9 +150,9 @@
/**
* Whether the back stack and the state of all destinations between the
- * current destination and [popUpTo] should be saved for later restoration via
+ * current destination and [popUpToId] should be saved for later restoration via
* [Builder.setRestoreState] or the `restoreState` attribute using the same ID
- * as [popUpTo] (note: this matching ID is true whether [isPopUpToInclusive] is true or
+ * as [popUpToId] (note: this matching ID is true whether [isPopUpToInclusive] is true or
* false).
*/
public fun shouldPopUpToSaveState(): Boolean {
@@ -112,7 +165,8 @@
val that = other as NavOptions
return singleTop == that.singleTop &&
restoreState == that.restoreState &&
- popUpTo == that.popUpTo &&
+ popUpToId == that.popUpToId &&
+ popUpToRoute == that.popUpToRoute &&
popUpToInclusive == that.popUpToInclusive &&
popUpToSaveState == that.popUpToSaveState &&
enterAnim == that.enterAnim &&
@@ -124,7 +178,8 @@
override fun hashCode(): Int {
var result = if (shouldLaunchSingleTop()) 1 else 0
result = 31 * result + if (shouldRestoreState()) 1 else 0
- result = 31 * result + popUpTo
+ result = 31 * result + popUpToId
+ result = 31 * result + popUpToRoute.hashCode()
result = 31 * result + if (isPopUpToInclusive()) 1 else 0
result = 31 * result + if (shouldPopUpToSaveState()) 1 else 0
result = 31 * result + enterAnim
@@ -142,7 +197,8 @@
private var restoreState = false
@IdRes
- private var popUpTo = -1
+ private var popUpToId = -1
+ private var popUpToRoute: String? = null
private var popUpToInclusive = false
private var popUpToSaveState = false
@@ -194,11 +250,11 @@
* @param saveState true if the back stack and the state of all destinations between the
* current destination and [destinationId] should be saved for later restoration via
* [setRestoreState] or the `restoreState` attribute using the same ID
- * as [popUpTo] (note: this matching ID is true whether [inclusive] is true or
+ * as [popUpToId] (note: this matching ID is true whether [inclusive] is true or
* false).
* @return this Builder
*
- * @see NavOptions.popUpTo
+ * @see NavOptions.popUpToId
* @see NavOptions.isPopUpToInclusive
*/
@JvmOverloads
@@ -207,7 +263,37 @@
inclusive: Boolean,
saveState: Boolean = false
): Builder {
- popUpTo = destinationId
+ popUpToId = destinationId
+ popUpToRoute = null
+ popUpToInclusive = inclusive
+ popUpToSaveState = saveState
+ return this
+ }
+
+ /**
+ * Pop up to a given destination before navigating. This pops all non-matching destinations
+ * from the back stack until this destination is found.
+ *
+ * @param route route for destination to pop up to, clearing all intervening destinations.
+ * @param inclusive true to also pop the given destination from the back stack.
+ * @param saveState true if the back stack and the state of all destinations between the
+ * current destination and [route] should be saved for later restoration via
+ * [setRestoreState] or the `restoreState` attribute using the same ID
+ * as [popUpToRoute] (note: this matching ID is true whether [inclusive] is true or
+ * false).
+ * @return this Builder
+ *
+ * @see NavOptions.popUpToId
+ * @see NavOptions.isPopUpToInclusive
+ */
+ @JvmOverloads
+ public fun setPopUpTo(
+ route: String?,
+ inclusive: Boolean,
+ saveState: Boolean = false
+ ): Builder {
+ popUpToRoute = route
+ popUpToId = -1
popUpToInclusive = inclusive
popUpToSaveState = saveState
return this
@@ -275,11 +361,18 @@
* @return a constructed NavOptions
*/
public fun build(): NavOptions {
- return NavOptions(
- singleTop, restoreState,
- popUpTo, popUpToInclusive, popUpToSaveState,
- enterAnim, exitAnim, popEnterAnim, popExitAnim
- )
+ return if (popUpToRoute != null)
+ NavOptions(
+ singleTop, restoreState,
+ popUpToRoute, popUpToInclusive, popUpToSaveState,
+ enterAnim, exitAnim, popEnterAnim, popExitAnim
+ )
+ else
+ NavOptions(
+ singleTop, restoreState,
+ popUpToId, popUpToInclusive, popUpToSaveState,
+ enterAnim, exitAnim, popEnterAnim, popExitAnim
+ )
}
}
}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavOptionsBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavOptionsBuilder.kt
index 05e7986..8865788 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavOptionsBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavOptionsBuilder.kt
@@ -55,14 +55,38 @@
public var restoreState: Boolean = false
/**
+ * Returns the current destination that the builder will pop up to.
+ */
+ @IdRes
+ public var popUpToId: Int = -1
+ internal set(value) {
+ field = value
+ inclusive = false
+ }
+
+ /**
* Pop up to a given destination before navigating. This pops all non-matching destinations
* from the back stack until this destination is found.
*/
- @IdRes
- public var popUpTo: Int = -1
+ @Deprecated("Use the popUpToId property.")
+ public var popUpTo: Int
+ get() = popUpToId
+ @Deprecated("Use the popUpTo function and passing in the id.")
set(value) {
- field = value
- inclusive = false
+ popUpTo(value)
+ }
+
+ /**
+ * Pop up to a given destination before navigating. This pops all non-matching destinations
+ * from the back stack until this destination is found.
+ */
+ public var popUpToRoute: String? = null
+ private set(value) {
+ if (value != null) {
+ require(value.isNotBlank()) { "Cannot pop up to an empty route" }
+ field = value
+ inclusive = false
+ }
}
private var inclusive = false
private var saveState = false
@@ -71,8 +95,24 @@
* Pop up to a given destination before navigating. This pops all non-matching destinations
* from the back stack until this destination is found.
*/
- public fun popUpTo(@IdRes id: Int, popUpToBuilder: PopUpToBuilder.() -> Unit) {
- popUpTo = id
+ public fun popUpTo(@IdRes id: Int, popUpToBuilder: PopUpToBuilder.() -> Unit = {}) {
+ popUpToId = id
+ popUpToRoute = null
+ val builder = PopUpToBuilder().apply(popUpToBuilder)
+ inclusive = builder.inclusive
+ saveState = builder.saveState
+ }
+
+ /**
+ * Pop up to a given destination before navigating. This pops all non-matching destination routes
+ * from the back stack until the destination with a matching route is found.
+ *
+ * @param route route for the destination
+ * @param popUpToBuilder builder used to construct a popUpTo operation
+ */
+ public fun popUpTo(route: String, popUpToBuilder: PopUpToBuilder.() -> Unit = {}) {
+ popUpToRoute = route
+ popUpToId = -1
val builder = PopUpToBuilder().apply(popUpToBuilder)
inclusive = builder.inclusive
saveState = builder.saveState
@@ -95,7 +135,11 @@
internal fun build() = builder.apply {
setLaunchSingleTop(launchSingleTop)
setRestoreState(restoreState)
- setPopUpTo(popUpTo, inclusive, saveState)
+ if (popUpToRoute != null) {
+ setPopUpTo(popUpToRoute, inclusive, saveState)
+ } else {
+ setPopUpTo(popUpToId, inclusive, saveState)
+ }
}.build()
}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
index ef64348..b52cb29 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
@@ -29,7 +29,7 @@
*
* You should only use one of the static NavType instances and subclasses defined in this class.
*
- * @param <T> the type of the data that is supported by this NavType
+ * @param T the type of the data that is supported by this NavType
*/
public abstract class NavType<T> internal constructor(
/**
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavViewModelStoreProvider.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavViewModelStoreProvider.kt
index 0357e57..74fdc56 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavViewModelStoreProvider.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavViewModelStoreProvider.kt
@@ -18,15 +18,14 @@
import androidx.annotation.RestrictTo
import androidx.lifecycle.ViewModelStore
-import java.util.UUID
/**
* Interface that allows you to retrieve a [ViewModelStore] associated with a
- * particular [UUID].
+ * particular [NavBackStackEntry.id].
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public interface NavViewModelStoreProvider {
- public fun getViewModelStore(backStackEntryUUID: UUID): ViewModelStore
+ public fun getViewModelStore(backStackEntryId: String): ViewModelStore
}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorProvider.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorProvider.kt
index 1d91ff0..8162b45 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorProvider.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorProvider.kt
@@ -103,6 +103,9 @@
): Navigator<out NavDestination>? {
require(validateName(name)) { "navigator name cannot be an empty string" }
val previousNavigator = _navigators[name]
+ if (previousNavigator == navigator) {
+ return navigator
+ }
check(previousNavigator?.isAttached != true) {
"Navigator $navigator is replacing an already attached $previousNavigator"
}
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/NavDestinationTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/NavDestinationTest.kt
index 2d031b6..a0a0af1 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/NavDestinationTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/NavDestinationTest.kt
@@ -150,7 +150,7 @@
val navGraphNavigator = NavGraphNavigator(mock(NavigatorProvider::class.java))
val parent = navGraphNavigator.createDestination().apply {
id = parentId
- startDestination = DESTINATION_ID
+ setStartDestination(DESTINATION_ID)
}
destination.parent = parent
val deepLinkIds = destination.buildDeepLinkIds()
@@ -181,13 +181,13 @@
val navGraphNavigator = NavGraphNavigator(mock(NavigatorProvider::class.java))
val parent = navGraphNavigator.createDestination().apply {
id = parentId
- startDestination = DESTINATION_ID
+ setStartDestination(DESTINATION_ID)
}
destination.parent = parent
val grandparentId = 3
val grandparent = navGraphNavigator.createDestination().apply {
id = grandparentId
- startDestination = parentId
+ setStartDestination(parentId)
}
parent.parent = grandparent
val deepLinkIds = destination.buildDeepLinkIds()
@@ -208,7 +208,7 @@
val grandparentId = 3
val grandparent = navGraphNavigator.createDestination().apply {
id = grandparentId
- startDestination = parentId
+ setStartDestination(parentId)
}
parent.parent = grandparent
val deepLinkIds = destination.buildDeepLinkIds()
@@ -229,7 +229,7 @@
val grandparentId = 3
val grandparent = navGraphNavigator.createDestination().apply {
id = grandparentId
- startDestination = parentId
+ setStartDestination(parentId)
}
parent.parent = grandparent
val deepLinkIds = destination.buildDeepLinkIds()
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt
index cd5e75f..5cd574a 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt
@@ -17,9 +17,11 @@
package androidx.navigation
import androidx.annotation.IdRes
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.navigation.testing.TestNavigatorState
import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -34,8 +36,13 @@
private const val SECOND_DESTINATION_ID = 2
}
+ @get:Rule
+ val instantTaskExecutorRule = InstantTaskExecutorRule()
+
private lateinit var provider: NavigatorProvider
+ private lateinit var noOpState: TestNavigatorState
private lateinit var noOpNavigator: NoOpNavigator
+ private lateinit var navGraphState: TestNavigatorState
private lateinit var navGraphNavigator: NavGraphNavigator
@Before
@@ -48,6 +55,10 @@
}
)
}
+ noOpState = TestNavigatorState()
+ noOpNavigator.onAttach(noOpState)
+ navGraphState = TestNavigatorState()
+ navGraphNavigator.onAttach(navGraphState)
}
private fun createFirstDestination() = noOpNavigator.createDestination().apply {
@@ -63,7 +74,7 @@
startId: Int = destination.id
) = navGraphNavigator.createDestination().apply {
addDestination(destination)
- startDestination = startId
+ setStartDestination(startId)
}
@Test(expected = IllegalStateException::class)
@@ -72,94 +83,19 @@
val graph = navGraphNavigator.createDestination().apply {
addDestination(destination)
id = 2 // can't match id of first destination or the start destination
- startDestination = 0
+ setStartDestination(0)
}
- navGraphNavigator.navigate(graph, null, null, null)
+ val entry = navGraphState.createBackStackEntry(graph, null)
+ navGraphNavigator.navigate(listOf(entry), null, null)
}
@Test
fun navigate() {
val destination = createFirstDestination()
val graph = createGraphWithDestination(destination)
- assertThat(navGraphNavigator.navigate(graph, null, null, null))
- .isEqualTo(destination)
- }
-
- @Test
- fun navigateThenPop() {
- val destination = createFirstDestination()
- val graph = createGraphWithDestination(destination)
- assertThat(navGraphNavigator.navigate(graph, null, null, null))
- .isEqualTo(destination)
- val success = navGraphNavigator.popBackStack()
- assertWithMessage("popBackStack should return true")
- .that(success)
- .isTrue()
- }
-
- @Test
- fun navigateSingleTopOnEmptyStack() {
- val destination = createFirstDestination()
- val graph = createGraphWithDestination(destination)
- // singleTop should still show as added on an empty stack
- assertThat(
- navGraphNavigator.navigate(
- graph, null,
- NavOptions.Builder().setLaunchSingleTop(true).build(), null
- )
- )
- .isEqualTo(destination)
- }
-
- @Test
- fun navigateSingleTop() {
- val destination = createFirstDestination()
- val graph = createGraphWithDestination(destination)
- assertThat(navGraphNavigator.navigate(graph, null, null, null))
- .isEqualTo(destination)
- assertThat(
- navGraphNavigator.navigate(
- graph, null,
- NavOptions.Builder().setLaunchSingleTop(true).build(), null
- )
- )
- .isEqualTo(destination)
- }
-
- @Test
- fun navigateSingleTopNotTop() {
- val destination = createFirstDestination()
- val graph = createGraphWithDestination(destination)
- val secondDestination = createSecondDestination()
- val secondGraph = createGraphWithDestination(secondDestination).apply {
- id = SECOND_DESTINATION_ID
- }
- assertThat(navGraphNavigator.navigate(graph, null, null, null))
- .isEqualTo(destination)
- assertThat(
- navGraphNavigator.navigate(
- secondGraph, null,
- NavOptions.Builder().setLaunchSingleTop(true).build(), null
- )
- )
- .isEqualTo(secondDestination)
- }
-
- @Test
- fun navigateSingleTopNested() {
- val destination = createFirstDestination()
- val nestedGraph = createGraphWithDestination(destination).apply {
- id = FIRST_DESTINATION_ID
- }
- val graph = createGraphWithDestination(nestedGraph)
- assertThat(navGraphNavigator.navigate(graph, null, null, null))
- .isEqualTo(destination)
- assertThat(
- navGraphNavigator.navigate(
- graph, null,
- NavOptions.Builder().setLaunchSingleTop(true).build(), null
- )
- )
- .isEqualTo(destination)
+ val entry = navGraphState.createBackStackEntry(graph, null)
+ navGraphNavigator.navigate(listOf(entry), null, null)
+ assertThat(noOpState.backStack.value.map { it.destination })
+ .containsExactly(destination)
}
}
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphTest.kt
index 003c8b2..296c666 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphTest.kt
@@ -107,7 +107,7 @@
id = FIRST_DESTINATION_ID
}
try {
- graph.startDestination = destination.id
+ graph.setStartDestination(destination.id)
} catch (e: IllegalArgumentException) {
assertWithMessage("Setting a start destination with same id as its parent should fail")
.that(e).hasMessageThat().contains(
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/NavigatorProviderTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/NavigatorProviderTest.kt
index d4dcfe6..8a6c0c6 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/NavigatorProviderTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/NavigatorProviderTest.kt
@@ -17,6 +17,7 @@
package androidx.navigation
import android.os.Bundle
+import androidx.navigation.testing.TestNavigatorState
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Assert.fail
@@ -100,6 +101,50 @@
.isEqualTo(navigator)
}
+ @Test
+ fun addExistingNavigatorDoesntReplace() {
+ val navigatorState = TestNavigatorState()
+ val provider = NavigatorProvider()
+ val navigator = EmptyNavigator()
+
+ provider.addNavigator(navigator)
+ assertThat(provider.getNavigator<EmptyNavigator>(EmptyNavigator.NAME))
+ .isEqualTo(navigator)
+
+ navigator.onAttach(navigatorState)
+ assertWithMessage("Navigator should be attached")
+ .that(provider.getNavigator<EmptyNavigator>(EmptyNavigator.NAME).isAttached)
+ .isTrue()
+
+ // addNavigator should throw when trying to replace an existing, attached navigator, but
+ // we should have returned before that
+ try {
+ provider.addNavigator(navigator)
+ } catch (navigatorAlreadyAttached: IllegalStateException) {
+ fail(
+ "addNavigator with an existing navigator should return early and not " +
+ "attempt to replace"
+ )
+ }
+ }
+
+ @Test
+ fun addWithSameNameButUnequalNavigatorDoesReplace() {
+ val provider = NavigatorProvider()
+ val navigatorA = EmptyNavigator()
+ val navigatorB = EmptyNavigator()
+
+ assertThat(navigatorA).isNotEqualTo(navigatorB)
+
+ provider.addNavigator(navigatorA)
+ assertThat(provider.getNavigator<EmptyNavigator>(EmptyNavigator.NAME))
+ .isEqualTo(navigatorA)
+
+ provider.addNavigator(navigatorB)
+ assertThat(provider.getNavigator<EmptyNavigator>(EmptyNavigator.NAME))
+ .isEqualTo(navigatorB)
+ }
+
private val provider = NavigatorProvider()
@Test
diff --git a/navigation/navigation-compose/api/current.txt b/navigation/navigation-compose/api/current.txt
index 3e486eb..7a9fbf0 100644
--- a/navigation/navigation-compose/api/current.txt
+++ b/navigation/navigation-compose/api/current.txt
@@ -11,24 +11,29 @@
}
public final class NamedNavArgument {
+ method public operator String component1();
+ method public operator androidx.navigation.NavArgument component2();
+ method public androidx.navigation.NavArgument getArgument();
+ method public String getName();
+ property public final androidx.navigation.NavArgument argument;
+ property public final String name;
}
public final class NamedNavArgumentKt {
method @androidx.navigation.NavDestinationDsl public static androidx.navigation.compose.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
}
+ public final class NavBackStackEntryProviderKt {
+ method @androidx.compose.runtime.Composable public static void LocalOwnersProvider(androidx.navigation.NavBackStackEntry, androidx.compose.runtime.saveable.SaveableStateHolder saveableStateHolder, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
public final class NavGraphBuilderKt {
method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.compose.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
- method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavHostControllerKt {
- method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
- method public static androidx.navigation.NavBackStackEntry getBackStackEntry(androidx.navigation.NavController, String route);
- method public static void navigate(androidx.navigation.NavController, String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
method @androidx.compose.runtime.Composable public static androidx.navigation.NavHostController rememberNavController();
- field public static final String KEY_ROUTE = "android-support-nav:controller:route";
}
public final class NavHostKt {
@@ -36,9 +41,5 @@
method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
}
- public final class NavOptionsBuilderKt {
- method public static void popUpTo(androidx.navigation.NavOptionsBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
- }
-
}
diff --git a/navigation/navigation-compose/api/public_plus_experimental_current.txt b/navigation/navigation-compose/api/public_plus_experimental_current.txt
index 3e486eb..7a9fbf0 100644
--- a/navigation/navigation-compose/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-compose/api/public_plus_experimental_current.txt
@@ -11,24 +11,29 @@
}
public final class NamedNavArgument {
+ method public operator String component1();
+ method public operator androidx.navigation.NavArgument component2();
+ method public androidx.navigation.NavArgument getArgument();
+ method public String getName();
+ property public final androidx.navigation.NavArgument argument;
+ property public final String name;
}
public final class NamedNavArgumentKt {
method @androidx.navigation.NavDestinationDsl public static androidx.navigation.compose.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
}
+ public final class NavBackStackEntryProviderKt {
+ method @androidx.compose.runtime.Composable public static void LocalOwnersProvider(androidx.navigation.NavBackStackEntry, androidx.compose.runtime.saveable.SaveableStateHolder saveableStateHolder, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
public final class NavGraphBuilderKt {
method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.compose.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
- method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavHostControllerKt {
- method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
- method public static androidx.navigation.NavBackStackEntry getBackStackEntry(androidx.navigation.NavController, String route);
- method public static void navigate(androidx.navigation.NavController, String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
method @androidx.compose.runtime.Composable public static androidx.navigation.NavHostController rememberNavController();
- field public static final String KEY_ROUTE = "android-support-nav:controller:route";
}
public final class NavHostKt {
@@ -36,9 +41,5 @@
method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
}
- public final class NavOptionsBuilderKt {
- method public static void popUpTo(androidx.navigation.NavOptionsBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
- }
-
}
diff --git a/navigation/navigation-compose/api/restricted_current.txt b/navigation/navigation-compose/api/restricted_current.txt
index 3e486eb..7a9fbf0 100644
--- a/navigation/navigation-compose/api/restricted_current.txt
+++ b/navigation/navigation-compose/api/restricted_current.txt
@@ -11,24 +11,29 @@
}
public final class NamedNavArgument {
+ method public operator String component1();
+ method public operator androidx.navigation.NavArgument component2();
+ method public androidx.navigation.NavArgument getArgument();
+ method public String getName();
+ property public final androidx.navigation.NavArgument argument;
+ property public final String name;
}
public final class NamedNavArgumentKt {
method @androidx.navigation.NavDestinationDsl public static androidx.navigation.compose.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
}
+ public final class NavBackStackEntryProviderKt {
+ method @androidx.compose.runtime.Composable public static void LocalOwnersProvider(androidx.navigation.NavBackStackEntry, androidx.compose.runtime.saveable.SaveableStateHolder saveableStateHolder, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
public final class NavGraphBuilderKt {
method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.compose.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
- method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavHostControllerKt {
- method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
- method public static androidx.navigation.NavBackStackEntry getBackStackEntry(androidx.navigation.NavController, String route);
- method public static void navigate(androidx.navigation.NavController, String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
method @androidx.compose.runtime.Composable public static androidx.navigation.NavHostController rememberNavController();
- field public static final String KEY_ROUTE = "android-support-nav:controller:route";
}
public final class NavHostKt {
@@ -36,9 +41,5 @@
method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
}
- public final class NavOptionsBuilderKt {
- method public static void popUpTo(androidx.navigation.NavOptionsBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
- }
-
}
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
index 60b3df3..6f7d3de 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
@@ -27,11 +27,9 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.navigation.compose.KEY_ROUTE
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
-import androidx.navigation.compose.navigate
import androidx.navigation.compose.rememberNavController
import androidx.navigation.compose.samples.Dashboard
import androidx.navigation.compose.samples.Profile
@@ -52,7 +50,7 @@
bottomBar = {
BottomNavigation {
val navBackStackEntry = navController.currentBackStackEntryAsState().value
- val entryRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
+ val entryRoute = navBackStackEntry?.destination?.route
items.forEach { (name, route) ->
BottomNavigationItem(
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
@@ -60,8 +58,9 @@
selected = entryRoute == route,
onClick = {
navController.navigate(route) {
+ launchSingleTop = true
restoreState = true
- popUpTo(navController.graph.startDestination) {
+ popUpTo(navController.graph.startDestinationId) {
saveState = true
}
}
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
index a195480a..243a1d1 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
@@ -30,8 +30,6 @@
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
-import androidx.navigation.compose.navigate
-import androidx.navigation.compose.popUpTo
import androidx.navigation.compose.rememberNavController
@Composable
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt
index 0cc7bcb..051d40b 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt
@@ -30,7 +30,6 @@
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
-import androidx.navigation.compose.navigate
import androidx.navigation.compose.rememberNavController
import androidx.navigation.compose.samples.NavigateButton
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
index e5253fb..2dbaf21 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
@@ -33,7 +33,6 @@
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
-import androidx.navigation.compose.navigate
import androidx.navigation.compose.rememberNavController
import androidx.navigation.compose.samples.Dashboard
import androidx.navigation.compose.samples.NavigateButton
diff --git a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
index 639aeef..3f988fc 100644
--- a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
+++ b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
@@ -41,9 +41,8 @@
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
-import androidx.navigation.compose.navigate
-import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navigation
sealed class Screen(val route: String, @StringRes val resourceId: Int) {
object Profile : Screen("profile", R.string.profile)
@@ -61,7 +60,6 @@
}
}
-@Sampled
@Composable
fun NestedNavStartDestination() {
val navController = rememberNavController()
@@ -74,7 +72,6 @@
}
}
-@Sampled
@Composable
fun NestedNavInGraph() {
val navController = rememberNavController()
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt
new file mode 100644
index 0000000..20edc30
--- /dev/null
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.navigation.compose
+
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
+import androidx.compose.ui.test.junit4.StateRestorationTester
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.testing.TestNavigatorState
+import androidx.savedstate.SavedStateRegistryOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.testutils.TestNavigator
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class NavBackStackEntryProviderTest {
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun testViewModelStoreOwnerProvided() {
+ val backStackEntry = createBackStackEntry()
+ var viewModelStoreOwner: ViewModelStoreOwner? = null
+
+ composeTestRule.setContent {
+ val saveableStateHolder = rememberSaveableStateHolder()
+ backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+ viewModelStoreOwner = LocalViewModelStoreOwner.current
+ }
+ }
+
+ assertWithMessage("ViewModelStoreOwner is provided by $backStackEntry")
+ .that(viewModelStoreOwner).isEqualTo(backStackEntry)
+ }
+
+ @Test
+ fun testLifecycleOwnerProvided() {
+ val backStackEntry = createBackStackEntry()
+ var lifecycleOwner: LifecycleOwner? = null
+
+ composeTestRule.setContent {
+ val saveableStateHolder = rememberSaveableStateHolder()
+ backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+ lifecycleOwner = LocalLifecycleOwner.current
+ }
+ }
+
+ assertWithMessage("LifecycleOwner is provided by $backStackEntry")
+ .that(lifecycleOwner).isEqualTo(backStackEntry)
+ }
+
+ @Test
+ fun testLocalSavedStateRegistryOwnerProvided() {
+ val backStackEntry = createBackStackEntry()
+ var localSavedStateRegistryOwner: SavedStateRegistryOwner? = null
+
+ composeTestRule.setContent {
+ val saveableStateHolder = rememberSaveableStateHolder()
+ backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+ localSavedStateRegistryOwner = LocalSavedStateRegistryOwner.current
+ }
+ }
+
+ assertWithMessage("LocalSavedStateRegistryOwner is provided by $backStackEntry")
+ .that(localSavedStateRegistryOwner).isEqualTo(backStackEntry)
+ }
+
+ @Test
+ fun testSaveableValueInContentIsSaved() {
+ val backStackEntry = createBackStackEntry()
+ val restorationTester = StateRestorationTester(composeTestRule)
+ var array: IntArray? = null
+
+ restorationTester.setContent {
+ val saveableStateHolder = rememberSaveableStateHolder()
+ backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+ array = rememberSaveable {
+ intArrayOf(0)
+ }
+ }
+ }
+
+ assertThat(array).isEqualTo(intArrayOf(0))
+
+ composeTestRule.runOnUiThread {
+ array!![0] = 1
+ // we null it to ensure recomposition happened
+ array = null
+ }
+
+ restorationTester.emulateSavedInstanceStateRestore()
+
+ assertThat(array).isEqualTo(intArrayOf(1))
+ }
+
+ @Test
+ fun testNonSaveableValueInContentIsNotSaved() {
+ val backStackEntry = createBackStackEntry()
+ val restorationTester = StateRestorationTester(composeTestRule)
+ var nonSaveable: IntArray? = null
+ val initialValue = intArrayOf(10)
+
+ restorationTester.setContent {
+ val saveableStateHolder = rememberSaveableStateHolder()
+ backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+ nonSaveable = remember { initialValue }
+ }
+ }
+
+ assertThat(nonSaveable).isEqualTo(initialValue)
+
+ composeTestRule.runOnUiThread {
+ nonSaveable!![0] = 1
+ // we null it to ensure recomposition happened
+ nonSaveable = null
+ }
+
+ restorationTester.emulateSavedInstanceStateRestore()
+
+ assertThat(nonSaveable).isEqualTo(initialValue)
+ }
+
+ private fun createBackStackEntry(): NavBackStackEntry {
+ val testNavigator = TestNavigator()
+ val testNavigatorState = TestNavigatorState()
+ testNavigator.onAttach(testNavigatorState)
+ val backStackEntry = testNavigatorState.createBackStackEntry(
+ testNavigator.createDestination(),
+ null
+ )
+ // We navigate to move the NavBackStackEntry to the correct state
+ testNavigator.navigate(listOf(backStackEntry), null, null)
+ return backStackEntry
+ }
+}
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavGraphBuilderTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavGraphBuilderTest.kt
index eac32a3..776d4a2 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavGraphBuilderTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavGraphBuilderTest.kt
@@ -20,8 +20,10 @@
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.net.toUri
+import androidx.navigation.contains
import androidx.navigation.NavDeepLinkRequest
import androidx.navigation.navDeepLink
+import androidx.navigation.navigation
import androidx.navigation.testing.TestNavHostController
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostControllerTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostControllerTest.kt
index 48fa470..e89ce23 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostControllerTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostControllerTest.kt
@@ -16,26 +16,26 @@
package androidx.navigation.compose
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
-import androidx.navigation.NavDestination
-import androidx.navigation.NavDestinationBuilder
-import androidx.navigation.NavGraphBuilder
+import androidx.navigation.createGraph
import androidx.navigation.get
+import androidx.navigation.plusAssign
import androidx.navigation.testing.TestNavHostController
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.testutils.TestNavigator
+import androidx.testutils.test
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import java.lang.IllegalArgumentException
@LargeTest
@RunWith(AndroidJUnit4::class)
@@ -47,7 +47,7 @@
fun testCurrentBackStackEntrySetGraph() {
var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
composeTestRule.setContent {
- val navController = TestNavHostController(LocalContext.current)
+ val navController = createNavController()
navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
test(FIRST_DESTINATION)
@@ -57,7 +57,7 @@
}
assertWithMessage("the currentBackStackEntry should be set with the graph")
- .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+ .that(currentBackStackEntry.value?.destination?.route)
.isEqualTo(FIRST_DESTINATION)
}
@@ -66,7 +66,7 @@
var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
lateinit var navController: NavController
composeTestRule.setContent {
- navController = TestNavHostController(LocalContext.current)
+ navController = createNavController()
navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
test(FIRST_DESTINATION)
@@ -77,7 +77,7 @@
}
assertWithMessage("the currentBackStackEntry should be set with the graph")
- .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+ .that(currentBackStackEntry.value?.destination?.route)
.isEqualTo(FIRST_DESTINATION)
composeTestRule.runOnUiThread {
@@ -85,7 +85,7 @@
}
assertWithMessage("the currentBackStackEntry should be after navigate")
- .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+ .that(currentBackStackEntry.value?.destination?.route)
.isEqualTo(SECOND_DESTINATION)
}
@@ -94,7 +94,7 @@
var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
lateinit var navController: TestNavHostController
composeTestRule.setContent {
- navController = TestNavHostController(LocalContext.current)
+ navController = createNavController()
navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
test(FIRST_DESTINATION)
@@ -110,7 +110,7 @@
}
assertWithMessage("the currentBackStackEntry should return to first destination after pop")
- .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+ .that(currentBackStackEntry.value?.destination?.route)
.isEqualTo(FIRST_DESTINATION)
}
@@ -118,10 +118,8 @@
fun testNavigateThenNavigateWithPop() {
var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
lateinit var navController: NavController
- val navigator = TestNavigator()
composeTestRule.setContent {
- navController = TestNavHostController(LocalContext.current)
- navController.navigatorProvider.addNavigator(navigator)
+ navController = createNavController()
navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
test(FIRST_DESTINATION)
@@ -131,8 +129,10 @@
currentBackStackEntry = navController.currentBackStackEntryAsState()
}
+ val navigator = navController.navigatorProvider[TestNavigator::class]
+
assertWithMessage("the currentBackStackEntry should be set with the graph")
- .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+ .that(currentBackStackEntry.value?.destination?.route)
.isEqualTo(FIRST_DESTINATION)
assertThat(navigator.backStack.size).isEqualTo(1)
@@ -143,7 +143,7 @@
}
assertWithMessage("the currentBackStackEntry should be after navigate")
- .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+ .that(currentBackStackEntry.value?.destination?.route)
.isEqualTo(SECOND_DESTINATION)
assertWithMessage("the second destination should be the only one on the back stack")
.that(navigator.backStack.size)
@@ -154,10 +154,8 @@
fun testNavigateOptionSingleTop() {
var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
lateinit var navController: NavController
- val navigator = TestNavigator()
composeTestRule.setContent {
- navController = TestNavHostController(LocalContext.current)
- navController.navigatorProvider.addNavigator(navigator)
+ navController = createNavController()
navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
test(FIRST_DESTINATION)
@@ -167,8 +165,9 @@
currentBackStackEntry = navController.currentBackStackEntryAsState()
}
+ val navigator = navController.navigatorProvider[TestNavigator::class]
assertWithMessage("the currentBackStackEntry should be set with the graph")
- .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+ .that(currentBackStackEntry.value?.destination?.route)
.isEqualTo(FIRST_DESTINATION)
assertThat(navigator.backStack.size).isEqualTo(1)
@@ -195,7 +194,7 @@
fun testGetBackStackEntry() {
lateinit var navController: NavController
composeTestRule.setContent {
- navController = TestNavHostController(LocalContext.current)
+ navController = createNavController()
navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
test(FIRST_DESTINATION)
@@ -209,13 +208,13 @@
assertWithMessage("first destination should be on back stack")
.that(
- navController.getBackStackEntry(FIRST_DESTINATION).arguments?.getString(KEY_ROUTE)
+ navController.getBackStackEntry(FIRST_DESTINATION).destination.route
)
.isEqualTo(FIRST_DESTINATION)
assertWithMessage("second destination should be on back stack")
.that(
- navController.getBackStackEntry(SECOND_DESTINATION).arguments?.getString(KEY_ROUTE)
+ navController.getBackStackEntry(SECOND_DESTINATION).destination.route
)
.isEqualTo(SECOND_DESTINATION)
}
@@ -224,7 +223,7 @@
fun testGetBackStackEntryNoEntryFound() {
lateinit var navController: NavController
composeTestRule.setContent {
- navController = TestNavHostController(LocalContext.current)
+ navController = createNavController()
navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
test(FIRST_DESTINATION)
@@ -247,21 +246,15 @@
)
}
}
+
+ @Composable
+ private fun createNavController(): TestNavHostController {
+ val navController = TestNavHostController(LocalContext.current)
+ val navigator = TestNavigator()
+ navController.navigatorProvider += navigator
+ return navController
+ }
}
-internal inline fun NavGraphBuilder.test(
- route: String,
- builder: NavDestinationBuilder<NavDestination>.() -> Unit = { deepLink(createRoute(route)) }
-) = destination(
- NavDestinationBuilder<NavDestination>(
- provider["test"],
- createRoute(route).hashCode()
- ).apply(builder).apply { argument(KEY_ROUTE) { defaultValue = route } }
-)
-
-internal fun TestNavHostController.setCurrentDestination(
- route: String
-) = setCurrentDestination(createRoute(route).hashCode())
-
private const val FIRST_DESTINATION = "first"
private const val SECOND_DESTINATION = "second"
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
index 637f279..68c9654 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
@@ -17,7 +17,7 @@
package androidx.navigation.compose
import android.annotation.SuppressLint
-import android.net.Uri
+import android.content.Context
import android.os.Bundle
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
@@ -35,17 +35,19 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
-import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
-import androidx.navigation.NavGraph
+import androidx.navigation.contains
import androidx.navigation.NavHostController
+import androidx.navigation.plusAssign
import androidx.navigation.testing.TestNavHostController
import androidx.savedstate.SavedStateRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import androidx.testutils.TestNavigator
+import androidx.testutils.test
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Rule
@@ -62,7 +64,7 @@
fun testSingleDestinationSet() {
lateinit var navController: NavHostController
composeTestRule.setContent {
- navController = TestNavHostController(LocalContext.current)
+ navController = createNavController(LocalContext.current)
NavHost(navController, startDestination = "first") {
test("first")
@@ -78,7 +80,7 @@
fun testNavigate() {
lateinit var navController: NavHostController
composeTestRule.setContent {
- navController = TestNavHostController(LocalContext.current)
+ navController = createNavController(LocalContext.current)
NavHost(navController, startDestination = "first") {
test("first")
@@ -95,9 +97,7 @@
}
assertWithMessage("second destination should be current")
- .that(
- navController.currentDestination?.hasDeepLink(Uri.parse(createRoute("second")))
- ).isTrue()
+ .that(navController.currentDestination?.route).isEqualTo("second")
}
@Test
@@ -134,9 +134,7 @@
composeTestRule.runOnIdle {
assertWithMessage("second destination should be current")
- .that(
- navController.currentDestination?.hasDeepLink(Uri.parse(createRoute("second")))
- ).isTrue()
+ .that(navController.currentDestination?.route).isEqualTo("second")
}
composeTestRule.onNodeWithText(text)
@@ -146,9 +144,7 @@
// ensure our click listener was fired
assertThat(counter).isEqualTo(1)
assertWithMessage("second destination should be current")
- .that(
- navController.currentDestination?.hasDeepLink(Uri.parse(createRoute("second")))
- ).isTrue()
+ .that(navController.currentDestination?.route).isEqualTo("second")
}
}
@@ -156,7 +152,7 @@
fun testPop() {
lateinit var navController: TestNavHostController
composeTestRule.setContent {
- navController = TestNavHostController(LocalContext.current)
+ navController = createNavController(LocalContext.current)
NavHost(navController, startDestination = "first") {
test("first")
@@ -170,10 +166,7 @@
}
assertWithMessage("First destination should be current")
- .that(
- navController.currentDestination?.hasDeepLink(createRoute("first").toUri())
- )
- .isTrue()
+ .that(navController.currentDestination?.route).isEqualTo("first")
}
@Test
@@ -185,7 +178,7 @@
val context = LocalContext.current
// added to avoid lint error b/184349025
@SuppressLint("RememberReturnType")
- navController = remember { TestNavHostController(context) }
+ navController = remember { createNavController(context) }
NavHost(navController, startDestination = state.value) {
test("first")
@@ -199,9 +192,7 @@
composeTestRule.runOnIdle {
assertWithMessage("First destination should be current")
- .that(
- navController.currentDestination?.hasDeepLink(createRoute("first").toUri())
- ).isTrue()
+ .that(navController.currentDestination?.route).isEqualTo("first")
}
}
@@ -214,7 +205,7 @@
state = remember { mutableStateOf(0) }
// added to avoid lint error b/184349025
@SuppressLint("RememberReturnType")
- navController = remember { TestNavHostController(context) }
+ navController = remember { createNavController(context) }
if (state.value == 0) {
NavHost(navController, startDestination = "first") {
test("first")
@@ -234,9 +225,7 @@
composeTestRule.runOnIdle {
assertWithMessage("First destination should be current")
- .that(
- navController.currentDestination?.hasDeepLink(createRoute("first").toUri())
- ).isTrue()
+ .that(navController.currentDestination?.route).isEqualTo("first")
}
}
@@ -283,9 +272,7 @@
composeTestRule.runOnIdle {
assertWithMessage("First destination should be current")
- .that(
- navController.currentDestination?.hasDeepLink(createRoute("first").toUri())
- ).isTrue()
+ .that(navController.currentDestination?.route).isEqualTo("first")
assertThat(savedViewModel.value).isEqualTo(viewModel.value)
}
}
@@ -393,11 +380,14 @@
.isNotEqualTo(registry2)
}
}
-}
-operator fun NavGraph.contains(
- route: String
-): Boolean = findNode(createRoute(route).hashCode()) != null
+ private fun createNavController(context: Context): TestNavHostController {
+ val navController = TestNavHostController(context)
+ val navigator = TestNavigator()
+ navController.navigatorProvider += navigator
+ return navController
+ }
+}
class TestViewModel : ViewModel() {
var value: String = "nothing"
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NamedNavArgument.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NamedNavArgument.kt
index 3589239..988c7c2 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NamedNavArgument.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NamedNavArgument.kt
@@ -33,9 +33,24 @@
* Construct a named [NavArgument] by using the [navArgument] method.
*/
public class NamedNavArgument internal constructor(
- private val name: String,
- private val argument: NavArgument
+
+ /**
+ * The name the argument is associated with
+ */
+ public val name: String,
+
+ /**
+ * The [NavArgument] associated with the name
+ */
+ public val argument: NavArgument
) {
- internal operator fun component1(): String = name
- internal operator fun component2(): NavArgument = argument
+ /**
+ * Provides destructuring access to this [NamedNavArgument]'s [name]
+ */
+ public operator fun component1(): String = name
+
+ /**
+ * Provides destructuring access to this [NamedNavArgument]'s [argument]
+ */
+ public operator fun component2(): NavArgument = argument
}
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt
new file mode 100644
index 0000000..c9ecf81
--- /dev/null
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.navigation.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.saveable.SaveableStateHolder
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavBackStackEntry
+import java.util.UUID
+
+/**
+ * Provides [this] [NavBackStackEntry] as [LocalViewModelStoreOwner], [LocalLifecycleOwner] and
+ * [LocalSavedStateRegistryOwner] to the [content] and saves the [content]'s saveable states with
+ * the given [saveableStateHolder].
+ *
+ * @param saveableStateHolder The [SaveableStateHolder] that holds the saved states. The same
+ * holder should be used for all [NavBackStackEntry]s in the encapsulating [Composable] and the
+ * holder should be hoisted.
+ * @param content The content [Composable]
+ */
+@Composable
+public fun NavBackStackEntry.LocalOwnersProvider(
+ saveableStateHolder: SaveableStateHolder,
+ content: @Composable () -> Unit
+) {
+ CompositionLocalProvider(
+ LocalViewModelStoreOwner provides this,
+ LocalLifecycleOwner provides this,
+ LocalSavedStateRegistryOwner provides this
+ ) {
+ saveableStateHolder.SaveableStateProvider(content)
+ }
+}
+
+@Composable
+private fun SaveableStateHolder.SaveableStateProvider(content: @Composable () -> Unit) {
+ val viewModel = viewModel<BackStackEntryIdViewModel>()
+ viewModel.saveableStateHolder = this
+ SaveableStateProvider(viewModel.id, content)
+}
+
+internal class BackStackEntryIdViewModel(handle: SavedStateHandle) : ViewModel() {
+
+ private val IdKey = "SaveableStateHolder_BackStackEntryKey"
+
+ // we create our own id for each back stack entry to support multiple entries of the same
+ // destination. this id will be restored by SavedStateHandle
+ val id: UUID = handle.get<UUID>(IdKey) ?: UUID.randomUUID().also { handle.set(IdKey, it) }
+
+ var saveableStateHolder: SaveableStateHolder? = null
+
+ // onCleared will be called on the entries removed from the back stack. here we notify
+ // RestorableStateHolder that we shouldn't save the state for this id, so when we open this
+ // destination again the state will not be restored.
+ override fun onCleared() {
+ super.onCleared()
+ saveableStateHolder?.removeState(id)
+ }
+}
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
index bda379b..3774f72 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
@@ -19,10 +19,8 @@
import androidx.compose.runtime.Composable
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDeepLink
-import androidx.navigation.NavGraph
import androidx.navigation.NavGraphBuilder
import androidx.navigation.get
-import androidx.navigation.navigation
/**
* Add the [Composable] to the [NavGraphBuilder]
@@ -40,11 +38,7 @@
) {
addDestination(
ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply {
- val internalRoute = createRoute(route)
- addDeepLink(internalRoute)
- val argument = navArgument(KEY_ROUTE) { defaultValue = route }
- addArgument(argument.component1(), argument.component2())
- id = internalRoute.hashCode()
+ this.route = route
arguments.forEach { (argumentName, argument) ->
addArgument(argumentName, argument)
}
@@ -54,21 +48,3 @@
}
)
}
-
-/**
- * Construct a nested [NavGraph]
- *
- * @sample androidx.navigation.compose.samples.NestedNavStartDestination
- * @sample androidx.navigation.compose.samples.NestedNavInGraph
- */
-public fun NavGraphBuilder.navigation(
- startDestination: String,
- route: String,
- builder: NavGraphBuilder.() -> Unit
-): Unit = navigation(
- createRoute(route).hashCode(),
- createRoute(startDestination).hashCode()
-) {
- deepLink(createRoute(route))
- apply(builder)
-}
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
index c2cad53..61eb0d8 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
@@ -19,28 +19,22 @@
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.SaveableStateHolder
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
-import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavDestination
import androidx.navigation.NavGraph
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
+import androidx.navigation.createGraph
import androidx.navigation.Navigator
import androidx.navigation.get
-import java.util.UUID
/**
* Provides in place in the Compose hierarchy for self contained navigation to occur.
@@ -136,41 +130,9 @@
// while in the scope of the composable, we provide the navBackStackEntry as the
// ViewModelStoreOwner and LifecycleOwner
Box(modifier, propagateMinConstraints = true) {
- CompositionLocalProvider(
- LocalViewModelStoreOwner provides backStackEntry,
- LocalLifecycleOwner provides backStackEntry,
- LocalSavedStateRegistryOwner provides backStackEntry
- ) {
- saveableStateHolder.SaveableStateProvider {
- destination.content(backStackEntry)
- }
+ backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+ destination.content(backStackEntry)
}
}
}
}
-
-@Composable
-private fun SaveableStateHolder.SaveableStateProvider(content: @Composable () -> Unit) {
- val viewModel = viewModel<BackStackEntryIdViewModel>()
- viewModel.saveableStateHolder = this
- SaveableStateProvider(viewModel.id, content)
-}
-
-internal class BackStackEntryIdViewModel(handle: SavedStateHandle) : ViewModel() {
-
- private val IdKey = "SaveableStateHolder_BackStackEntryKey"
-
- // we create our own id for each back stack entry to support multiple entries of the same
- // destination. this id will be restored by SavedStateHandle
- val id: UUID = handle.get<UUID>(IdKey) ?: UUID.randomUUID().also { handle.set(IdKey, it) }
-
- var saveableStateHolder: SaveableStateHolder? = null
-
- // onCleared will be called on the entries removed from the back stack. here we notify
- // RestorableStateHolder that we shouldn't save the state for this id, so when we open this
- // destination again the state will not be restored.
- override fun onCleared() {
- super.onCleared()
- saveableStateHolder?.removeState(id)
- }
-}
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHostController.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHostController.kt
index 08c66a0..b564e4c 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHostController.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHostController.kt
@@ -27,22 +27,9 @@
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext
-import androidx.core.net.toUri
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
-import androidx.navigation.NavDeepLinkRequest
-import androidx.navigation.NavGraph
-import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
-import androidx.navigation.NavOptions
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.navOptions
-import androidx.navigation.navigation
-
-/**
- * The route linked to the current destination.
- */
-const val KEY_ROUTE = "android-support-nav:controller:route"
/**
* Gets the current navigation back stack entry as a [MutableState]. When the given navController
@@ -96,56 +83,3 @@
save = { it.saveState() },
restore = { createNavController(context).apply { restoreState(it) } }
)
-
-/**
- * Navigate to a route in the current NavGraph.
- *
- * @param route route for the destination
- * @param builder DSL for constructing a new [NavOptions]
- */
-public fun NavController.navigate(route: String, builder: NavOptionsBuilder.() -> Unit = {}) {
- navigate(
- NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build(),
- navOptions(builder)
- )
-}
-
-/**
- * Construct a new [NavGraph]
- *
- * @param route the route for the graph
- * @param startDestination the route for the start destination
- * @param builder the builder used to construct the graph
- */
-fun NavController.createGraph(
- startDestination: String,
- route: String? = null,
- builder: NavGraphBuilder.() -> Unit
-): NavGraph = navigatorProvider.navigation(
- if (route != null) createRoute(route).hashCode() else 0,
- createRoute(startDestination).hashCode(),
- builder
-)
-
-/**
- * Gets the topmost {@link NavBackStackEntry} for a route.
- * <p>
- * This is always safe to use with {@link #getCurrentDestination() the current destination} or
- * {@link NavDestination#getParent() its parent} or grandparent navigation graphs as these
- * destinations are guaranteed to be on the back stack.
- *
- * @param route route of a destination that exists on the back stack
- * @throws IllegalArgumentException if the destination is not on the back stack
- */
-public fun NavController.getBackStackEntry(route: String): NavBackStackEntry {
- try {
- return getBackStackEntry(createRoute(route).hashCode())
- } catch (e: IllegalArgumentException) {
- throw IllegalArgumentException(
- "No destination with route $route is on the NavController's back stack. The current " +
- "destination is $currentDestination"
- )
- }
-}
-
-internal fun createRoute(route: String) = "android-app://androidx.navigation.compose/$route"
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavOptionsBuilder.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavOptionsBuilder.kt
deleted file mode 100644
index 7701f08..0000000
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavOptionsBuilder.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2020 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.navigation.compose
-
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.PopUpToBuilder
-
-/**
- * Pop up to a given destination before navigating. This pops all non-matching destination routes
- * from the back stack until the destination with a matching route is found.
- *
- * @param route route for the destination
- * @param popUpToBuilder builder used to construct a popUpTo operation
- */
-fun NavOptionsBuilder.popUpTo(route: String, popUpToBuilder: PopUpToBuilder.() -> Unit) {
- popUpTo(createRoute(route).hashCode(), popUpToBuilder)
-}
diff --git a/navigation/navigation-dynamic-features-fragment/lint-baseline.xml b/navigation/navigation-dynamic-features-fragment/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-dynamic-features-fragment/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/DynamicFragmentNavigator.kt b/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/DynamicFragmentNavigator.kt
index 574c877..a3ac6d7 100644
--- a/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/DynamicFragmentNavigator.kt
+++ b/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/DynamicFragmentNavigator.kt
@@ -17,11 +17,10 @@
package androidx.navigation.dynamicfeatures.fragment
import android.content.Context
-import android.os.Bundle
import android.util.AttributeSet
import androidx.core.content.withStyledAttributes
import androidx.fragment.app.FragmentManager
-import androidx.navigation.NavDestination
+import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import androidx.navigation.NavigatorProvider
@@ -43,21 +42,32 @@
override fun createDestination(): Destination = Destination(this)
override fun navigate(
- destination: FragmentNavigator.Destination,
- args: Bundle?,
+ entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
- ): NavDestination? {
+ ) {
+ for (entry in entries) {
+ navigate(entry, navOptions, navigatorExtras)
+ }
+ }
+
+ private fun navigate(
+ entry: NavBackStackEntry,
+ navOptions: NavOptions?,
+ navigatorExtras: Navigator.Extras?
+ ) {
+ val destination = entry.destination
+ val args = entry.arguments
val extras = navigatorExtras as? DynamicExtras
if (destination is Destination) {
val moduleName = destination.moduleName
if (moduleName != null && installManager.needsInstall(moduleName)) {
- return installManager.performInstall(destination, args, extras, moduleName)
+ installManager.performInstall(destination, args, extras, moduleName)
+ return
}
}
- return super.navigate(
- destination,
- args,
+ super.navigate(
+ listOf(entry),
navOptions,
if (extras != null) extras.destinationExtras else navigatorExtras
)
diff --git a/navigation/navigation-dynamic-features-runtime/build.gradle b/navigation/navigation-dynamic-features-runtime/build.gradle
index 2b45ff2..09812400 100644
--- a/navigation/navigation-dynamic-features-runtime/build.gradle
+++ b/navigation/navigation-dynamic-features-runtime/build.gradle
@@ -37,6 +37,8 @@
api(PLAY_CORE)
api(KOTLIN_STDLIB)
+ testImplementation(project(":navigation:navigation-testing"))
+ testImplementation("androidx.arch.core:core-testing:2.1.0")
testImplementation(ANDROIDX_TEST_CORE)
testImplementation(ANDROIDX_TEST_EXT_JUNIT)
testImplementation(ANDROIDX_TEST_RUNNER)
diff --git a/navigation/navigation-dynamic-features-runtime/lint-baseline.xml b/navigation/navigation-dynamic-features-runtime/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-dynamic-features-runtime/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicGraphNavigator.kt b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicGraphNavigator.kt
index 04ec5fa..1aa62d6 100644
--- a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicGraphNavigator.kt
+++ b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicGraphNavigator.kt
@@ -21,6 +21,7 @@
import android.util.AttributeSet
import androidx.annotation.RestrictTo
import androidx.core.content.withStyledAttributes
+import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
import androidx.navigation.NavGraph
import androidx.navigation.NavGraphNavigator
@@ -58,20 +59,32 @@
* module has successfully been installed.
*/
override fun navigate(
- destination: NavGraph,
- args: Bundle?,
+ entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Extras?
- ): NavDestination? {
+ ) {
+ for (entry in entries) {
+ navigate(entry, navOptions, navigatorExtras)
+ }
+ }
+
+ private fun navigate(
+ entry: NavBackStackEntry,
+ navOptions: NavOptions?,
+ navigatorExtras: Extras?
+ ) {
+ val destination = entry.destination
+ val args = entry.arguments
val extras = if (navigatorExtras is DynamicExtras) navigatorExtras else null
if (destination is DynamicNavGraph) {
val moduleName = destination.moduleName
if (moduleName != null && installManager.needsInstall(moduleName)) {
- return installManager.performInstall(destination, args, extras, moduleName)
+ installManager.performInstall(destination, args, extras, moduleName)
+ return
}
}
- return super.navigate(
- destination, args, navOptions,
+ super.navigate(
+ listOf(entry), navOptions,
if (extras != null) extras.destinationExtras else navigatorExtras
)
}
@@ -110,7 +123,7 @@
internal fun navigateToProgressDestination(
dynamicNavGraph: DynamicNavGraph,
progressArgs: Bundle?
- ): NavDestination? {
+ ) {
var progressDestinationId = dynamicNavGraph.progressDestination
if (progressDestinationId == 0) {
progressDestinationId = installDefaultProgressDestination(dynamicNavGraph)
@@ -124,7 +137,8 @@
val navigator = navigatorProvider.getNavigator<Navigator<NavDestination>>(
progressDestination.navigatorName
)
- return navigator.navigate(progressDestination, progressArgs, null, null)
+ val entry = state.createBackStackEntry(progressDestination, progressArgs)
+ navigator.navigate(listOf(entry), null, null)
}
/**
diff --git a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallManager.kt b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallManager.kt
index 0d14629e..eab3688 100644
--- a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallManager.kt
+++ b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallManager.kt
@@ -79,8 +79,9 @@
val dynamicNavGraph = DynamicNavGraph.getOrThrow(destination)
val navigator: Navigator<*> =
dynamicNavGraph.navigatorProvider[dynamicNavGraph.navigatorName]
- return if (navigator is DynamicGraphNavigator) {
+ if (navigator is DynamicGraphNavigator) {
navigator.navigateToProgressDestination(dynamicNavGraph, progressArgs)
+ return null
} else {
throw IllegalStateException(
"You must use a DynamicNavGraph to perform a module installation."
diff --git a/navigation/navigation-dynamic-features-runtime/src/test/java/androidx/navigation/dynamicfeatures/DynamicNavGraphTest.kt b/navigation/navigation-dynamic-features-runtime/src/test/java/androidx/navigation/dynamicfeatures/DynamicNavGraphTest.kt
index 73897f5..f48fdbb 100644
--- a/navigation/navigation-dynamic-features-runtime/src/test/java/androidx/navigation/dynamicfeatures/DynamicNavGraphTest.kt
+++ b/navigation/navigation-dynamic-features-runtime/src/test/java/androidx/navigation/dynamicfeatures/DynamicNavGraphTest.kt
@@ -16,14 +16,17 @@
package androidx.navigation.dynamicfeatures
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.navigation.NavDestination
import androidx.navigation.NavigatorProvider
import androidx.navigation.NoOpNavigator
import androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph
import androidx.navigation.dynamicfeatures.shared.TestDynamicInstallManager
+import androidx.navigation.testing.TestNavigatorState
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -31,10 +34,15 @@
@RunWith(JUnit4::class)
class DynamicNavGraphTest {
+ @get:Rule
+ val instantTaskExecutorRule = InstantTaskExecutorRule()
+
private val progressId = 1
private lateinit var provider: NavigatorProvider
+ private lateinit var navigatorState: TestNavigatorState
private lateinit var navigator: DynamicGraphNavigator
private lateinit var dynamicNavGraph: DynamicNavGraph
+ private lateinit var noOpState: TestNavigatorState
private lateinit var noOpNavigator: NoOpNavigator
@Before
@@ -46,6 +54,8 @@
TestDynamicInstallManager()
)
provider.addNavigator(noOpNavigator)
+ noOpState = TestNavigatorState()
+ noOpNavigator.onAttach(noOpState)
dynamicNavGraph = navigator.createDestination()
}
@@ -61,7 +71,8 @@
id = progressId
}
)
- val progressDestination = navigator.navigateToProgressDestination(dynamicNavGraph, null)
+ navigator.navigateToProgressDestination(dynamicNavGraph, null)
+ val progressDestination = noOpState.backStack.value.lastOrNull()?.destination
assertNotNull(progressDestination)
progressDestination?.let {
DynamicNavGraph.getOrThrow(progressDestination)
@@ -82,7 +93,8 @@
id = progressId
}
)
- val destination = navigator.navigateToProgressDestination(dynamicNavGraph, null)
+ navigator.navigateToProgressDestination(dynamicNavGraph, null)
+ val destination = noOpState.backStack.value.lastOrNull()?.destination
assertTrue(destination?.parent is DynamicNavGraph)
}
@@ -91,6 +103,8 @@
navigator.installDefaultProgressDestination { it }
}
provider.addNavigator(navigator)
+ navigatorState = TestNavigatorState()
+ navigator.onAttach(navigatorState)
dynamicNavGraph = navigator.createDestination()
}
}
diff --git a/navigation/navigation-fragment-ktx/lint-baseline.xml b/navigation/navigation-fragment-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-fragment-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-fragment/api/current.ignore b/navigation/navigation-fragment/api/current.ignore
index e92246d..bed10fd 100644
--- a/navigation/navigation-fragment/api/current.ignore
+++ b/navigation/navigation-fragment/api/current.ignore
@@ -5,5 +5,7 @@
RemovedMethod: androidx.navigation.fragment.DialogFragmentNavigator#popBackStack():
Removed method androidx.navigation.fragment.DialogFragmentNavigator.popBackStack()
+RemovedMethod: androidx.navigation.fragment.FragmentNavigator#navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle, androidx.navigation.NavOptions, androidx.navigation.Navigator.Extras):
+ Removed method androidx.navigation.fragment.FragmentNavigator.navigate(androidx.navigation.fragment.FragmentNavigator.Destination,android.os.Bundle,androidx.navigation.NavOptions,androidx.navigation.Navigator.Extras)
RemovedMethod: androidx.navigation.fragment.FragmentNavigator#popBackStack():
Removed method androidx.navigation.fragment.FragmentNavigator.popBackStack()
diff --git a/navigation/navigation-fragment/api/current.txt b/navigation/navigation-fragment/api/current.txt
index ac16ffe..5f8b262 100644
--- a/navigation/navigation-fragment/api/current.txt
+++ b/navigation/navigation-fragment/api/current.txt
@@ -45,7 +45,6 @@
ctor public FragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, int containerId);
method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, String className, android.os.Bundle? args);
- method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
}
@androidx.navigation.NavDestination.ClassType(Fragment::class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
diff --git a/navigation/navigation-fragment/api/public_plus_experimental_current.txt b/navigation/navigation-fragment/api/public_plus_experimental_current.txt
index ac16ffe..5f8b262 100644
--- a/navigation/navigation-fragment/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-fragment/api/public_plus_experimental_current.txt
@@ -45,7 +45,6 @@
ctor public FragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, int containerId);
method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, String className, android.os.Bundle? args);
- method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
}
@androidx.navigation.NavDestination.ClassType(Fragment::class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
diff --git a/navigation/navigation-fragment/api/restricted_current.ignore b/navigation/navigation-fragment/api/restricted_current.ignore
index e92246d..bed10fd 100644
--- a/navigation/navigation-fragment/api/restricted_current.ignore
+++ b/navigation/navigation-fragment/api/restricted_current.ignore
@@ -5,5 +5,7 @@
RemovedMethod: androidx.navigation.fragment.DialogFragmentNavigator#popBackStack():
Removed method androidx.navigation.fragment.DialogFragmentNavigator.popBackStack()
+RemovedMethod: androidx.navigation.fragment.FragmentNavigator#navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle, androidx.navigation.NavOptions, androidx.navigation.Navigator.Extras):
+ Removed method androidx.navigation.fragment.FragmentNavigator.navigate(androidx.navigation.fragment.FragmentNavigator.Destination,android.os.Bundle,androidx.navigation.NavOptions,androidx.navigation.Navigator.Extras)
RemovedMethod: androidx.navigation.fragment.FragmentNavigator#popBackStack():
Removed method androidx.navigation.fragment.FragmentNavigator.popBackStack()
diff --git a/navigation/navigation-fragment/api/restricted_current.txt b/navigation/navigation-fragment/api/restricted_current.txt
index ac16ffe..5f8b262 100644
--- a/navigation/navigation-fragment/api/restricted_current.txt
+++ b/navigation/navigation-fragment/api/restricted_current.txt
@@ -45,7 +45,6 @@
ctor public FragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, int containerId);
method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, String className, android.os.Bundle? args);
- method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
}
@androidx.navigation.NavDestination.ClassType(Fragment::class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
diff --git a/navigation/navigation-fragment/build.gradle b/navigation/navigation-fragment/build.gradle
index fa1132c..5837d97 100644
--- a/navigation/navigation-fragment/build.gradle
+++ b/navigation/navigation-fragment/build.gradle
@@ -31,6 +31,7 @@
api(project(":navigation:navigation-runtime"))
api(KOTLIN_STDLIB)
+ androidTestImplementation(project(":navigation:navigation-testing"))
androidTestImplementation(projectOrArtifact(":fragment:fragment-testing"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/navigation/navigation-fragment/lint-baseline.xml b/navigation/navigation-fragment/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-fragment/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
index 8b7571e..2498514 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
@@ -22,9 +22,11 @@
import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
+import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavOptions
import androidx.navigation.fragment.test.EmptyFragment
import androidx.navigation.fragment.test.R
+import androidx.navigation.testing.TestNavigatorState
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -38,6 +40,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.reflect.KClass
@LargeTest
@RunWith(AndroidJUnit4::class)
@@ -57,24 +60,26 @@
private lateinit var emptyActivity: EmptyActivity
private lateinit var fragmentManager: FragmentManager
+ private lateinit var navigatorState: TestNavigatorState
+ private lateinit var fragmentNavigator: FragmentNavigator
@Before
fun setup() {
emptyActivity = activityRule.activity
fragmentManager = emptyActivity.supportFragmentManager
+ navigatorState = TestNavigatorState()
+ fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
+ fragmentNavigator.onAttach(navigatorState)
}
@UiThreadTest
@Test
fun testNavigate() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination().apply {
- id = INITIAL_FRAGMENT
- setClassName(EmptyFragment::class.java.name)
- }
+ val entry = createBackStackEntry()
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Fragment should be added", fragment)
@@ -92,14 +97,11 @@
@Test
fun testNavigateWithFragmentFactory() {
fragmentManager.fragmentFactory = NonEmptyFragmentFactory()
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination().apply {
- id = INITIAL_FRAGMENT
- setClassName(NonEmptyConstructorFragment::class.java.name)
- }
+ val entry = createBackStackEntry(clazz = NonEmptyConstructorFragment::class)
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertWithMessage("Fragment should be added")
@@ -116,14 +118,11 @@
@UiThreadTest
@Test
fun testNavigateTwice() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination().apply {
- id = INITIAL_FRAGMENT
- setClassName(EmptyFragment::class.java.name)
- }
+ val entry = createBackStackEntry()
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Fragment should be added", fragment)
@@ -137,9 +136,10 @@
)
// Now push a second fragment
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+ fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, replacementEntry).inOrder()
fragmentManager.executePendingTransactions()
val replacementFragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -156,60 +156,55 @@
@UiThreadTest
@Test
fun testNavigateWithPopUpToThenPop() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.setClassName(EmptyFragment::class.java.name)
+ val entry = createBackStackEntry()
// Push initial fragment
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
fragmentManager.executePendingTransactions()
// Push a second fragment
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+ fragmentNavigator.navigate(listOf(secondEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, secondEntry).inOrder()
fragmentManager.executePendingTransactions()
// Pop and then push third fragment, simulating popUpTo to initial.
- val success = fragmentNavigator.popBackStack()
- assertTrue("FragmentNavigator should return true when popping the third fragment", success)
- destination.id = THIRD_FRAGMENT
- assertThat(
- fragmentNavigator.navigate(
- destination, null,
- NavOptions.Builder().setPopUpTo(INITIAL_FRAGMENT, false).build(), null
- )
- )
- .isEqualTo(destination)
+ fragmentNavigator.popBackStack(secondEntry, false)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
+ val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
+ fragmentNavigator.navigate(listOf(thirdEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, thirdEntry).inOrder()
fragmentManager.executePendingTransactions()
// Now pop the Fragment
- val popped = fragmentNavigator.popBackStack()
- assertTrue("FragmentNavigator should return true when popping the third fragment", popped)
+ fragmentNavigator.popBackStack(thirdEntry, false)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
}
@UiThreadTest
@Test
fun testSingleTopInitial() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination()
- destination.setClassName(EmptyFragment::class.java.name)
+ val entry = createBackStackEntry()
- fragmentNavigator.navigate(destination, null, null, null)
+ fragmentNavigator.navigate(listOf(entry), null, null)
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Fragment should be added", fragment)
val lifecycle = fragment!!.lifecycle
- assertThat(
- fragmentNavigator.navigate(
- destination, null,
- NavOptions.Builder().setLaunchSingleTop(true).build(), null
- )
+ fragmentNavigator.navigate(
+ listOf(entry),
+ NavOptions.Builder().setLaunchSingleTop(true).build(),
+ null
)
- .isNull()
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
fragmentManager.executePendingTransactions()
val replacementFragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -234,13 +229,12 @@
@UiThreadTest
@Test
fun testSingleTop() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination()
- destination.setClassName(EmptyFragment::class.java.name)
+ val entry = createBackStackEntry()
// First push an initial Fragment
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
fragmentManager.executePendingTransactions()
val initialFragment = fragmentManager.findFragmentById(R.id.container)
assertWithMessage("Initial Fragment should be added")
@@ -248,9 +242,10 @@
.isNotNull()
// Now push the Fragment that we want to replace with a singleTop operation
- destination.id = 1
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+ fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, replacementEntry).inOrder()
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertWithMessage("Fragment should be added")
@@ -258,13 +253,13 @@
.isNotNull()
val lifecycle = fragment!!.lifecycle
- assertThat(
- fragmentNavigator.navigate(
- destination, null,
- NavOptions.Builder().setLaunchSingleTop(true).build(), null
- )
+ fragmentNavigator.navigate(
+ listOf(replacementEntry),
+ NavOptions.Builder().setLaunchSingleTop(true).build(),
+ null
)
- .isNull()
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, replacementEntry).inOrder()
fragmentManager.executePendingTransactions()
val replacementFragment = fragmentManager.findFragmentById(R.id.container)
assertWithMessage("Replacement Fragment should be added")
@@ -283,8 +278,10 @@
.that(lifecycle.currentState)
.isEqualTo(Lifecycle.State.DESTROYED)
- assertThat(fragmentNavigator.popBackStack())
- .isTrue()
+ fragmentNavigator.popBackStack(replacementEntry, false)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
+
fragmentManager.executePendingTransactions()
assertWithMessage("Initial Fragment should be on top of back stack after pop")
.that(fragmentManager.findFragmentById(R.id.container))
@@ -297,47 +294,37 @@
@UiThreadTest
@Test
fun testPopInitial() {
- val fragmentNavigator = FragmentNavigator(
- emptyActivity,
- fragmentManager, R.id.container
- )
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.setClassName(EmptyFragment::class.java.name)
+ val entry = createBackStackEntry()
// First push an initial Fragment
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
// Now pop the initial Fragment
- val popped = fragmentNavigator.popBackStack()
- assertWithMessage(
- "FragmentNavigator should return false when popping " +
- "the initial Fragment"
- )
- .that(popped)
- .isTrue()
+ fragmentNavigator.popBackStack(entry, false)
+ assertThat(navigatorState.backStack.value)
+ .isEmpty()
}
@UiThreadTest
@Test
fun testPop() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.setClassName(EmptyFragment::class.java.name)
+ val entry = createBackStackEntry()
// First push an initial Fragment
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Fragment should be added", fragment)
// Now push the Fragment that we want to pop
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+ fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, replacementEntry).inOrder()
fragmentManager.executePendingTransactions()
val replacementFragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -351,9 +338,10 @@
)
// Now pop the Fragment
- val popped = fragmentNavigator.popBackStack()
+ fragmentNavigator.popBackStack(replacementEntry, false)
fragmentManager.executePendingTransactions()
- assertTrue("FragmentNavigator should return true when popping a Fragment", popped)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
assertEquals(
"Fragment should be the primary navigation Fragment after pop",
fragment, fragmentManager.primaryNavigationFragment
@@ -363,14 +351,12 @@
@UiThreadTest
@Test
fun testPopWithSameDestinationTwice() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.setClassName(EmptyFragment::class.java.name)
+ val entry = createBackStackEntry()
// First push an initial Fragment
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertWithMessage("Fragment should be added")
@@ -378,9 +364,10 @@
.isNotNull()
// Now push a second Fragment
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+ fragmentNavigator.navigate(listOf(secondEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, secondEntry).inOrder()
fragmentManager.executePendingTransactions()
val replacementFragment = fragmentManager.findFragmentById(R.id.container)
assertWithMessage("Replacement Fragment should be added")
@@ -392,8 +379,10 @@
// Push the same Fragment a second time, creating a stack of two
// identical Fragments
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+ fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, secondEntry, replacementEntry).inOrder()
fragmentManager.executePendingTransactions()
val fragmentToPop = fragmentManager.findFragmentById(R.id.container)
assertWithMessage("Fragment to pop should be added")
@@ -404,11 +393,10 @@
.isSameInstanceAs(fragmentToPop)
// Now pop the Fragment
- val popped = fragmentNavigator.popBackStack()
+ fragmentNavigator.popBackStack(replacementEntry, false)
fragmentManager.executePendingTransactions()
- assertWithMessage("FragmentNavigator should return true when popping a Fragment")
- .that(popped)
- .isTrue()
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, secondEntry).inOrder()
assertWithMessage(
"Replacement Fragment should be the primary navigation Fragment " +
"after pop"
@@ -420,22 +408,21 @@
@UiThreadTest
@Test
fun testPopWithChildFragmentBackStack() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.setClassName(EmptyFragment::class.java.name)
+ val entry = createBackStackEntry()
// First push an initial Fragment
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Fragment should be added", fragment)
// Now push the Fragment that we want to pop
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+ fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, replacementEntry).inOrder()
fragmentManager.executePendingTransactions()
val replacementFragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -458,9 +445,10 @@
}
// Now pop the Fragment
- val popped = fragmentNavigator.popBackStack()
+ fragmentNavigator.popBackStack(replacementEntry, false)
fragmentManager.executePendingTransactions()
- assertTrue("FragmentNavigator should return true when popping a Fragment", popped)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
assertEquals(
"Fragment should be the primary navigation Fragment after pop",
fragment, fragmentManager.primaryNavigationFragment
@@ -470,22 +458,19 @@
@UiThreadTest
@Test
fun testDeepLinkPop() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.setClassName(EmptyFragment::class.java.name)
+ val entry = createBackStackEntry()
+ val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
// First push two Fragments as our 'deep link'
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ fragmentNavigator.navigate(listOf(entry, secondEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, secondEntry)
// Now push the Fragment that we want to pop
- destination.id = THIRD_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
+ val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
+ fragmentNavigator.navigate(listOf(thirdEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, secondEntry, thirdEntry)
fragmentManager.executePendingTransactions()
val replacementFragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -499,7 +484,7 @@
)
// Now pop the Fragment
- fragmentNavigator.popBackStack()
+ fragmentNavigator.popBackStack(thirdEntry, false)
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertEquals(
@@ -510,156 +495,19 @@
@UiThreadTest
@Test
- fun testDeepLinkPopWithSaveState() {
- var fragmentNavigator = FragmentNavigator(
- emptyActivity,
- fragmentManager, R.id.container
- )
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.setClassName(EmptyFragment::class.java.name)
-
- // First push two Fragments as our 'deep link'
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
-
- // Now push the Fragment that we want to pop
- destination.id = THIRD_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- fragmentManager.executePendingTransactions()
- val replacementFragment = fragmentManager.findFragmentById(R.id.container)
- assertNotNull("Replacement Fragment should be added", replacementFragment)
- assertTrue(
- "Replacement Fragment should be the correct type",
- replacementFragment is EmptyFragment
- )
- assertEquals(
- "Replacement Fragment should be the primary navigation Fragment",
- replacementFragment, fragmentManager.primaryNavigationFragment
- )
-
- // Create a new FragmentNavigator, replacing the previous one
- val savedState = fragmentNavigator.onSaveState() as Bundle
- fragmentNavigator = FragmentNavigator(
- emptyActivity,
- fragmentManager, R.id.container
- )
- fragmentNavigator.onRestoreState(savedState)
-
- // Now pop the Fragment
- fragmentNavigator.popBackStack()
- fragmentManager.executePendingTransactions()
- val fragment = fragmentManager.findFragmentById(R.id.container)
- assertEquals(
- "Fragment should be the primary navigation Fragment after pop",
- fragment, fragmentManager.primaryNavigationFragment
- )
- }
-
- @UiThreadTest
- @Test
- fun testNavigateThenPopAfterSaveState() {
- var fragmentNavigator = FragmentNavigator(
- emptyActivity,
- fragmentManager, R.id.container
- )
- val destination = fragmentNavigator.createDestination()
- destination.id = INITIAL_FRAGMENT
- destination.setClassName(EmptyFragment::class.java.name)
-
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- fragmentManager.executePendingTransactions()
- var fragment = fragmentManager.findFragmentById(R.id.container)
- assertNotNull("Fragment should be added", fragment)
- assertEquals(
- "Fragment should be the correct type",
- EmptyFragment::class.java, fragment!!::class.java
- )
- assertEquals(
- "Fragment should be the primary navigation Fragment",
- fragment, fragmentManager.primaryNavigationFragment
- )
-
- // Now push a second fragment
- destination.id = SECOND_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- fragmentManager.executePendingTransactions()
- var replacementFragment = fragmentManager.findFragmentById(R.id.container)
- assertNotNull("Replacement Fragment should be added", replacementFragment)
- assertEquals(
- "Replacement Fragment should be the correct type",
- EmptyFragment::class.java, replacementFragment!!::class.java
- )
- assertEquals(
- "Replacement Fragment should be the primary navigation Fragment",
- replacementFragment, fragmentManager.primaryNavigationFragment
- )
-
- // Create a new FragmentNavigator, replacing the previous one
- val savedState = fragmentNavigator.onSaveState() as Bundle
- fragmentNavigator = FragmentNavigator(
- emptyActivity,
- fragmentManager, R.id.container
- )
- fragmentNavigator.onRestoreState(savedState)
-
- // Now push a third fragment after the state save
- destination.id = THIRD_FRAGMENT
- assertThat(fragmentNavigator.navigate(destination, null, null, null))
- .isEqualTo(destination)
- fragmentManager.executePendingTransactions()
- replacementFragment = fragmentManager.findFragmentById(R.id.container)
- assertNotNull("Replacement Fragment should be added", replacementFragment)
- assertTrue(
- "Replacement Fragment should be the correct type",
- replacementFragment is EmptyFragment
- )
- assertEquals(
- "Replacement Fragment should be the primary navigation Fragment",
- replacementFragment, fragmentManager.primaryNavigationFragment
- )
-
- // Now pop the Fragment
- fragmentNavigator.popBackStack()
- fragmentManager.executePendingTransactions()
- fragment = fragmentManager.findFragmentById(R.id.container)
- assertEquals(
- "Fragment should be the primary navigation Fragment after pop",
- fragment, fragmentManager.primaryNavigationFragment
- )
- }
-
- @UiThreadTest
- @Test
fun testMultipleNavigateFragmentTransactionsThenPop() {
- val fragmentNavigator = FragmentNavigator(
- emptyActivity,
- fragmentManager, R.id.container
- )
- val destination = fragmentNavigator.createDestination()
- destination.setClassName(EmptyFragment::class.java.name)
- val destination2 = fragmentNavigator.createDestination()
- destination2.setClassName(Fragment::class.java.name)
+ val entry = createBackStackEntry()
+ val secondEntry = createBackStackEntry(SECOND_FRAGMENT, clazz = Fragment::class)
+ val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
// Push 3 fragments without executing pending transactions.
- destination.id = INITIAL_FRAGMENT
- fragmentNavigator.navigate(destination, null, null, null)
- destination2.id = SECOND_FRAGMENT
- fragmentNavigator.navigate(destination2, null, null, null)
- destination.id = THIRD_FRAGMENT
- fragmentNavigator.navigate(destination, null, null, null)
+ fragmentNavigator.navigate(listOf(entry, secondEntry, thirdEntry), null, null)
// Now pop the Fragment
- val popped = fragmentNavigator.popBackStack()
+ fragmentNavigator.popBackStack(thirdEntry, false)
fragmentManager.executePendingTransactions()
- assertWithMessage("FragmentNavigator should return true when popping the third fragment")
- .that(popped).isTrue()
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, secondEntry)
// We should ensure the fragment manager is on the proper fragment at the end
assertWithMessage("FragmentManager back stack should have only SECOND_FRAGMENT")
.that(fragmentManager.backStackEntryCount)
@@ -672,36 +520,176 @@
@UiThreadTest
@Test
fun testMultiplePopFragmentTransactionsThenPop() {
- val fragmentNavigator = FragmentNavigator(
- emptyActivity,
- fragmentManager, R.id.container
- )
- val destination = fragmentNavigator.createDestination()
- destination.setClassName(EmptyFragment::class.java.name)
+ val entry = createBackStackEntry()
+ val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+ val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
+ val fourthEntry = createBackStackEntry(FOURTH_FRAGMENT)
// Push 4 fragments
- destination.id = INITIAL_FRAGMENT
- fragmentNavigator.navigate(destination, null, null, null)
- destination.id = SECOND_FRAGMENT
- fragmentNavigator.navigate(destination, null, null, null)
- destination.id = THIRD_FRAGMENT
- fragmentNavigator.navigate(destination, null, null, null)
- destination.id = FOURTH_FRAGMENT
- fragmentNavigator.navigate(destination, null, null, null)
+ fragmentNavigator.navigate(
+ listOf(entry, secondEntry, thirdEntry, fourthEntry),
+ null, null
+ )
fragmentManager.executePendingTransactions()
// Pop 2 fragments without executing pending transactions.
- fragmentNavigator.popBackStack()
- fragmentNavigator.popBackStack()
+ fragmentNavigator.popBackStack(thirdEntry, false)
- val popped = fragmentNavigator.popBackStack()
+ fragmentNavigator.popBackStack(secondEntry, false)
fragmentManager.executePendingTransactions()
- assertTrue("FragmentNavigator should return true when popping the third fragment", popped)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSaveRestoreState() {
+ val entry = createBackStackEntry()
+
+ // First push an initial Fragment
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
+ fragmentManager.executePendingTransactions()
+ val fragment = fragmentManager.findFragmentById(R.id.container)
+ assertWithMessage("Fragment should be added")
+ .that(fragment)
+ .isNotNull()
+
+ // Now push the Fragment that we want to save
+ val replacementEntry = createBackStackEntry(SECOND_FRAGMENT, SavedStateFragment::class)
+ fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, replacementEntry).inOrder()
+ fragmentManager.executePendingTransactions()
+ val replacementFragment = fragmentManager.findFragmentById(R.id.container)
+ assertWithMessage("Replacement Fragment should be added")
+ .that(replacementFragment)
+ .isNotNull()
+ assertWithMessage("Replacement Fragment should be the correct type")
+ .that(replacementFragment)
+ .isInstanceOf(SavedStateFragment::class.java)
+ assertWithMessage("Replacement Fragment should be the primary navigation Fragment")
+ .that(fragmentManager.primaryNavigationFragment)
+ .isSameInstanceAs(replacementFragment)
+
+ // Save some state into the replacement fragment
+ (replacementFragment as SavedStateFragment).savedState = "test"
+
+ // Now save the Fragment
+ fragmentNavigator.popBackStack(replacementEntry, true)
+ fragmentManager.executePendingTransactions()
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
+ assertWithMessage("Fragment should be the primary navigation Fragment after pop")
+ .that(fragmentManager.primaryNavigationFragment)
+ .isSameInstanceAs(fragment)
+
+ // And now restore the fragment
+ val restoredEntry = navigatorState.restoreBackStackEntry(replacementEntry)
+ fragmentNavigator.navigate(
+ listOf(restoredEntry),
+ NavOptions.Builder().setRestoreState(true).build(), null
+ )
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, restoredEntry).inOrder()
+ fragmentManager.executePendingTransactions()
+ val restoredFragment = fragmentManager.findFragmentById(R.id.container)
+ assertWithMessage("Restored Fragment should be added")
+ .that(restoredFragment)
+ .isNotNull()
+ assertWithMessage("Restored Fragment should be the correct type")
+ .that(restoredFragment)
+ .isInstanceOf(SavedStateFragment::class.java)
+ assertWithMessage("Restored Fragment should be the primary navigation Fragment")
+ .that(fragmentManager.primaryNavigationFragment)
+ .isSameInstanceAs(restoredFragment)
+
+ assertWithMessage("Restored Fragment should have its state restored")
+ .that((restoredFragment as SavedStateFragment).savedState)
+ .isEqualTo("test")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSaveRestoreStateAfterSaveState() {
+ val entry = createBackStackEntry()
+
+ // First push an initial Fragment
+ fragmentNavigator.navigate(listOf(entry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
+ fragmentManager.executePendingTransactions()
+ val fragment = fragmentManager.findFragmentById(R.id.container)
+ assertWithMessage("Fragment should be added")
+ .that(fragment)
+ .isNotNull()
+
+ // Now push the Fragment that we want to save
+ val replacementEntry = createBackStackEntry(SECOND_FRAGMENT, SavedStateFragment::class)
+ fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, replacementEntry).inOrder()
+ fragmentManager.executePendingTransactions()
+ val replacementFragment = fragmentManager.findFragmentById(R.id.container)
+ assertWithMessage("Replacement Fragment should be added")
+ .that(replacementFragment)
+ .isNotNull()
+ assertWithMessage("Replacement Fragment should be the correct type")
+ .that(replacementFragment)
+ .isInstanceOf(SavedStateFragment::class.java)
+ assertWithMessage("Replacement Fragment should be the primary navigation Fragment")
+ .that(fragmentManager.primaryNavigationFragment)
+ .isSameInstanceAs(replacementFragment)
+
+ // Save some state into the replacement fragment
+ (replacementFragment as SavedStateFragment).savedState = "test"
+
+ // Now save the Fragment
+ fragmentNavigator.popBackStack(replacementEntry, true)
+ fragmentManager.executePendingTransactions()
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry)
+ assertWithMessage("Fragment should be the primary navigation Fragment after pop")
+ .that(fragmentManager.primaryNavigationFragment)
+ .isSameInstanceAs(fragment)
+
+ // Create a new FragmentNavigator, replacing the previous one
+ val savedState = fragmentNavigator.onSaveState() as Bundle
+ fragmentNavigator = FragmentNavigator(
+ emptyActivity,
+ fragmentManager, R.id.container
+ )
+ fragmentNavigator.onAttach(navigatorState)
+ fragmentNavigator.onRestoreState(savedState)
+
+ // And now restore the fragment
+ val restoredEntry = navigatorState.restoreBackStackEntry(replacementEntry)
+ fragmentNavigator.navigate(
+ listOf(restoredEntry),
+ NavOptions.Builder().setRestoreState(true).build(), null
+ )
+ assertThat(navigatorState.backStack.value)
+ .containsExactly(entry, restoredEntry).inOrder()
+ fragmentManager.executePendingTransactions()
+ val restoredFragment = fragmentManager.findFragmentById(R.id.container)
+ assertWithMessage("Restored Fragment should be added")
+ .that(restoredFragment)
+ .isNotNull()
+ assertWithMessage("Restored Fragment should be the correct type")
+ .that(restoredFragment)
+ .isInstanceOf(SavedStateFragment::class.java)
+ assertWithMessage("Restored Fragment should be the primary navigation Fragment")
+ .that(fragmentManager.primaryNavigationFragment)
+ .isSameInstanceAs(restoredFragment)
+
+ assertWithMessage("Restored Fragment should have its state restored")
+ .that((restoredFragment as SavedStateFragment).savedState)
+ .isEqualTo("test")
}
@Test
fun testToString() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
val destination = fragmentNavigator.createDestination().apply {
id = INITIAL_FRAGMENT
setClassName(EmptyFragment::class.java.name)
@@ -714,7 +702,6 @@
@Test
fun testToStringNoClassName() {
- val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
val destination = fragmentNavigator.createDestination().apply {
id = INITIAL_FRAGMENT
label = TEST_LABEL
@@ -723,6 +710,17 @@
"class=null"
assertThat(destination.toString()).isEqualTo(expected)
}
+
+ private fun createBackStackEntry(
+ destId: Int = INITIAL_FRAGMENT,
+ clazz: KClass<out Fragment> = EmptyFragment::class
+ ): NavBackStackEntry {
+ val destination = fragmentNavigator.createDestination().apply {
+ id = destId
+ setClassName(clazz.java.name)
+ }
+ return navigatorState.createBackStackEntry(destination, null)
+ }
}
class EmptyActivity : FragmentActivity() {
@@ -732,6 +730,20 @@
}
}
+class SavedStateFragment : Fragment() {
+ var savedState: String? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ savedState = savedInstanceState?.getString("savedState")
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putString("savedState", savedState)
+ }
+}
+
class NonEmptyConstructorFragment(val test: String) : Fragment()
class NonEmptyFragmentFactory : FragmentFactory() {
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
index 4dd7e37..7e575fc 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
@@ -23,8 +23,10 @@
import androidx.annotation.CallSuper
import androidx.annotation.IdRes
import androidx.core.content.res.use
+import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
+import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
import androidx.navigation.Navigator
@@ -50,7 +52,7 @@
private val fragmentManager: FragmentManager,
private val containerId: Int
) : Navigator<Destination>() {
- private val backStack = ArrayDeque<Int>()
+ private val savedIds = mutableSetOf<String>()
/**
* {@inheritDoc}
@@ -64,22 +66,41 @@
* asynchronously, so the newly visible Fragment from the back stack
* is not instantly available after this call completes.
*/
- public override fun popBackStack(): Boolean {
- if (backStack.isEmpty()) {
- return false
- }
+ override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
if (fragmentManager.isStateSaved) {
Log.i(
TAG, "Ignoring popBackStack() call: FragmentManager has already saved its state"
)
- return false
+ return
}
- fragmentManager.popBackStack(
- generateBackStackName(backStack.size, backStack.last()),
- FragmentManager.POP_BACK_STACK_INCLUSIVE
- )
- backStack.removeLast()
- return true
+ if (savedState) {
+ val beforePopList = state.backStack.value
+ val initialEntry = beforePopList.first()
+ // Get the set of entries that are going to be popped
+ val poppedList = beforePopList.subList(
+ beforePopList.indexOf(popUpTo),
+ beforePopList.size
+ )
+ // Now go through the list in reversed order (i.e., started from the most added)
+ // and save the back stack state of each.
+ for (entry in poppedList.reversed()) {
+ if (entry == initialEntry) {
+ Log.i(
+ TAG,
+ "FragmentManager cannot save the state of the initial destination $entry"
+ )
+ } else {
+ fragmentManager.saveBackStack(entry.id)
+ savedIds += entry.id
+ }
+ }
+ } else {
+ fragmentManager.popBackStack(
+ popUpTo.id,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE
+ )
+ }
+ state.pop(popUpTo, savedState)
}
public override fun createDestination(): Destination {
@@ -126,18 +147,42 @@
* asynchronously, so the new Fragment is not instantly available
* after this call completes.
*/
- public override fun navigate(
- destination: Destination,
- args: Bundle?,
+ override fun navigate(
+ entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
- ): NavDestination? {
+ ) {
if (fragmentManager.isStateSaved) {
Log.i(
TAG, "Ignoring navigate() call: FragmentManager has already saved its state"
)
- return null
+ return
}
+ for (entry in entries) {
+ navigate(entry, navOptions, navigatorExtras)
+ }
+ }
+
+ private fun navigate(
+ entry: NavBackStackEntry,
+ navOptions: NavOptions?,
+ navigatorExtras: Navigator.Extras?
+ ) {
+ val backStack = state.backStack.value
+ val initialNavigation = backStack.isEmpty()
+ val restoreState = (
+ navOptions != null && !initialNavigation &&
+ navOptions.shouldRestoreState() &&
+ savedIds.remove(entry.id)
+ )
+ if (restoreState) {
+ // Restore back stack does all the work to restore the entry
+ fragmentManager.restoreBackStack(entry.id)
+ state.add(entry)
+ return
+ }
+ val destination = entry.destination as Destination
+ val args = entry.arguments
var className = destination.className
if (className[0] == '.') {
className = context.packageName + className
@@ -159,15 +204,13 @@
ft.replace(containerId, frag)
ft.setPrimaryNavigationFragment(frag)
@IdRes val destId = destination.id
- val initialNavigation = backStack.isEmpty()
// TODO Build first class singleTop behavior for fragments
val isSingleTopReplacement = (
navOptions != null && !initialNavigation &&
navOptions.shouldLaunchSingleTop() &&
- backStack.last() == destId
+ backStack.last().destination.id == destId
)
- val isAdded: Boolean
- isAdded = when {
+ val isAdded = when {
initialNavigation -> {
true
}
@@ -179,15 +222,15 @@
// remove it from the back stack and put our replacement
// on the back stack in its place
fragmentManager.popBackStack(
- generateBackStackName(backStack.size, backStack.last()),
+ entry.id,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
- ft.addToBackStack(generateBackStackName(backStack.size, destId))
+ ft.addToBackStack(entry.id)
}
false
}
else -> {
- ft.addToBackStack(generateBackStackName(backStack.size + 1, destId))
+ ft.addToBackStack(entry.id)
true
}
}
@@ -199,35 +242,26 @@
ft.setReorderingAllowed(true)
ft.commit()
// The commit succeeded, update our view of the world
- return if (isAdded) {
- backStack.add(destId)
- destination
- } else {
- null
+ if (isAdded) {
+ state.add(entry)
}
}
public override fun onSaveState(): Bundle? {
- val b = Bundle()
- val backStack = backStack.toIntArray()
- b.putIntArray(KEY_BACK_STACK_IDS, backStack)
- return b
+ if (savedIds.isEmpty()) {
+ return null
+ }
+ return bundleOf(KEY_SAVED_IDS to ArrayList(savedIds))
}
public override fun onRestoreState(savedState: Bundle) {
- val backStack = savedState.getIntArray(KEY_BACK_STACK_IDS)
- if (backStack != null) {
- this.backStack.clear()
- for (destId in backStack) {
- this.backStack.add(destId)
- }
+ val savedIds = savedState.getStringArrayList(KEY_SAVED_IDS)
+ if (savedIds != null) {
+ this.savedIds.clear()
+ this.savedIds += savedIds
}
}
- private fun generateBackStackName(backStackIndex: Int, destId: Int): String {
- return "$backStackIndex-$destId"
- }
-
/**
* NavDestination specific to [FragmentNavigator]
*/
@@ -368,6 +402,6 @@
private companion object {
private const val TAG = "FragmentNavigator"
- private const val KEY_BACK_STACK_IDS = "androidx-nav-fragment:navigator:backStackIds"
+ private const val KEY_SAVED_IDS = "androidx-nav-fragment:navigator:savedIds"
}
}
diff --git a/navigation/navigation-runtime-ktx/lint-baseline.xml b/navigation/navigation-runtime-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-runtime-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-runtime-truth/lint-baseline.xml b/navigation/navigation-runtime-truth/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-runtime-truth/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-runtime/api/current.txt b/navigation/navigation-runtime/api/current.txt
index ebbad33..219326d 100644
--- a/navigation/navigation-runtime/api/current.txt
+++ b/navigation/navigation-runtime/api/current.txt
@@ -91,6 +91,7 @@
method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
+ method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
method public androidx.navigation.NavDestination? getCurrentDestination();
@@ -113,10 +114,16 @@
method @MainThread public void navigate(androidx.navigation.NavDirections directions);
method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.NavOptions? navOptions);
method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.Navigator.Extras navigatorExtras);
+ method public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+ method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+ method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
+ method public final void navigate(String route);
method @MainThread public boolean navigateUp();
method @MainThread public boolean popBackStack();
method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
+ method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+ method @MainThread public final boolean popBackStack(String route, boolean inclusive);
method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
method @CallSuper public void restoreState(android.os.Bundle? navState);
method @CallSuper public android.os.Bundle? saveState();
@@ -144,12 +151,15 @@
public final class NavControllerKt {
method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavDeepLinkBuilder {
ctor public NavDeepLinkBuilder(android.content.Context context);
method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId, optional android.os.Bundle? args);
method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId);
+ method public androidx.navigation.NavDeepLinkBuilder addDestination(String route, optional android.os.Bundle? args);
+ method public androidx.navigation.NavDeepLinkBuilder addDestination(String route);
method public android.app.PendingIntent createPendingIntent();
method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle? args);
@@ -157,6 +167,8 @@
method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName componentName);
method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId, optional android.os.Bundle? args);
method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute, optional android.os.Bundle? args);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute);
method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId);
method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph navGraph);
}
diff --git a/navigation/navigation-runtime/api/public_plus_experimental_current.txt b/navigation/navigation-runtime/api/public_plus_experimental_current.txt
index d627bde..308daa9 100644
--- a/navigation/navigation-runtime/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-runtime/api/public_plus_experimental_current.txt
@@ -94,7 +94,9 @@
method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void enableOnBackPressed(boolean enabled);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.navigation.NavDestination? findDestination(@IdRes int destinationId);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.navigation.NavDestination? findDestination(String destinationRoute);
method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
+ method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final android.content.Context getContext();
method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
@@ -118,10 +120,16 @@
method @MainThread public void navigate(androidx.navigation.NavDirections directions);
method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.NavOptions? navOptions);
method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.Navigator.Extras navigatorExtras);
+ method public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+ method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+ method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
+ method public final void navigate(String route);
method @MainThread public boolean navigateUp();
method @MainThread public boolean popBackStack();
method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
+ method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+ method @MainThread public final boolean popBackStack(String route, boolean inclusive);
method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
method @CallSuper public void restoreState(android.os.Bundle? navState);
method @CallSuper public android.os.Bundle? saveState();
@@ -157,12 +165,15 @@
public final class NavControllerKt {
method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavDeepLinkBuilder {
ctor public NavDeepLinkBuilder(android.content.Context context);
method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId, optional android.os.Bundle? args);
method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId);
+ method public androidx.navigation.NavDeepLinkBuilder addDestination(String route, optional android.os.Bundle? args);
+ method public androidx.navigation.NavDeepLinkBuilder addDestination(String route);
method public android.app.PendingIntent createPendingIntent();
method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle? args);
@@ -170,6 +181,8 @@
method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName componentName);
method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId, optional android.os.Bundle? args);
method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute, optional android.os.Bundle? args);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute);
method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId);
method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph navGraph);
}
diff --git a/navigation/navigation-runtime/api/restricted_current.txt b/navigation/navigation-runtime/api/restricted_current.txt
index ebbad33..219326d 100644
--- a/navigation/navigation-runtime/api/restricted_current.txt
+++ b/navigation/navigation-runtime/api/restricted_current.txt
@@ -91,6 +91,7 @@
method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
+ method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
method public androidx.navigation.NavDestination? getCurrentDestination();
@@ -113,10 +114,16 @@
method @MainThread public void navigate(androidx.navigation.NavDirections directions);
method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.NavOptions? navOptions);
method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.Navigator.Extras navigatorExtras);
+ method public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+ method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+ method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
+ method public final void navigate(String route);
method @MainThread public boolean navigateUp();
method @MainThread public boolean popBackStack();
method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
+ method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+ method @MainThread public final boolean popBackStack(String route, boolean inclusive);
method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
method @CallSuper public void restoreState(android.os.Bundle? navState);
method @CallSuper public android.os.Bundle? saveState();
@@ -144,12 +151,15 @@
public final class NavControllerKt {
method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavDeepLinkBuilder {
ctor public NavDeepLinkBuilder(android.content.Context context);
method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId, optional android.os.Bundle? args);
method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId);
+ method public androidx.navigation.NavDeepLinkBuilder addDestination(String route, optional android.os.Bundle? args);
+ method public androidx.navigation.NavDeepLinkBuilder addDestination(String route);
method public android.app.PendingIntent createPendingIntent();
method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle? args);
@@ -157,6 +167,8 @@
method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName componentName);
method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId, optional android.os.Bundle? args);
method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute, optional android.os.Bundle? args);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute);
method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId);
method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph navGraph);
}
diff --git a/navigation/navigation-runtime/lint-baseline.xml b/navigation/navigation-runtime/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-runtime/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
new file mode 100644
index 0000000..654ffd9
--- /dev/null
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -0,0 +1,1338 @@
+/*
+ * 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.navigation
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Parcel
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.addCallback
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.navigation.NavDestination.Companion.createRoute
+import androidx.navigation.test.R
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.intent.Intents
+import androidx.test.espresso.intent.Intents.intended
+import androidx.test.espresso.intent.matcher.BundleMatchers
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
+import androidx.test.espresso.intent.matcher.IntentMatchers.toPackage
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.ext.truth.os.BundleSubject.assertThat
+import androidx.test.filters.LargeTest
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.testutils.TestNavigator
+import androidx.testutils.test
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.hamcrest.CoreMatchers.allOf
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.Matchers
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class NavControllerRouteTest {
+
+ val nav_simple_route_graph =
+ createNavController().createGraph(route = "nav_root", startDestination = "start_test") {
+ test("start_test")
+ test("start_test_with_default_arg") {
+ argument("defaultArg") { defaultValue = true }
+ }
+ test("second_test") {
+ argument("arg2") { type = NavType.StringType }
+ argument("defaultArg") {
+ type = NavType.StringType
+ defaultValue = "defaultValue"
+ }
+ deepLink {
+ uriPattern = "android-app://androidx.navigation.test/test"
+ action = "test.action"
+ mimeType = "type/test"
+ }
+ deepLink {
+ uriPattern = "android-app://androidx.navigation.test/test/{arg1}/{arg2}"
+ }
+ }
+ }
+
+ val nav_start_destination_route_graph =
+ createNavController().createGraph(route = "graph", startDestination = "start_test") {
+ test("start_test") {
+ argument("test") {
+ type = NavType.StringType
+ defaultValue = "@null"
+ }
+ }
+ }
+
+ val nav_nested_start_destination_route_graph =
+ createNavController().createGraph(route = "graph", startDestination = "nested") {
+ navigation(route = "nested", startDestination = "nested_test") {
+ test("nested_test")
+ test("nested_second_test")
+ }
+ test("second_test")
+ }
+
+ val nav_deeplink_route_graph =
+ createNavController().createGraph(route = "nav_root", startDestination = "first_test") {
+ test("first_test") {
+ deepLink {
+ uriPattern = "android-app://androidx.navigation.test/test"
+ action = "test.action"
+ mimeType = "*/*"
+ }
+ }
+ test("second_test") {
+ deepLink {
+ uriPattern = "android-app://androidx.navigation.test/test"
+ action = "test.action"
+ mimeType = "image/*"
+ }
+ }
+ test("third_test") {
+ deepLink {
+ uriPattern = "android-app://androidx.navigation.test/test"
+ action = "test.action"
+ mimeType = "*/test"
+ }
+ }
+ test("forth_test") {
+ deepLink {
+ uriPattern = "android-app://androidx.navigation.test/test"
+ action = "test.action"
+ mimeType = "type/test"
+ }
+ }
+ }
+
+ val nav_multiple_navigation_route_graph =
+ createNavController().createGraph(
+ route = "nav_multi_module_base", startDestination = "simple_child_start"
+ ) {
+ navigation(route = "simple_child_start", startDestination = "simple_child_start_test") {
+ test("simple_child_start_test")
+ test("simple_child_second_test")
+ }
+ navigation(
+ route = "deep_link_child_start", startDestination = "deep_link_child_start_test"
+ ) {
+ test("deep_link_child_start_test")
+ test("deep_link_child_second_test") {
+ deepLink { uriPattern = "android-app://androidx.navigation.test/test" }
+ }
+ navigation(
+ route = "deep_link_child_second",
+ startDestination = "deep_link_grandchild_start_test"
+ ) {
+ test("deep_link_grandchild_start_test") {
+ deepLink {
+ uriPattern = "android-app://androidx.navigation.test/grand_child_test"
+ }
+ }
+ test("deep_link_child_second_test") {
+ deepLink { uriPattern = "android-app://androidx.navigation.test/test" }
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val UNKNOWN_DESTINATION_ID = -1
+ private const val TEST_ARG = "test"
+ private const val TEST_ARG_VALUE = "value"
+ private const val TEST_OVERRIDDEN_VALUE_ARG = "test_overridden_value"
+ private const val TEST_OVERRIDDEN_VALUE_ARG_VALUE = "override"
+ }
+
+ @UiThreadTest
+ @Test
+ fun testGetCurrentBackStackEntry() {
+ val navController = createNavController()
+ navController.graph = nav_start_destination_route_graph
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("start_test")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testGetPreviousBackStackEntry() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ navController.navigate("second_test")
+ assertThat(navController.previousBackStackEntry?.destination?.route).isEqualTo("start_test")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testStartDestination() {
+ val navController = createNavController()
+ navController.graph = nav_start_destination_route_graph
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSetGraphTwice() {
+ val navController = createNavController()
+ navController.graph = nav_start_destination_route_graph
+ val navigator = navController.navigatorProvider[TestNavigator::class]
+ assertThat(navController.currentDestination?.route)
+ .isEqualTo("start_test")
+ assertThat(navigator.backStack.size)
+ .isEqualTo(1)
+
+ // Now set a new graph, overriding the first
+ navController.graph = nav_nested_start_destination_route_graph
+ assertThat(navController.currentDestination?.route)
+ .isEqualTo("nested_test")
+ assertThat(navigator.backStack.size)
+ .isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testStartDestinationWithArgs() {
+ val navController = createNavController()
+ val args = Bundle().apply {
+ putString(TEST_ARG, TEST_ARG_VALUE)
+ }
+ navController.setGraph(nav_simple_route_graph, args)
+ val navigator = navController.navigatorProvider[TestNavigator::class]
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ val foundArgs = navigator.current.arguments
+ assertThat(foundArgs).isNotNull()
+ assertThat(foundArgs?.getString(TEST_ARG)).isEqualTo(TEST_ARG_VALUE)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testStartDestinationWithArgsProgrammatic() {
+ val navController = createNavController()
+ val args = Bundle().apply {
+ putString(TEST_ARG, TEST_ARG_VALUE)
+ }
+
+ val navGraph = navController.navigatorProvider.navigation(
+ route = "graph", startDestination = "start"
+ ) {
+ test("start")
+ }
+ navController.setGraph(navGraph, args)
+ val navigator = navController.navigatorProvider[TestNavigator::class]
+ assertThat(navController.currentDestination?.route).isEqualTo("start")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ val foundArgs = navigator.current.arguments
+ assertThat(foundArgs).isNotNull()
+ assertThat(foundArgs?.getString(TEST_ARG)).isEqualTo(TEST_ARG_VALUE)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNestedStartDestination() {
+ val navController = createNavController()
+ navController.graph = nav_nested_start_destination_route_graph
+ assertThat(navController.currentDestination?.route).isEqualTo("nested_test")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSetGraph() {
+ val navController = createNavController()
+
+ navController.graph = nav_start_destination_route_graph
+ assertThat(navController.graph).isNotNull()
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSetViewModelStoreOwnerAfterGraphSet() {
+ val navController = createNavController()
+ navController.setViewModelStore(ViewModelStore())
+ val navGraph = navController.navigatorProvider.navigation(
+ route = "graph", startDestination = "start"
+ ) {
+ test("start")
+ }
+ navController.setGraph(navGraph, null)
+
+ try {
+ navController.setViewModelStore(ViewModelStore())
+ } catch (e: IllegalStateException) {
+ assertThat(e).hasMessageThat().contains(
+ "ViewModelStore should be set before setGraph call"
+ )
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSetSameViewModelStoreOwnerAfterGraphSet() {
+ val navController = createNavController()
+ val viewModelStore = ViewModelStore()
+ navController.setViewModelStore(viewModelStore)
+ val navGraph = navController.navigatorProvider.navigation(
+ route = "graph",
+ startDestination = "start"
+ ) {
+ test("start")
+ }
+ navController.setGraph(navGraph, null)
+
+ navController.setViewModelStore(viewModelStore)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigate() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ navController.navigate("second_test")
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateViaDeepLink() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ val intent = navigator.current.arguments?.getParcelable<Intent>(
+ NavController.KEY_DEEP_LINK_INTENT
+ )
+ assertThat(intent?.data).isEqualTo(deepLink)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateViaDeepLinkDefaultArgs() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+ navController.navigate(deepLink)
+
+ val destination = navController.currentDestination
+ assertThat(destination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ assertThat(destination?.arguments?.get("defaultArg")?.defaultValue.toString())
+ .isEqualTo("defaultValue")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateViaDeepLinkActionDifferentURI() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = NavDeepLinkRequest(Uri.parse("invalidDeepLink.com"), "test.action", null)
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateViaDeepLinkMimeTypeDifferentUri() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = NavDeepLinkRequest(Uri.parse("invalidDeepLink.com"), null, "type/test")
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateViaDeepLinkMimeType() {
+ val navController = createNavController()
+ navController.graph = nav_deeplink_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val mimeType = "type/test"
+ val deepLink = NavDeepLinkRequest(null, null, mimeType)
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route).isEqualTo("forth_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ val intent = navigator.current.arguments?.getParcelable<Intent>(
+ NavController.KEY_DEEP_LINK_INTENT
+ )
+ assertThat(intent?.type).isEqualTo(mimeType)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateViaDeepLinkMimeTypeWildCard() {
+ val navController = createNavController()
+ navController.graph = nav_deeplink_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = NavDeepLinkRequest(null, null, "any/thing")
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route).isEqualTo("first_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateViaDeepLinkMimeTypeWildCardSubtype() {
+ val navController = createNavController()
+ navController.graph = nav_deeplink_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = NavDeepLinkRequest(null, null, "image/jpg")
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateViaDeepLinkMimeTypeWildCardType() {
+ val navController = createNavController()
+ navController.graph = nav_deeplink_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = NavDeepLinkRequest(null, null, "doesNotEvenMatter/test")
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route).isEqualTo("third_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigationViaDeepLinkPopUpTo() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+ navController.navigate(
+ deepLink,
+ navOptions {
+ popUpTo("nav_root") { inclusive = true }
+ }
+ )
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateToDifferentGraphViaDeepLink() {
+ val navController = createNavController()
+ navController.graph = nav_multiple_navigation_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route).isEqualTo("deep_link_child_second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ val popped = navController.popBackStack()
+ assertWithMessage("NavController should return true when popping a non-root destination")
+ .that(popped)
+ .isTrue()
+ assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateToDifferentGraphViaDeepLink3x() {
+ val navController = createNavController()
+ navController.graph = nav_multiple_navigation_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route).isEqualTo("deep_link_child_second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ navController.popBackStack()
+ assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ // repeat nav and pop 2 more times.
+ navController.navigate(deepLink)
+ navController.popBackStack()
+ navController.navigate(deepLink)
+
+ val popped = navController.popBackStack()
+ assertWithMessage("NavController should return true when popping a non-root destination")
+ .that(popped)
+ .isTrue()
+ assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateToDifferentGraphViaDeepLinkToGrandchild3x() {
+ val navController = createNavController()
+ navController.graph = nav_multiple_navigation_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val deepLink = Uri.parse("android-app://androidx.navigation.test/grand_child_test")
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route)
+ .isEqualTo("deep_link_grandchild_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ navController.popBackStack()
+ assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ // repeat nav and pop 2 more times.
+ navController.navigate(deepLink)
+ navController.popBackStack()
+ navController.navigate(deepLink)
+
+ val popped = navController.popBackStack()
+ assertWithMessage("NavController should return true when popping a non-root destination")
+ .that(popped)
+ .isTrue()
+ assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @LargeTest
+ @Test
+ @SdkSuppress(minSdkVersion = 17)
+ fun testNavigateViaImplicitDeepLink() {
+ val intent = Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse("android-app://androidx.navigation.test/test/argument1/argument2"),
+ ApplicationProvider.getApplicationContext() as Context,
+ TestActivity::class.java
+ )
+
+ Intents.init()
+
+ with(ActivityScenario.launch<TestActivity>(intent)) {
+ moveToState(Lifecycle.State.CREATED)
+ onActivity { activity ->
+ run {
+ val navController = activity.navController
+ navController.graph = nav_simple_route_graph
+
+ val navigator =
+ navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+
+ // Only the leaf destination should be on the stack.
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ // The parent will be constructed in a new Activity after navigateUp()
+ navController.navigateUp()
+ }
+ }
+
+ assertThat(this.state).isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ // this relies on MonitoringInstrumentation.execStartActivity() which was added in API 17
+ intended(
+ allOf(
+ toPackage((ApplicationProvider.getApplicationContext() as Context).packageName),
+ not(hasData(anyString())), // The rethrow should not use the URI as primary target.
+ hasExtra(
+ NavController.KEY_DEEP_LINK_IDS, intArrayOf(createRoute("nav_root").hashCode())
+ ),
+ hasExtra(
+ Matchers.`is`(NavController.KEY_DEEP_LINK_EXTRAS),
+ allOf(
+ BundleMatchers.hasEntry("arg1", "argument1"),
+ BundleMatchers.hasEntry("arg2", "argument2"),
+ BundleMatchers.hasEntry(
+ NavController.KEY_DEEP_LINK_INTENT,
+ allOf(
+ hasAction(intent.action),
+ hasData(intent.data),
+ hasComponent(intent.component)
+ )
+ )
+ )
+ )
+ )
+ )
+
+ Intents.release()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSaveRestoreStateXml() {
+ val context = ApplicationProvider.getApplicationContext() as Context
+ var navController = NavController(context)
+ var navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+ navController.graph = nav_simple_route_graph
+ navController.navigate("second_test")
+
+ val savedState = navController.saveState()
+ navController = NavController(context)
+ navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+
+ // Restore state doesn't recreate any graph
+ navController.restoreState(savedState)
+ assertThat(navController.currentDestination).isNull()
+
+ // Explicitly setting a graph then restores the state
+ navController.graph = nav_simple_route_graph
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ // Save state should be called on the navigator exactly once
+ assertThat(navigator.saveStateCount).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSaveRestoreStateDestinationChanged() {
+ val context = ApplicationProvider.getApplicationContext() as Context
+ var navController = NavController(context)
+ var navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+
+ navController.graph = nav_simple_route_graph
+
+ val savedState = navController.saveState()
+ navController = NavController(context)
+ navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+
+ // Restore state doesn't recreate any graph
+ navController.restoreState(savedState)
+ assertThat(navController.currentDestination).isNull()
+
+ var destinationChangedCount = 0
+
+ navController.addOnDestinationChangedListener { _, _, _ ->
+ destinationChangedCount++
+ }
+
+ // Explicitly setting a graph then restores the state
+ navController.graph = nav_simple_route_graph
+ // Save state should be called on the navigator exactly once
+ assertThat(navigator.saveStateCount).isEqualTo(1)
+ // listener should have been fired again when state restored
+ assertThat(destinationChangedCount).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSaveRestoreStateProgrammatic() {
+ val context = ApplicationProvider.getApplicationContext() as Context
+ var navController = NavController(context)
+ var navigator = TestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+ navController.graph = nav_simple_route_graph
+ navController.navigate("second_test")
+
+ val savedState = navController.saveState()
+ navController = NavController(context)
+ navigator = TestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+
+ // Restore state doesn't recreate any graph
+ navController.restoreState(savedState)
+ assertThat(navController.currentDestination).isNull()
+
+ // Explicitly setting a graph then restores the state
+ navController.graph = nav_simple_route_graph
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSaveRestoreStateBundleParceled() {
+ val context = ApplicationProvider.getApplicationContext() as Context
+ var navController = NavController(context)
+ var navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+ navController.graph = nav_simple_route_graph
+
+ navigator.customParcel = CustomTestParcelable(TEST_ARG_VALUE)
+
+ val savedState = navController.saveState()
+
+ val parcel = Parcel.obtain()
+ savedState?.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+
+ val restoredState = Bundle.CREATOR.createFromParcel(parcel)
+
+ navController = NavController(context)
+ navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+
+ navController.restoreState(restoredState)
+ navController.graph = nav_simple_route_graph
+
+ // Ensure custom parcelable is present and can be read
+ assertThat(navigator.customParcel?.name).isEqualTo(TEST_ARG_VALUE)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSaveRestoreAfterNavigateToDifferentNavGraph() {
+ val context = ApplicationProvider.getApplicationContext() as Context
+ var navController = NavController(context)
+ var navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+ navController.graph = nav_multiple_navigation_route_graph
+ assertThat(navController.currentDestination?.route)
+ .isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+ navController.navigate(deepLink)
+ assertThat(navController.currentDestination?.route)
+ .isEqualTo("deep_link_child_second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ navController.navigate("simple_child_start")
+ assertThat(navController.currentDestination?.route)
+ .isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(3)
+
+ val savedState = navController.saveState()
+ navController = NavController(context)
+ navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+
+ // Restore state doesn't recreate any graph
+ navController.restoreState(savedState)
+ assertThat(navController.currentDestination).isNull()
+
+ // Explicitly setting a graph then restores the state
+ navController.graph = nav_multiple_navigation_route_graph
+ assertThat(navController.currentDestination?.route)
+ .isEqualTo("simple_child_start_test")
+ assertThat(navigator.backStack.size).isEqualTo(3)
+ // Save state should be called on the navigator exactly once
+ assertThat(navigator.saveStateCount).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testBackstackArgsBundleParceled() {
+ val context = ApplicationProvider.getApplicationContext() as Context
+ var navController = NavController(context)
+ var navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+
+ val backStackArg1 = Bundle()
+ backStackArg1.putParcelable(TEST_ARG, CustomTestParcelable(TEST_ARG_VALUE))
+ navController.setGraph(R.navigation.nav_arguments)
+ navController.navigate(R.id.second_test, backStackArg1)
+
+ val savedState = navController.saveState()
+
+ val parcel = Parcel.obtain()
+ savedState?.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+
+ val restoredState = Bundle.CREATOR.createFromParcel(parcel)
+
+ navController = NavController(context)
+ navigator = SaveStateTestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+
+ navController.restoreState(restoredState)
+ navController.setGraph(R.navigation.nav_arguments)
+
+ navController.addOnDestinationChangedListener { _, _, arguments ->
+ assertThat(arguments?.getParcelable<CustomTestParcelable>(TEST_ARG)?.name)
+ .isEqualTo(TEST_ARG_VALUE)
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateArgs() {
+ val navController = createNavController()
+ navController.setGraph(R.navigation.nav_arguments)
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val returnedArgs = navigator.current.arguments
+ assertThat(returnedArgs).isNotNull()
+ assertThat(returnedArgs!!["test_start_default"])
+ .isEqualTo("default")
+
+ navController.addOnDestinationChangedListener { _, _, arguments ->
+ assertThat(arguments).isNotNull()
+ assertThat(arguments!!["test_start_default"])
+ .isEqualTo("default")
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateWithNoDefaultValue() {
+ val returnedArgs = navigateWithArgs(null)
+
+ // Test that arguments without a default value aren't passed through at all
+ assertThat(returnedArgs.containsKey("test_no_default_value")).isFalse()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateWithDefaultArgs() {
+ val returnedArgs = navigateWithArgs(null)
+
+ // Test that default values are passed through
+ assertThat(returnedArgs.getString("test_default_value")).isEqualTo("default")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateWithArgs() {
+ val args = Bundle()
+ args.putString(TEST_ARG, TEST_ARG_VALUE)
+ val returnedArgs = navigateWithArgs(args)
+
+ // Test that programmatically constructed arguments are passed through
+ assertThat(returnedArgs.getString(TEST_ARG)).isEqualTo(TEST_ARG_VALUE)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateWithOverriddenDefaultArgs() {
+ val args = Bundle()
+ args.putString(TEST_OVERRIDDEN_VALUE_ARG, TEST_OVERRIDDEN_VALUE_ARG_VALUE)
+ val returnedArgs = navigateWithArgs(args)
+
+ // Test that default values can be overridden by programmatic values
+ assertThat(returnedArgs.getString(TEST_OVERRIDDEN_VALUE_ARG))
+ .isEqualTo(TEST_OVERRIDDEN_VALUE_ARG_VALUE)
+ }
+
+ private fun navigateWithArgs(args: Bundle?): Bundle {
+ val navController = createNavController()
+ navController.setGraph(R.navigation.nav_arguments)
+
+ navController.navigate(R.id.second_test, args)
+
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val returnedArgs = navigator.current.arguments
+ assertThat(returnedArgs).isNotNull()
+
+ return returnedArgs!!
+ }
+
+ @UiThreadTest
+ @Test
+ fun testPopRoot() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val success = navController.popBackStack()
+ assertWithMessage("NavController should return false when popping the root")
+ .that(success)
+ .isFalse()
+ assertThat(navController.currentDestination).isNull()
+ assertThat(navigator.backStack.size).isEqualTo(0)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testPopOnEmptyStack() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ val success = navController.popBackStack()
+ assertWithMessage("NavController should return false when popping the root")
+ .that(success)
+ .isFalse()
+ assertThat(navController.currentDestination).isNull()
+ assertThat(navigator.backStack.size).isEqualTo(0)
+
+ val popped = navController.popBackStack()
+ assertWithMessage(
+ "popBackStack should return false when there's nothing on the " +
+ "back stack"
+ )
+ .that(popped)
+ .isFalse()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateThenPop() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ navController.navigate("second_test")
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ val popped = navController.popBackStack()
+ assertWithMessage("NavController should return true when popping a non-root destination")
+ .that(popped)
+ .isTrue()
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateThenPopToUnknownDestination() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ navController.navigate("second_test")
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ val popped = navController.popBackStack(UNKNOWN_DESTINATION_ID, false)
+ assertWithMessage("Popping to an invalid destination should return false")
+ .that(popped)
+ .isFalse()
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateThenNavigateWithPop() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ navController.navigate("second_test") {
+ popUpTo("start_test") { inclusive = true }
+ }
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateThenNavigateWithPopRoot() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ navController.navigate("second_test") {
+ popUpTo("nav_root") { inclusive = true }
+ }
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateThenNavigateUp() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ navController.navigate("second_test")
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ // This should function identically to popBackStack()
+ val success = navController.navigateUp()
+ assertThat(success)
+ .isTrue()
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateThenNavigateUpWithDefaultArgs() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+ assertThat(navigator.backStack.size).isEqualTo(1)
+
+ navController.navigate("second_test")
+ assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+
+ navController.navigate("start_test_with_default_arg")
+ assertThat(navController.currentDestination?.route).isEqualTo("start_test_with_default_arg")
+ assertThat(navigator.backStack.size).isEqualTo(3)
+
+ // This should function identically to popBackStack()
+ val success = navController.navigateUp()
+ assertThat(success).isTrue()
+ val destination = navController.currentDestination
+ assertThat(destination?.route).isEqualTo("second_test")
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ assertThat(destination?.arguments?.get("defaultArg")?.defaultValue.toString())
+ .isEqualTo("defaultValue")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testDeepLinkFromNavGraph() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+
+ val taskStackBuilder = navController.createDeepLink()
+ .setDestination("second_test")
+ .createTaskStackBuilder()
+ assertThat(taskStackBuilder).isNotNull()
+ assertThat(taskStackBuilder.intentCount).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testDeepLinkIntent() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+
+ val args = Bundle()
+ args.putString("test", "test")
+ val taskStackBuilder = navController.createDeepLink()
+ .setDestination("second_test")
+ .setArguments(args)
+ .createTaskStackBuilder()
+
+ val intent = taskStackBuilder.editIntentAt(0)
+ assertThat(intent).isNotNull()
+ navController.handleDeepLink(intent)
+
+ // The original Intent should be untouched and safely writable to a Parcel
+ val p = Parcel.obtain()
+ intent!!.writeToParcel(p, 0)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testDeepLinkIntentWithDefaultArgs() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+
+ val taskStackBuilder = navController.createDeepLink()
+ .setDestination("second_test")
+ .createTaskStackBuilder()
+
+ val intent = taskStackBuilder.editIntentAt(0)
+ assertThat(intent).isNotNull()
+ navController.handleDeepLink(intent)
+
+ // The original Intent should be untouched and safely writable to a Parcel
+ val p = Parcel.obtain()
+ intent!!.writeToParcel(p, 0)
+
+ val destination = navController.currentDestination
+ assertThat(destination?.route).isEqualTo("second_test")
+ assertThat(destination?.arguments?.get("defaultArg")?.defaultValue.toString())
+ .isEqualTo("defaultValue")
+ }
+
+ @UiThreadTest
+ @Test
+ fun testHandleDeepLinkValid() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val collectedDestinationIds = mutableListOf<String?>()
+ navController.addOnDestinationChangedListener { _, destination, _ ->
+ collectedDestinationIds.add(destination.route)
+ }
+
+ val taskStackBuilder = navController.createDeepLink()
+ .setDestination("second_test")
+ .createTaskStackBuilder()
+
+ val intent = taskStackBuilder.editIntentAt(0)
+ assertThat(intent).isNotNull()
+ assertWithMessage("NavController should handle deep links to its own graph")
+ .that(navController.handleDeepLink(intent))
+ .isTrue()
+ // Verify that we navigated down to the deep link
+ assertThat(collectedDestinationIds)
+ .containsExactly("start_test", "start_test", "second_test")
+ .inOrder()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testHandleDeepLinkNestedStartDestination() {
+ val navController = createNavController()
+ navController.graph = nav_nested_start_destination_route_graph
+ val collectedDestinationIds = mutableListOf<String?>()
+ navController.addOnDestinationChangedListener { _, destination, _ ->
+ collectedDestinationIds.add(destination.route)
+ }
+
+ val taskStackBuilder = navController.createDeepLink()
+ .setDestination("second_test")
+ .createTaskStackBuilder()
+
+ val intent = taskStackBuilder.editIntentAt(0)
+ assertThat(intent).isNotNull()
+ assertWithMessage("NavController should handle deep links to its own graph")
+ .that(navController.handleDeepLink(intent))
+ .isTrue()
+
+ // Verify that we navigated down to the deep link
+ assertThat(collectedDestinationIds)
+ .containsExactly("nested_test", "nested_test", "second_test")
+ .inOrder()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testHandleDeepLinkMultipleDestinations() {
+ val navController = createNavController()
+ navController.graph = nav_multiple_navigation_route_graph
+ val collectedDestinationRoutes = mutableListOf<String?>()
+ navController.addOnDestinationChangedListener { _, destination, _ ->
+ collectedDestinationRoutes.add(destination.route)
+ }
+
+ val taskStackBuilder = navController.createDeepLink()
+ .setDestination("simple_child_second_test")
+ .addDestination("deep_link_child_second_test")
+ .createTaskStackBuilder()
+
+ val intent = taskStackBuilder.editIntentAt(0)
+ assertThat(intent).isNotNull()
+ assertWithMessage("NavController should handle deep links to its own graph")
+ .that(navController.handleDeepLink(intent))
+ .isTrue()
+
+ // Verify that we navigated down to the deep link
+ assertThat(collectedDestinationRoutes)
+ .containsExactly(
+ // First to the destination added via setDestination()
+ "simple_child_start_test", "simple_child_start_test",
+ "simple_child_second_test",
+ // Then to the second destination added via addDestination()
+ "deep_link_child_start_test", "deep_link_child_second_test"
+ )
+ .inOrder()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testHandleDeepLinkMultipleDestinationsWithArgs() {
+ val navController = createNavController()
+ navController.graph = nav_multiple_navigation_route_graph
+ val collectedDestinations = mutableListOf<Pair<String?, Bundle?>>()
+ navController.addOnDestinationChangedListener { _, destination, arguments ->
+ collectedDestinations.add(destination.route to arguments)
+ }
+
+ val globalBundle = Bundle().apply {
+ putString("global", "global")
+ }
+ val firstBundle = Bundle().apply {
+ putString("test", "first")
+ }
+ val secondBundle = Bundle().apply {
+ putString("global", "overridden")
+ putString("test", "second")
+ }
+ val taskStackBuilder = navController.createDeepLink()
+ .setDestination(createRoute("simple_child_second_test").hashCode(), firstBundle)
+ .addDestination(createRoute("deep_link_child_second_test").hashCode(), secondBundle)
+ .setArguments(globalBundle)
+ .createTaskStackBuilder()
+
+ val intent = taskStackBuilder.editIntentAt(0)
+ assertThat(intent).isNotNull()
+ assertWithMessage("NavController should handle deep links to its own graph")
+ .that(navController.handleDeepLink(intent))
+ .isTrue()
+
+ // Verify that we navigated down to the deep link
+ // First to the destination added via setDestination()
+ val (destinationRoutes, bundle) = collectedDestinations[0]
+ assertThat(destinationRoutes).isEqualTo("simple_child_start_test")
+ assertThat(bundle).isEqualTo(null)
+
+ val (destinationId1, bundle1) = collectedDestinations[1]
+ assertThat(destinationId1).isEqualTo("simple_child_start_test")
+ assertThat(bundle1).string("global").isEqualTo("global")
+ assertThat(bundle1).string("test").isEqualTo("first")
+
+ val (destinationId2, bundle2) = collectedDestinations[2]
+ assertThat(destinationId2).isEqualTo("simple_child_second_test")
+ assertThat(bundle2).string("global").isEqualTo("global")
+ assertThat(bundle2).string("test").isEqualTo("first")
+
+ // Then to the second destination added via addDestination()
+ val (destinationId3, bundle3) = collectedDestinations[3]
+ assertThat(destinationId3).isEqualTo("deep_link_child_start_test")
+ assertThat(bundle3).string("global").isEqualTo("overridden")
+ assertThat(bundle3).string("test").isEqualTo("second")
+
+ val (destinationId4, bundle4) = collectedDestinations[4]
+ assertThat(destinationId4).isEqualTo("deep_link_child_second_test")
+ assertThat(bundle4).string("global").isEqualTo("overridden")
+ assertThat(bundle4).string("test").isEqualTo("second")
+
+ assertWithMessage("$collectedDestinations should have 5 destinations")
+ .that(collectedDestinations).hasSize(5)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testHandleDeepLinkInvalid() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val collectedDestinationRoutes = mutableListOf<String?>()
+ navController.addOnDestinationChangedListener { _, destination, _ ->
+ collectedDestinationRoutes.add(destination.route)
+ }
+
+ assertThat(collectedDestinationRoutes).containsExactly("start_test")
+
+ val taskStackBuilder = navController.createDeepLink()
+ .setGraph(nav_nested_start_destination_route_graph)
+ .setDestination("nested_second_test")
+ .createTaskStackBuilder()
+
+ val intent = taskStackBuilder.editIntentAt(0)
+ assertThat(intent).isNotNull()
+ assertWithMessage("handleDeepLink should return false when passed an invalid deep link")
+ .that(navController.handleDeepLink(intent))
+ .isFalse()
+
+ assertWithMessage("$collectedDestinationRoutes should have 1 destination id")
+ .that(collectedDestinationRoutes).hasSize(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testHandleDeepLinkToRootInvalid() {
+ val navController = createNavController()
+ navController.graph = nav_simple_route_graph
+ val collectedDestinationRoutes = mutableListOf<String?>()
+ navController.addOnDestinationChangedListener { _, destination, _ ->
+ collectedDestinationRoutes.add(destination.route)
+ }
+
+ assertThat(collectedDestinationRoutes).containsExactly("start_test")
+
+ val taskStackBuilder = navController.createDeepLink()
+ .setGraph(nav_nested_start_destination_route_graph)
+ .setDestination("nested_test")
+ .createTaskStackBuilder()
+
+ val intent = taskStackBuilder.editIntentAt(0)
+ assertThat(intent).isNotNull()
+ assertWithMessage("handleDeepLink should return false when passed an invalid deep link")
+ .that(navController.handleDeepLink(intent))
+ .isFalse()
+
+ assertWithMessage("$collectedDestinationRoutes should have 1 destination id")
+ .that(collectedDestinationRoutes).hasSize(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testSetOnBackPressedDispatcherOnNavBackStackEntry() {
+ var backPressedIntercepted = false
+ val navController = createNavController()
+ val lifecycleOwner = TestLifecycleOwner()
+ val dispatcher = OnBackPressedDispatcher()
+
+ navController.setLifecycleOwner(lifecycleOwner)
+ navController.setOnBackPressedDispatcher(dispatcher)
+
+ navController.graph = nav_simple_route_graph
+ navController.navigate("second_test")
+ assertThat(navController.previousBackStackEntry?.destination?.route)
+ .isEqualTo("start_test")
+
+ dispatcher.addCallback(navController.currentBackStackEntry!!) {
+ backPressedIntercepted = true
+ }
+
+ // Move to STOPPED
+ lifecycleOwner.currentState = Lifecycle.State.CREATED
+ // Move back up to RESUMED
+ lifecycleOwner.currentState = Lifecycle.State.RESUMED
+
+ dispatcher.onBackPressed()
+
+ assertThat(backPressedIntercepted).isTrue()
+ }
+
+ private fun createNavController(): NavController {
+ val navController = NavController(ApplicationProvider.getApplicationContext())
+ val navigator = TestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+ return navController
+ }
+}
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index ff2ff9d..5dd486d 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -1581,7 +1581,7 @@
assertThat(navigator.backStack.size).isEqualTo(2)
val navOptions = navOptions {
- popUpTo = R.id.start_test
+ popUpTo(R.id.start_test)
}
// the same as to call .navigate(R.id.finish)
navController.navigate(0, null, navOptions)
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt
index ca5ed11..d786d7a 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt
@@ -30,7 +30,7 @@
@Test
fun testGetViewModelStore() {
- val navGraphId = UUID.randomUUID()
+ val navGraphId = UUID.randomUUID().toString()
val viewModel = NavControllerViewModel()
val viewModelStore = viewModel.getViewModelStore(navGraphId)
assertThat(viewModel.getViewModelStore(navGraphId)).isSameInstanceAs(viewModelStore)
@@ -46,7 +46,7 @@
@Test
fun testClear() {
val viewModel = NavControllerViewModel.getInstance(ViewModelStore())
- val navGraphId = UUID.randomUUID()
+ val navGraphId = UUID.randomUUID().toString()
val viewModelStore = viewModel.getViewModelStore(navGraphId)
assertThat(viewModelStore).isNotNull()
@@ -58,10 +58,10 @@
fun testOnCleared() {
val baseViewModelStore = ViewModelStore()
val viewModel = NavControllerViewModel.getInstance(baseViewModelStore)
- val navGraphId = UUID.randomUUID()
+ val navGraphId = UUID.randomUUID().toString()
val navGraphViewModelStore = viewModel.getViewModelStore(navGraphId)
// test clearing two viewmodel stores.
- viewModel.getViewModelStore(UUID.randomUUID())
+ viewModel.getViewModelStore(UUID.randomUUID().toString())
assertThat(navGraphViewModelStore).isNotNull()
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavDeepLinkBuilderTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavDeepLinkBuilderTest.kt
index afdccd1..951a236 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavDeepLinkBuilderTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavDeepLinkBuilderTest.kt
@@ -36,6 +36,29 @@
private val targetContext get() = ApplicationProvider.getApplicationContext() as Context
+ val nav_simple_route_graph =
+ createNavController().createGraph(route = "nav_root", startDestination = "start_test") {
+ test("start_test")
+ test("start_test_with_default_arg") {
+ argument("defaultArg") { defaultValue = true }
+ }
+ test("second_test") {
+ argument("arg2") { type = NavType.StringType }
+ argument("defaultArg") {
+ type = NavType.StringType
+ defaultValue = "defaultValue"
+ }
+ deepLink {
+ uriPattern = "android-app://androidx.navigation.test/test"
+ action = "test.action"
+ mimeType = "type/test"
+ }
+ deepLink {
+ uriPattern = "android-app://androidx.navigation.test/test/{arg1}/{arg2}"
+ }
+ }
+ }
+
@Test
fun fromContextSetGraphXml() {
val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
@@ -47,6 +70,16 @@
}
@Test
+ fun fromContextSetGraphXmlRoute() {
+ val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+ deepLinkBuilder.setGraph(nav_simple_route_graph)
+ deepLinkBuilder.setDestination("second_test")
+ val taskStackBuilder = deepLinkBuilder.createTaskStackBuilder()
+ assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
+ }
+
+ @Test
fun fromContextSetGraphNavInflater() {
val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
@@ -63,6 +96,16 @@
}
@Test
+ fun fromContextSetGraphNavInflaterRoute() {
+ val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+ deepLinkBuilder.setGraph(nav_simple_route_graph)
+ deepLinkBuilder.setDestination("second_test")
+ val taskStackBuilder = deepLinkBuilder.createTaskStackBuilder()
+ assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
+ }
+
+ @Test
fun fromContextSetGraphProgrammatic() {
val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
@@ -79,6 +122,25 @@
assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
}
+ @Test
+ fun fromContextSetGraphProgrammaticRoute() {
+ val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+ val navigatorProvider = NavigatorProvider().apply {
+ addNavigator(NavGraphNavigator(this))
+ addNavigator(TestNavigator())
+ }
+ val navGraph = navigatorProvider.navigation(
+ route = "graph", startDestination = "test"
+ ) {
+ test("test")
+ }
+ deepLinkBuilder.setGraph(navGraph)
+ deepLinkBuilder.setDestination("test")
+ val taskStackBuilder = deepLinkBuilder.createTaskStackBuilder()
+ assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
+ }
+
@UiThreadTest
@Test
fun fromNavController() {
@@ -93,6 +155,20 @@
assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
}
+ @UiThreadTest
+ @Test
+ fun fromNavControllerRoute() {
+ val navController = NavController(targetContext).apply {
+ navigatorProvider.addNavigator(TestNavigator())
+ graph = nav_simple_route_graph
+ }
+ val deepLinkBuilder = NavDeepLinkBuilder(navController)
+
+ deepLinkBuilder.setDestination("second_test")
+ val taskStackBuilder = deepLinkBuilder.createTaskStackBuilder()
+ assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
+ }
+
@Test
fun pendingIntentEqualsWithSameArgs() {
val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
@@ -113,6 +189,25 @@
}
@Test
+ fun pendingIntentEqualsWithSameArgsRoute() {
+ val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+ deepLinkBuilder.setGraph(nav_simple_route_graph)
+ deepLinkBuilder.setDestination("second_test")
+ val args = Bundle().apply {
+ putString("test", "test")
+ }
+ deepLinkBuilder.setArguments(args)
+ val firstPendingIntent = deepLinkBuilder.createPendingIntent()
+
+ // Don't change anything and generate a new PendingIntent
+ val secondPendingIntent = deepLinkBuilder.createPendingIntent()
+ assertWithMessage("PendingIntents with the same destination and args should be the same")
+ .that(firstPendingIntent)
+ .isEqualTo(secondPendingIntent)
+ }
+
+ @Test
fun pendingIntentNotEqualsWithDifferentDestination() {
val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
@@ -133,6 +228,26 @@
}
@Test
+ fun pendingIntentNotEqualsWithDifferentDestinationRoute() {
+ val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+ deepLinkBuilder.setGraph(nav_simple_route_graph)
+ deepLinkBuilder.setDestination("second_test")
+ val args = Bundle().apply {
+ putString("test", "test")
+ }
+ deepLinkBuilder.setArguments(args)
+ val firstPendingIntent = deepLinkBuilder.createPendingIntent()
+
+ // Change the destination but not the args
+ deepLinkBuilder.setDestination("start_test")
+ val secondPendingIntent = deepLinkBuilder.createPendingIntent()
+ assertWithMessage("PendingIntents with different destinations should be different")
+ .that(firstPendingIntent)
+ .isNotEqualTo(secondPendingIntent)
+ }
+
+ @Test
fun pendingIntentNotEqualsWithDifferentArgs() {
val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
@@ -153,6 +268,26 @@
}
@Test
+ fun pendingIntentNotEqualsWithDifferentArgsRoute() {
+ val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+ deepLinkBuilder.setGraph(nav_simple_route_graph)
+ deepLinkBuilder.setDestination("second_test")
+ val args = Bundle().apply {
+ putString("test", "test")
+ }
+ deepLinkBuilder.setArguments(args)
+ val firstPendingIntent = deepLinkBuilder.createPendingIntent()
+
+ // Change the args but not the destination
+ args.putString("test", "test2")
+ val secondPendingIntent = deepLinkBuilder.createPendingIntent()
+ assertWithMessage("PendingIntents with different arguments should be different")
+ .that(firstPendingIntent)
+ .isNotEqualTo(secondPendingIntent)
+ }
+
+ @Test
fun pendingIntentNotEqualsWithDifferentDestinationArgs() {
val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
@@ -172,4 +307,32 @@
.that(firstPendingIntent)
.isNotEqualTo(secondPendingIntent)
}
+
+ @Test
+ fun pendingIntentNotEqualsWithDifferentDestinationArgsRoute() {
+ val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+ deepLinkBuilder.setGraph(nav_simple_route_graph)
+ val args = Bundle().apply {
+ putString("test", "test")
+ }
+ deepLinkBuilder.setDestination("second_test", args)
+ val firstPendingIntent = deepLinkBuilder.createPendingIntent()
+
+ // Change the args but not the destination
+ args.putString("test", "test2")
+ val secondPendingIntent = deepLinkBuilder.createPendingIntent()
+ assertWithMessage(
+ "PendingIntents with different destination arguments should be different"
+ )
+ .that(firstPendingIntent)
+ .isNotEqualTo(secondPendingIntent)
+ }
+
+ private fun createNavController(): NavController {
+ val navController = NavController(ApplicationProvider.getApplicationContext())
+ val navigator = TestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+ return navController
+ }
}
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavInflaterTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavInflaterTest.kt
index 8f6c3a5..9f25763 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavInflaterTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavInflaterTest.kt
@@ -48,7 +48,7 @@
val graph = navInflater.inflate(R.navigation.nav_simple)
assertThat(graph).isNotNull()
- assertThat(graph.startDestination)
+ assertThat(graph.startDestinationId)
.isEqualTo(R.id.start_test)
}
@@ -264,7 +264,7 @@
val context = ApplicationProvider.getApplicationContext() as Context
val navInflater = NavInflater(context, TestNavigatorProvider())
val graph = navInflater.inflate(R.navigation.nav_default_arguments)
- val startDestination = graph.findNode(graph.startDestination)
+ val startDestination = graph.findNode(graph.startDestinationId)
val action = startDestination?.getAction(R.id.my_action)
assertThat(action?.defaultArguments?.get("test_action_arg"))
.isEqualTo(123L)
@@ -275,7 +275,7 @@
val navInflater = NavInflater(context, TestNavigatorProvider())
val graph = navInflater.inflate(R.navigation.nav_default_arguments)
- val startDestination = graph.findNode(graph.startDestination)
+ val startDestination = graph.findNode(graph.startDestinationId)
val defaultArguments = startDestination?.arguments
assertThat(defaultArguments).isNotNull()
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.kt
index 212c62b..0caf28a 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.kt
@@ -21,17 +21,16 @@
import android.os.Parcelable
import android.os.Parcel
import androidx.lifecycle.LifecycleOwner
-import java.util.UUID
@SuppressLint("BanParcelableUsage")
internal class NavBackStackEntryState : Parcelable {
- val uuid: UUID
+ val id: String
val destinationId: Int
val args: Bundle?
val savedState: Bundle
constructor(entry: NavBackStackEntry) {
- uuid = entry.id
+ id = entry.id
destinationId = entry.destination.id
args = entry.arguments
savedState = Bundle()
@@ -39,7 +38,7 @@
}
constructor(inParcel: Parcel) {
- uuid = UUID.fromString(inParcel.readString())
+ id = inParcel.readString()!!
destinationId = inParcel.readInt()
args = inParcel.readBundle(javaClass.classLoader)
savedState = inParcel.readBundle(javaClass.classLoader)!!
@@ -57,7 +56,7 @@
return NavBackStackEntry.create(
context, destination, args,
lifecycleOwner, viewModel,
- uuid, savedState
+ id, savedState
)
}
@@ -66,7 +65,7 @@
}
override fun writeToParcel(parcel: Parcel, i: Int) {
- parcel.writeString(uuid.toString())
+ parcel.writeString(id)
parcel.writeInt(destinationId)
parcel.writeBundle(args)
parcel.writeBundle(savedState)
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index a8c319b..054d302 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -31,17 +31,18 @@
import androidx.annotation.NavigationRes
import androidx.annotation.RestrictTo
import androidx.core.app.TaskStackBuilder
+import androidx.core.net.toUri
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
+import androidx.navigation.NavDestination.Companion.createRoute
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
-import java.util.UUID
/**
* NavController manages app navigation within a [NavHost].
@@ -108,8 +109,8 @@
*/
@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public open val backQueue: ArrayDeque<NavBackStackEntry> = ArrayDeque()
- private val backStackMap = mutableMapOf<Int, UUID?>()
- private val backStackStates = mutableMapOf<UUID, ArrayDeque<NavBackStackEntryState>>()
+ private val backStackMap = mutableMapOf<Int, String?>()
+ private val backStackStates = mutableMapOf<String, ArrayDeque<NavBackStackEntryState>>()
private var lifecycleOwner: LifecycleOwner? = null
private var viewModel: NavControllerViewModel? = null
private val onDestinationChangedListeners =
@@ -388,6 +389,28 @@
}
/**
+ * Attempts to pop the controller's back stack back to a specific destination.
+ *
+ * @param route The topmost destination to retain
+ * @param inclusive Whether the given destination should also be popped.
+ * @param saveState Whether the back stack and the state of all destinations between the
+ * current destination and the [route] should be saved for later
+ * restoration via [NavOptions.Builder.setRestoreState] or the `restoreState` attribute using
+ * the same [route] (note: this matching ID is true whether
+ * [inclusive] is true or false).
+ *
+ * @return true if the stack was popped at least once and the user has been navigated to
+ * another destination, false otherwise
+ */
+ @MainThread
+ @JvmOverloads
+ public fun popBackStack(
+ route: String,
+ inclusive: Boolean,
+ saveState: Boolean = false
+ ): Boolean = popBackStack(createRoute(route).hashCode(), inclusive, saveState)
+
+ /**
* Attempts to pop the controller's back stack back to a specific destination. This does
* **not** handle calling [dispatchOnDestinationChanged]
*
@@ -460,7 +483,7 @@
// saved state to the destination you've actually passed to popUpTo
// as well as its parents (if it is the start destination)
generateSequence(foundDestination) { destination ->
- if (destination.parent?.startDestination == destination.id) {
+ if (destination.parent?.startDestinationId == destination.id) {
destination.parent
} else {
null
@@ -469,7 +492,7 @@
// Only add the state if it doesn't already exist
!backStackMap.containsKey(destination.id)
}.forEach { destination ->
- backStackMap[destination.id] = savedState.firstOrNull()?.uuid
+ backStackMap[destination.id] = savedState.firstOrNull()?.id
}
}
if (savedState.isNotEmpty()) {
@@ -479,7 +502,7 @@
// as well as its parents (if it is the start destination)
val firstStateDestination = findDestination(firstState.destinationId)
generateSequence(firstStateDestination) { destination ->
- if (destination.parent?.startDestination == destination.id) {
+ if (destination.parent?.startDestinationId == destination.id) {
destination.parent
} else {
null
@@ -488,10 +511,10 @@
// Only add the state if it doesn't already exist
!backStackMap.containsKey(destination.id)
}.forEach { destination ->
- backStackMap[destination.id] = firstState.uuid
+ backStackMap[destination.id] = firstState.id
}
// And finally, store the actual state itself
- backStackStates[firstState.uuid] = savedState
+ backStackStates[firstState.id] = savedState
}
}
updateOnBackPressedCallbackEnabled()
@@ -546,7 +569,7 @@
var destId = currentDestination!!.id
var parent = currentDestination.parent
while (parent != null) {
- if (parent.startDestination != destId) {
+ if (parent.startDestinationId != destId) {
val args = Bundle()
if (activity != null && activity!!.intent != null) {
val data = activity!!.intent.data
@@ -955,8 +978,8 @@
graph = node
// Automatically go down the navigation graph when
// the start destination is also a NavGraph
- while (graph!!.findNode(graph.startDestination) is NavGraph) {
- graph = graph.findNode(graph.startDestination) as NavGraph?
+ while (graph!!.findNode(graph.startDestinationId) is NavGraph) {
+ graph = graph.findNode(graph.startDestinationId) as NavGraph?
}
}
} else {
@@ -1003,8 +1026,8 @@
graph = node
// Automatically go down the navigation graph when
// the start destination is also a NavGraph
- while (graph!!.findNode(graph.startDestination) is NavGraph) {
- graph = graph.findNode(graph.startDestination) as NavGraph?
+ while (graph!!.findNode(graph.startDestinationId) is NavGraph) {
+ graph = graph.findNode(graph.startDestinationId) as NavGraph?
}
}
}
@@ -1041,6 +1064,19 @@
return currentGraph.findNode(destinationId)
}
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public fun findDestination(destinationRoute: String): NavDestination? {
+ if (_graph == null) {
+ return null
+ }
+ if (_graph!!.route == destinationRoute) {
+ return _graph
+ }
+ val currentNode = backQueue.lastOrNull()?.destination ?: _graph!!
+ val currentGraph = if (currentNode is NavGraph) currentNode else currentNode.parent!!
+ return currentGraph.findNode(destinationRoute)
+ }
+
/**
* Navigate to a destination from the current navigation graph. This supports both navigating
* via an [action][NavDestination.getAction] and directly navigating to a destination.
@@ -1141,8 +1177,8 @@
}
combinedArgs.putAll(args)
}
- if (destId == 0 && finalNavOptions != null && finalNavOptions.popUpTo != -1) {
- popBackStack(finalNavOptions.popUpTo, finalNavOptions.isPopUpToInclusive())
+ if (destId == 0 && finalNavOptions != null && finalNavOptions.popUpToId != -1) {
+ popBackStack(finalNavOptions.popUpToId, finalNavOptions.isPopUpToInclusive())
return
}
require(destId != 0) {
@@ -1298,9 +1334,9 @@
var launchSingleTop = false
var navigated = false
if (navOptions != null) {
- if (navOptions.popUpTo != -1) {
+ if (navOptions.popUpToId != -1) {
popped = popBackStackInternal(
- navOptions.popUpTo,
+ navOptions.popUpToId,
navOptions.isPopUpToInclusive(),
navOptions.shouldPopUpToSaveState()
)
@@ -1321,10 +1357,10 @@
}
// Now determine what new destinations we need to add to the back stack
if (navOptions?.shouldRestoreState() == true && backStackMap.containsKey(node.id)) {
- val backStackUUID = backStackMap[node.id]
+ val backStackId = backStackMap[node.id]
// Clear out the state we're going to restore so that it isn't restored a second time
- backStackMap.values.removeAll { it == backStackUUID }
- val backStackState = backStackStates.remove(backStackUUID)
+ backStackMap.values.removeAll { it == backStackId }
+ val backStackState = backStackStates.remove(backStackId)
// Now restore the back stack from its saved state
val entries = instantiateBackStack(backStackState)
// Split up the entries by Navigator so we can restore them as an atomic operation
@@ -1541,6 +1577,41 @@
}
/**
+ * Navigate to a route in the current NavGraph. If an invalid route is given, an
+ * [IllegalArgumentException] will be thrown.
+ *
+ * @param route route for the destination
+ * @param builder DSL for constructing a new [NavOptions]
+ *
+ * @throws IllegalArgumentException if the given route is invalid
+ */
+ public fun navigate(route: String, builder: NavOptionsBuilder.() -> Unit) {
+ navigate(route, navOptions(builder))
+ }
+
+ /**
+ * Navigate to a route in the current NavGraph. If an invalid route is given, an
+ * [IllegalArgumentException] will be thrown.
+ *
+ * @param route route for the destination
+ * @param navOptions special options for this navigation operation
+ * @param navigatorExtras extras to pass to the [Navigator]
+ *
+ * @throws IllegalArgumentException if the given route is invalid
+ */
+ @JvmOverloads
+ public fun navigate(
+ route: String,
+ navOptions: NavOptions? = null,
+ navigatorExtras: Navigator.Extras? = null
+ ) {
+ navigate(
+ NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build(), navOptions,
+ navigatorExtras
+ )
+ }
+
+ /**
* Create a deep link to a destination within this NavController.
*
* @return a [NavDeepLinkBuilder] suitable for constructing a deep link
@@ -1590,30 +1661,30 @@
if (b == null) {
b = Bundle()
}
- val backStackIds = IntArray(backStackMap.size)
- val backStackUUIDs = ArrayList<String>()
+ val backStackDestIds = IntArray(backStackMap.size)
+ val backStackIds = ArrayList<String?>()
var index = 0
- for ((id, uuid) in backStackMap) {
- backStackIds[index++] = id
- backStackUUIDs += uuid.toString()
+ for ((destId, id) in backStackMap) {
+ backStackDestIds[index++] = destId
+ backStackIds += id
}
- b.putIntArray(KEY_BACK_STACK_IDS, backStackIds)
- b.putStringArrayList(KEY_BACK_STACK_UUIDS, backStackUUIDs)
+ b.putIntArray(KEY_BACK_STACK_DEST_IDS, backStackDestIds)
+ b.putStringArrayList(KEY_BACK_STACK_IDS, backStackIds)
}
if (backStackStates.isNotEmpty()) {
if (b == null) {
b = Bundle()
}
- val backStackStateUUIDs = ArrayList<String>()
- for ((uuid, backStackStates) in backStackStates) {
- backStackStateUUIDs += uuid.toString()
+ val backStackStateIds = ArrayList<String>()
+ for ((id, backStackStates) in backStackStates) {
+ backStackStateIds += id
val states = arrayOfNulls<Parcelable>(backStackStates.size)
backStackStates.forEachIndexed { stateIndex, backStackState ->
states[stateIndex] = backStackState
}
- b.putParcelableArray(KEY_BACK_STACK_STATES_PREFIX + uuid, states)
+ b.putParcelableArray(KEY_BACK_STACK_STATES_PREFIX + id, states)
}
- b.putStringArrayList(KEY_BACK_STACK_STATES_UUIDS, backStackStateUUIDs)
+ b.putStringArrayList(KEY_BACK_STACK_STATES_IDS, backStackStateIds)
}
if (deepLinkHandled) {
if (b == null) {
@@ -1642,18 +1713,18 @@
navigatorStateToRestore = navState.getBundle(KEY_NAVIGATOR_STATE)
backStackToRestore = navState.getParcelableArray(KEY_BACK_STACK)
backStackStates.clear()
- val backStackIds = navState.getIntArray(KEY_BACK_STACK_IDS)
- val backStackUUIDs = navState.getStringArrayList(KEY_BACK_STACK_UUIDS)
- if (backStackIds != null && backStackUUIDs != null) {
- backStackIds.forEachIndexed { index, id ->
- backStackMap[id] = UUID.fromString(backStackUUIDs[index])
+ val backStackDestIds = navState.getIntArray(KEY_BACK_STACK_DEST_IDS)
+ val backStackIds = navState.getStringArrayList(KEY_BACK_STACK_IDS)
+ if (backStackDestIds != null && backStackIds != null) {
+ backStackDestIds.forEachIndexed { index, id ->
+ backStackMap[id] = backStackIds[index]
}
}
- val backStackStateIds = navState.getStringArrayList(KEY_BACK_STACK_STATES_UUIDS)
- backStackStateIds?.map { UUID.fromString(it) }?.forEach { uuid ->
- val backStackState = navState.getParcelableArray(KEY_BACK_STACK_STATES_PREFIX + uuid)
+ val backStackStateIds = navState.getStringArrayList(KEY_BACK_STACK_STATES_IDS)
+ backStackStateIds?.forEach { id ->
+ val backStackState = navState.getParcelableArray(KEY_BACK_STACK_STATES_PREFIX + id)
if (backStackState != null) {
- backStackStates[uuid] = ArrayDeque<NavBackStackEntryState>(
+ backStackStates[id] = ArrayDeque<NavBackStackEntryState>(
backStackState.size
).apply {
for (parcelable in backStackState) {
@@ -1758,6 +1829,27 @@
}
/**
+ * Gets the topmost {@link NavBackStackEntry} for a route.
+ * <p>
+ * This is always safe to use with {@link #getCurrentDestination() the current destination} or
+ * {@link NavDestination#getParent() its parent} or grandparent navigation graphs as these
+ * destinations are guaranteed to be on the back stack.
+ *
+ * @param route route of a destination that exists on the back stack
+ * @throws IllegalArgumentException if the destination is not on the back stack
+ */
+ public fun getBackStackEntry(route: String): NavBackStackEntry {
+ val lastFromBackStack: NavBackStackEntry? = backQueue.lastOrNull { entry ->
+ entry.destination.route == route
+ }
+ requireNotNull(lastFromBackStack) {
+ "No destination with route $route is on the NavController's back stack. The " +
+ "current destination is $currentDestination"
+ }
+ return lastFromBackStack
+ }
+
+ /**
* Gets the topmost [NavBackStackEntry].
*
* @return the topmost entry on the back stack or null if the back stack is empty
@@ -1801,11 +1893,11 @@
private const val KEY_NAVIGATOR_STATE_NAMES =
"android-support-nav:controller:navigatorState:names"
private const val KEY_BACK_STACK = "android-support-nav:controller:backStack"
+ private const val KEY_BACK_STACK_DEST_IDS =
+ "android-support-nav:controller:backStackDestIds"
private const val KEY_BACK_STACK_IDS =
"android-support-nav:controller:backStackIds"
- private const val KEY_BACK_STACK_UUIDS =
- "android-support-nav:controller:backStackUUIDs"
- private const val KEY_BACK_STACK_STATES_UUIDS =
+ private const val KEY_BACK_STACK_STATES_IDS =
"android-support-nav:controller:backStackStates"
private const val KEY_BACK_STACK_STATES_PREFIX =
"android-support-nav:controller:backStackStates:"
@@ -1837,3 +1929,16 @@
@IdRes startDestination: Int,
builder: NavGraphBuilder.() -> Unit
): NavGraph = navigatorProvider.navigation(id, startDestination, builder)
+
+/**
+ * Construct a new [NavGraph]
+ *
+ * @param route the route for the graph
+ * @param startDestination the route for the start destination
+ * @param builder the builder used to construct the graph
+ */
+public fun NavController.createGraph(
+ startDestination: String,
+ route: String? = null,
+ builder: NavGraphBuilder.() -> Unit
+): NavGraph = navigatorProvider.navigation(startDestination, route, builder)
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt
index aee4d6b..4534b3a 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt
@@ -19,18 +19,17 @@
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.get
-import java.util.UUID
/**
* NavControllerViewModel is the always up to date view of the NavController's
* non configuration state
*/
internal class NavControllerViewModel : ViewModel(), NavViewModelStoreProvider {
- private val viewModelStores = mutableMapOf<UUID, ViewModelStore>()
+ private val viewModelStores = mutableMapOf<String, ViewModelStore>()
- fun clear(backStackEntryUUID: UUID) {
+ fun clear(backStackEntryId: String) {
// Clear and remove the NavGraph's ViewModelStore
- val viewModelStore = viewModelStores.remove(backStackEntryUUID)
+ val viewModelStore = viewModelStores.remove(backStackEntryId)
viewModelStore?.clear()
}
@@ -41,11 +40,11 @@
viewModelStores.clear()
}
- override fun getViewModelStore(backStackEntryUUID: UUID): ViewModelStore {
- var viewModelStore = viewModelStores[backStackEntryUUID]
+ override fun getViewModelStore(backStackEntryId: String): ViewModelStore {
+ var viewModelStore = viewModelStores[backStackEntryId]
if (viewModelStore == null) {
viewModelStore = ViewModelStore()
- viewModelStores[backStackEntryUUID] = viewModelStore
+ viewModelStores[backStackEntryId] = viewModelStore
}
return viewModelStore
}
@@ -54,7 +53,7 @@
val sb = StringBuilder("NavControllerViewModel{")
sb.append(Integer.toHexString(System.identityHashCode(this)))
sb.append("} ViewModelStores (")
- val viewModelStoreIterator: Iterator<UUID> = viewModelStores.keys.iterator()
+ val viewModelStoreIterator: Iterator<String> = viewModelStores.keys.iterator()
while (viewModelStoreIterator.hasNext()) {
sb.append(viewModelStoreIterator.next())
if (viewModelStoreIterator.hasNext()) {
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt
index 9229747..0d8bbb9 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt
@@ -24,6 +24,7 @@
import androidx.annotation.IdRes
import androidx.annotation.NavigationRes
import androidx.core.app.TaskStackBuilder
+import androidx.navigation.NavDestination.Companion.createRoute
/**
* Class used to construct deep links to a particular destination in a [NavGraph].
@@ -151,6 +152,26 @@
}
/**
+ * Sets the destination route to deep link to. Any destinations previous added via
+ * [.addDestination] are cleared, effectively resetting this object
+ * back to only this single destination.
+ *
+ * @param destRoute destination route to deep link to.
+ * @param args Arguments to pass to this destination and any synthetic back stack created
+ * due to this destination being added.
+ * @return this object for chaining
+ */
+ @JvmOverloads
+ public fun setDestination(destRoute: String, args: Bundle? = null): NavDeepLinkBuilder {
+ destinations.clear()
+ destinations.add(DeepLinkDestination(createRoute(destRoute).hashCode(), args))
+ if (graph != null) {
+ verifyAllDestinations()
+ }
+ return this
+ }
+
+ /**
* Add a new destination id to deep link to. This builds off any previous calls to this method
* or calls to [setDestination], building the minimal synthetic back stack of
* start destinations between the previous deep link destination and the newly added
@@ -170,6 +191,26 @@
return this
}
+ /**
+ * Add a new destination route to deep link to. This builds off any previous calls to this
+ * method or calls to [.setDestination], building the minimal synthetic back stack of
+ * start destinations between the previous deep link destination and the newly added
+ * deep link destination.
+ *
+ * @param route destination route to deep link to.
+ * @param args Arguments to pass to this destination and any synthetic back stack created
+ * due to this destination being added.
+ * @return this object for chaining
+ */
+ @JvmOverloads
+ public fun addDestination(route: String, args: Bundle? = null): NavDeepLinkBuilder {
+ destinations.add(DeepLinkDestination(createRoute(route).hashCode(), args))
+ if (graph != null) {
+ verifyAllDestinations()
+ }
+ return this
+ }
+
private fun findDestination(@IdRes destId: Int): NavDestination? {
val possibleDestinations = ArrayDeque<NavDestination>()
possibleDestinations.add(graph!!)
diff --git a/navigation/navigation-safe-args-generator/build.gradle b/navigation/navigation-safe-args-generator/build.gradle
index 82b41da..22d143a 100644
--- a/navigation/navigation-safe-args-generator/build.gradle
+++ b/navigation/navigation-safe-args-generator/build.gradle
@@ -58,8 +58,8 @@
it.classpath = files(classpath.minus(androidJar).plus(androidJar))
}
-tasks.findByName("compileKotlin").dependsOn(":navigation:navigation-common:jarDebug")
-tasks.findByName("compileKotlin").dependsOn(":lifecycle:lifecycle-viewmodel-savedstate:jarDebug")
+tasks.findByName("compileKotlin").dependsOn(":navigation:navigation-common:jarRelease")
+tasks.findByName("compileKotlin").dependsOn(":lifecycle:lifecycle-viewmodel-savedstate:jarRelease")
androidx {
name = "Android Navigation TypeSafe Arguments Generator"
diff --git a/navigation/navigation-safe-args-generator/lint-baseline.xml b/navigation/navigation-safe-args-generator/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/navigation/navigation-safe-args-generator/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml b/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-testing/api/current.txt b/navigation/navigation-testing/api/current.txt
index f9ce0d4..8fc24e8 100644
--- a/navigation/navigation-testing/api/current.txt
+++ b/navigation/navigation-testing/api/current.txt
@@ -6,12 +6,16 @@
method public java.util.List<androidx.navigation.NavBackStackEntry> getBackStack();
method public void setCurrentDestination(@IdRes int destId, optional android.os.Bundle args);
method public void setCurrentDestination(@IdRes int destId);
+ method public void setCurrentDestination(String destRoute, optional android.os.Bundle args);
+ method public void setCurrentDestination(String destRoute);
property public final java.util.List<androidx.navigation.NavBackStackEntry> backStack;
}
public final class TestNavigatorState extends androidx.navigation.NavigatorState {
+ ctor public TestNavigatorState(optional android.content.Context? context, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher);
ctor public TestNavigatorState(optional android.content.Context? context);
method public androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+ method public androidx.navigation.NavBackStackEntry restoreBackStackEntry(androidx.navigation.NavBackStackEntry previouslySavedEntry);
}
}
diff --git a/navigation/navigation-testing/api/public_plus_experimental_current.txt b/navigation/navigation-testing/api/public_plus_experimental_current.txt
index f9ce0d4..8fc24e8 100644
--- a/navigation/navigation-testing/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-testing/api/public_plus_experimental_current.txt
@@ -6,12 +6,16 @@
method public java.util.List<androidx.navigation.NavBackStackEntry> getBackStack();
method public void setCurrentDestination(@IdRes int destId, optional android.os.Bundle args);
method public void setCurrentDestination(@IdRes int destId);
+ method public void setCurrentDestination(String destRoute, optional android.os.Bundle args);
+ method public void setCurrentDestination(String destRoute);
property public final java.util.List<androidx.navigation.NavBackStackEntry> backStack;
}
public final class TestNavigatorState extends androidx.navigation.NavigatorState {
+ ctor public TestNavigatorState(optional android.content.Context? context, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher);
ctor public TestNavigatorState(optional android.content.Context? context);
method public androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+ method public androidx.navigation.NavBackStackEntry restoreBackStackEntry(androidx.navigation.NavBackStackEntry previouslySavedEntry);
}
}
diff --git a/navigation/navigation-testing/api/restricted_current.txt b/navigation/navigation-testing/api/restricted_current.txt
index f9ce0d4..8fc24e8 100644
--- a/navigation/navigation-testing/api/restricted_current.txt
+++ b/navigation/navigation-testing/api/restricted_current.txt
@@ -6,12 +6,16 @@
method public java.util.List<androidx.navigation.NavBackStackEntry> getBackStack();
method public void setCurrentDestination(@IdRes int destId, optional android.os.Bundle args);
method public void setCurrentDestination(@IdRes int destId);
+ method public void setCurrentDestination(String destRoute, optional android.os.Bundle args);
+ method public void setCurrentDestination(String destRoute);
property public final java.util.List<androidx.navigation.NavBackStackEntry> backStack;
}
public final class TestNavigatorState extends androidx.navigation.NavigatorState {
+ ctor public TestNavigatorState(optional android.content.Context? context, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher);
ctor public TestNavigatorState(optional android.content.Context? context);
method public androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+ method public androidx.navigation.NavBackStackEntry restoreBackStackEntry(androidx.navigation.NavBackStackEntry previouslySavedEntry);
}
}
diff --git a/navigation/navigation-testing/lint-baseline.xml b/navigation/navigation-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt b/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt
new file mode 100644
index 0000000..b20964e
--- /dev/null
+++ b/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.navigation.testing
+
+import androidx.lifecycle.Lifecycle
+import androidx.navigation.FloatingWindow
+import androidx.navigation.NavDestination
+import androidx.navigation.Navigator
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TestNavigatorStateTest {
+ private lateinit var state: TestNavigatorState
+
+ @Before
+ fun setUp() {
+ state = TestNavigatorState()
+ }
+
+ @Test
+ fun testLifecycle() {
+ val navigator = TestNavigator()
+ navigator.onAttach(state)
+ val firstEntry = state.createBackStackEntry(navigator.createDestination(), null)
+ assertThat(firstEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.INITIALIZED)
+
+ navigator.navigate(listOf(firstEntry), null, null)
+ assertThat(firstEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+
+ val secondEntry = state.createBackStackEntry(navigator.createDestination(), null)
+ navigator.navigate(listOf(secondEntry), null, null)
+ assertThat(firstEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.CREATED)
+ assertThat(secondEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+
+ navigator.popBackStack(secondEntry, false)
+ assertThat(firstEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+ assertThat(secondEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ @Test
+ fun testFloatingWindowLifecycle() {
+ val navigator = FloatingWindowTestNavigator()
+ navigator.onAttach(state)
+ val firstEntry = state.createBackStackEntry(navigator.createDestination(), null)
+ assertThat(firstEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.INITIALIZED)
+
+ navigator.navigate(listOf(firstEntry), null, null)
+ assertThat(firstEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+
+ val secondEntry = state.createBackStackEntry(navigator.createDestination(), null)
+ navigator.navigate(listOf(secondEntry), null, null)
+ assertThat(firstEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.STARTED)
+ assertThat(secondEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+
+ navigator.popBackStack(secondEntry, false)
+ assertThat(firstEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+ assertThat(secondEntry.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ @Navigator.Name("test")
+ internal class TestNavigator : Navigator<NavDestination>() {
+ override fun createDestination(): NavDestination = NavDestination(this)
+ }
+
+ @Navigator.Name("test")
+ internal class FloatingWindowTestNavigator : Navigator<FloatingTestDestination>() {
+ override fun createDestination(): FloatingTestDestination = FloatingTestDestination(this)
+ }
+
+ internal class FloatingTestDestination(
+ navigator: Navigator<out NavDestination>
+ ) : NavDestination(navigator), FloatingWindow
+}
\ No newline at end of file
diff --git a/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavHostController.kt b/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavHostController.kt
index ecf869a..8308c17 100644
--- a/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavHostController.kt
+++ b/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavHostController.kt
@@ -55,4 +55,24 @@
val intent = taskStackBuilder.editIntentAt(0)
require(handleDeepLink(intent)) { "Destination does not exist on the NavGraph." }
}
+
+ /**
+ * Navigate directly to any destination on the current [androidx.navigation.NavGraph] via an
+ * explicit deep link. If an implicit deep link exists for this destination use
+ * [#navigate(Uri)] instead.
+ *
+ * @param destRoute The destination route to navigate to.
+ * @param args The arguments to pass to the destination.
+ * @throws IllegalArgumentException If the [destination][destRoute] does not exist on the
+ * NavGraph.
+ */
+ @JvmOverloads
+ public fun setCurrentDestination(destRoute: String, args: Bundle = Bundle()) {
+ val taskStackBuilder = createDeepLink()
+ .setDestination(destRoute)
+ .setArguments(args)
+ .createTaskStackBuilder()
+ val intent = taskStackBuilder.editIntentAt(0)
+ require(handleDeepLink(intent)) { "Destination does not exist on the NavGraph." }
+ }
}
diff --git a/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavigatorState.kt b/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavigatorState.kt
index d91c6d2..9140e76 100644
--- a/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavigatorState.kt
+++ b/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavigatorState.kt
@@ -22,11 +22,15 @@
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.navigation.FloatingWindow
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
import androidx.navigation.NavViewModelStoreProvider
import androidx.navigation.NavigatorState
-import java.util.UUID
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
/**
* An implementation of [NavigatorState] that allows testing a
@@ -36,26 +40,100 @@
* An optional [context] can be provided to allow for the usages of
* [androidx.lifecycle.AndroidViewModel] within the created [NavBackStackEntry]
* instances.
+ *
+ * The [Lifecycle] of all [NavBackStackEntry] instances added to this TestNavigatorState
+ * will be updated as they are added and removed from the state. This work is kicked off
+ * on the [coroutineDispatcher].
*/
public class TestNavigatorState @JvmOverloads constructor(
- private val context: Context? = null
+ private val context: Context? = null,
+ private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.Main.immediate
) : NavigatorState() {
- private val lifecycleOwner: LifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED)
+ private val lifecycleOwner: LifecycleOwner = TestLifecycleOwner(
+ Lifecycle.State.RESUMED,
+ coroutineDispatcher
+ )
private val viewModelStoreProvider = object : NavViewModelStoreProvider {
- private val viewModelStores = mutableMapOf<UUID, ViewModelStore>()
+ private val viewModelStores = mutableMapOf<String, ViewModelStore>()
override fun getViewModelStore(
- backStackEntryUUID: UUID
- ) = viewModelStores.getOrPut(backStackEntryUUID) {
+ backStackEntryId: String
+ ) = viewModelStores.getOrPut(backStackEntryId) {
ViewModelStore()
}
}
+ private val savedStates = mutableMapOf<String, Bundle>()
+
override fun createBackStackEntry(
destination: NavDestination,
arguments: Bundle?
): NavBackStackEntry = NavBackStackEntry.create(
context, destination, arguments, lifecycleOwner, viewModelStoreProvider
)
+
+ /**
+ * Restore a previously saved [NavBackStackEntry]. You must have previously called
+ * [pop] with [previouslySavedEntry] and `true`.
+ */
+ public fun restoreBackStackEntry(previouslySavedEntry: NavBackStackEntry): NavBackStackEntry {
+ val savedState = checkNotNull(savedStates[previouslySavedEntry.id]) {
+ "restoreBackStackEntry(previouslySavedEntry) must be passed a NavBackStackEntry " +
+ "that was previously popped with popBackStack(previouslySavedEntry, true)"
+ }
+ return NavBackStackEntry.create(
+ context,
+ previouslySavedEntry.destination, previouslySavedEntry.arguments,
+ lifecycleOwner, viewModelStoreProvider,
+ previouslySavedEntry.id, savedState
+ )
+ }
+
+ override fun add(backStackEntry: NavBackStackEntry) {
+ super.add(backStackEntry)
+ updateMaxLifecycle()
+ }
+
+ override fun pop(popUpTo: NavBackStackEntry, saveState: Boolean) {
+ val beforePopList = backStack.value
+ val poppedList = beforePopList.subList(beforePopList.indexOf(popUpTo), beforePopList.size)
+ super.pop(popUpTo, saveState)
+ updateMaxLifecycle(poppedList, saveState)
+ }
+
+ private fun updateMaxLifecycle(
+ poppedList: List<NavBackStackEntry> = emptyList(),
+ saveState: Boolean = false
+ ) {
+ runBlocking(coroutineDispatcher) {
+ // NavBackStackEntry Lifecycles must be updated on the main thread
+ // as per the contract within Lifecycle, so we explicitly swap to the main thread
+ // no matter what CoroutineDispatcher was passed to us.
+ withContext(Dispatchers.Main.immediate) {
+ // Mark all removed NavBackStackEntries as DESTROYED
+ for (entry in poppedList.reversed()) {
+ if (saveState) {
+ // Move the NavBackStackEntry to the stopped state, then save its state
+ entry.maxLifecycle = Lifecycle.State.CREATED
+ val savedState = Bundle()
+ entry.saveState(savedState)
+ savedStates[entry.id] = savedState
+ }
+ entry.maxLifecycle = Lifecycle.State.DESTROYED
+ }
+ // Now go through the current list of destinations, updating their Lifecycle state
+ val currentList = backStack.value
+ var previousEntry: NavBackStackEntry? = null
+ for (entry in currentList.reversed()) {
+ entry.maxLifecycle = when {
+ previousEntry == null -> Lifecycle.State.RESUMED
+ previousEntry.destination is FloatingWindow -> Lifecycle.State.STARTED
+ else -> Lifecycle.State.CREATED
+ }
+ previousEntry = entry
+ }
+ }
+ }
+ }
}
diff --git a/navigation/navigation-ui-ktx/lint-baseline.xml b/navigation/navigation-ui-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-ui-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-ui/build.gradle b/navigation/navigation-ui/build.gradle
index 446191d..ab64177 100644
--- a/navigation/navigation-ui/build.gradle
+++ b/navigation/navigation-ui/build.gradle
@@ -30,6 +30,9 @@
buildTypes.all {
consumerProguardFiles "proguard-rules.pro"
}
+ defaultConfig {
+ multiDexEnabled true
+ }
}
dependencies {
@@ -47,6 +50,7 @@
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(TRUTH)
+ androidTestImplementation(MULTIDEX)
}
androidx {
diff --git a/navigation/navigation-ui/lint-baseline.xml b/navigation/navigation-ui/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-ui/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt
index e7e5f9c..e385658 100644
--- a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt
+++ b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt
@@ -62,7 +62,7 @@
*/
@JvmStatic
public fun onNavDestinationSelected(item: MenuItem, navController: NavController): Boolean {
- val builder = NavOptions.Builder().setLaunchSingleTop(true)
+ val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true)
if (
navController.currentDestination!!.parent!!.findNode(item.itemId)
is ActivityNavigator.Destination
@@ -78,7 +78,11 @@
.setPopExitAnim(R.animator.nav_default_pop_exit_anim)
}
if (item.order and Menu.CATEGORY_SECONDARY == 0) {
- builder.setPopUpTo(findStartDestination(navController.graph).id, false)
+ builder.setPopUpTo(
+ findStartDestination(navController.graph).id,
+ inclusive = false,
+ saveState = true
+ )
}
val options = builder.build()
return try {
@@ -518,9 +522,9 @@
@JvmStatic
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun findStartDestination(graph: NavGraph): NavDestination =
- generateSequence(graph.findNode(graph.startDestination)) {
+ generateSequence(graph.findNode(graph.startDestinationId)) {
if (it is NavGraph) {
- it.findNode(it.startDestination)
+ it.findNode(it.startDestinationId)
} else {
null
}
diff --git a/paging/common/ktx/lint-baseline.xml b/paging/common/ktx/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/paging/common/ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
index 500558c..76ad766 100644
--- a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
@@ -93,7 +93,7 @@
* can differentiate unloaded placeholder items from content that has been paged in.
*
* @param Key Unique identifier for item loaded from DataSource. Often an integer to represent
- * position in data set. Note - this is distinct from e.g. Room's `<Value> Value type
+ * position in data set. Note - this is distinct from e.g. Room's `<Value>` Value type
* loaded by the DataSource.
*/
public abstract class DataSource<Key : Any, Value : Any>
diff --git a/paging/guava/lint-baseline.xml b/paging/guava/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/guava/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/paging-compose/build.gradle b/paging/paging-compose/build.gradle
index a481bd4..669f393d3 100644
--- a/paging/paging-compose/build.gradle
+++ b/paging/paging-compose/build.gradle
@@ -32,7 +32,7 @@
implementation(libs.kotlinStdlib)
api(projectOrArtifact(":compose:foundation:foundation"))
- api(projectOrArtifact(":paging:paging-common"))
+ api("androidx.paging:paging-common:3.0.0")
androidTestImplementation(projectOrArtifact(":compose:ui:ui-test-junit4"))
androidTestImplementation(project(":compose:test-utils"))
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
index 0df5445..7a4302d 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
@@ -48,10 +48,12 @@
* This instance can be used by the [items] and [itemsIndexed] methods inside [LazyListScope] to
* display data received from the [Flow] of [PagingData].
*
- * @param flow the [Flow] object which contains a stream of [PagingData] elements.
* @param T the type of value used by [PagingData].
*/
public class LazyPagingItems<T : Any> internal constructor(
+ /**
+ * the [Flow] object which contains a stream of [PagingData] elements.
+ */
private val flow: Flow<PagingData<T>>
) {
private val mainDispatcher = Dispatchers.Main
diff --git a/paging/runtime/ktx/lint-baseline.xml b/paging/runtime/ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/runtime/ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/rxjava2/ktx/lint-baseline.xml b/paging/rxjava2/ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/rxjava2/ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/rxjava2/lint-baseline.xml b/paging/rxjava2/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/rxjava2/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/rxjava3/lint-baseline.xml b/paging/rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/samples/lint-baseline.xml b/paging/samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/palette/palette-ktx/lint-baseline.xml b/palette/palette-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/palette/palette-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/palette/palette/lint-baseline.xml b/palette/palette/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/palette/palette/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/preference/preference-ktx/lint-baseline.xml b/preference/preference-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/preference/preference-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/recyclerview/recyclerview-benchmark/lint-baseline.xml b/recyclerview/recyclerview-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/recyclerview/recyclerview-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/recyclerview/recyclerview-lint/lint-baseline.xml b/recyclerview/recyclerview-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/recyclerview/recyclerview-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
index 7938e7c..1f308a1 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
@@ -2693,6 +2693,7 @@
});
}
+ @FlakyTest(bugId = 187331322)
@Test
public void smoothScrollBy_durationOf1_completesAsynchronously() throws Throwable {
smoothScrollBy_completesAsynchronously(1);
diff --git a/resourceinspection/resourceinspection-processor/lint-baseline.xml b/resourceinspection/resourceinspection-processor/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/resourceinspection/resourceinspection-processor/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/benchmark/lint-baseline.xml b/room/benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/compiler-processing-testing/lint-baseline.xml b/room/compiler-processing-testing/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/room/compiler-processing-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/compiler-processing/lint-baseline.xml b/room/compiler-processing/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/room/compiler-processing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
index 075f0ea..7a93f35 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
@@ -23,6 +23,7 @@
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
import com.google.devtools.ksp.symbol.KSTypeArgument
import com.google.devtools.ksp.symbol.KSTypeParameter
import com.google.devtools.ksp.symbol.KSTypeReference
@@ -56,6 +57,9 @@
*/
@OptIn(KspExperimental::class)
internal fun KSDeclaration.typeName(resolver: Resolver): TypeName {
+ if (this is KSTypeAlias) {
+ return this.type.typeName(resolver)
+ }
// if there is no qualified name, it is a resolution error so just return shared instance
// KSP may improve that later and if not, we can improve it in Room
// TODO: https://issuetracker.google.com/issues/168639183
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index e07b338..febd6cc 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -28,6 +28,7 @@
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
import com.google.devtools.ksp.symbol.KSTypeArgument
import com.google.devtools.ksp.symbol.KSTypeParameter
import com.google.devtools.ksp.symbol.KSTypeReference
@@ -169,8 +170,20 @@
* decision.
*/
fun wrap(ksType: KSType, allowPrimitives: Boolean): KspType {
- val qName = ksType.declaration.qualifiedName?.asString()
val declaration = ksType.declaration
+ if (declaration is KSTypeAlias) {
+ val actual = wrap(
+ ksType = declaration.type.resolve(),
+ allowPrimitives = allowPrimitives && ksType.nullability == Nullability.NOT_NULL
+ )
+ // if this type is nullable, carry it over
+ return if (ksType.nullability == Nullability.NULLABLE) {
+ actual.makeNullable()
+ } else {
+ actual
+ }
+ }
+ val qName = ksType.declaration.qualifiedName?.asString()
if (declaration is KSTypeParameter) {
return KspTypeArgumentType(
env = this,
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
index 7495463..91c26a8 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
@@ -54,7 +54,7 @@
return this
}
- override fun extendsBound(): XType? {
+ override fun extendsBound(): XType {
return _extendsBound
}
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/TypeAliasTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/TypeAliasTest.kt
new file mode 100644
index 0000000..8b864c2
--- /dev/null
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/TypeAliasTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.room.compiler.processing
+
+import androidx.room.compiler.processing.util.CONTINUATION_CLASS_NAME
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.className
+import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.getField
+import androidx.room.compiler.processing.util.getMethod
+import androidx.room.compiler.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.WildcardTypeName
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class TypeAliasTest {
+ @Test
+ fun kotlinTypeAlias() {
+ fun produceSource(pkg: String) = Source.kotlin(
+ "$pkg/Foo.kt",
+ """
+ package $pkg
+ typealias MyLong = Long
+ class Subject {
+ var prop: MyLong = 1L
+ val nullable: MyLong? = null
+ val inGeneric : List<MyLong> = TODO()
+ suspend fun suspendFun() : MyLong = TODO()
+ }
+ """.trimIndent()
+ )
+ val lib = compileFiles(listOf(produceSource("lib")))
+ runProcessorTest(
+ sources = listOf(produceSource("app")),
+ classpath = listOf(lib)
+ ) { invocation ->
+ listOf("lib", "app").forEach { pkg ->
+ val elm = invocation.processingEnv.requireTypeElement("$pkg.Subject")
+ elm.getField("prop").type.let {
+ assertThat(it.nullability).isEqualTo(XNullability.NONNULL)
+ assertThat(it.typeName).isEqualTo(TypeName.LONG)
+ }
+ elm.getField("nullable").type.let {
+ assertThat(it.nullability).isEqualTo(XNullability.NULLABLE)
+ assertThat(it.typeName).isEqualTo(TypeName.LONG.box())
+ }
+ elm.getField("inGeneric").type.let {
+ assertThat(it.nullability).isEqualTo(XNullability.NONNULL)
+ assertThat(it.typeName).isEqualTo(
+ ParameterizedTypeName.get(
+ List::class.className(),
+ TypeName.LONG.box()
+ )
+ )
+ }
+ elm.getMethod("suspendFun").parameters.last().type.let {
+ assertThat(it.typeName).isEqualTo(
+ ParameterizedTypeName.get(
+ CONTINUATION_CLASS_NAME,
+ WildcardTypeName.supertypeOf(TypeName.LONG.box())
+ )
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/build.gradle b/room/compiler/build.gradle
index f5e3073..26115ea 100644
--- a/room/compiler/build.gradle
+++ b/room/compiler/build.gradle
@@ -242,8 +242,8 @@
tasks.findByName("compileKotlin").dependsOn(generateAntlrTask)
tasks.findByName("sourceJar").dependsOn(generateAntlrTask)
-tasks.findByName("compileKotlin").dependsOn(":room:room-runtime:jarDebug")
-tasks.findByName("compileKotlin").dependsOn(":sqlite:sqlite:jarDebug")
+tasks.findByName("compileKotlin").dependsOn(":room:room-runtime:jarRelease")
+tasks.findByName("compileKotlin").dependsOn(":sqlite:sqlite:jarRelease")
tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
diff --git a/room/compiler/lint-baseline.xml b/room/compiler/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/room/compiler/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
index 4b2c808..79f15d2 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
@@ -20,8 +20,10 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.compileFiles
import androidx.room.compiler.processing.util.getSystemClasspathFiles
+import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.parser.SQLTypeAffinity
import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
+import androidx.room.testing.context
import androidx.room.vo.CallType
import androidx.room.vo.Field
import androidx.room.vo.FieldGetter
@@ -2466,4 +2468,29 @@
}
}
}
+
+ @Test
+ fun typeAlias() {
+ val src = Source.kotlin(
+ "Entity.kt",
+ """
+ import androidx.room.*;
+
+ typealias MyLong = Long
+ @Entity(tableName = "par_table")
+ data class Subject(@PrimaryKey @ColumnInfo(name = "my_long") val myLong: MyLong)
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(src)
+ ) { invocation ->
+ val parser = TableEntityProcessor(
+ invocation.context,
+ invocation.processingEnv.requireTypeElement("Subject")
+ )
+ val parsed = parser.process()
+ val field = parsed.primaryKey.fields.first()
+ assertThat(field.typeName).isEqualTo(TypeName.LONG)
+ }
+ }
}
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
index 4e2dcb4..8869c08 100644
--- a/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -46,10 +46,12 @@
import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureInsertMethodBinderProvider
import androidx.room.solver.shortcut.binderprovider.RxCallableDeleteOrUpdateMethodBinderProvider
import androidx.room.solver.shortcut.binderprovider.RxCallableInsertMethodBinderProvider
+import androidx.room.solver.types.BoxedPrimitiveColumnTypeAdapter
import androidx.room.solver.types.CompositeAdapter
import androidx.room.solver.types.CompositeTypeConverter
import androidx.room.solver.types.CustomTypeConverterWrapper
import androidx.room.solver.types.EnumColumnTypeAdapter
+import androidx.room.solver.types.PrimitiveColumnTypeAdapter
import androidx.room.solver.types.TypeConverter
import androidx.room.testing.context
import com.google.common.truth.Truth.assertThat
@@ -877,6 +879,93 @@
}
}
+ @Test
+ fun typeAliases() {
+ val source = Source.kotlin(
+ "Foo.kt",
+ """
+ import androidx.room.*
+ typealias MyLongAlias = Long
+ typealias MyNullableLongAlias = Long?
+
+ data class MyClass(val foo:String)
+ typealias MyClassAlias = MyClass
+ typealias MyClassNullableAlias = MyClass?
+
+ object MyConverters {
+ @TypeConverter
+ fun myClassToString(myClass : MyClass): String = TODO()
+ @TypeConverter
+ fun nullableMyClassToString(myClass : MyClass?): String? = TODO()
+ }
+ class Subject {
+ val myLongAlias : MyLongAlias = TODO()
+ val myLongAlias_nullable : MyLongAlias? = TODO()
+ val myNullableLongAlias : MyNullableLongAlias = TODO()
+ val myNullableLongAlias_nullable : MyNullableLongAlias? = TODO()
+ val myClass : MyClass = TODO()
+ val myClassAlias : MyClassAlias = TODO()
+ val myClassAlias_nullable : MyClassAlias? = TODO()
+ val myClassNullableAlias : MyClassNullableAlias = TODO()
+ val myClassNullableAlias_nullable : MyClassNullableAlias = TODO()
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(source)
+ ) { invocation ->
+ val converters = CustomConverterProcessor(
+ context = invocation.context,
+ element = invocation.processingEnv.requireTypeElement("MyConverters")
+ ).process().map(::CustomTypeConverterWrapper)
+ val typeAdapterStore = TypeAdapterStore.create(
+ context = invocation.context,
+ extras = converters.toTypedArray()
+ )
+ val subject = invocation.processingEnv.requireTypeElement("Subject")
+ val results = subject.getAllFieldsIncludingPrivateSupers().associate { field ->
+ val binder = typeAdapterStore.findStatementValueBinder(
+ input = field.type,
+ affinity = null
+ )
+
+ val signature = when (binder) {
+ null -> null
+ is PrimitiveColumnTypeAdapter -> "primitive"
+ is BoxedPrimitiveColumnTypeAdapter -> "boxed"
+ is CompositeAdapter -> {
+ when (val converter = binder.intoStatementConverter) {
+ null -> "composite null"
+ is CustomTypeConverterWrapper -> converter.custom.methodName
+ else -> "composite unknown"
+ }
+ }
+ else -> "unknown"
+ }
+ field.name to signature
+ }
+ // see: 187359339. We use nullability for assignments only in KSP
+ val nullableClassAdapter = if (invocation.isKsp) {
+ "nullableMyClassToString"
+ } else {
+ "myClassToString"
+ }
+ assertThat(results).containsExactlyEntriesIn(
+ mapOf(
+ "myLongAlias" to "primitive",
+ "myLongAlias_nullable" to "boxed",
+ "myNullableLongAlias" to "boxed",
+ "myNullableLongAlias_nullable" to "boxed",
+ "myClass" to "myClassToString",
+ "myClassAlias" to "myClassToString",
+ "myClassAlias_nullable" to nullableClassAdapter,
+ "myClassNullableAlias" to nullableClassAdapter,
+ "myClassNullableAlias_nullable" to nullableClassAdapter,
+ )
+ )
+ }
+ }
+
private fun createIntListToStringBinders(invocation: XTestInvocation): List<TypeConverter> {
val intType = invocation.processingEnv.requireType(Integer::class)
val listElement = invocation.processingEnv.requireTypeElement(java.util.List::class)
diff --git a/room/integration-tests/autovaluetestapp/lint-baseline.xml b/room/integration-tests/autovaluetestapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/integration-tests/autovaluetestapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/integration-tests/incremental-annotation-processing/lint-baseline.xml b/room/integration-tests/incremental-annotation-processing/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/room/integration-tests/incremental-annotation-processing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/integration-tests/kotlintestapp/lint-baseline.xml b/room/integration-tests/kotlintestapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/integration-tests/kotlintestapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/integration-tests/noappcompattestapp/lint-baseline.xml b/room/integration-tests/noappcompattestapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/integration-tests/noappcompattestapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/rxjava3/lint-baseline.xml b/room/rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/savedstate/savedstate-ktx/lint-baseline.xml b/savedstate/savedstate-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/savedstate/savedstate-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/savedstate/savedstate/lint-baseline.xml b/savedstate/savedstate/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/savedstate/savedstate/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/security/identity-credential/lint-baseline.xml b/security/identity-credential/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/security/identity-credential/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/security/security-app-authenticator/lint-baseline.xml b/security/security-app-authenticator/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/security/security-app-authenticator/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/security/security-biometric/lint-baseline.xml b/security/security-biometric/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/security/security-biometric/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/security/security-crypto-ktx/lint-baseline.xml b/security/security-crypto-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/security/security-crypto-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/settings.gradle b/settings.gradle
index b657ebe..48fb65e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -384,6 +384,10 @@
includeProject(":emoji2:emoji2-views", "emoji2/emoji2-views", [BuildType.MAIN])
includeProject(":emoji2:emoji2-views-helper", "emoji2/emoji2-views-helper", [BuildType.MAIN])
includeProject(":emoji2:emoji2-benchmark", "emoji2/emoji2-benchmark", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-disabled-macrobenchmark", "emoji2/integration-tests/init-disabled-macrobenchmark", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-disabled-macrobenchmark-target", "emoji2/integration-tests/init-disabled-macrobenchmark-target", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-enabled-macrobenchmark", "emoji2/integration-tests/init-enabled-macrobenchmark", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-enabled-macrobenchmark-target", "emoji2/integration-tests/init-enabled-macrobenchmark-target", [BuildType.MAIN])
includeProject(":enterprise-feedback", "enterprise/feedback", [BuildType.MAIN])
includeProject(":enterprise-feedback-testing", "enterprise/feedback/testing", [BuildType.MAIN])
includeProject(":exifinterface:exifinterface", "exifinterface/exifinterface", [BuildType.MAIN])
@@ -396,6 +400,7 @@
includeProject(":fragment:fragment-truth", "fragment/fragment-truth", [BuildType.MAIN, BuildType.FLAN])
includeProject(":fragment:integration-tests:testapp", "fragment/integration-tests/testapp", [BuildType.MAIN, BuildType.FLAN])
includeProject(":gridlayout:gridlayout", "gridlayout/gridlayout", [BuildType.MAIN])
+includeProject(":health:health-services-client", "health/health-services-client", [BuildType.MAIN])
includeProject(":heifwriter:heifwriter", "heifwriter/heifwriter", [BuildType.MAIN])
includeProject(":hilt:hilt-common", "hilt/hilt-common", [BuildType.MAIN])
includeProject(":hilt:hilt-compiler", "hilt/hilt-compiler", [BuildType.MAIN])
@@ -665,6 +670,7 @@
includeProject(":internal-testutils-espresso", "testutils/testutils-espresso", [BuildType.MAIN])
includeProject(":internal-testutils-truth", "testutils/testutils-truth", [BuildType.MAIN, BuildType.FLAN])
includeProject(":internal-testutils-ktx", "testutils/testutils-ktx", [BuildType.MAIN, BuildType.COMPOSE])
+includeProject(":internal-testutils-macrobenchmark", "testutils/testutils-macrobenchmark", [BuildType.MAIN, BuildType.COMPOSE])
includeProject(":internal-testutils-navigation", "testutils/testutils-navigation", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
includeProject(":internal-testutils-paging", "testutils/testutils-paging", [BuildType.MAIN, BuildType.COMPOSE])
includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin", [BuildType.MAIN, BuildType.FLAN])
diff --git a/slices/benchmark/lint-baseline.xml b/slices/benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/slices/benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/slices/builders/ktx/lint-baseline.xml b/slices/builders/ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/slices/builders/ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/slices/core/src/main/res/values-af/strings.xml b/slices/core/src/main/res/values-af/strings.xml
index 1619490..c6a4763 100644
--- a/slices/core/src/main/res/values-af/strings.xml
+++ b/slices/core/src/main/res/values-af/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Laat <xliff:g id="APP_0">%1$s</xliff:g> toe om <xliff:g id="APP_2">%2$s</xliff:g>-skyfies te wys?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Dit kan inligting in <xliff:g id="APP">%1$s</xliff:g> lees"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Dit kan handelinge binne <xliff:g id="APP">%1$s</xliff:g> uitvoer"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Laat <xliff:g id="APP">%1$s</xliff:g> toe om skyfies uit enige program te wys"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Laat toe"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Weier"</string>
</resources>
diff --git a/slices/core/src/main/res/values-am/strings.xml b/slices/core/src/main/res/values-am/strings.xml
index c187e0f..3fc492a 100644
--- a/slices/core/src/main/res/values-am/strings.xml
+++ b/slices/core/src/main/res/values-am/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን እንዲያሳይ ይፈቀድለት?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ከ<xliff:g id="APP">%1$s</xliff:g> የመጣ መረጃን ማንበብ ይችላል"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- በ<xliff:g id="APP">%1$s</xliff:g> ውስጥ እርምጃዎችን መውሰድ ይችላል"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ከማንኛውም መተግበሪያ የመጡ ቁራጮችን እንዲያሳይ ፍቀድለት"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ፍቀድ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ከልክል"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ar/strings.xml b/slices/core/src/main/res/values-ar/strings.xml
index b5de01c..2d76a8d 100644
--- a/slices/core/src/main/res/values-ar/strings.xml
+++ b/slices/core/src/main/res/values-ar/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"هل تريد السماح لتطبيق <xliff:g id="APP_0">%1$s</xliff:g> بعرض شرائح <xliff:g id="APP_2">%2$s</xliff:g>؟"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- إمكانية قراءة المعلومات من <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- إمكانية اتخاذ إجراءات داخل <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"السماح لتطبيق <xliff:g id="APP">%1$s</xliff:g> بعرض شرائح من أي تطبيق"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"سماح"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"رفض"</string>
</resources>
diff --git a/slices/core/src/main/res/values-as/strings.xml b/slices/core/src/main/res/values-as/strings.xml
index 95722b9..ea2a877 100644
--- a/slices/core/src/main/res/values-as/strings.xml
+++ b/slices/core/src/main/res/values-as/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>ক <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাবলৈ অনুমতি দিবনে?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ তথ্য পঢ়িব পাৰে"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ ভিতৰত কাৰ্য কৰিব পাৰে"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>ক যিকোনো এপৰ অংশ দেখুওৱাবলৈ অনুমতি দিয়ক"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"অনুমতি দিয়ক"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"অস্বীকাৰ কৰক"</string>
</resources>
diff --git a/slices/core/src/main/res/values-az/strings.xml b/slices/core/src/main/res/values-az/strings.xml
index ff7be30..2f1bce4 100644
--- a/slices/core/src/main/res/values-az/strings.xml
+++ b/slices/core/src/main/res/values-az/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> tətbiqinə <xliff:g id="APP_2">%2$s</xliff:g> hissələrini göstərmək üçün icazə verilsin?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> tətbiqindən məlumat oxuya bilər"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> daxilində əməliyyatlar edə bilər"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> tətbiqinə istənilən tətbiqdən hissə göstərmək icazəsi verin"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"İcazə verin"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rədd edin"</string>
</resources>
diff --git a/slices/core/src/main/res/values-b+sr+Latn/strings.xml b/slices/core/src/main/res/values-b+sr+Latn/strings.xml
index 95c1692c..3e20b9e 100644
--- a/slices/core/src/main/res/values-b+sr+Latn/strings.xml
+++ b/slices/core/src/main/res/values-b+sr+Latn/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Želite li da dozvolite aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Može da čita podatke iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Može da obavlja radnje u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dozvoli aplikaciji <xliff:g id="APP">%1$s</xliff:g> da prikazuje isečke iz bilo koje aplikacije"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dozvoli"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odbij"</string>
</resources>
diff --git a/slices/core/src/main/res/values-be/strings.xml b/slices/core/src/main/res/values-be/strings.xml
index 5fadc3f..84cd4a22 100644
--- a/slices/core/src/main/res/values-be/strings.xml
+++ b/slices/core/src/main/res/values-be/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Дазволіць праграме <xliff:g id="APP_0">%1$s</xliff:g> паказваць фрагменты праграмы <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Можа счытваць інфармацыю з праграмы <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Можа выконваць дзеянні ў праграме <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дазволіць праграме <xliff:g id="APP">%1$s</xliff:g> паказваць фрагменты іншых праграм"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дазволіць"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Адмовіць"</string>
</resources>
diff --git a/slices/core/src/main/res/values-bg/strings.xml b/slices/core/src/main/res/values-bg/strings.xml
index cb6f067..dcd2606 100644
--- a/slices/core/src/main/res/values-bg/strings.xml
+++ b/slices/core/src/main/res/values-bg/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Ще разрешите ли на <xliff:g id="APP_0">%1$s</xliff:g> да показва части от <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Може да чете информация от <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Може да предприема действия в/ъв <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Разрешаване на <xliff:g id="APP">%1$s</xliff:g> да показва части от което и да е приложение"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Разрешаване"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Отказ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-bn/strings.xml b/slices/core/src/main/res/values-bn/strings.xml
index cfa569d..4b46f9b 100644
--- a/slices/core/src/main/res/values-bn/strings.xml
+++ b/slices/core/src/main/res/values-bn/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটিকে <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখানোর অনুমতি দেবেন?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর তথ্য অ্যাক্সেস করতে পারবে"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর মধ্যে কাজ করতে পারবে"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> অ্যাপটিকে যেকোনও অ্যাপের অংশ দেখাতে দিন"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"অনুমতি দিন"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"খারিজ করুন"</string>
</resources>
diff --git a/slices/core/src/main/res/values-bs/strings.xml b/slices/core/src/main/res/values-bs/strings.xml
index 651e4d6..b203a79 100644
--- a/slices/core/src/main/res/values-bs/strings.xml
+++ b/slices/core/src/main/res/values-bs/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Dozvoliti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> prikazivanje isječaka aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Može čitati informacije iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Može poduzeti radnje u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dozvoli aplikaciji <xliff:g id="APP">%1$s</xliff:g> prikazivanje isječaka iz svake aplikacije"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dozvoli"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odbij"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ca/strings.xml b/slices/core/src/main/res/values-ca/strings.xml
index 0c953cf..ac93488 100644
--- a/slices/core/src/main/res/values-ca/strings.xml
+++ b/slices/core/src/main/res/values-ca/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vols permetre que <xliff:g id="APP_0">%1$s</xliff:g> mostri porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pot llegir informació de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pot dur a terme accions dins de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permet que <xliff:g id="APP">%1$s</xliff:g> mostri porcions de qualsevol aplicació"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permet"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Denega"</string>
</resources>
diff --git a/slices/core/src/main/res/values-cs/strings.xml b/slices/core/src/main/res/values-cs/strings.xml
index 6db0934..da1053b 100644
--- a/slices/core/src/main/res/values-cs/strings.xml
+++ b/slices/core/src/main/res/values-cs/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Povolit aplikaci <xliff:g id="APP_0">%1$s</xliff:g> zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Může číst informace z aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Může provádět akce v aplikaci <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Povolit aplikaci <xliff:g id="APP">%1$s</xliff:g> zobrazovat ukázky z libovolné aplikace"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Povolit"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Zamítnout"</string>
</resources>
diff --git a/slices/core/src/main/res/values-da/strings.xml b/slices/core/src/main/res/values-da/strings.xml
index 70f1a22..67a7ed0 100644
--- a/slices/core/src/main/res/values-da/strings.xml
+++ b/slices/core/src/main/res/values-da/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vil du give <xliff:g id="APP_0">%1$s</xliff:g> tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Den kan læse oplysninger fra <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Den kan foretage handlinger i <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Tillad, at <xliff:g id="APP">%1$s</xliff:g> viser eksempler fra enhver app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Tillad"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Afvis"</string>
</resources>
diff --git a/slices/core/src/main/res/values-de/strings.xml b/slices/core/src/main/res/values-de/strings.xml
index 89ce98e..419fa20 100644
--- a/slices/core/src/main/res/values-de/strings.xml
+++ b/slices/core/src/main/res/values-de/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> erlauben, Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzuzeigen?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Darf Informationen aus <xliff:g id="APP">%1$s</xliff:g> lesen"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Darf Aktionen in <xliff:g id="APP">%1$s</xliff:g> ausführen"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> darf Teile aus jeder beliebigen App anzeigen"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Zulassen"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Ablehnen"</string>
</resources>
diff --git a/slices/core/src/main/res/values-el/strings.xml b/slices/core/src/main/res/values-el/strings.xml
index ed0b624..898d2ea 100644
--- a/slices/core/src/main/res/values-el/strings.xml
+++ b/slices/core/src/main/res/values-el/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Να επιτρέπεται στην εφαρμογή <xliff:g id="APP_0">%1$s</xliff:g> να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>;"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Μπορεί να διαβάζει πληροφορίες από την εφαρμογή <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Μπορεί να εκτελεί ενέργειες εντός της εφαρμογής <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Να επιτρέπεται στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> να εμφανίζει τμήματα από οποιαδήποτε εφαρμογή"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Να επιτρέπεται"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Να μην επιτρέπεται"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rAU/strings.xml b/slices/core/src/main/res/values-en-rAU/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rAU/strings.xml
+++ b/slices/core/src/main/res/values-en-rAU/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rCA/strings.xml b/slices/core/src/main/res/values-en-rCA/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rCA/strings.xml
+++ b/slices/core/src/main/res/values-en-rCA/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rGB/strings.xml b/slices/core/src/main/res/values-en-rGB/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rGB/strings.xml
+++ b/slices/core/src/main/res/values-en-rGB/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rIN/strings.xml b/slices/core/src/main/res/values-en-rIN/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rIN/strings.xml
+++ b/slices/core/src/main/res/values-en-rIN/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rXC/strings.xml b/slices/core/src/main/res/values-en-rXC/strings.xml
index 5f82056..efb1d5f 100644
--- a/slices/core/src/main/res/values-en-rXC/strings.xml
+++ b/slices/core/src/main/res/values-en-rXC/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-es-rUS/strings.xml b/slices/core/src/main/res/values-es-rUS/strings.xml
index 9db16c5..31746f0 100644
--- a/slices/core/src/main/res/values-es-rUS/strings.xml
+++ b/slices/core/src/main/res/values-es-rUS/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Puede realizar acciones en <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> muestre fragmentos de cualquier app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rechazar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-es/strings.xml b/slices/core/src/main/res/values-es/strings.xml
index 44283aa..3a65289 100644
--- a/slices/core/src/main/res/values-es/strings.xml
+++ b/slices/core/src/main/res/values-es/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Puede realizar acciones en <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> muestre fragmentos de cualquier aplicación"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Denegar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-et/strings.xml b/slices/core/src/main/res/values-et/strings.xml
index 6c45d68..5462dd7 100644
--- a/slices/core/src/main/res/values-et/strings.xml
+++ b/slices/core/src/main/res/values-et/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Kas lubada rakendusel <xliff:g id="APP_0">%1$s</xliff:g> näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- See saab lugeda teavet rakendusest <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- See saab rakenduses <xliff:g id="APP">%1$s</xliff:g> toiminguid teha"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Luba rakendus <xliff:g id="APP">%1$s</xliff:g>, et kuvada lõike mis tahes rakendusest"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Lubamine"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Keelamine"</string>
</resources>
diff --git a/slices/core/src/main/res/values-eu/strings.xml b/slices/core/src/main/res/values-eu/strings.xml
index e608918..8776fec 100644
--- a/slices/core/src/main/res/values-eu/strings.xml
+++ b/slices/core/src/main/res/values-eu/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakusteko baimena eman nahi diozu <xliff:g id="APP_0">%1$s</xliff:g> aplikazioari?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioaren informazioa irakur dezake."</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioan ekintzak gauza ditzake."</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Eman aplikazio guztien zatiak erakusteko baimena <xliff:g id="APP">%1$s</xliff:g> aplikazioari"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Baimendu"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Ukatu"</string>
</resources>
diff --git a/slices/core/src/main/res/values-fa/strings.xml b/slices/core/src/main/res/values-fa/strings.xml
index 38a286d..d5e0416 100644
--- a/slices/core/src/main/res/values-fa/strings.xml
+++ b/slices/core/src/main/res/values-fa/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"به <xliff:g id="APP_0">%1$s</xliff:g> اجازه داده شود تکههای <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد؟"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- میتواند اطلاعات <xliff:g id="APP">%1$s</xliff:g> را بخواند"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- میتواند در <xliff:g id="APP">%1$s</xliff:g> اقدام انجام دهد"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"به <xliff:g id="APP">%1$s</xliff:g> اجازه داده شود تکههایی از برنامهها نشان دهد"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"مجاز بودن"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"مجاز نبودن"</string>
</resources>
diff --git a/slices/core/src/main/res/values-fi/strings.xml b/slices/core/src/main/res/values-fi/strings.xml
index 1cc8122..d682f1a 100644
--- a/slices/core/src/main/res/values-fi/strings.xml
+++ b/slices/core/src/main/res/values-fi/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Saako <xliff:g id="APP_0">%1$s</xliff:g> näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Se voi lukea tietoja sovelluksesta <xliff:g id="APP">%1$s</xliff:g>."</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Se voi suorittaa toimintoja sovelluksessa <xliff:g id="APP">%1$s</xliff:g>."</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Salli sovelluksen <xliff:g id="APP">%1$s</xliff:g> näyttää osia mistä tahansa sovelluksesta"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Salli"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Estä"</string>
</resources>
diff --git a/slices/core/src/main/res/values-fr-rCA/strings.xml b/slices/core/src/main/res/values-fr-rCA/strings.xml
index d92e649..a0e55d3 100644
--- a/slices/core/src/main/res/values-fr-rCA/strings.xml
+++ b/slices/core/src/main/res/values-fr-rCA/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Il peut lire de l\'information de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Il peut effectuer des actions dans <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à afficher des tranches de n\'importe quelle application"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Autoriser"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuser"</string>
</resources>
diff --git a/slices/core/src/main/res/values-fr/strings.xml b/slices/core/src/main/res/values-fr/strings.xml
index 9edcb27..65f06bc 100644
--- a/slices/core/src/main/res/values-fr/strings.xml
+++ b/slices/core/src/main/res/values-fr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g> ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Accès aux informations de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Capacité d\'action dans <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à afficher des éléments de n\'importe quelle application"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Autoriser"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuser"</string>
</resources>
diff --git a/slices/core/src/main/res/values-gl/strings.xml b/slices/core/src/main/res/values-gl/strings.xml
index 617e5fc..50befe5 100644
--- a/slices/core/src/main/res/values-gl/strings.xml
+++ b/slices/core/src/main/res/values-gl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Queres permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler información da aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode levar a cabo accións dentro da aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> mostre fragmentos de calquera aplicación"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Denegar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-gu/strings.xml b/slices/core/src/main/res/values-gu/strings.xml
index 4e8fcb8..804b774 100644
--- a/slices/core/src/main/res/values-gu/strings.xml
+++ b/slices/core/src/main/res/values-gu/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>ને <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવાની મંજૂરી આપીએ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- તે <xliff:g id="APP">%1$s</xliff:g>માંથી માહિતી વાંચી શકે છે"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- તે <xliff:g id="APP">%1$s</xliff:g>ની અંદર ક્રિયાઓ કરી શકે છે"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>ને કોઈપણ ઍપના સ્લાઇસ બતાવવાની મંજૂરી આપો"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"મંજૂરી આપો"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"નકારો"</string>
</resources>
diff --git a/slices/core/src/main/res/values-hi/strings.xml b/slices/core/src/main/res/values-hi/strings.xml
index 23e4d3f..9dfe2b0 100644
--- a/slices/core/src/main/res/values-hi/strings.xml
+++ b/slices/core/src/main/res/values-hi/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> को <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- यह <xliff:g id="APP">%1$s</xliff:g> से जानकारी पा सकता है"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- यह <xliff:g id="APP">%1$s</xliff:g> में कार्रवाई कर सकता है"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> काे किसी भी ऐप्लिकेशन के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"अनुमति दें"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"अनुमति न दें"</string>
</resources>
diff --git a/slices/core/src/main/res/values-hr/strings.xml b/slices/core/src/main/res/values-hr/strings.xml
index e0bffaa..9e614b5 100644
--- a/slices/core/src/main/res/values-hr/strings.xml
+++ b/slices/core/src/main/res/values-hr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Želite li dopustiti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– može čitati informacije aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– može vršiti radnje u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dopusti aplikaciji <xliff:g id="APP">%1$s</xliff:g> da prikazuje isječke iz bilo koje aplikacije"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dopusti"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odbij"</string>
</resources>
diff --git a/slices/core/src/main/res/values-hu/strings.xml b/slices/core/src/main/res/values-hu/strings.xml
index 4601a88..004829f 100644
--- a/slices/core/src/main/res/values-hu/strings.xml
+++ b/slices/core/src/main/res/values-hu/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Engedélyezi a(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazásnak, hogy részleteket mutasson a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Információkat olvashat a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásból"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Műveleteket végezhet a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazáson belül"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Engedélyezi a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásnak, hogy bármely alkalmazásból részletet jelenítsen meg"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Engedélyezés"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Elutasítás"</string>
</resources>
diff --git a/slices/core/src/main/res/values-hy/strings.xml b/slices/core/src/main/res/values-hy/strings.xml
index c8850ff..1d716d7 100644
--- a/slices/core/src/main/res/values-hy/strings.xml
+++ b/slices/core/src/main/res/values-hy/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Թույլատրե՞լ <xliff:g id="APP_0">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Կարող է կարդալ տեղեկություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածից"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Կարող է կատարել գործողություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածում"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Թույլատրել <xliff:g id="APP">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ ցանկացած հավելվածից"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Թույլատրել"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Մերժել"</string>
</resources>
diff --git a/slices/core/src/main/res/values-in/strings.xml b/slices/core/src/main/res/values-in/strings.xml
index a68a7c7..860ed1c 100644
--- a/slices/core/src/main/res/values-in/strings.xml
+++ b/slices/core/src/main/res/values-in/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Izinkan <xliff:g id="APP_0">%1$s</xliff:g> menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Dapat membaca informasi dari <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Dapat mengambil tindakan di dalam <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Izinkan <xliff:g id="APP">%1$s</xliff:g> menampilkan potongan dari aplikasi apa pun"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Izinkan"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Tolak"</string>
</resources>
diff --git a/slices/core/src/main/res/values-is/strings.xml b/slices/core/src/main/res/values-is/strings.xml
index 24f3ace..da79a76 100644
--- a/slices/core/src/main/res/values-is/strings.xml
+++ b/slices/core/src/main/res/values-is/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Viltu leyfa <xliff:g id="APP_0">%1$s</xliff:g> að sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Það getur lesið upplýsingar úr <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Það getur gripið til aðgerða í <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Leyfa <xliff:g id="APP">%1$s</xliff:g> að sýna sneiðar úr hvaða forriti sem er"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Leyfa"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Hafna"</string>
</resources>
diff --git a/slices/core/src/main/res/values-it/strings.xml b/slices/core/src/main/res/values-it/strings.xml
index 333547d..5518d66 100644
--- a/slices/core/src/main/res/values-it/strings.xml
+++ b/slices/core/src/main/res/values-it/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vuoi consentire all\'app <xliff:g id="APP_0">%1$s</xliff:g> di mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Può leggere informazioni dell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Può compiere azioni nell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Consenti all\'app <xliff:g id="APP">%1$s</xliff:g> di mostrare porzioni di qualsiasi app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Consenti"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rifiuta"</string>
</resources>
diff --git a/slices/core/src/main/res/values-iw/strings.xml b/slices/core/src/main/res/values-iw/strings.xml
index 7534117..2151548 100644
--- a/slices/core/src/main/res/values-iw/strings.xml
+++ b/slices/core/src/main/res/values-iw/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"האם לאפשר ל-<xliff:g id="APP_0">%1$s</xliff:g> להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- תהיה לה אפשרות לקרוא מידע מהאפליקציה <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- תהיה לה יכולת לנקוט פעולה בתוך <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"יש לאשר לאפליקציית <xliff:g id="APP">%1$s</xliff:g> להציג חלקים מכל אפליקציה שהיא"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"אישור"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"אני לא מרשה"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ja/strings.xml b/slices/core/src/main/res/values-ja/strings.xml
index 315326f..1c28f4c 100644
--- a/slices/core/src/main/res/values-ja/strings.xml
+++ b/slices/core/src/main/res/values-ja/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> のスライスの表示を <xliff:g id="APP_0">%1$s</xliff:g> に許可しますか?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> からの情報を読み取ることができます"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> 内部で操作することがあります"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"すべてのアプリのスライスを表示することを <xliff:g id="APP">%1$s</xliff:g> に許可する"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"許可"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒否"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ka/strings.xml b/slices/core/src/main/res/values-ka/strings.xml
index f21d1be..b78206c 100644
--- a/slices/core/src/main/res/values-ka/strings.xml
+++ b/slices/core/src/main/res/values-ka/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"ანიჭებთ ნებართვას <xliff:g id="APP_0">%1$s</xliff:g>-ს, აჩვენოს <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- მას შეუძლია ინფორმაციის <xliff:g id="APP">%1$s</xliff:g>-დან წაკითხვა"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- მას შეუძლია ქმედებების <xliff:g id="APP">%1$s</xliff:g>-ში განხორციელება"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>-ისთვის ფრაგმენტების ნებისმიერი აპიდან ჩვენების ნების დართვა"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"დაშვება"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"უარყოფა"</string>
</resources>
diff --git a/slices/core/src/main/res/values-kk/strings.xml b/slices/core/src/main/res/values-kk/strings.xml
index aa9409f..fcb0509 100644
--- a/slices/core/src/main/res/values-kk/strings.xml
+++ b/slices/core/src/main/res/values-kk/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасына <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсетуге рұқсат берілсін бе?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> қолданбасындағы ақпаратты оқи алады"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> қолданбасында әрекет ете алады"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> қолданбасына кез келген қолданбаның үзіндісін көрсетуге рұқсат беру"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Рұқсат беру"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Тыйым салу"</string>
</resources>
diff --git a/slices/core/src/main/res/values-km/strings.xml b/slices/core/src/main/res/values-km/strings.xml
index a98f409..5d0a988 100644
--- a/slices/core/src/main/res/values-km/strings.xml
+++ b/slices/core/src/main/res/values-km/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"អនុញ្ញាតឱ្យ <xliff:g id="APP_0">%1$s</xliff:g> បង្ហាញស្ថិតិប្រើប្រាស់របស់ <xliff:g id="APP_2">%2$s</xliff:g> ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- វាអាចអានព័ត៌មានពី <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- វាអាចធ្វើសកម្មភាពនៅក្នុង <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"អនុញ្ញាតឱ្យ <xliff:g id="APP">%1$s</xliff:g> បង្ហាញស្ថិតិប្រើប្រាស់ពីកម្មវិធីនានា"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"អនុញ្ញាត"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"បដិសេធ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-kn/strings.xml b/slices/core/src/main/res/values-kn/strings.xml
index e8a0559..7fe32e0 100644
--- a/slices/core/src/main/res/values-kn/strings.xml
+++ b/slices/core/src/main/res/values-kn/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP_0">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ನಿಂದ ಮಾಹಿತಿಯನ್ನು ಓದಬಹುದು"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ಒಳಗಡೆ ಕ್ರಿಯೆಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಹುದು"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್ನಿಂದ ಸ್ಲೈಸ್ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸಿ"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ಅನುಮತಿಸಿ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ನಿರಾಕರಿಸಿ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ko/strings.xml b/slices/core/src/main/res/values-ko/strings.xml
index cc88f25..93c62f0 100644
--- a/slices/core/src/main/res/values-ko/strings.xml
+++ b/slices/core/src/main/res/values-ko/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하도록 허용하시겠습니까?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g>의 정보를 읽을 수 있음"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g>에서 작업할 수 있음"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>에서 모든 앱의 슬라이스를 표시하도록 허용"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"허용"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"거부"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ky/strings.xml b/slices/core/src/main/res/values-ky/strings.xml
index 99ce121..9615740 100644
--- a/slices/core/src/main/res/values-ky/strings.xml
+++ b/slices/core/src/main/res/values-ky/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосуна <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөтүүгө уруксат берилсинби?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунун маалыматын окуйт"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунда аракеттерди аткарат"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> бардык колдонмолордун үлгүлөрүн көрсөтүүгө уруксат берүү"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Уруксат берүү"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Тыюу салынат"</string>
</resources>
diff --git a/slices/core/src/main/res/values-lo/strings.xml b/slices/core/src/main/res/values-lo/strings.xml
index 74bb119..9b18f5b 100644
--- a/slices/core/src/main/res/values-lo/strings.xml
+++ b/slices/core/src/main/res/values-lo/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"ອະນຸຍາດ <xliff:g id="APP_0">%1$s</xliff:g> ໃຫ້ສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້ບໍ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ມັນສາມາດອ່ານຂໍ້ມູນຈາກ <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ມັນສາມາດໃຊ້ຄຳສັ່ງພາຍໃນ <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ອະນຸຍາດ <xliff:g id="APP">%1$s</xliff:g> ເພື່ອສະແດງສະໄລ້ຈາກແອັບ"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ອະນຸຍາດ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ປະຕິເສດ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-lt/strings.xml b/slices/core/src/main/res/values-lt/strings.xml
index 50eb4a6..1e88cd6 100644
--- a/slices/core/src/main/res/values-lt/strings.xml
+++ b/slices/core/src/main/res/values-lt/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Leisti „<xliff:g id="APP_0">%1$s</xliff:g>“ rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Gali nuskaityti informaciją iš „<xliff:g id="APP">%1$s</xliff:g>“"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Gali imtis veiksmų programoje „<xliff:g id="APP">%1$s</xliff:g>“"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Leisti „<xliff:g id="APP">%1$s</xliff:g>“ rodyti bet kurios programos fragmentus"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Leisti"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Atmesti"</string>
</resources>
diff --git a/slices/core/src/main/res/values-lv/strings.xml b/slices/core/src/main/res/values-lv/strings.xml
index 4bfb688..1f3ccde 100644
--- a/slices/core/src/main/res/values-lv/strings.xml
+++ b/slices/core/src/main/res/values-lv/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vai atļaut lietotnei <xliff:g id="APP_0">%1$s</xliff:g> rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Var lasīt informāciju no lietotnes <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Var veikt darbības lietotnē <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Atļaut lietotnei <xliff:g id="APP">%1$s</xliff:g> rādīt sadaļas no jebkuras lietotnes"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Atļaut"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Neatļaut"</string>
</resources>
diff --git a/slices/core/src/main/res/values-mk/strings.xml b/slices/core/src/main/res/values-mk/strings.xml
index 2525781..a5ccc53 100644
--- a/slices/core/src/main/res/values-mk/strings.xml
+++ b/slices/core/src/main/res/values-mk/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Да се дозволи <xliff:g id="APP_0">%1$s</xliff:g> да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Може да чита информации од <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Може да презема дејства во <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дозволете <xliff:g id="APP">%1$s</xliff:g> да прикажува делови од која било апликација"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дозволете"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Одбијте"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ml/strings.xml b/slices/core/src/main/res/values-ml/strings.xml
index ce177d2..21802b7 100644
--- a/slices/core/src/main/res/values-ml/strings.xml
+++ b/slices/core/src/main/res/values-ml/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g>-നെ അനുവദിക്കണോ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-ൽ നിന്ന് വിവരങ്ങൾ വായിക്കാനാകും"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-നുള്ളിൽ പ്രവർത്തനങ്ങൾ ചെയ്യാനാകും"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ഏത് ആപ്പിൽ നിന്നും സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP">%1$s</xliff:g>-നെ അനുവദിക്കുക"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"അനുവദിക്കുക"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"നിരസിക്കുക"</string>
</resources>
diff --git a/slices/core/src/main/res/values-mn/strings.xml b/slices/core/src/main/res/values-mn/strings.xml
index 50764d9..8e0661a 100644
--- a/slices/core/src/main/res/values-mn/strings.xml
+++ b/slices/core/src/main/res/values-mn/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>-д <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг харуулахыг зөвшөөрөх үү?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Энэ <xliff:g id="APP">%1$s</xliff:g>-с мэдээлэл унших боломжтой"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Энэ <xliff:g id="APP">%1$s</xliff:g> дотор үйлдэл хийх боломжтой"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>-д дурын аппаас хэсэг харуулахыг зөвшөөрөх"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Зөвшөөрөх"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Татгалзах"</string>
</resources>
diff --git a/slices/core/src/main/res/values-mr/strings.xml b/slices/core/src/main/res/values-mr/strings.xml
index f938652..ab48521 100644
--- a/slices/core/src/main/res/values-mr/strings.xml
+++ b/slices/core/src/main/res/values-mr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवण्याची अनुमती द्यायची का?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ते <xliff:g id="APP">%1$s</xliff:g> ची माहिती वाचू शकते"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ते <xliff:g id="APP">%1$s</xliff:g> मध्ये कृती करू शकते"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ला कुठल्याही अॅपमधील तुकडे दाखवण्याची अनुमती द्या"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"अनुमती द्या"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"नकार द्या"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ms/strings.xml b/slices/core/src/main/res/values-ms/strings.xml
index af2a67e..4b20a63 100644
--- a/slices/core/src/main/res/values-ms/strings.xml
+++ b/slices/core/src/main/res/values-ms/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Benarkan <xliff:g id="APP_0">%1$s</xliff:g> menunjukkan hirisan <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Hos hirisan boleh membaca maklumat daripada <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Hos hirisan boleh mengambil tindakan dalam <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Benarkan <xliff:g id="APP">%1$s</xliff:g> menunjukkan hirisan daripada mana-mana apl"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Benarkan"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Tolak"</string>
</resources>
diff --git a/slices/core/src/main/res/values-my/strings.xml b/slices/core/src/main/res/values-my/strings.xml
index 0cf5d3e..666640ef 100644
--- a/slices/core/src/main/res/values-my/strings.xml
+++ b/slices/core/src/main/res/values-my/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> အား <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များ ပြသခွင့်ပြုပါသလား။"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> မှ အချက်အလက်ကို ဖတ်နိုင်သည်"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> အတွင်း လုပ်ဆောင်ချက်များ ပြုလုပ်နိုင်သည်"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"မည်သည့်အက်ပ်မဆိုမှ အချပ်များ ပြသရန်အတွက် <xliff:g id="APP">%1$s</xliff:g> ကို ခွင့်ပြုရန်"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ခွင့်ပြုရန်"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ငြင်းပယ်ရန်"</string>
</resources>
diff --git a/slices/core/src/main/res/values-nb/strings.xml b/slices/core/src/main/res/values-nb/strings.xml
index 634eac3..9448d03 100644
--- a/slices/core/src/main/res/values-nb/strings.xml
+++ b/slices/core/src/main/res/values-nb/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vil du tillate at <xliff:g id="APP_0">%1$s</xliff:g> viser <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Den kan lese informasjon fra <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Den kan utføre handlinger i <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Tillat at <xliff:g id="APP">%1$s</xliff:g> viser utsnitt fra alle apper"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Tillat"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Ikke tillat"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ne/strings.xml b/slices/core/src/main/res/values-ne/strings.xml
index adf3a1b..bfa42445 100644
--- a/slices/core/src/main/res/values-ne/strings.xml
+++ b/slices/core/src/main/res/values-ne/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> लाई <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन अनुमति दिने हो?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- यसले <xliff:g id="APP">%1$s</xliff:g> को जानकारी पढ्न सक्छ"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- यसले <xliff:g id="APP">%1$s</xliff:g> भित्र कारबाही गर्न सक्छ"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> लाई सबै एपका स्लाइसहरू देखाउन अनुमति दिनुहोस्"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"अनुमति दिनुहोस्"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"नदिने"</string>
</resources>
diff --git a/slices/core/src/main/res/values-nl/strings.xml b/slices/core/src/main/res/values-nl/strings.xml
index 46b2c9f..4d8006e 100644
--- a/slices/core/src/main/res/values-nl/strings.xml
+++ b/slices/core/src/main/res/values-nl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> toestaan om segmenten van <xliff:g id="APP_2">%2$s</xliff:g> te tonen?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Deze kan informatie lezen van <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Deze kan acties uitvoeren in <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> toestaan om segmenten van apps te tonen"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Toestaan"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Weigeren"</string>
</resources>
diff --git a/slices/core/src/main/res/values-or/strings.xml b/slices/core/src/main/res/values-or/strings.xml
index a87a506..a757613 100644
--- a/slices/core/src/main/res/values-or/strings.xml
+++ b/slices/core/src/main/res/values-or/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_0">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ଏହା <xliff:g id="APP">%1$s</xliff:g>ରୁ ସୂଚନାକୁ ପଢ଼ିପାରିବ"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ଏହା <xliff:g id="APP">%1$s</xliff:g> ଭିତରେ କାମ କରିପାରିବ"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ଯେକୌଣସି ଆପ୍ରେ ସ୍ଲାଇସ୍କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP">%1$s</xliff:g>କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ଅଗ୍ରାହ୍ୟ କରନ୍ତୁ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pa/strings.xml b/slices/core/src/main/res/values-pa/strings.xml
index e1ef272..d68cc39 100644
--- a/slices/core/src/main/res/values-pa/strings.xml
+++ b/slices/core/src/main/res/values-pa/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"ਕੀ <xliff:g id="APP_0">%1$s</xliff:g> ਨੂੰ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੇਣੇ ਹਨ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ਇਹ <xliff:g id="APP">%1$s</xliff:g> ਵਿੱਚੋਂ ਜਾਣਕਾਰੀ ਪੜ੍ਹ ਸਕਦਾ ਹੈ"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ਇਸ <xliff:g id="APP">%1$s</xliff:g> ਦੇ ਅੰਦਰ ਕਾਰਵਾਈਆਂ ਕਰ ਸਕਦਾ ਹੈ"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ਨੂੰ ਕਿਸੇ ਵੀ ਐਪ ਵਿੱਚੋਂ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦਿਓ"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ਕਰਨ ਦਿਓ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ਅਸਵੀਕਾਰ ਕਰੋ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pl/strings.xml b/slices/core/src/main/res/values-pl/strings.xml
index f66d32c..ea32902 100644
--- a/slices/core/src/main/res/values-pl/strings.xml
+++ b/slices/core/src/main/res/values-pl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Zezwolić aplikacji <xliff:g id="APP_0">%1$s</xliff:g> na pokazywanie wycinków z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Może odczytywać informacje z aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Może wykonywać działania w aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Zezwalaj aplikacji <xliff:g id="APP">%1$s</xliff:g> na pokazywanie wycinków z dowolnych aplikacji"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Zezwól"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odmów"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pt-rBR/strings.xml b/slices/core/src/main/res/values-pt-rBR/strings.xml
index 246fc77..b58e786 100644
--- a/slices/core/src/main/res/values-pt-rBR/strings.xml
+++ b/slices/core/src/main/res/values-pt-rBR/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode realizar ações no app <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> mostre partes de qualquer app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Negar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pt-rPT/strings.xml b/slices/core/src/main/res/values-pt-rPT/strings.xml
index 472dce8..832b5e5 100644
--- a/slices/core/src/main/res/values-pt-rPT/strings.xml
+++ b/slices/core/src/main/res/values-pt-rPT/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Permitir que a app <xliff:g id="APP_0">%1$s</xliff:g> mostre partes da app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler informações da app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode realizar ações na app <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que a app <xliff:g id="APP">%1$s</xliff:g> mostre partes de qualquer app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Recusar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pt/strings.xml b/slices/core/src/main/res/values-pt/strings.xml
index 246fc77..b58e786 100644
--- a/slices/core/src/main/res/values-pt/strings.xml
+++ b/slices/core/src/main/res/values-pt/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode realizar ações no app <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> mostre partes de qualquer app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Negar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ro/strings.xml b/slices/core/src/main/res/values-ro/strings.xml
index f4b1135..2db5e57 100644
--- a/slices/core/src/main/res/values-ro/strings.xml
+++ b/slices/core/src/main/res/values-ro/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Permiteți <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Poate citi informații din <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Poate efectua acțiuni în <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permiteți <xliff:g id="APP">%1$s</xliff:g> să afișeze porțiuni din orice aplicație"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permiteți"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuzați"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ru/strings.xml b/slices/core/src/main/res/values-ru/strings.xml
index 43e261f..89dafbc 100644
--- a/slices/core/src/main/res/values-ru/strings.xml
+++ b/slices/core/src/main/res/values-ru/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Разрешить приложению \"<xliff:g id="APP_0">%1$s</xliff:g>\" показывать фрагменты приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Ему станут доступны данные из приложения \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Оно сможет совершать действия в приложении \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Разрешить приложению \"<xliff:g id="APP">%1$s</xliff:g>\" показывать фрагменты других приложений"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Да"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Нет"</string>
</resources>
diff --git a/slices/core/src/main/res/values-si/strings.xml b/slices/core/src/main/res/values-si/strings.xml
index 70816e1..e5e8478 100644
--- a/slices/core/src/main/res/values-si/strings.xml
+++ b/slices/core/src/main/res/values-si/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට ඉඩ දෙන්නද?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- එයට <xliff:g id="APP">%1$s</xliff:g> වෙතින් තොරතුරු කියවිය හැකිය"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- එයට <xliff:g id="APP">%1$s</xliff:g> ඇතුළත ක්රියාමාර්ග ගත හැකිය"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ඕනෑම යෙදුමකින් කොටස් පෙන්වීමට <xliff:g id="APP">%1$s</xliff:g> හට ඉඩ දෙන්න"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ඉඩ දෙන්න"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ප්රතික්ෂේප කර."</string>
</resources>
diff --git a/slices/core/src/main/res/values-sk/strings.xml b/slices/core/src/main/res/values-sk/strings.xml
index 4937a6c..8070718 100644
--- a/slices/core/src/main/res/values-sk/strings.xml
+++ b/slices/core/src/main/res/values-sk/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Povoliť aplikácii <xliff:g id="APP_0">%1$s</xliff:g> zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Môže čítať informácie z aplikácie <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Môže vykonávať akcie v aplikácii <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Povoliť aplikácii <xliff:g id="APP">%1$s</xliff:g> zobrazovať rezy z ľubovoľnej aplikácie"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Povoliť"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Zamietnuť"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sl/strings.xml b/slices/core/src/main/res/values-sl/strings.xml
index 30f67c4..b3ab6f4 100644
--- a/slices/core/src/main/res/values-sl/strings.xml
+++ b/slices/core/src/main/res/values-sl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Ali aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> dovolite prikazovanje izrezov iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– lahko bere podatke v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– lahko izvaja dejanja v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dovoli, da aplikacija <xliff:g id="APP">%1$s</xliff:g> prikaže izreze iz poljubne aplikacije"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dovoli"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Zavrni"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sq/strings.xml b/slices/core/src/main/res/values-sq/strings.xml
index 47ce934..9ae77dd 100644
--- a/slices/core/src/main/res/values-sq/strings.xml
+++ b/slices/core/src/main/res/values-sq/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Të lejohet <xliff:g id="APP_0">%1$s</xliff:g> që të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Mund të lexojë informacion nga <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Mund të ndërmarrë veprime brenda <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Lejo <xliff:g id="APP">%1$s</xliff:g> për të shfaqur pjesë nga çdo aplikacion"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Lejo"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuzo"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sr/strings.xml b/slices/core/src/main/res/values-sr/strings.xml
index e059b1c..b7c29c5 100644
--- a/slices/core/src/main/res/values-sr/strings.xml
+++ b/slices/core/src/main/res/values-sr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Желите ли да дозволите апликацији <xliff:g id="APP_0">%1$s</xliff:g> да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Може да чита податке из апликације <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Може да обавља радње у апликацији <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дозволи апликацији <xliff:g id="APP">%1$s</xliff:g> да приказује исечке из било које апликације"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дозволи"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Одбиј"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sv/strings.xml b/slices/core/src/main/res/values-sv/strings.xml
index e065cb3..9be28b3 100644
--- a/slices/core/src/main/res/values-sv/strings.xml
+++ b/slices/core/src/main/res/values-sv/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Tillåter du att bitar av <xliff:g id="APP_2">%2$s</xliff:g> visas i <xliff:g id="APP_0">%1$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Kan läsa information från <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Kan vidta åtgärder i <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Tillåt att bitar av vilken app som helst visas i <xliff:g id="APP">%1$s</xliff:g>"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Tillåt"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Neka"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sw/strings.xml b/slices/core/src/main/res/values-sw/strings.xml
index 2365547..28459cd 100644
--- a/slices/core/src/main/res/values-sw/strings.xml
+++ b/slices/core/src/main/res/values-sw/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Ungependa kuruhusu <xliff:g id="APP_0">%1$s</xliff:g> ionyeshe vipengee <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Inaweza kusoma maelezo kutoka <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Inaweza kuchukua hatua ndani ya <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Ruhusu <xliff:g id="APP">%1$s</xliff:g> ionyeshe vipengee kutoka programu yoyote"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Ruhusu"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Kataa"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ta/strings.xml b/slices/core/src/main/res/values-ta/strings.xml
index 0c8d874..36be75a 100644
--- a/slices/core/src/main/res/values-ta/strings.xml
+++ b/slices/core/src/main/res/values-ta/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க, <xliff:g id="APP_0">%1$s</xliff:g> ஆப்ஸை அனுமதிக்கவா?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிலிருக்கும் தகவலைப் படிக்கும்"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிற்குள் செயல்பாடுகளில் ஈடுபடும்"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"எந்தப் பயன்பாட்டிலிருந்தும் விழிப்பூட்டல்களைக் காண்பிக்க, <xliff:g id="APP">%1$s</xliff:g> ஆப்ஸை அனுமதி"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"அனுமதி"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"நிராகரி"</string>
</resources>
diff --git a/slices/core/src/main/res/values-te/strings.xml b/slices/core/src/main/res/values-te/strings.xml
index 3f89a75..ce51a47 100644
--- a/slices/core/src/main/res/values-te/strings.xml
+++ b/slices/core/src/main/res/values-te/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> స్లైస్లను చూపించడానికి <xliff:g id="APP_0">%1$s</xliff:g>ని అనుమతించాలా?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ఇది <xliff:g id="APP">%1$s</xliff:g> నుండి సమాచారాన్ని చదవగలుగుతుంది"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ఇది <xliff:g id="APP">%1$s</xliff:g> లోపల చర్యలు తీసుకోగలుగుతుంది"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ఏ యాప్ నుండి అయినా స్లైస్లను చూపించడానికి <xliff:g id="APP">%1$s</xliff:g>ని అనుమతించండి"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"అనుమతించు"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"తిరస్కరించు"</string>
</resources>
diff --git a/slices/core/src/main/res/values-th/strings.xml b/slices/core/src/main/res/values-th/strings.xml
index afc42f2..07371a0 100644
--- a/slices/core/src/main/res/values-th/strings.xml
+++ b/slices/core/src/main/res/values-th/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"อนุญาตให้ <xliff:g id="APP_0">%1$s</xliff:g> แสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- อ่านข้อมูลจาก <xliff:g id="APP">%1$s</xliff:g> ได้"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ดำเนินการใน <xliff:g id="APP">%1$s</xliff:g> ได้"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"อนุญาตให้ <xliff:g id="APP">%1$s</xliff:g> แสดงส่วนต่างๆ จากแอปใดก็ได้"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"อนุญาต"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ปฏิเสธ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-tl/strings.xml b/slices/core/src/main/res/values-tl/strings.xml
index aa9d7b4..e4bf3e0 100644
--- a/slices/core/src/main/res/values-tl/strings.xml
+++ b/slices/core/src/main/res/values-tl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Payagan ang <xliff:g id="APP_0">%1$s</xliff:g> na ipakita ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Nakakabasa ito ng impormasyon mula sa <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Nakakagawa ito ng mga pagkilos sa loob ng <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Payagan ang <xliff:g id="APP">%1$s</xliff:g> na ipakita ang mga slice mula sa anumang app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Payagan"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Tanggihan"</string>
</resources>
diff --git a/slices/core/src/main/res/values-tr/strings.xml b/slices/core/src/main/res/values-tr/strings.xml
index 216a87c..2cbcfda 100644
--- a/slices/core/src/main/res/values-tr/strings.xml
+++ b/slices/core/src/main/res/values-tr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> uygulamasının, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermesine izin verilsin mi?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasından bilgileri okuyabilir"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasında işlem yapabilir"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> uygulamasının tüm uygulamalardan dilimleri göstermesine izin ver"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"İzin ver"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Reddet"</string>
</resources>
diff --git a/slices/core/src/main/res/values-uk/strings.xml b/slices/core/src/main/res/values-uk/strings.xml
index e908f43..b842f5c 100644
--- a/slices/core/src/main/res/values-uk/strings.xml
+++ b/slices/core/src/main/res/values-uk/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Дозволити додатку <xliff:g id="APP_0">%1$s</xliff:g> показувати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Може переглядати інформацію з додатка <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Може виконувати дії в додатку <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дозволити додатку <xliff:g id="APP">%1$s</xliff:g> показувати фрагменти будь-якого додатка"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дозволити"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Заборонити"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ur/strings.xml b/slices/core/src/main/res/values-ur/strings.xml
index 0e97e52..8999e25 100644
--- a/slices/core/src/main/res/values-ur/strings.xml
+++ b/slices/core/src/main/res/values-ur/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> کو <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانے کی اجازت دیں؟"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- یہ <xliff:g id="APP">%1$s</xliff:g> کی معلومات پڑھ سکتا ہے"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- یہ <xliff:g id="APP">%1$s</xliff:g> کے اندر کارروائیاں کر سکتا ہے"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> کو کسی بھی ایپ سے سلائسز دکھانے کی اجازت دیں"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"اجازت دیں"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"مسترد کریں"</string>
</resources>
diff --git a/slices/core/src/main/res/values-uz/strings.xml b/slices/core/src/main/res/values-uz/strings.xml
index 8de5b2e..9fcb4ce 100644
--- a/slices/core/src/main/res/values-uz/strings.xml
+++ b/slices/core/src/main/res/values-uz/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasiga <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatishga ruxsat berilsinmi?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– <xliff:g id="APP">%1$s</xliff:g> ma’lumotlarini o‘qiy oladi"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– <xliff:g id="APP">%1$s</xliff:g> ichida amallar bajara oladi"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ilovasiga boshqa ilovalardan fragmentlarni ko‘rsatishga ruxsat berish"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Ruxsat"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rad etish"</string>
</resources>
diff --git a/slices/core/src/main/res/values-vi/strings.xml b/slices/core/src/main/res/values-vi/strings.xml
index 8ecd246..c067ac8 100644
--- a/slices/core/src/main/res/values-vi/strings.xml
+++ b/slices/core/src/main/res/values-vi/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Cho phép <xliff:g id="APP_0">%1$s</xliff:g> hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Có thể đọc thông tin từ <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Có thể thực hiện hành động bên trong <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Cho phép <xliff:g id="APP">%1$s</xliff:g> hiển thị các lát từ mọi ứng dụng"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Cho phép"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Từ chối"</string>
</resources>
diff --git a/slices/core/src/main/res/values-zh-rCN/strings.xml b/slices/core/src/main/res/values-zh-rCN/strings.xml
index 3a99e82..6ab2d3e 100644
--- a/slices/core/src/main/res/values-zh-rCN/strings.xml
+++ b/slices/core/src/main/res/values-zh-rCN/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"要允许“<xliff:g id="APP_0">%1$s</xliff:g>”显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块吗?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- 可以读取“<xliff:g id="APP">%1$s</xliff:g>”中的信息"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- 可以在“<xliff:g id="APP">%1$s</xliff:g>”内执行操作"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"允许“<xliff:g id="APP">%1$s</xliff:g>”显示任何应用的图块"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"允许"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒绝"</string>
</resources>
diff --git a/slices/core/src/main/res/values-zh-rHK/strings.xml b/slices/core/src/main/res/values-zh-rHK/strings.xml
index bb478fc..5cf6943 100644
--- a/slices/core/src/main/res/values-zh-rHK/strings.xml
+++ b/slices/core/src/main/res/values-zh-rHK/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊嗎?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- 可以讀取「<xliff:g id="APP">%1$s</xliff:g>」中的資料"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- 可以在「<xliff:g id="APP">%1$s</xliff:g>」內執行操作"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"允許「<xliff:g id="APP">%1$s</xliff:g>」顯示任何應用程式的快訊"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"允許"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒絕"</string>
</resources>
diff --git a/slices/core/src/main/res/values-zh-rTW/strings.xml b/slices/core/src/main/res/values-zh-rTW/strings.xml
index caeeff1..df1ab8b 100644
--- a/slices/core/src/main/res/values-zh-rTW/strings.xml
+++ b/slices/core/src/main/res/values-zh-rTW/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊嗎?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- 它可以讀取「<xliff:g id="APP">%1$s</xliff:g>」的資訊"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- 它可以在「<xliff:g id="APP">%1$s</xliff:g>」內執行操作"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"允許「<xliff:g id="APP">%1$s</xliff:g>」顯示任何應用程式的區塊"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"允許"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒絕"</string>
</resources>
diff --git a/slices/core/src/main/res/values-zu/strings.xml b/slices/core/src/main/res/values-zu/strings.xml
index 940334c..d9ada48 100644
--- a/slices/core/src/main/res/values-zu/strings.xml
+++ b/slices/core/src/main/res/values-zu/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vumela i-<xliff:g id="APP_0">%1$s</xliff:g> ukuthi ibonise izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Ingafunda ulwazi kusukela ku-<xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Ingenza izenzo ngaphakathi kwe-<xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Vumela i-<xliff:g id="APP">%1$s</xliff:g> ukuthi ikubonise izingcezu kusukela kunoma iluphi uhlelo lokusebenza"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Vumela"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Phika"</string>
</resources>
diff --git a/slices/core/src/main/res/values/strings.xml b/slices/core/src/main/res/values/strings.xml
index 2959af8..6f96605 100644
--- a/slices/core/src/main/res/values/strings.xml
+++ b/slices/core/src/main/res/values/strings.xml
@@ -28,9 +28,6 @@
<!-- Description of what kind of access is given to a slice host [CHAR LIMIT=NONE] -->
<string name="abc_slice_permission_text_2"> - It can take actions inside <xliff:g id="app" example="Example App">%1$s</xliff:g></string>
- <!-- Text on checkbox allowing the app to show slices from all apps [CHAR LIMIT=NONE] -->
- <string name="abc_slice_permission_checkbox">Allow <xliff:g id="app" example="Example App">%1$s</xliff:g> to show slices from any app</string>
-
<!-- Option to grant the slice permission request on the screen [CHAR LIMIT=15] -->
<string name="abc_slice_permission_allow">Allow</string>
diff --git a/slices/remotecallback/lint-baseline.xml b/slices/remotecallback/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/slices/remotecallback/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/sqlite/sqlite-ktx/lint-baseline.xml b/sqlite/sqlite-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/sqlite/sqlite-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/integration-tests/first-library/lint-baseline.xml b/startup/integration-tests/first-library/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/startup/integration-tests/first-library/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/integration-tests/second-library/lint-baseline.xml b/startup/integration-tests/second-library/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/startup/integration-tests/second-library/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/integration-tests/test-app/lint-baseline.xml b/startup/integration-tests/test-app/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/startup/integration-tests/test-app/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/startup-runtime-lint/lint-baseline.xml b/startup/startup-runtime-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/startup/startup-runtime-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/startup-runtime/lint-baseline.xml b/startup/startup-runtime/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/startup/startup-runtime/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/testutils/testutils-appcompat/lint-baseline.xml b/testutils/testutils-appcompat/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/testutils/testutils-appcompat/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/testutils/testutils-gradle-plugin/lint-baseline.xml b/testutils/testutils-gradle-plugin/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/testutils/testutils-gradle-plugin/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/testutils/testutils-macrobenchmark/OWNERS b/testutils/testutils-macrobenchmark/OWNERS
new file mode 100644
index 0000000..bd5d7e4
--- /dev/null
+++ b/testutils/testutils-macrobenchmark/OWNERS
@@ -0,0 +1 @@
+ccraik@google.com
\ No newline at end of file
diff --git a/testutils/testutils-macrobenchmark/build.gradle b/testutils/testutils-macrobenchmark/build.gradle
new file mode 100644
index 0000000..d331ce5
--- /dev/null
+++ b/testutils/testutils-macrobenchmark/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 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.
+ */
+
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+dependencies {
+ implementation(project(":benchmark:benchmark-macro"))
+ implementation(project(":benchmark:benchmark-macro-junit4"))
+
+ implementation(KOTLIN_STDLIB)
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 18
+ }
+}
diff --git a/testutils/testutils-macrobenchmark/src/main/AndroidManifest.xml b/testutils/testutils-macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e78b95a
--- /dev/null
+++ b/testutils/testutils-macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<manifest package="androidx.testutils"/>
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/MacrobenchUtils.kt b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
similarity index 92%
rename from compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/MacrobenchUtils.kt
rename to testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
index f893da1..2ec05dd 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/MacrobenchUtils.kt
+++ b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
@@ -14,24 +14,25 @@
* limitations under the License.
*/
-package androidx.compose.integration.macrobenchmark
+package androidx.testutils
import android.content.Intent
+import androidx.annotation.RequiresApi
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.isSupportedWithVmSettings
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
-const val TARGET_PACKAGE = "androidx.compose.integration.macrobenchmark.target"
-
+@RequiresApi(29)
fun MacrobenchmarkRule.measureStartup(
compilationMode: CompilationMode,
startupMode: StartupMode,
+ packageName: String,
iterations: Int = 3,
setupIntent: Intent.() -> Unit = {}
) = measureRepeated(
- packageName = TARGET_PACKAGE,
+ packageName = packageName,
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
iterations = iterations,
@@ -39,7 +40,7 @@
) {
pressHome()
val intent = Intent()
- intent.setPackage(TARGET_PACKAGE)
+ intent.setPackage(packageName)
setupIntent(intent)
startActivityAndWait(intent)
}
diff --git a/testutils/testutils-mockito/lint-baseline.xml b/testutils/testutils-mockito/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/testutils/testutils-mockito/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/testutils/testutils-navigation/build.gradle b/testutils/testutils-navigation/build.gradle
index a1494e1..61664a7 100644
--- a/testutils/testutils-navigation/build.gradle
+++ b/testutils/testutils-navigation/build.gradle
@@ -30,6 +30,7 @@
api(projectOrArtifact(":navigation:navigation-common"))
testImplementation(projectOrArtifact(":navigation:navigation-testing"))
+ testImplementation("androidx.arch.core:core-testing:2.1.0")
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
diff --git a/testutils/testutils-navigation/src/androidTest/java/androidx/testutils/TestNavigatorDestinationBuilderTest.kt b/testutils/testutils-navigation/src/androidTest/java/androidx/testutils/TestNavigatorDestinationBuilderTest.kt
index 198a897..fb2d187 100644
--- a/testutils/testutils-navigation/src/androidTest/java/androidx/testutils/TestNavigatorDestinationBuilderTest.kt
+++ b/testutils/testutils-navigation/src/androidTest/java/androidx/testutils/TestNavigatorDestinationBuilderTest.kt
@@ -43,6 +43,17 @@
}
@Test
+ fun testRoute() {
+ val graph = provider.navigation(startDestination = DESTINATION_ROUTE) {
+ test(DESTINATION_ROUTE)
+ }
+ assertTrue(
+ "Destination should be added to the graph",
+ DESTINATION_ROUTE in graph
+ )
+ }
+
+ @Test
fun testWithBody() {
val graph = provider.navigation(startDestination = DESTINATION_ID) {
test(DESTINATION_ID) {
@@ -58,7 +69,25 @@
LABEL, graph[DESTINATION_ID].label
)
}
+
+ @Test
+ fun testRouteWithBody() {
+ val graph = provider.navigation(startDestination = DESTINATION_ROUTE) {
+ test(DESTINATION_ROUTE) {
+ label = LABEL
+ }
+ }
+ assertTrue(
+ "Destination should be added to the graph",
+ DESTINATION_ROUTE in graph
+ )
+ assertEquals(
+ "Destination should have label set",
+ LABEL, graph[DESTINATION_ROUTE].label
+ )
+ }
}
private const val DESTINATION_ID = 1
+private const val DESTINATION_ROUTE = "route"
private const val LABEL = "Test"
diff --git a/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
index bcda994..f8b90e7 100644
--- a/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
+++ b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
@@ -32,6 +32,11 @@
/**
* Construct a new [TestNavigator.Destination]
*/
+inline fun NavGraphBuilder.test(route: String) = test(route) {}
+
+/**
+ * Construct a new [TestNavigator.Destination]
+ */
inline fun NavGraphBuilder.test(
@IdRes id: Int,
builder: TestNavigatorDestinationBuilder.() -> Unit
@@ -43,10 +48,20 @@
)
/**
+ * Construct a new [TestNavigator.Destination]
+ */
+inline fun NavGraphBuilder.test(
+ route: String,
+ builder: TestNavigatorDestinationBuilder.() -> Unit
+) = destination(
+ TestNavigatorDestinationBuilder(provider[TestNavigator::class], route).apply(builder)
+)
+
+/**
* DSL for constructing a new [TestNavigator.Destination]
*/
@NavDestinationDsl
-class TestNavigatorDestinationBuilder(
- navigator: TestNavigator,
- @IdRes id: Int
-) : NavDestinationBuilder<TestNavigator.Destination>(navigator, id)
+class TestNavigatorDestinationBuilder : NavDestinationBuilder<TestNavigator.Destination> {
+ constructor(navigator: TestNavigator, @IdRes id: Int = 0) : super(navigator, id)
+ constructor(navigator: TestNavigator, route: String) : super(navigator, route)
+}
diff --git a/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt b/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt
index 454883a..ce8b38d 100644
--- a/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt
+++ b/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt
@@ -17,8 +17,10 @@
package androidx.testutils
import android.os.Bundle
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.navigation.testing.TestNavigatorState
import org.junit.Assert.assertEquals
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -26,6 +28,9 @@
@RunWith(JUnit4::class)
class TestNavigatorTest {
+ @get:Rule
+ val instantTaskExecutorRule = InstantTaskExecutorRule()
+
@Test
fun backStack() {
val testNavigator = TestNavigator()
diff --git a/testutils/testutils-truth/lint-baseline.xml b/testutils/testutils-truth/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/testutils/testutils-truth/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.kt
new file mode 100644
index 0000000..afa6802
--- /dev/null
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.compose.ui.text.android.style
+
+import android.text.TextPaint
+import android.text.style.CharacterStyle
+import androidx.compose.ui.text.android.InternalPlatformTextApi
+
+/**
+ * A span which applies the underline and strike through to the affected text.
+ *
+ * @property isUnderlineText whether to draw the under for the affected text.
+ * @property isStrikethroughText whether to draw strikethrough line for the affected text.
+ * @suppress
+ */
+@InternalPlatformTextApi
+class TextDecorationSpan(
+ val isUnderlineText: Boolean,
+ val isStrikethroughText: Boolean
+) : CharacterStyle() {
+ override fun updateDrawState(textPaint: TextPaint) {
+ textPaint.isUnderlineText = isUnderlineText
+ textPaint.isStrikeThruText = isStrikethroughText
+ }
+}
\ No newline at end of file
diff --git a/transition/transition-ktx/lint-baseline.xml b/transition/transition-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/transition/transition-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/vectordrawable/vectordrawable-seekable/lint-baseline.xml b/vectordrawable/vectordrawable-seekable/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/vectordrawable/vectordrawable-seekable/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/compose/foundation/build.gradle b/wear/compose/foundation/build.gradle
index eaa74c9..ad3829e 100644
--- a/wear/compose/foundation/build.gradle
+++ b/wear/compose/foundation/build.gradle
@@ -13,34 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
-import androidx.build.RunApiTasks
-
-import static androidx.build.dependencies.DependenciesKt.*
-
import androidx.build.LibraryGroups
import androidx.build.LibraryType
import androidx.build.LibraryVersions
+import androidx.build.RunApiTasks
+import androidx.build.AndroidXUiPlugin
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("AndroidXPlugin")
id("com.android.library")
+ id("AndroidXUiPlugin")
+ id("org.jetbrains.kotlin.android")
}
+AndroidXUiPlugin.applyAndConfigureKotlinPlugin(project, false)
+
dependencies {
- api("androidx.annotation:annotation:1.1.0")
- api(GUAVA_LISTENABLE_FUTURE)
+ kotlinPlugin(project(":compose:compiler:compiler"))
- implementation 'androidx.annotation:annotation:1.2.0-alpha01'
-
- testImplementation(ANDROIDX_TEST_EXT_JUNIT)
- testImplementation(ANDROIDX_TEST_EXT_TRUTH)
- testImplementation(ANDROIDX_TEST_CORE)
- testImplementation(ANDROIDX_TEST_RUNNER)
- testImplementation(ANDROIDX_TEST_RULES)
- testImplementation(ROBOLECTRIC)
- testImplementation(MOCKITO_CORE)
+ implementation(KOTLIN_STDLIB)
}
android {
@@ -50,7 +42,6 @@
buildTypes.all {
consumerProguardFiles "proguard-rules.pro"
}
-
// Use Robolectric 4.+
testOptions.unitTests.includeAndroidResources = true
}
@@ -65,5 +56,6 @@
"to write Jetpack Compose applications for Wearable devices by providing " +
"functionality to support wearable specific devices sizes, shapes and navigation " +
"gestures. It builds upon the Jetpack Compose libraries."
+ targetsJavaConsumers = false
runApiTasks = new RunApiTasks.No("API tracking disabled while the package is empty")
}
diff --git a/wear/compose/foundation/src/main/AndroidManifest.xml b/wear/compose/foundation/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from wear/compose/foundation/src/main/AndroidManifest.xml
rename to wear/compose/foundation/src/androidMain/AndroidManifest.xml
diff --git a/wear/compose/material/build.gradle b/wear/compose/material/build.gradle
index fb76b02..62cbb74 100644
--- a/wear/compose/material/build.gradle
+++ b/wear/compose/material/build.gradle
@@ -13,14 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
import androidx.build.LibraryGroups
import androidx.build.LibraryType
import androidx.build.LibraryVersions
import androidx.build.RunApiTasks
-
-import static androidx.build.dependencies.DependenciesKt.getKOTLIN_STDLIB
+import androidx.build.AndroidXUiPlugin
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("AndroidXPlugin")
@@ -29,20 +27,19 @@
id("org.jetbrains.kotlin.android")
}
+AndroidXUiPlugin.applyAndConfigureKotlinPlugin(project, false)
+
dependencies {
kotlinPlugin(project(":compose:compiler:compiler"))
+ api(project(":compose:foundation:foundation"))
+ api(project(":compose:ui:ui"))
+ api(project(":compose:ui:ui-text"))
+ api(project(":compose:runtime:runtime"))
+
implementation(KOTLIN_STDLIB)
-
- compileOnly(project(":annotation:annotation-sampled"))
-
- implementation(project(":compose:animation:animation"))
- implementation(project(":compose:foundation:foundation"))
- implementation(project(":compose:foundation:foundation-layout"))
implementation(project(":compose:material:material"))
- implementation(project(":compose:runtime:runtime"))
- implementation(project(":compose:ui:ui"))
- implementation(project(":compose:ui:ui-text"))
+ implementation(project(":compose:material:material-ripple"))
}
android {
@@ -52,7 +49,6 @@
buildTypes.all {
consumerProguardFiles "proguard-rules.pro"
}
-
// Use Robolectric 4.+
testOptions.unitTests.includeAndroidResources = true
}
@@ -67,5 +63,6 @@
"to write Jetpack Compose applications for Wearable devices that implement Wear " +
"Material Design UX guidelines and specifications. It builds upon the Jetpack Compose" +
" libraries."
+ targetsJavaConsumers = false
runApiTasks = new RunApiTasks.No("API tracking disabled while the package is empty")
}
diff --git a/wear/compose/material/src/main/AndroidManifest.xml b/wear/compose/material/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from wear/compose/material/src/main/AndroidManifest.xml
rename to wear/compose/material/src/androidMain/AndroidManifest.xml
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Colors.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Colors.kt
new file mode 100644
index 0000000..892fed5
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Colors.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.wear.compose.material
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.runtime.structuralEqualityPolicy
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
+
+@Stable
+public class Colors(
+ primary: Color = Color(0xFFAECBFA),
+ primaryVariant: Color = Color(0xFF669DF6),
+ secondary: Color = Color(0xFFFDE293),
+ secondaryVariant: Color = Color(0xFF594F33),
+ error: Color = Color(0xFFEE675C),
+) {
+ public var primary: Color by mutableStateOf(primary, structuralEqualityPolicy())
+ internal set
+ public var primaryVariant: Color by mutableStateOf(primaryVariant, structuralEqualityPolicy())
+ internal set
+ public var secondary: Color by mutableStateOf(secondary, structuralEqualityPolicy())
+ internal set
+ public var secondaryVariant: Color by mutableStateOf(
+ secondaryVariant,
+ structuralEqualityPolicy()
+ )
+ internal set
+ public val background: Color = Color.Black
+ public val surface: Color = Color(0xFF202124)
+ public var error: Color by mutableStateOf(error, structuralEqualityPolicy())
+ internal set
+ public val onPrimary: Color = Color(0xFF202124)
+ public val onSecondary: Color = Color(0xFF202124)
+ public val onBackground: Color = Color.White
+ public val onSurface: Color = Color.White
+ public val onSurfaceVariant: Color = Color(0xFFDADCE0)
+ public val onSurfaceVariant2: Color = Color(0xFFBDC1C6)
+ public val onError: Color = Color(0xFF202124)
+
+ /**
+ * Returns a copy of this Colors, optionally overriding some of the values.
+ */
+ public fun copy(
+ primary: Color = this.primary,
+ primaryVariant: Color = this.primaryVariant,
+ secondary: Color = this.secondary,
+ secondaryVariant: Color = this.secondaryVariant,
+ error: Color = this.error,
+ ): Colors = Colors(
+ primary,
+ primaryVariant,
+ secondary,
+ secondaryVariant,
+ error,
+ )
+
+ override fun toString(): String {
+ return "Colors(" +
+ "primary=$primary, " +
+ "primaryVariant=$primaryVariant, " +
+ "secondary=$secondary, " +
+ "secondaryVariant=$secondaryVariant, " +
+ "background=$background, " +
+ "surface=$surface, " +
+ "error=$error, " +
+ "onPrimary=$onPrimary, " +
+ "onSecondary=$onSecondary, " +
+ "onBackground=$onBackground, " +
+ "onSurface=$onSurface, " +
+ "onSurfaceVariant=$onSurfaceVariant, " +
+ "onSurfaceVariant2=$onSurfaceVariant2, " +
+ "onError=$onError" +
+ ")"
+ }
+}
+
+/**
+ * The Material color system contains pairs of colors that are typically used for the background
+ * and content color inside a component. For example, a [Button] typically uses `primary` for its
+ * background, and `onPrimary` for the color of its content (usually text or iconography).
+ *
+ * This function tries to match the provided [backgroundColor] to a 'background' color in this
+ * [Colors], and then will return the corresponding color used for content. For example, when
+ * [backgroundColor] is [Colors.primary], this will return [Colors.onPrimary].
+ *
+ * If [backgroundColor] does not match a background color in the theme, this will return
+ * [Color.Unspecified].
+ *
+ * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
+ * the theme's [Colors], then returns [Color.Unspecified].
+ *
+ * @see contentColorFor
+ */
+fun Colors.contentColorFor(backgroundColor: Color): Color {
+ return when (backgroundColor) {
+ primary -> onPrimary
+ primaryVariant -> onPrimary
+ secondary -> onSecondary
+ secondaryVariant -> onSecondary
+ background -> onBackground
+ surface -> onSurface
+ error -> onError
+ else -> Color.Unspecified
+ }
+}
+
+/**
+ * The Material color system contains pairs of colors that are typically used for the background
+ * and content color inside a component. For example, a [Button] typically uses `primary` for its
+ * background, and `onPrimary` for the color of its content (usually text or iconography).
+ *
+ * This function tries to match the provided [backgroundColor] to a 'background' color in this
+ * [Colors], and then will return the corresponding color used for content. For example, when
+ * [backgroundColor] is [Colors.primary], this will return [Colors.onPrimary].
+ *
+ * If [backgroundColor] does not match a background color in the theme, this will return
+ * the current value of [LocalContentColor] as a best-effort color.
+ *
+ * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
+ * the theme's [Colors], then returns the current value of [LocalContentColor].
+ *
+ * @see Colors.contentColorFor
+ */
+@Composable
+@ReadOnlyComposable
+public fun contentColorFor(backgroundColor: Color) =
+ MaterialTheme.colors.contentColorFor(backgroundColor).takeOrElse { LocalContentColor.current }
+
+/**
+ * Updates the internal values of the given [Colors] with values from the [other] [Colors]. This
+ * allows efficiently updating a subset of [Colors], without recomposing every composable that
+ * consumes values from [LocalColors].
+ *
+ * Because [Colors] is very wide-reaching, and used by many expensive composables in the
+ * hierarchy, providing a new value to [LocalColors] causes every composable consuming
+ * [LocalColors] to recompose, which is prohibitively expensive in cases such as animating one
+ * color in the theme. Instead, [Colors] is internally backed by [mutableStateOf], and this
+ * function mutates the internal state of [this] to match values in [other]. This means that any
+ * changes will mutate the internal state of [this], and only cause composables that are reading
+ * the specific changed value to recompose.
+ */
+internal fun Colors.updateColorsFrom(other: Colors) {
+ primary = other.primary
+ primaryVariant = other.primaryVariant
+ secondary = other.secondary
+ secondaryVariant = other.secondaryVariant
+ error = other.error
+}
+
+internal val LocalColors = staticCompositionLocalOf<Colors> { Colors() }
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentAlpha.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentAlpha.kt
new file mode 100644
index 0000000..fc29f92
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentAlpha.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.wear.compose.material
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.ui.graphics.luminance
+
+/**
+ * Default alpha levels used by Material components.
+ *
+ * See [LocalContentAlpha].
+ */
+object ContentAlpha {
+ /**
+ * A high level of content alpha, used to represent high emphasis text such as input text in a
+ * selected [TextField].
+ */
+ val high: Float
+ @Composable
+ get() = contentAlpha(
+ highContrastAlpha = HighContrastContentAlpha.high,
+ lowContrastAlpha = LowContrastContentAlpha.high
+ )
+
+ /**
+ * A medium level of content alpha, used to represent medium emphasis text such as
+ * placeholder text in a [TextField].
+ */
+ val medium: Float
+ @Composable
+ get() = contentAlpha(
+ highContrastAlpha = HighContrastContentAlpha.medium,
+ lowContrastAlpha = LowContrastContentAlpha.medium
+ )
+
+ /**
+ * A low level of content alpha used to represent disabled components, such as text in a
+ * disabled [Button].
+ */
+ val disabled: Float
+ @Composable
+ get() = contentAlpha(
+ highContrastAlpha = HighContrastContentAlpha.disabled,
+ lowContrastAlpha = LowContrastContentAlpha.disabled
+ )
+
+ /**
+ * This default implementation uses separate alpha levels depending on the luminance of the
+ * incoming color, and whether the theme is light or dark. This is to ensure correct contrast
+ * and accessibility on all surfaces.
+ *
+ * See [HighContrastContentAlpha] and [LowContrastContentAlpha] for what the levels are
+ * used for, and under what circumstances.
+ */
+ @Composable
+ private fun contentAlpha(
+ /*@FloatRange(from = 0.0, to = 1.0)*/
+ highContrastAlpha: Float,
+ /*@FloatRange(from = 0.0, to = 1.0)*/
+ lowContrastAlpha: Float
+ ): Float {
+ val contentColor = LocalContentColor.current
+ return if (contentColor.luminance() < 0.5) highContrastAlpha else lowContrastAlpha
+ }
+}
+
+/**
+ * CompositionLocal containing the preferred content alpha for a given position in the hierarchy.
+ * This alpha is used for text and iconography ([Text] and [Icon]) to emphasize / de-emphasize
+ * different parts of a component. See the Material guide on
+ * [Text Legibility](https://material.io/design/color/text-legibility.html) for more information on
+ * alpha levels used by text and iconography.
+ *
+ * See [ContentAlpha] for the default levels used by most Material components.
+ *
+ * [MaterialTheme] sets this to [ContentAlpha.high] by default, as this is the default alpha for
+ * body text.
+ *
+ */
+val LocalContentAlpha = compositionLocalOf { 1f }
+
+/**
+ * Alpha levels for high luminance content in light theme, or low luminance content in dark theme.
+ *
+ * This content will typically be placed on colored surfaces, so it is important that the
+ * contrast here is higher to meet accessibility standards, and increase legibility.
+ *
+ * These levels are typically used for text / iconography in primary colored tabs /
+ * bottom navigation / etc.
+ */
+private object HighContrastContentAlpha {
+ const val high: Float = 1.00f
+ const val medium: Float = 0.74f
+ const val disabled: Float = 0.38f
+}
+
+/**
+ * Alpha levels for low luminance content in light theme, or high luminance content in dark theme.
+ *
+ * This content will typically be placed on grayscale surfaces, so the contrast here can be lower
+ * without sacrificing accessibility and legibility.
+ *
+ * These levels are typically used for body text on the main surface (white in light theme, grey
+ * in dark theme) and text / iconography in surface colored tabs / bottom navigation / etc.
+ */
+private object LowContrastContentAlpha {
+ const val high: Float = 0.87f
+ const val medium: Float = 0.60f
+ const val disabled: Float = 0.38f
+}
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentColor.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentColor.kt
new file mode 100644
index 0000000..a3d369b
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentColor.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.wear.compose.material
+
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.ui.graphics.Color
+
+/**
+ * CompositionLocal containing the preferred content color for a given position in the hierarchy.
+ * This typically represents the `on` color for a color in [Colors]. For example, if the background
+ * color is [Colors.surface], this color is typically set to [Colors.onSurface].
+ *
+ * This color should be used for any typography / iconography, to ensure that the color of these
+ * adjusts when the background color changes. For example, on a dark background, text should be
+ * light, and on a light background, text should be dark.
+ *
+ * Defaults to [Color.White] if no color has been explicitly set.
+ */
+val LocalContentColor = compositionLocalOf { Color.White }
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTextSelectionColors.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTextSelectionColors.kt
new file mode 100644
index 0000000..2838258
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTextSelectionColors.kt
@@ -0,0 +1,239 @@
+/*
+ * 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.wear.compose.material
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.foundation.text.selection.TextSelectionColors
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Remembers a [TextSelectionColors] based on [colors]. The handle color will be [Colors.primary]
+ * and the background color will be [Colors.primary] with alpha applied.
+ *
+ * See [calculateSelectionBackgroundColor].
+ */
+@Composable
+internal fun rememberTextSelectionColors(colors: Colors): TextSelectionColors {
+ val primaryColor = colors.primary
+ val backgroundColor = colors.background
+ // Test with ContentAlpha.medium to ensure that the selection background is accessible in the
+ // 'worst case' scenario. We explicitly don't test with ContentAlpha.disabled, as disabled
+ // text shouldn't be selectable / is noted as disabled for accessibility purposes.
+ val textColorWithLowestAlpha = colors.contentColorFor(backgroundColor)
+ .takeOrElse {
+ LocalContentColor.current
+ }.copy(
+ alpha = ContentAlpha.medium
+ )
+ return remember(primaryColor, backgroundColor, textColorWithLowestAlpha) {
+ TextSelectionColors(
+ handleColor = colors.primary,
+ backgroundColor = calculateSelectionBackgroundColor(
+ selectionColor = primaryColor,
+ textColor = textColorWithLowestAlpha,
+ backgroundColor = backgroundColor
+ )
+ )
+ }
+}
+
+/**
+ * Best-effort calculates a color (with alpha) for the selection background that (if possible)
+ * will have at least [DesiredContrastRatio] with [textColor], when the selection background
+ * is on top of [backgroundColor].
+ *
+ * Since this is a minimum contrast ratio, [textColor] should have the lowest alpha that
+ * may be applied to content so we can ensure that the selection background color is accessible
+ * in that worst-case scenario for contrast.
+ *
+ * @param selectionColor the 'raw' (without alpha) selection color that we should search alpha for
+ * @param textColor the color of text with minimal alpha applied to test for contrast with
+ * @param backgroundColor the color of the background that the selection color will typically be
+ * placed against
+ *
+ * @return a resulting [selectionColor] with alpha applied that results in acceptable contrast
+ * (if possible with the values for [selectionColor], [textColor] and [backgroundColor]).
+ */
+/*@VisibleForTesting*/
+internal fun calculateSelectionBackgroundColor(
+ selectionColor: Color,
+ textColor: Color,
+ backgroundColor: Color
+): Color {
+ val maximumContrastRatio = calculateContrastRatio(
+ selectionColor = selectionColor,
+ selectionColorAlpha = DefaultSelectionBackgroundAlpha,
+ textColor = textColor,
+ backgroundColor = backgroundColor
+ )
+
+ val minimumContrastRatio = calculateContrastRatio(
+ selectionColor = selectionColor,
+ selectionColorAlpha = MinimumSelectionBackgroundAlpha,
+ textColor = textColor,
+ backgroundColor = backgroundColor
+ )
+
+ val alpha = when {
+ // If the default alpha has enough contrast, use that
+ maximumContrastRatio >= DesiredContrastRatio -> DefaultSelectionBackgroundAlpha
+ // If the minimum alpha still does not have enough contrast, just use the minimum and return
+ minimumContrastRatio < DesiredContrastRatio -> MinimumSelectionBackgroundAlpha
+ else -> binarySearchForAccessibleSelectionColorAlpha(
+ selectionColor = selectionColor,
+ textColor = textColor,
+ backgroundColor = backgroundColor
+ )
+ }
+
+ return selectionColor.copy(alpha = alpha)
+}
+
+/**
+ * Binary searches for the highest alpha for selection color that results in a contrast ratio at
+ * least equal to and within 1% of [DesiredContrastRatio].
+ *
+ * The resulting alpha will be within the range of [MinimumSelectionBackgroundAlpha] and
+ * [DefaultSelectionBackgroundAlpha] - since not all values for [selectionColor], [textColor] and
+ * [backgroundColor] can be guaranteed to produce an accessible contrast ratio, this is a
+ * best-effort attempt and [MinimumSelectionBackgroundAlpha] might still not produce an
+ * accessible contrast ratio. In this case developers are encouraged to manually choose a
+ * different color for selection that _is_ accessible with their chosen content and background
+ * colors.
+ *
+ * Caps the number of attempts at 7 for performance and to avoid infinite searching when there is
+ * no value that results in an accessible contrast ratio. Because alpha is limited to [0,1], 7
+ * steps results in a precision of ~0.01, since log2(1/0.01) ≈ 7.
+ *
+ * Note: binary searching here is chosen since it is not possible to 'solve' for alpha, since the
+ * transformation from color -> contrast ratio is not linear (the gamma exponent for sRGB colors
+ * is 2.4). We can approximate this to 2, but this results in not that accurate solutions, and we
+ * need to guarantee that they are at least above [DesiredContrastRatio] - falling just below is
+ * not an acceptable result.
+ *
+ * @param selectionColor the 'raw' (without alpha) selection color that we should search alpha for
+ * @param textColor the color of text with minimal alpha applied to test for contrast with
+ * @param backgroundColor the color of the background that the selection color will typically be
+ * placed against
+ */
+private fun binarySearchForAccessibleSelectionColorAlpha(
+ selectionColor: Color,
+ textColor: Color,
+ backgroundColor: Color
+): Float {
+ var attempts = 0
+ val maxAttempts = 7
+
+ var lowAlpha = MinimumSelectionBackgroundAlpha
+ var alpha = DefaultSelectionBackgroundAlpha
+ var highAlpha = DefaultSelectionBackgroundAlpha
+
+ while (attempts < maxAttempts) {
+ val contrastRatio = calculateContrastRatio(
+ selectionColor = selectionColor,
+ selectionColorAlpha = alpha,
+ textColor = textColor,
+ backgroundColor = backgroundColor
+ )
+
+ // Percentage error of the calculated contrast compared to the actual contrast. Positive
+ // numbers here mean we have higher contrast than needed.
+ val percentageError = (contrastRatio / DesiredContrastRatio) - 1f
+ when {
+ // Contrast is at most 1% above the guideline, return
+ percentageError in 0f..0.01f -> break
+ // Contrast too low, decrease alpha
+ percentageError < 0f -> highAlpha = alpha
+ // Contrast higher than required, increase alpha
+ else -> lowAlpha = alpha
+ }
+ alpha = (highAlpha + lowAlpha) / 2f
+ attempts++
+ }
+
+ return alpha
+}
+
+/**
+ * Calculates the contrast ratio of [textColor] against [selectionColor] with
+ * [selectionColorAlpha], all on top of [backgroundColor].
+ *
+ * Both the [selectionColor] and [textColor] will be composited to handle transparency.
+ *
+ * @param selectionColor the 'raw' (without alpha) selection color that we should search alpha for
+ * @param selectionColorAlpha the alpha for [selectionColor] to test contrast with
+ * @param textColor the color of text with minimal alpha applied to test for contrast with
+ * @param backgroundColor the color of the background that the selection color will typically be
+ * placed against
+ *
+ * @return the contrast ratio as a value between 1 and 21. See [calculateContrastRatio]
+ */
+private fun calculateContrastRatio(
+ selectionColor: Color,
+ selectionColorAlpha: Float,
+ textColor: Color,
+ backgroundColor: Color
+): Float {
+ val compositeBackground = selectionColor.copy(alpha = selectionColorAlpha)
+ .compositeOver(backgroundColor)
+ val compositeTextColor = textColor.compositeOver(compositeBackground)
+ return calculateContrastRatio(compositeTextColor, compositeBackground)
+}
+
+/**
+ * Calculates the contrast ratio of [foreground] against [background], returning a value between
+ * 1 and 21. (1:1 and 21:1 ratios).
+ *
+ * Formula taken from [WCAG 2.0](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html#contrast-ratiodef)
+ *
+ * Note: [foreground] and [background] *must* be opaque. See [Color.compositeOver] to pre-composite
+ * a translucent foreground over the background.
+ *
+ * @return the contrast ratio as a value between 1 and 21. See [calculateContrastRatio]
+ */
+/*@VisibleForTesting*/
+internal fun calculateContrastRatio(foreground: Color, background: Color): Float {
+ val foregroundLuminance = foreground.luminance() + 0.05f
+ val backgroundLuminance = background.luminance() + 0.05f
+
+ return max(foregroundLuminance, backgroundLuminance) /
+ min(foregroundLuminance, backgroundLuminance)
+}
+
+/**
+ * Default selection background alpha - we will try and use this if it is accessible and produces
+ * the correct contrast ratio.
+ */
+private const val DefaultSelectionBackgroundAlpha = 0.4f
+
+/**
+ * Not all combinations of text color and selection color will have a reasonable alpha that
+ * produces a contrast ratio of at least [DesiredContrastRatio] - in this case just pick a low
+ * but still visible alpha so at least the contrast ratio is as good as it can be - this is
+ * preferable to crashing at runtime.
+ */
+private const val MinimumSelectionBackgroundAlpha = DefaultSelectionBackgroundAlpha / 2f
+
+/**
+ * Material and WCAG 2.0 sc 1.4.3 minimum contrast for AA text
+ */
+private const val DesiredContrastRatio = 4.5f
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTheme.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTheme.kt
new file mode 100644
index 0000000..f92126e
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTheme.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.wear.compose.material
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.material.ripple.LocalRippleTheme
+import androidx.compose.material.ripple.RippleTheme
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.structuralEqualityPolicy
+import androidx.compose.ui.text.TextStyle
+
+// TODO: Provide references to the Wear material design specs.
+/**
+ * MaterialTheme defines the styling principles from the WearOS Material design specification
+ * which extends the Material design specification.
+ *
+ * Wear Material components from package/sub-packages in [androidx.wear.compose.material] use values
+ * provided here when retrieving default values.
+ *
+ * It defines colors as specified in the [Wear Material Color theme spec](https://),
+ * typography defined in the [Wear Material Type Scale spec](https://),
+ * and shapes defined in the [Wear Shape scheme](https://).
+ *
+ * All values may be set by providing this component with the [colors][Colors],
+ * [typography][Typography], and [shapes][Shapes] attributes. Use this to configure the
+ * overall theme of elements within this MaterialTheme.
+ *
+ * Any values that are not set will inherit the current value from the theme, falling back to the
+ * defaults if there is no parent MaterialTheme. This allows using a MaterialTheme at the
+ * top of your application, and then separate MaterialTheme(s) for different screens / parts of
+ * your UI, overriding only the parts of the theme definition that need to change.
+ *
+ * @param colors A complete definition of the Wear Material Color theme for this hierarchy
+ * @param typography A set of text styles to be used as this hierarchy's typography system
+ * @param shapes A set of shapes to be used by the components in this hierarchy
+ */
+@Composable
+public fun MaterialTheme(
+ colors: Colors = MaterialTheme.colors,
+ typography: Typography = MaterialTheme.typography,
+ shapes: Shapes = MaterialTheme.shapes,
+ content: @Composable () -> Unit
+) {
+ val rememberedColors = remember {
+ // Explicitly creating a new object here so we don't mutate the initial [colors]
+ // provided, and overwrite the values set in it.
+ colors.copy()
+ }.apply { updateColorsFrom(colors) }
+ val rippleIndication = rememberRipple()
+ val selectionColors = rememberTextSelectionColors(rememberedColors)
+ CompositionLocalProvider(
+ LocalColors provides rememberedColors,
+ LocalShapes provides shapes,
+ LocalTypography provides typography,
+ LocalContentAlpha provides ContentAlpha.high,
+ LocalIndication provides rippleIndication,
+ LocalRippleTheme provides MaterialRippleTheme,
+ LocalTextSelectionColors provides selectionColors,
+
+ ) {
+ ProvideTextStyle(value = typography.body1, content = content)
+ }
+}
+
+public object MaterialTheme {
+ public val colors: Colors
+ @ReadOnlyComposable
+ @Composable
+ get() = LocalColors.current
+
+ public val typography: Typography
+ @ReadOnlyComposable
+ @Composable
+ get() = LocalTypography.current
+
+ public val shapes: Shapes
+ @ReadOnlyComposable
+ @Composable
+ get() = LocalShapes.current
+}
+
+@Immutable
+private object MaterialRippleTheme : RippleTheme {
+ @Composable
+ override fun defaultColor() = RippleTheme.defaultRippleColor(
+ contentColor = LocalContentColor.current,
+ lightTheme = false
+ )
+
+ @Composable
+ override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
+ contentColor = LocalContentColor.current,
+ lightTheme = false
+ )
+}
+
+/**
+ * CompositionLocal containing the preferred [TextStyle] that will be used by [Text] components by
+ * default. To set the value for this CompositionLocal, see [ProvideTextStyle] which will merge any
+ * missing [TextStyle] properties with the existing [TextStyle] set in this CompositionLocal.
+ *
+ * @see ProvideTextStyle
+ */
+val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { TextStyle.Default }
+
+// TODO: b/156598010 remove this and replace with fold definition on the backing CompositionLocal
+/**
+ * This function is used to set the current value of [LocalTextStyle], merging the given style
+ * with the current style values for any missing attributes. Any [Text] components included in
+ * this component's [content] will be styled with this style unless styled explicitly.
+ *
+ * @see LocalTextStyle
+ */
+@Composable
+fun ProvideTextStyle(value: TextStyle, content: @Composable () -> Unit) {
+ val mergedStyle = LocalTextStyle.current.merge(value)
+ CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content)
+}
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Shapes.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Shapes.kt
new file mode 100644
index 0000000..4248dfb
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Shapes.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.wear.compose.material
+
+import androidx.compose.foundation.shape.CornerBasedShape
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.unit.dp
+
+/**
+ * Components are grouped into shape categories based on common features. These categories provide a
+ * way to change multiple component values at once, by changing the category’s values.
+ *
+ */
+@Immutable
+public class Shapes(
+ /**
+ * Buttons and Chips use this shape
+ */
+ val small: CornerBasedShape = RoundedCornerShape(corner = CornerSize(50)),
+
+ val medium: CornerBasedShape = RoundedCornerShape(4.dp),
+ /**
+ * Cards use this shape
+ */
+ val large: CornerBasedShape = RoundedCornerShape(24.dp),
+) {
+
+ /**
+ * Returns a copy of this Shapes, optionally overriding some of the values.
+ */
+ public fun copy(
+ small: CornerBasedShape = this.small,
+ medium: CornerBasedShape = this.medium,
+ large: CornerBasedShape = this.large,
+ ): Shapes = Shapes(
+ small = small,
+ medium = medium,
+ large = large,
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Shapes) return false
+
+ if (small != other.small) return false
+ if (medium != other.medium) return false
+ if (large != other.large) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = small.hashCode()
+ result = 31 * result + medium.hashCode()
+ result = 31 * result + large.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "Shapes(small=$small, medium=$medium, large=$large)"
+ }
+}
+
+/**
+ * CompositionLocal used to specify the default shapes for the surfaces.
+ */
+internal val LocalShapes = staticCompositionLocalOf { Shapes() }
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Typography.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Typography.kt
new file mode 100644
index 0000000..e222efa
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Typography.kt
@@ -0,0 +1,242 @@
+/*
+ * 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.wear.compose.material
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+/**
+ * Class holding typography definitions as defined by the Wear Material typography specification.
+ *
+ * @property display1 display1 is the largest headline, reserved for short, important text or
+ * numerals. For headlines, you can choose an expressive font, such as a display, handwritten, or
+ * script style. These unconventional font designs have details and intricacy that help attract the
+ * eye.Ø
+ *
+ * @property display2 display2 is the second largest headline, reserved for short, important text or
+ * numerals. For headlines, you can choose an expressive font, such as a display, handwritten, or
+ * script style. These unconventional font designs have details and intricacy that help attract the
+ * eye.
+ *
+ * @property display3 display3 is the third largest headline, reserved for short, important text or
+ * numerals. For headlines, you can choose an expressive font, such as a display, handwritten, or
+ * script style. These unconventional font designs have details and intricacy that help attract the
+ * eye.
+ *
+ * @property title1 title1 is the largest title, and is typically reserved for medium-emphasis text
+ * that is shorter in length. Serif or sans serif typefaces work well for subtitles.
+ *
+ * @property title2 title2 is the medium title, and is typically reserved for medium-emphasis text
+ * that is shorter in length. Serif or sans serif typefaces work well for subtitles.
+ *
+ * @property title3 title3 is the smallest title, and is typically reserved for medium-emphasis text
+ * that is shorter in length. Serif or sans serif typefaces work well for subtitles.
+ *
+ * @property body1 body1 is the largest body, and is typically used for long-form writing as it
+ * works well for small text sizes. For longer sections of text, a serif or sans serif typeface is
+ * recommended.
+ *
+ * @property body2 body2 is the smallest body, and is typically used for long-form writing as it
+ * works well for small text sizes. For longer sections of text, a serif or sans serif typeface is
+ * recommended.
+ *
+ * @property button button text is a call to action used in different types of buttons (such as
+ * text, outlined and contained buttons) and in tabs, dialogs, and cards. Button text is typically
+ * sans serif, using all caps text.
+ *
+ * @property caption1 caption1 is one of the smallest font sizes. It is used sparingly to annotate
+ * imagery or to introduce a headline.
+ *
+ * @property caption2 caption2 is one of the smallest font sizes. It is used sparingly to annotate
+ * imagery or to introduce a headline.
+ */
+@Immutable
+public class Typography internal constructor (
+ val display1: TextStyle,
+ val display2: TextStyle,
+ val display3: TextStyle,
+ val title1: TextStyle,
+ val title2: TextStyle,
+ val title3: TextStyle,
+ val body1: TextStyle,
+ val body2: TextStyle,
+ val button: TextStyle,
+ val caption1: TextStyle,
+ val caption2: TextStyle,
+) {
+ constructor (
+ defaultFontFamily: FontFamily = FontFamily.Default,
+ display1: TextStyle = TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 50.sp,
+ letterSpacing = 0.2.sp
+ ),
+ display2: TextStyle = TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 40.sp,
+ letterSpacing = 0.5.sp
+ ),
+ display3: TextStyle = TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 30.sp,
+ letterSpacing = 0.5.sp
+ ),
+ title1: TextStyle = TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 24.sp,
+ letterSpacing = 0.sp
+ ),
+ title2: TextStyle = TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 20.sp,
+ letterSpacing = 0.2.sp
+ ),
+ title3: TextStyle = TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ letterSpacing = 0.2.sp
+ ),
+ body1: TextStyle = TextStyle(
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ letterSpacing = 0.sp
+ ),
+ body2: TextStyle = TextStyle(
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ letterSpacing = 0.25.sp
+ ),
+ button: TextStyle = TextStyle(
+ fontWeight = FontWeight.Bold,
+ fontSize = 14.sp,
+ letterSpacing = 0.sp
+ ),
+ caption1: TextStyle = TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ letterSpacing = 0.sp
+ ),
+ caption2: TextStyle = TextStyle(
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ letterSpacing = 0.sp
+ )
+ ) : this(
+ display1 = display1.withDefaultFontFamily(defaultFontFamily),
+ display2 = display2.withDefaultFontFamily(defaultFontFamily),
+ display3 = display3.withDefaultFontFamily(defaultFontFamily),
+ title1 = title1.withDefaultFontFamily(defaultFontFamily),
+ title2 = title2.withDefaultFontFamily(defaultFontFamily),
+ title3 = title3.withDefaultFontFamily(defaultFontFamily),
+ body1 = body1.withDefaultFontFamily(defaultFontFamily),
+ body2 = body2.withDefaultFontFamily(defaultFontFamily),
+ button = button.withDefaultFontFamily(defaultFontFamily),
+ caption1 = caption1.withDefaultFontFamily(defaultFontFamily),
+ caption2 = caption2.withDefaultFontFamily(defaultFontFamily),
+ )
+
+ /**
+ * Returns a copy of this Typography, optionally overriding some of the values.
+ */
+ public fun copy(
+ display1: TextStyle = this.display1,
+ display2: TextStyle = this.display2,
+ display3: TextStyle = this.display3,
+ title1: TextStyle = this.title1,
+ title2: TextStyle = this.title2,
+ title3: TextStyle = this.title3,
+ body1: TextStyle = this.body1,
+ body2: TextStyle = this.body2,
+ button: TextStyle = this.button,
+ caption1: TextStyle = this.caption1,
+ caption2: TextStyle = this.caption2,
+ ): Typography = Typography(
+ display1,
+ display2,
+ display3,
+ title1,
+ title2,
+ title3,
+ body1,
+ body2,
+ button,
+ caption1,
+ caption2
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Typography) return false
+
+ if (display1 != other.display1) return false
+ if (display2 != other.display2) return false
+ if (display3 != other.display3) return false
+ if (title1 != other.title1) return false
+ if (title2 != other.title2) return false
+ if (title3 != other.title3) return false
+ if (body1 != other.body1) return false
+ if (body2 != other.body2) return false
+ if (button != other.button) return false
+ if (caption1 != other.caption1) return false
+ if (caption2 != other.caption2) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = display1.hashCode()
+ result = 31 * result + display2.hashCode()
+ result = 31 * result + display3.hashCode()
+ result = 31 * result + title1.hashCode()
+ result = 31 * result + title2.hashCode()
+ result = 31 * result + title3.hashCode()
+ result = 31 * result + body1.hashCode()
+ result = 31 * result + body2.hashCode()
+ result = 31 * result + button.hashCode()
+ result = 31 * result + caption1.hashCode()
+ result = 31 * result + caption2.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "Typography(display1=$display1, display2=$display2, display3=$display3, " +
+ "title1=$title1, title2=$title2, title3=$title3, body1=$body1, body2=$body2, " +
+ "button=$button, caption1=$caption1, caption2=$caption2)"
+ }
+}
+
+/**
+ * @return [this] if there is a [FontFamily] defined, otherwise copies [this] with [default] as
+ * the [FontFamily].
+ */
+private fun TextStyle.withDefaultFontFamily(default: FontFamily): TextStyle {
+ return if (fontFamily != null) this else copy(fontFamily = default)
+}
+
+/**
+ * This Ambient holds on to the current definition of typography for this application as described
+ * by the Wear Material spec. You can read the values in it when creating custom components that
+ * want to use Wear Material types, as well as override the values when you want to re-style a part
+ * of your hierarchy. Material components related to text such as [Button] will use this Ambient
+ * to set values with which to style children text components.
+ *
+ * To access values within this ambient, use [MaterialTheme.typography].
+ */
+internal val LocalTypography = staticCompositionLocalOf { Typography() }
diff --git a/wear/tiles/tiles-proto/src/main/proto/layout.proto b/wear/tiles/tiles-proto/src/main/proto/layout.proto
index ea3d8d8..008344f 100644
--- a/wear/tiles/tiles-proto/src/main/proto/layout.proto
+++ b/wear/tiles/tiles-proto/src/main/proto/layout.proto
@@ -312,6 +312,7 @@
// An extensible ContentScaleMode property.
message ContentScaleModeProp {
+ // The value
ContentScaleMode value = 1;
}
diff --git a/wear/tiles/tiles-proto/src/main/proto/requests.proto b/wear/tiles/tiles-proto/src/main/proto/requests.proto
index 17e9522..9fcdfa0 100644
--- a/wear/tiles/tiles-proto/src/main/proto/requests.proto
+++ b/wear/tiles/tiles-proto/src/main/proto/requests.proto
@@ -22,7 +22,8 @@
// Parameters passed to a Tile provider when the renderer is requesting a
// specific resource version.
message ResourcesRequest {
- // The version of the resources being fetched
+ // The version of the resources being fetched. This is the same as the
+ // requested resource version, passed in Tile.
string version = 1;
// Requested resource IDs. If not specified, all resources for the given
diff --git a/wear/tiles/tiles-proto/src/main/proto/resources.proto b/wear/tiles/tiles-proto/src/main/proto/resources.proto
index babb7d8..eb4d8af 100644
--- a/wear/tiles/tiles-proto/src/main/proto/resources.proto
+++ b/wear/tiles/tiles-proto/src/main/proto/resources.proto
@@ -49,7 +49,7 @@
// to what it thinks is appropriate.
message ImageResource {
// An image resource that maps to an Android drawable by resource ID.
- AndroidImageResourceByResId android_resource_by_resid = 1;
+ AndroidImageResourceByResId android_resource_by_res_id = 1;
// An image resource that contains the image data inline.
InlineImageResource inline_resource = 2;
@@ -64,7 +64,8 @@
// to separately fetch the resources.
//
// This value must match the version of the resources required by the tile
- // for the tile to render successfully.
+ // for the tile to render successfully, and must match the resource version
+ // specified in ResourcesRequest which triggered this request.
string version = 1;
// A map of resource_ids to images, which can be used by layouts.
diff --git a/wear/tiles/tiles-proto/src/main/proto/tile.proto b/wear/tiles/tiles-proto/src/main/proto/tile.proto
index 1bbded9..fb41af4 100644
--- a/wear/tiles/tiles-proto/src/main/proto/tile.proto
+++ b/wear/tiles/tiles-proto/src/main/proto/tile.proto
@@ -12,7 +12,10 @@
// A holder for a tile. This specifies the resources to use for this delivery
// of the tile, and the timeline for the tile.
message Tile {
- // The resource version required for these tiles.
+ // The resource version required for these tiles. This can be any developer-defined
+ // string; it is only used to cache resources, and is passed in
+ // ResourcesRequest if the system does not have a copy of the specified
+ // resource version.
string resources_version = 1;
// The tiles to show in the carousel, along with their validity periods.
@@ -26,5 +29,12 @@
// your tile at some point in the future after this interval has lapsed. A
// value of 0 here signifies that auto-refreshes should not be used (i.e. you
// will manually request updates via TileProviderService#getRequester).
+ //
+ // This mechanism should not be used to update your tile more frequently than
+ // once a minute, and the system may throttle your updates if you request
+ // updates faster than this interval. This interval is also inexact; the
+ // system will generally update your tile if it is on-screen, or about to be
+ // on-screen, although this is not guaranteed due to system-level
+ // optimizations.
uint64 freshness_interval_millis = 4;
}
diff --git a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
index 5fb0cfb..90053ee 100644
--- a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
+++ b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
@@ -170,14 +170,14 @@
.putIdToImage(
"android",
ImageResource.newBuilder()
- .setAndroidResourceByResid(
+ .setAndroidResourceByResId(
AndroidImageResourceByResId.newBuilder()
.setResourceId(R.drawable.android_24dp))
.build())
.putIdToImage(
"android_withbg_120dp",
ImageResource.newBuilder()
- .setAndroidResourceByResid(
+ .setAndroidResourceByResId(
AndroidImageResourceByResId.newBuilder()
.setResourceId(R.mipmap.android_withbg_120dp))
.build())
@@ -194,14 +194,14 @@
.putIdToImage(
"broken_image",
ImageResource.newBuilder()
- .setAndroidResourceByResid(
+ .setAndroidResourceByResId(
AndroidImageResourceByResId.newBuilder()
.setResourceId(R.drawable.broken_drawable))
.build())
.putIdToImage(
"missing_image",
ImageResource.newBuilder()
- .setAndroidResourceByResid(
+ .setAndroidResourceByResId(
AndroidImageResourceByResId.newBuilder().setResourceId(-1))
.build())
.build();
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
index 528c8fe..0d63cdd 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
@@ -113,10 +113,10 @@
"Resource " + protoResourceId + " is not defined in resources bundle"));
}
- if (imageResource.hasAndroidResourceByResid()
+ if (imageResource.hasAndroidResourceByResId()
&& mAndroidImageResourceByResIdResolver != null) {
AndroidImageResourceByResIdResolver resolver = mAndroidImageResourceByResIdResolver;
- return resolver.getDrawable(imageResource.getAndroidResourceByResid());
+ return resolver.getDrawable(imageResource.getAndroidResourceByResId());
}
if (imageResource.hasInlineResource() && mInlineImageResourceResolver != null) {
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/TilesTimelineManager.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/TilesTimelineManager.java
index 1261606..643dc18 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/TilesTimelineManager.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/TilesTimelineManager.java
@@ -98,6 +98,6 @@
/** Tears down this Timeline Manager. This will ensure any set alarms are cleared up. */
@Override
public void close() {
- mManager.deInit();
+ mManager.close();
}
}
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
index ac69c6f..f1cbf3b 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
@@ -146,6 +146,7 @@
* @return The time in millis that {@code entry} should be considered to be expired. This value
* will be {@link Long#MAX_VALUE} if {@code entry} does not expire.
*/
+ @MainThread
public long findCurrentTimelineEntryExpiry(@NonNull TimelineEntry entry, long fromTimeMillis) {
long currentSmallestExpiry = Long.MAX_VALUE;
long entryValidityLength = Long.MAX_VALUE;
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineManagerInternal.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineManagerInternal.java
index d83777f..31fb68f 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineManagerInternal.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineManagerInternal.java
@@ -36,7 +36,7 @@
* <p>This handles the dispatching of single Tiles layouts from a full timeline. It will set the
* correct alarms to detect when a layout should be updated, and dispatch it to its listener.
*/
-public class TilesTimelineManagerInternal {
+public class TilesTimelineManagerInternal implements AutoCloseable {
// 1 minute min delay between tiles.
public static final long MIN_TILE_UPDATE_DELAY_MILLIS = MINUTES.toMillis(1);
@@ -102,7 +102,8 @@
}
/** Tears down this Timeline Manager. This will ensure any set alarms are cleared up. */
- public void deInit() {
+ @Override
+ public void close() {
if (mAlarmListener != null) {
mAlarmManager.cancel(mAlarmListener);
mAlarmListener = null;
diff --git a/wear/tiles/tiles/api/current.txt b/wear/tiles/tiles/api/current.txt
index ee9c959..708ea18 100644
--- a/wear/tiles/tiles/api/current.txt
+++ b/wear/tiles/tiles/api/current.txt
@@ -691,8 +691,8 @@
public static final class ResourceBuilders.ImageResource.Builder {
method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource build();
- method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
- method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
+ method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
+ method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource);
method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource.Builder);
}
@@ -807,19 +807,15 @@
}
public static class EventReaders.TileAddEvent {
- method public int getTileId();
}
public static class EventReaders.TileEnterEvent {
- method public int getTileId();
}
public static class EventReaders.TileLeaveEvent {
- method public int getTileId();
}
public static class EventReaders.TileRemoveEvent {
- method public int getTileId();
}
public class RequestReaders {
@@ -828,14 +824,12 @@
public static class RequestReaders.ResourcesRequest {
method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
method public java.util.List<java.lang.String!> getResourceIds();
- method public int getTileId();
method public String getVersion();
}
public static class RequestReaders.TileRequest {
method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
method public androidx.wear.tiles.builders.StateBuilders.State getState();
- method public int getTileId();
}
}
diff --git a/wear/tiles/tiles/api/public_plus_experimental_current.txt b/wear/tiles/tiles/api/public_plus_experimental_current.txt
index 5a4d2b4..4d017ea9 100644
--- a/wear/tiles/tiles/api/public_plus_experimental_current.txt
+++ b/wear/tiles/tiles/api/public_plus_experimental_current.txt
@@ -696,8 +696,8 @@
public static final class ResourceBuilders.ImageResource.Builder {
method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource build();
- method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
- method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
+ method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
+ method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource);
method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource.Builder);
}
@@ -812,19 +812,15 @@
}
public static class EventReaders.TileAddEvent {
- method public int getTileId();
}
public static class EventReaders.TileEnterEvent {
- method public int getTileId();
}
public static class EventReaders.TileLeaveEvent {
- method public int getTileId();
}
public static class EventReaders.TileRemoveEvent {
- method public int getTileId();
}
public class RequestReaders {
@@ -833,14 +829,12 @@
public static class RequestReaders.ResourcesRequest {
method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
method public java.util.List<java.lang.String!> getResourceIds();
- method public int getTileId();
method public String getVersion();
}
public static class RequestReaders.TileRequest {
method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
method public androidx.wear.tiles.builders.StateBuilders.State getState();
- method public int getTileId();
}
}
diff --git a/wear/tiles/tiles/api/restricted_current.txt b/wear/tiles/tiles/api/restricted_current.txt
index ee9c959..708ea18 100644
--- a/wear/tiles/tiles/api/restricted_current.txt
+++ b/wear/tiles/tiles/api/restricted_current.txt
@@ -691,8 +691,8 @@
public static final class ResourceBuilders.ImageResource.Builder {
method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource build();
- method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
- method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
+ method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
+ method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource);
method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource.Builder);
}
@@ -807,19 +807,15 @@
}
public static class EventReaders.TileAddEvent {
- method public int getTileId();
}
public static class EventReaders.TileEnterEvent {
- method public int getTileId();
}
public static class EventReaders.TileLeaveEvent {
- method public int getTileId();
}
public static class EventReaders.TileRemoveEvent {
- method public int getTileId();
}
public class RequestReaders {
@@ -828,14 +824,12 @@
public static class RequestReaders.ResourcesRequest {
method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
method public java.util.List<java.lang.String!> getResourceIds();
- method public int getTileId();
method public String getVersion();
}
public static class RequestReaders.TileRequest {
method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
method public androidx.wear.tiles.builders.StateBuilders.State getState();
- method public int getTileId();
}
}
diff --git a/wear/tiles/tiles/lint-baseline.xml b/wear/tiles/tiles/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/tiles/tiles/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/ResourceBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/ResourceBuilders.java
index 16abf2d..e2d8230 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/ResourceBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/ResourceBuilders.java
@@ -241,18 +241,18 @@
/** Sets an image resource that maps to an Android drawable by resource ID. */
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder setAndroidResourceByResid(
- @NonNull AndroidImageResourceByResId androidResourceByResid) {
- mImpl.setAndroidResourceByResid(androidResourceByResid.toProto());
+ public Builder setAndroidResourceByResId(
+ @NonNull AndroidImageResourceByResId androidResourceByResId) {
+ mImpl.setAndroidResourceByResId(androidResourceByResId.toProto());
return this;
}
/** Sets an image resource that maps to an Android drawable by resource ID. */
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder setAndroidResourceByResid(
- @NonNull AndroidImageResourceByResId.Builder androidResourceByResidBuilder) {
- mImpl.setAndroidResourceByResid(androidResourceByResidBuilder.build().toProto());
+ public Builder setAndroidResourceByResId(
+ @NonNull AndroidImageResourceByResId.Builder androidResourceByResIdBuilder) {
+ mImpl.setAndroidResourceByResId(androidResourceByResIdBuilder.build().toProto());
return this;
}
@@ -324,7 +324,8 @@
* the resources.
*
* <p>This value must match the version of the resources required by the tile for the
- * tile to render successfully.
+ * tile to render successfully, and must match the resource version specified in
+ * ResourcesRequest which triggered this request.
*/
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/TileBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/TileBuilders.java
index ff5b034..847aacd 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/TileBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/TileBuilders.java
@@ -66,8 +66,12 @@
Builder() {}
- /** Sets the resource version required for these tiles. */
@SuppressLint("MissingGetterMatchingBuilder")
+ /**
+ * Sets the resource version required for these tiles. This can be any developer-defined
+ * string; it is only used to cache resources, and is passed in ResourcesRequest if the
+ * system does not have a copy of the specified resource version.
+ */
@NonNull
public Builder setResourcesVersion(@NonNull String resourcesVersion) {
mImpl.setResourcesVersion(resourcesVersion);
@@ -96,6 +100,12 @@
* point in the future after this interval has lapsed. A value of 0 here signifies that
* auto-refreshes should not be used (i.e. you will manually request updates via
* TileProviderService#getRequester).
+ *
+ * <p>This mechanism should not be used to update your tile more frequently than once a
+ * minute, and the system may throttle your updates if you request updates faster than
+ * this interval. This interval is also inexact; the system will generally update your
+ * tile if it is on-screen, or about to be on-screen, although this is not guaranteed
+ * due to system-level optimizations.
*/
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/EventReaders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/EventReaders.java
index 2ae5a0a..f015db7 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/EventReaders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/EventReaders.java
@@ -33,6 +33,7 @@
/** Reader for Tile add event parameters. */
public static class TileAddEvent {
+ @SuppressWarnings("UnusedVariable")
private final EventProto.TileAddEvent mProto;
private TileAddEvent(@NonNull EventProto.TileAddEvent proto) {
@@ -57,15 +58,11 @@
"Passed TileAddEventData did not contain a valid proto payload", ex);
}
}
-
- /** Get the tile ID of the tile added to the carousel. */
- public int getTileId() {
- return mProto.getTileId();
- }
}
/** Reader for Tile remove event parameters. */
public static class TileRemoveEvent {
+ @SuppressWarnings("UnusedVariable")
private final EventProto.TileRemoveEvent mProto;
private TileRemoveEvent(@NonNull EventProto.TileRemoveEvent proto) {
@@ -90,15 +87,11 @@
"Passed TileRemoveEventData did not contain a valid proto payload", ex);
}
}
-
- /** Get the tile ID of the tile removed from the carousel. */
- public int getTileId() {
- return mProto.getTileId();
- }
}
/** Reader for Tile enter event parameters. */
public static class TileEnterEvent {
+ @SuppressWarnings("UnusedVariable")
private final EventProto.TileEnterEvent mProto;
private TileEnterEvent(@NonNull EventProto.TileEnterEvent proto) {
@@ -123,15 +116,11 @@
"Passed TileEnterEventData did not contain a valid proto payload", ex);
}
}
-
- /** Get the tile ID of the tile that was entered. */
- public int getTileId() {
- return mProto.getTileId();
- }
}
/** Reader for a Tile leave event parameters. */
public static class TileLeaveEvent {
+ @SuppressWarnings("UnusedVariable")
private final EventProto.TileLeaveEvent mProto;
private TileLeaveEvent(@NonNull EventProto.TileLeaveEvent proto) {
@@ -156,10 +145,5 @@
"Passed TileLeaveEventData did not contain a valid proto payload", ex);
}
}
-
- /** Get the tile ID of the tile that was left. */
- public int getTileId() {
- return mProto.getTileId();
- }
}
}
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/RequestReaders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/RequestReaders.java
index d3a50de6..4b2d024 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/RequestReaders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/RequestReaders.java
@@ -36,6 +36,7 @@
/** Reader for Tile request parameters. */
public static class TileRequest {
private final RequestProto.TileRequest mProto;
+ @SuppressWarnings("UnusedVariable")
private final int mTileId;
private TileRequest(RequestProto.TileRequest proto, int tileId) {
@@ -55,10 +56,6 @@
return new DeviceParameters(mProto.getDeviceParameters());
}
- public int getTileId() {
- return mTileId;
- }
-
/** @hide */
@RestrictTo(Scope.LIBRARY)
@NonNull
@@ -78,6 +75,7 @@
/** Reader for resource request parameters. */
public static class ResourcesRequest {
private final RequestProto.ResourcesRequest mProto;
+ @SuppressWarnings("UnusedVariable")
private final int mTileId;
private ResourcesRequest(@NonNull RequestProto.ResourcesRequest proto, int tileId) {
@@ -101,10 +99,6 @@
}
}
- public int getTileId() {
- return mTileId;
- }
-
/** Get the requested resource version. */
@NonNull
public String getVersion() {
diff --git a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java
index b9b7c3a..b297695 100644
--- a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java
+++ b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java
@@ -139,78 +139,74 @@
@Test
public void tileProvider_onTileAdd() throws Exception {
EventProto.TileAddEvent addRequest =
- EventProto.TileAddEvent.newBuilder().setTileId(TILE_ID).build();
+ EventProto.TileAddEvent.getDefaultInstance();
mTileProviderServiceStub.onTileAddEvent(
new TileAddEventData(addRequest.toByteArray(), TileAddEventData.VERSION_PROTOBUF));
shadowOf(Looper.getMainLooper()).idle();
- expect.that(mDummyTileProviderServiceServiceController.get().mLastOnTileAddId)
- .isEqualTo(TILE_ID);
+ expect.that(mDummyTileProviderServiceServiceController.get().mOnTileAddCalled).isTrue();
}
@Test
public void tileProvider_onTileRemove() throws Exception {
EventProto.TileRemoveEvent removeRequest =
- EventProto.TileRemoveEvent.newBuilder().setTileId(TILE_ID).build();
+ EventProto.TileRemoveEvent.getDefaultInstance();
mTileProviderServiceStub.onTileRemoveEvent(
new TileRemoveEventData(
removeRequest.toByteArray(), TileRemoveEventData.VERSION_PROTOBUF));
shadowOf(Looper.getMainLooper()).idle();
- expect.that(mDummyTileProviderServiceServiceController.get().mLastOnTileRemoveId)
- .isEqualTo(TILE_ID);
+ expect.that(mDummyTileProviderServiceServiceController.get().mOnTileRemoveCalled).isTrue();
}
@Test
public void tileProvider_onTileEnter() throws Exception {
EventProto.TileEnterEvent enterRequest =
- EventProto.TileEnterEvent.newBuilder().setTileId(TILE_ID).build();
+ EventProto.TileEnterEvent.getDefaultInstance();
mTileProviderServiceStub.onTileEnterEvent(
new TileEnterEventData(
enterRequest.toByteArray(), TileEnterEventData.VERSION_PROTOBUF));
shadowOf(Looper.getMainLooper()).idle();
- expect.that(mDummyTileProviderServiceServiceController.get().mLastOnTileEnterId)
- .isEqualTo(TILE_ID);
+ expect.that(mDummyTileProviderServiceServiceController.get().mOnTileEnterCalled).isTrue();
}
@Test
public void tileProvider_onTileLeave() throws Exception {
EventProto.TileLeaveEvent leaveRequest =
- EventProto.TileLeaveEvent.newBuilder().setTileId(TILE_ID).build();
+ EventProto.TileLeaveEvent.getDefaultInstance();
mTileProviderServiceStub.onTileLeaveEvent(
new TileLeaveEventData(
leaveRequest.toByteArray(), TileLeaveEventData.VERSION_PROTOBUF));
shadowOf(Looper.getMainLooper()).idle();
- expect.that(mDummyTileProviderServiceServiceController.get().mLastOnTileLeaveId)
- .isEqualTo(TILE_ID);
+ expect.that(mDummyTileProviderServiceServiceController.get().mOnTileLeaveCalled).isTrue();
}
public static class DummyTileProviderService extends TileProviderService {
- int mLastOnTileAddId = -1;
- int mLastOnTileRemoveId = -1;
- int mLastOnTileEnterId = -1;
- int mLastOnTileLeaveId = -1;
+ boolean mOnTileAddCalled = false;
+ boolean mOnTileRemoveCalled = false;
+ boolean mOnTileEnterCalled = false;
+ boolean mOnTileLeaveCalled = false;
@Override
protected void onTileAddEvent(@NonNull TileAddEvent requestParams) {
- this.mLastOnTileAddId = requestParams.getTileId();
+ mOnTileAddCalled = true;
}
@Override
protected void onTileRemoveEvent(@NonNull TileRemoveEvent requestParams) {
- this.mLastOnTileRemoveId = requestParams.getTileId();
+ mOnTileRemoveCalled = true;
}
@Override
protected void onTileEnterEvent(@NonNull TileEnterEvent requestParams) {
- this.mLastOnTileEnterId = requestParams.getTileId();
+ mOnTileEnterCalled = true;
}
@Override
protected void onTileLeaveEvent(@NonNull TileLeaveEvent requestParams) {
- this.mLastOnTileLeaveId = requestParams.getTileId();
+ mOnTileLeaveCalled = true;
}
@Override
diff --git a/wear/wear-complications-data/api/restricted_current.txt b/wear/wear-complications-data/api/restricted_current.txt
index 1dc59bd..1ca52a5 100644
--- a/wear/wear-complications-data/api/restricted_current.txt
+++ b/wear/wear-complications-data/api/restricted_current.txt
@@ -173,6 +173,8 @@
field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String ACTION_REQUEST_UPDATE_ALL_ACTIVE = "android.support.wearable.complications.ACTION_REQUEST_UPDATE_ALL_ACTIVE";
field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String ACTION_START_PROVIDER_CHOOSER = "android.support.wearable.complications.ACTION_START_PROVIDER_CHOOSER";
field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String EXTRA_WATCH_FACE_COMPONENT = "android.support.wearable.complications.EXTRA_WATCH_FACE_COMPONENT";
+ field public static boolean skipPermissionCheck;
+ field public static boolean useTestComplicationProviderChooserActivity;
}
public final class ComplicationProviderInfo {
@@ -191,11 +193,11 @@
}
public final class DefaultComplicationProviderPolicy {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public DefaultComplicationProviderPolicy(java.util.List<android.content.ComponentName> providers, @androidx.wear.complications.SystemProviders.Companion.ProviderId int systemProviderFallback);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public DefaultComplicationProviderPolicy(java.util.List<android.content.ComponentName> providers, @androidx.wear.complications.SystemProviders.ProviderId int systemProviderFallback);
ctor public DefaultComplicationProviderPolicy();
- ctor public DefaultComplicationProviderPolicy(@androidx.wear.complications.SystemProviders.Companion.ProviderId int systemProvider);
- ctor public DefaultComplicationProviderPolicy(android.content.ComponentName provider, @androidx.wear.complications.SystemProviders.Companion.ProviderId int systemProviderFallback);
- ctor public DefaultComplicationProviderPolicy(android.content.ComponentName primaryProvider, android.content.ComponentName secondaryProvider, @androidx.wear.complications.SystemProviders.Companion.ProviderId int systemProviderFallback);
+ ctor public DefaultComplicationProviderPolicy(@androidx.wear.complications.SystemProviders.ProviderId int systemProvider);
+ ctor public DefaultComplicationProviderPolicy(android.content.ComponentName provider, @androidx.wear.complications.SystemProviders.ProviderId int systemProviderFallback);
+ ctor public DefaultComplicationProviderPolicy(android.content.ComponentName primaryProvider, android.content.ComponentName secondaryProvider, @androidx.wear.complications.SystemProviders.ProviderId int systemProviderFallback);
method public android.content.ComponentName? getPrimaryProvider();
method public android.content.ComponentName? getSecondaryProvider();
method public int getSystemProviderFallback();
@@ -266,7 +268,7 @@
public static final class SystemProviders.Companion {
}
- @IntDef({androidx.wear.complications.SystemProviders.NO_PROVIDER, androidx.wear.complications.SystemProviders.PROVIDER_WATCH_BATTERY, androidx.wear.complications.SystemProviders.PROVIDER_DATE, androidx.wear.complications.SystemProviders.PROVIDER_TIME_AND_DATE, androidx.wear.complications.SystemProviders.PROVIDER_STEP_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_WORLD_CLOCK, androidx.wear.complications.SystemProviders.PROVIDER_APP_SHORTCUT, androidx.wear.complications.SystemProviders.PROVIDER_UNREAD_NOTIFICATION_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_NEXT_EVENT, androidx.wear.complications.SystemProviders.PROVIDER_RETAIL_STEP_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_RETAIL_CHAT, androidx.wear.complications.SystemProviders.PROVIDER_SUNRISE_SUNSET, androidx.wear.complications.SystemProviders.PROVIDER_DAY_OF_WEEK, androidx.wear.complications.SystemProviders.PROVIDER_FAVORITE_CONTACT, androidx.wear.complications.SystemProviders.PROVIDER_MOST_RECENT_APP, androidx.wear.complications.SystemProviders.PROVIDER_DAY_AND_DATE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) public static @interface SystemProviders.Companion.ProviderId {
+ @IntDef({androidx.wear.complications.SystemProviders.NO_PROVIDER, androidx.wear.complications.SystemProviders.PROVIDER_WATCH_BATTERY, androidx.wear.complications.SystemProviders.PROVIDER_DATE, androidx.wear.complications.SystemProviders.PROVIDER_TIME_AND_DATE, androidx.wear.complications.SystemProviders.PROVIDER_STEP_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_WORLD_CLOCK, androidx.wear.complications.SystemProviders.PROVIDER_APP_SHORTCUT, androidx.wear.complications.SystemProviders.PROVIDER_UNREAD_NOTIFICATION_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_NEXT_EVENT, androidx.wear.complications.SystemProviders.PROVIDER_RETAIL_STEP_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_RETAIL_CHAT, androidx.wear.complications.SystemProviders.PROVIDER_SUNRISE_SUNSET, androidx.wear.complications.SystemProviders.PROVIDER_DAY_OF_WEEK, androidx.wear.complications.SystemProviders.PROVIDER_FAVORITE_CONTACT, androidx.wear.complications.SystemProviders.PROVIDER_MOST_RECENT_APP, androidx.wear.complications.SystemProviders.PROVIDER_DAY_AND_DATE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) public static @interface SystemProviders.ProviderId {
}
}
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java
index a0a7b71..de5f491 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java
@@ -54,6 +54,7 @@
* <p>Or, to request the permission, for instance if {@link ComplicationData} of {@link
* ComplicationData#TYPE_NO_PERMISSION TYPE_NO_PERMISSION} has been received and tapped on, use
* {@link #createPermissionRequestHelperIntent}.
+ *
* @hide
*/
@TargetApi(Build.VERSION_CODES.N)
@@ -62,6 +63,20 @@
public final class ComplicationHelperActivity extends Activity
implements ActivityCompat.OnRequestPermissionsResultCallback {
+ /**
+ * Whether to invoke a specified activity instead of the system's provider chooser.
+ *
+ * To be used in tests.
+ */
+ public static boolean useTestComplicationProviderChooserActivity = false;
+
+ /**
+ * Whether to skip th permission check and directly attempt to invoke the provider chooser.
+ *
+ * To be used in tests.
+ */
+ public static boolean skipPermissionCheck = false;
+
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final String ACTION_REQUEST_UPDATE_ALL_ACTIVE =
@@ -95,9 +110,14 @@
private static final String COMPLICATIONS_PERMISSION_PRIVILEGED =
"com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA_PRIVILEGED";
- @Nullable private ComponentName mWatchFace;
+ @Nullable
+ private ComponentName mWatchFace;
private int mWfComplicationId;
- @Nullable @ComplicationData.ComplicationType private int[] mTypes;
+ @Nullable
+ private Bundle mAdditionalExtras;
+ @Nullable
+ @ComplicationData.ComplicationType
+ private int[] mTypes;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -115,12 +135,13 @@
mWfComplicationId =
intent.getIntExtra(ProviderChooserIntent.EXTRA_COMPLICATION_ID, 0);
mTypes = intent.getIntArrayExtra(ProviderChooserIntent.EXTRA_SUPPORTED_TYPES);
+ mAdditionalExtras = getAdditionalExtras(intent);
if (checkPermission()) {
startProviderChooser();
} else {
ActivityCompat.requestPermissions(
this,
- new String[] {COMPLICATIONS_PERMISSION},
+ new String[]{COMPLICATIONS_PERMISSION},
PERMISSION_REQUEST_CODE_PROVIDER_CHOOSER);
}
break;
@@ -133,7 +154,7 @@
} else {
ActivityCompat.requestPermissions(
this,
- new String[] {COMPLICATIONS_PERMISSION},
+ new String[]{COMPLICATIONS_PERMISSION},
PERMISSION_REQUEST_CODE_REQUEST_ONLY);
}
break;
@@ -171,9 +192,10 @@
private boolean checkPermission() {
return ActivityCompat.checkSelfPermission(this, COMPLICATIONS_PERMISSION_PRIVILEGED)
- == PackageManager.PERMISSION_GRANTED
+ == PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this, COMPLICATIONS_PERMISSION)
- == PackageManager.PERMISSION_GRANTED;
+ == PackageManager.PERMISSION_GRANTED
+ || skipPermissionCheck;
}
/**
@@ -204,15 +226,18 @@
*
* <p>From android R onwards this API can only be called during an editing session.
*
- * @param context context for the current app, that must contain a ComplicationHelperActivity
- * @param watchFace the ComponentName of the WatchFaceService being configured.
+ * @param context context for the current app, that must contain a
+ * ComplicationHelperActivity
+ * @param watchFace the ComponentName of the WatchFaceService being configured.
* @param watchFaceComplicationId the watch face's id for the complication being configured.
- * This must match the id passed in when the watch face calls
- * WatchFaceService.Engine#setActiveComplications.
- * @param supportedTypes the types supported by the complication, in decreasing order of
- * preference. If a provider can supply data for more than one of these types, the type
- * chosen will be whichever was specified first.
- * @param watchFaceInstanceId The ID of the watchface being edited.
+ * This must match the id passed in when the watch face calls
+ * WatchFaceService.Engine#setActiveComplications.
+ * @param supportedTypes the types supported by the complication, in decreasing
+ * order of
+ * preference. If a provider can supply data for more than one
+ * of these types, the type
+ * chosen will be whichever was specified first.
+ * @param watchFaceInstanceId The ID of the watchface being edited.
*/
@NonNull
public static Intent createProviderChooserHelperIntent(
@@ -253,7 +278,7 @@
* watch face will be triggered. The provided {@code watchFace} must match the current watch
* face for this to occur.
*
- * @param context context for the current app, that must contain a ComplicationHelperActivity
+ * @param context context for the current app, that must contain a ComplicationHelperActivity
* @param watchFace the ComponentName of the WatchFaceService for the current watch face
*/
@NonNull
@@ -266,10 +291,22 @@
}
private void startProviderChooser() {
- startActivityForResult(
+ Intent intent =
ProviderChooserIntent.createProviderChooserIntent(
- mWatchFace, mWfComplicationId, mTypes),
- START_REQUEST_CODE_PROVIDER_CHOOSER);
+ mWatchFace, mWfComplicationId, mTypes);
+ // Add the extras that were provided to the ComplicationHelperActivity. This is done by
+ // first taking the additional extras and adding to that anything that was set in the
+ // chooser intent, and setting them back on the intent itself to avoid the additional
+ // extras being able to override anything that was set by the chooser intent.
+ Bundle extras = new Bundle(mAdditionalExtras);
+ extras.putAll(intent.getExtras());
+ intent.replaceExtras(extras);
+ if (useTestComplicationProviderChooserActivity) {
+ intent.setComponent(new ComponentName(
+ "androidx.wear.watchface.editor.test",
+ "androidx.wear.watchface.editor.TestComplicationProviderChooserActivity"));
+ }
+ startActivityForResult(intent, START_REQUEST_CODE_PROVIDER_CHOOSER);
}
/** Requests that the system update all active complications on the watch face. */
@@ -283,4 +320,17 @@
PendingIntent.getActivity(this, 0, new Intent(""), 0));
sendBroadcast(intent);
}
+
+ /**
+ * Returns any extras that were not handled by the activity itself.
+ *
+ * <p>These will be forwarded to the chooser activity.
+ */
+ private Bundle getAdditionalExtras(Intent intent) {
+ Bundle extras = intent.getExtras();
+ extras.remove(ProviderChooserIntent.EXTRA_WATCH_FACE_COMPONENT_NAME);
+ extras.remove(ProviderChooserIntent.EXTRA_COMPLICATION_ID);
+ extras.remove(ProviderChooserIntent.EXTRA_SUPPORTED_TYPES);
+ return extras;
+ }
}
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt b/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt
index 902dece..1ab0bb6 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt
@@ -18,7 +18,7 @@
import android.content.ComponentName
import androidx.annotation.RestrictTo
-import androidx.wear.complications.SystemProviders.Companion.ProviderId
+import androidx.wear.complications.SystemProviders.ProviderId
import java.util.ArrayList
/**
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/SystemProviders.kt b/wear/wear-complications-data/src/main/java/androidx/wear/complications/SystemProviders.kt
index 862bf3d..839ca39 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/SystemProviders.kt
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/SystemProviders.kt
@@ -194,34 +194,34 @@
* This provider supports only [ComplicationType.SHORT_TEXT].
*/
public const val PROVIDER_DAY_AND_DATE: Int = 16
-
- /**
- * System provider id as defined in [SystemProviders].
- *
- * @hide
- */
- @IntDef(
- NO_PROVIDER,
- PROVIDER_WATCH_BATTERY,
- PROVIDER_DATE,
- PROVIDER_TIME_AND_DATE,
- PROVIDER_STEP_COUNT,
- PROVIDER_WORLD_CLOCK,
- PROVIDER_APP_SHORTCUT,
- PROVIDER_UNREAD_NOTIFICATION_COUNT,
- PROVIDER_NEXT_EVENT,
- PROVIDER_RETAIL_STEP_COUNT,
- PROVIDER_RETAIL_CHAT,
- PROVIDER_SUNRISE_SUNSET,
- PROVIDER_DAY_OF_WEEK,
- PROVIDER_FAVORITE_CONTACT,
- PROVIDER_MOST_RECENT_APP,
- PROVIDER_DAY_AND_DATE
- )
- @RestrictTo(
- RestrictTo.Scope.LIBRARY_GROUP
- )
- @Retention(AnnotationRetention.SOURCE)
- public annotation class ProviderId
}
+
+ /**
+ * System provider id as defined in [SystemProviders].
+ *
+ * @hide
+ */
+ @IntDef(
+ NO_PROVIDER,
+ PROVIDER_WATCH_BATTERY,
+ PROVIDER_DATE,
+ PROVIDER_TIME_AND_DATE,
+ PROVIDER_STEP_COUNT,
+ PROVIDER_WORLD_CLOCK,
+ PROVIDER_APP_SHORTCUT,
+ PROVIDER_UNREAD_NOTIFICATION_COUNT,
+ PROVIDER_NEXT_EVENT,
+ PROVIDER_RETAIL_STEP_COUNT,
+ PROVIDER_RETAIL_CHAT,
+ PROVIDER_SUNRISE_SUNSET,
+ PROVIDER_DAY_OF_WEEK,
+ PROVIDER_FAVORITE_CONTACT,
+ PROVIDER_MOST_RECENT_APP,
+ PROVIDER_DAY_AND_DATE
+ )
+ @RestrictTo(
+ RestrictTo.Scope.LIBRARY_GROUP
+ )
+ @Retention(AnnotationRetention.SOURCE)
+ public annotation class ProviderId
}
\ No newline at end of file
diff --git a/wear/wear-complications-provider/lint-baseline.xml b/wear/wear-complications-provider/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-complications-provider/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-complications-provider/samples/lint-baseline.xml b/wear/wear-complications-provider/samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-complications-provider/samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-input-testing/lint-baseline.xml b/wear/wear-input-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-input-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-input/api/current.txt b/wear/wear-input/api/current.txt
index dc46d45..5909312 100644
--- a/wear/wear-input/api/current.txt
+++ b/wear/wear-input/api/current.txt
@@ -9,39 +9,39 @@
@RequiresApi(android.os.Build.VERSION_CODES.N) public final class RemoteInputIntentHelper {
method public static android.content.Intent createActionRemoteInputIntent();
- method public static String? getCancelLabelExtra(android.content.Intent intent);
- method public static String? getConfirmLabelExtra(android.content.Intent intent);
- method public static String? getInProgressLabelExtra(android.content.Intent intent);
+ method public static CharSequence? getCancelLabelExtra(android.content.Intent intent);
+ method public static CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+ method public static CharSequence? getInProgressLabelExtra(android.content.Intent intent);
method public static java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
method public static java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
- method public static String? getTitleExtra(android.content.Intent intent);
+ method public static CharSequence? getTitleExtra(android.content.Intent intent);
method public static boolean hasRemoteInputsExtra(android.content.Intent intent);
method public static boolean isActionRemoteInput(android.content.Intent intent);
- method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
- method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
- method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+ method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+ method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+ method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
method public static android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
method public static android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
- method public static android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+ method public static android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
field public static final androidx.wear.input.RemoteInputIntentHelper.Companion Companion;
}
public static final class RemoteInputIntentHelper.Companion {
method public android.content.Intent createActionRemoteInputIntent();
- method public String? getCancelLabelExtra(android.content.Intent intent);
- method public String? getConfirmLabelExtra(android.content.Intent intent);
- method public String? getInProgressLabelExtra(android.content.Intent intent);
+ method public CharSequence? getCancelLabelExtra(android.content.Intent intent);
+ method public CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+ method public CharSequence? getInProgressLabelExtra(android.content.Intent intent);
method public java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
method public java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
- method public String? getTitleExtra(android.content.Intent intent);
+ method public CharSequence? getTitleExtra(android.content.Intent intent);
method public boolean hasRemoteInputsExtra(android.content.Intent intent);
method public boolean isActionRemoteInput(android.content.Intent intent);
- method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
- method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
- method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+ method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+ method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+ method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
method public android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
method public android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
- method public android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+ method public android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
}
public final class WearableButtons {
diff --git a/wear/wear-input/api/public_plus_experimental_current.txt b/wear/wear-input/api/public_plus_experimental_current.txt
index dc46d45..5909312 100644
--- a/wear/wear-input/api/public_plus_experimental_current.txt
+++ b/wear/wear-input/api/public_plus_experimental_current.txt
@@ -9,39 +9,39 @@
@RequiresApi(android.os.Build.VERSION_CODES.N) public final class RemoteInputIntentHelper {
method public static android.content.Intent createActionRemoteInputIntent();
- method public static String? getCancelLabelExtra(android.content.Intent intent);
- method public static String? getConfirmLabelExtra(android.content.Intent intent);
- method public static String? getInProgressLabelExtra(android.content.Intent intent);
+ method public static CharSequence? getCancelLabelExtra(android.content.Intent intent);
+ method public static CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+ method public static CharSequence? getInProgressLabelExtra(android.content.Intent intent);
method public static java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
method public static java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
- method public static String? getTitleExtra(android.content.Intent intent);
+ method public static CharSequence? getTitleExtra(android.content.Intent intent);
method public static boolean hasRemoteInputsExtra(android.content.Intent intent);
method public static boolean isActionRemoteInput(android.content.Intent intent);
- method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
- method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
- method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+ method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+ method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+ method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
method public static android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
method public static android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
- method public static android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+ method public static android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
field public static final androidx.wear.input.RemoteInputIntentHelper.Companion Companion;
}
public static final class RemoteInputIntentHelper.Companion {
method public android.content.Intent createActionRemoteInputIntent();
- method public String? getCancelLabelExtra(android.content.Intent intent);
- method public String? getConfirmLabelExtra(android.content.Intent intent);
- method public String? getInProgressLabelExtra(android.content.Intent intent);
+ method public CharSequence? getCancelLabelExtra(android.content.Intent intent);
+ method public CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+ method public CharSequence? getInProgressLabelExtra(android.content.Intent intent);
method public java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
method public java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
- method public String? getTitleExtra(android.content.Intent intent);
+ method public CharSequence? getTitleExtra(android.content.Intent intent);
method public boolean hasRemoteInputsExtra(android.content.Intent intent);
method public boolean isActionRemoteInput(android.content.Intent intent);
- method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
- method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
- method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+ method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+ method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+ method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
method public android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
method public android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
- method public android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+ method public android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
}
public final class WearableButtons {
diff --git a/wear/wear-input/api/restricted_current.txt b/wear/wear-input/api/restricted_current.txt
index dc46d45..5909312 100644
--- a/wear/wear-input/api/restricted_current.txt
+++ b/wear/wear-input/api/restricted_current.txt
@@ -9,39 +9,39 @@
@RequiresApi(android.os.Build.VERSION_CODES.N) public final class RemoteInputIntentHelper {
method public static android.content.Intent createActionRemoteInputIntent();
- method public static String? getCancelLabelExtra(android.content.Intent intent);
- method public static String? getConfirmLabelExtra(android.content.Intent intent);
- method public static String? getInProgressLabelExtra(android.content.Intent intent);
+ method public static CharSequence? getCancelLabelExtra(android.content.Intent intent);
+ method public static CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+ method public static CharSequence? getInProgressLabelExtra(android.content.Intent intent);
method public static java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
method public static java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
- method public static String? getTitleExtra(android.content.Intent intent);
+ method public static CharSequence? getTitleExtra(android.content.Intent intent);
method public static boolean hasRemoteInputsExtra(android.content.Intent intent);
method public static boolean isActionRemoteInput(android.content.Intent intent);
- method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
- method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
- method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+ method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+ method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+ method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
method public static android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
method public static android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
- method public static android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+ method public static android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
field public static final androidx.wear.input.RemoteInputIntentHelper.Companion Companion;
}
public static final class RemoteInputIntentHelper.Companion {
method public android.content.Intent createActionRemoteInputIntent();
- method public String? getCancelLabelExtra(android.content.Intent intent);
- method public String? getConfirmLabelExtra(android.content.Intent intent);
- method public String? getInProgressLabelExtra(android.content.Intent intent);
+ method public CharSequence? getCancelLabelExtra(android.content.Intent intent);
+ method public CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+ method public CharSequence? getInProgressLabelExtra(android.content.Intent intent);
method public java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
method public java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
- method public String? getTitleExtra(android.content.Intent intent);
+ method public CharSequence? getTitleExtra(android.content.Intent intent);
method public boolean hasRemoteInputsExtra(android.content.Intent intent);
method public boolean isActionRemoteInput(android.content.Intent intent);
- method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
- method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
- method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+ method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+ method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+ method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
method public android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
method public android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
- method public android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+ method public android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
}
public final class WearableButtons {
diff --git a/wear/wear-input/lint-baseline.xml b/wear/wear-input/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-input/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-input/src/main/java/androidx/wear/input/RemoteInputIntentHelper.kt b/wear/wear-input/src/main/java/androidx/wear/input/RemoteInputIntentHelper.kt
index e1f6a54..42cd43d 100644
--- a/wear/wear-input/src/main/java/androidx/wear/input/RemoteInputIntentHelper.kt
+++ b/wear/wear-input/src/main/java/androidx/wear/input/RemoteInputIntentHelper.kt
@@ -120,107 +120,109 @@
): Intent = intent.putExtra(EXTRA_REMOTE_INPUTS, ArrayList(remoteInputs))
/**
- * Returns the [String] from the given [Intent] that specifies what is displayed on top of
- * the confirmation screen to describe the action.
+ * Returns the [CharSequence] from the given [Intent] that specifies what is displayed on
+ * top of the confirmation screen to describe the action.
*
* @param intent The intent with given data.
- * @return The string previously added with [putTitleExtra] or null if no value is found.
+ * @return The CharSequence previously added with [putTitleExtra] or null if no value is
+ * found.
*/
@JvmStatic
@Nullable
- public fun getTitleExtra(intent: Intent): String? = intent.getStringExtra(EXTRA_TITLE)
+ public fun getTitleExtra(intent: Intent): CharSequence? =
+ intent.getCharSequenceExtra(EXTRA_TITLE)
/**
- * Adds the [String] to the given [Intent] that specifies what is displayed on top of
+ * Adds the [CharSequence] to the given [Intent] that specifies what is displayed on top of
* the confirmation screen to describe the action like "SMS" or "Email".
*
* @param intent The intent with given data.
- * @param title The string to be added.
+ * @param title The CharSequence to be added.
* @return The given intent.
*/
@JvmStatic
@NonNull
- public fun putTitleExtra(intent: Intent, title: String): Intent =
+ public fun putTitleExtra(intent: Intent, title: CharSequence): Intent =
intent.putExtra(EXTRA_TITLE, title)
/**
- * Returns the [String] from the given [Intent] that specifies what is displayed to
+ * Returns the [CharSequence] from the given [Intent] that specifies what is displayed to
* cancel the action.
*
* @param intent The intent with given data.
- * @return The string previously added with [putCancelLabelExtra] or null if no value is
- * found.
+ * @return The CharSequence previously added with [putCancelLabelExtra] or null if no value
+ * is found.
*/
@JvmStatic
@Nullable
- public fun getCancelLabelExtra(intent: Intent): String? =
- intent.getStringExtra(EXTRA_CANCEL_LABEL)
+ public fun getCancelLabelExtra(intent: Intent): CharSequence? =
+ intent.getCharSequenceExtra(EXTRA_CANCEL_LABEL)
/**
- * Adds the [String] to the given [Intent] that specifies what is displayed to cancel the
- * action. This is usually an imperative verb, like "Cancel". Defaults to Cancel.
+ * Adds the [CharSequence] to the given [Intent] that specifies what is displayed to cancel
+ * the action. This is usually an imperative verb, like "Cancel". Defaults to Cancel.
*
* @param intent The intent with given data.
- * @param label The string to be added.
+ * @param label The CharSequence to be added.
* @return The given intent.
*/
@JvmStatic
@NonNull
- public fun putCancelLabelExtra(intent: Intent, label: String): Intent =
+ public fun putCancelLabelExtra(intent: Intent, label: CharSequence): Intent =
intent.putExtra(EXTRA_CANCEL_LABEL, label)
/**
- * Returns the [String] from the given [Intent] that specifies what is displayed to
+ * Returns the [CharSequence] from the given [Intent] that specifies what is displayed to
* confirm that the action should be executed.
*
* @param intent The intent with given data.
- * @return The string previously added with [putConfirmLabelExtra] or null if no value is
- * found.
+ * @return The CharSequence previously added with [putConfirmLabelExtra] or null if no value
+ * is found.
*/
@JvmStatic
@Nullable
- public fun getConfirmLabelExtra(intent: Intent): String? =
- intent.getStringExtra(EXTRA_CONFIRM_LABEL)
+ public fun getConfirmLabelExtra(intent: Intent): CharSequence? =
+ intent.getCharSequenceExtra(EXTRA_CONFIRM_LABEL)
/**
- * Adds the [String] to the given [Intent] that specifies what is displayed to confirm that
- * the action should be executed. This is usually an imperative verb like "Send".
+ * Adds the [CharSequence] to the given [Intent] that specifies what is displayed to confirm
+ * that the action should be executed. This is usually an imperative verb like "Send".
* Defaults to "Send".
*
* @param intent The intent with given data.
- * @param label The string to be added.
+ * @param label The CharSequence to be added.
* @return The given intent.
*/
@JvmStatic
@NonNull
- public fun putConfirmLabelExtra(intent: Intent, label: String): Intent =
+ public fun putConfirmLabelExtra(intent: Intent, label: CharSequence): Intent =
intent.putExtra(EXTRA_CONFIRM_LABEL, label)
/**
- * Returns the [String] from the given [Intent] that specifies what is displayed while the
- * wearable is preparing to automatically execute the action.
+ * Returns the [CharSequence] from the given [Intent] that specifies what is displayed while
+ * the wearable is preparing to automatically execute the action.
*
* @param intent The intent with given data.
- * @return The string previously added with [putInProgressLabelExtra] or null if no
+ * @return The CharSequence previously added with [putInProgressLabelExtra] or null if no
* value is found.
*/
@JvmStatic
@Nullable
- public fun getInProgressLabelExtra(intent: Intent): String? =
- intent.getStringExtra(EXTRA_IN_PROGRESS_LABEL)
+ public fun getInProgressLabelExtra(intent: Intent): CharSequence? =
+ intent.getCharSequenceExtra(EXTRA_IN_PROGRESS_LABEL)
/**
- * Adds the [String] to the given [Intent] that specifies what is displayed while the
+ * Adds the [CharSequence] to the given [Intent] that specifies what is displayed while the
* wearable is preparing to automatically execute the action. This is usually a 'ing'
* verb ending in ellipsis like "Sending...". Defaults to "Sending...".
*
* @param intent The intent with given data.
- * @param label The string to be added.
+ * @param label The CharSequence to be added.
* @return The given intent.
*/
@JvmStatic
@NonNull
- public fun putInProgressLabelExtra(intent: Intent, label: String): Intent =
+ public fun putInProgressLabelExtra(intent: Intent, label: CharSequence): Intent =
intent.putExtra(EXTRA_IN_PROGRESS_LABEL, label)
/**
diff --git a/wear/wear-ongoing/api/current.txt b/wear/wear-ongoing/api/current.txt
index 779ba555..edd5ae4 100644
--- a/wear/wear-ongoing/api/current.txt
+++ b/wear/wear-ongoing/api/current.txt
@@ -9,14 +9,14 @@
method public int getNotificationId();
method public int getOngoingActivityId();
method public android.graphics.drawable.Icon getStaticIcon();
- method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
+ method public androidx.wear.ongoing.Status? getStatus();
method public String? getTag();
method public long getTimestamp();
method public android.app.PendingIntent getTouchIntent();
- method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivityData!>);
+ method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivity!>);
method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context);
method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, int);
- method public void update(android.content.Context, androidx.wear.ongoing.OngoingActivityStatus);
+ method public void update(android.content.Context, androidx.wear.ongoing.Status);
}
public static final class OngoingActivity.Builder {
@@ -30,61 +30,49 @@
method public androidx.wear.ongoing.OngoingActivity.Builder setOngoingActivityId(int);
method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(@DrawableRes int);
- method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.OngoingActivityStatus);
+ method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.Status);
method public androidx.wear.ongoing.OngoingActivity.Builder setTouchIntent(android.app.PendingIntent);
}
- public class OngoingActivityData implements androidx.versionedparcelable.VersionedParcelable {
+ public class SerializationHelper {
method public static void copy(android.os.Bundle, android.os.Bundle);
- method public static androidx.wear.ongoing.OngoingActivityData? create(android.app.Notification);
- method public static androidx.wear.ongoing.OngoingActivityData? create(android.os.Bundle);
- method public android.graphics.drawable.Icon? getAnimatedIcon();
- method public String? getCategory();
- method public androidx.core.content.LocusIdCompat? getLocusId();
- method public int getOngoingActivityId();
- method public android.graphics.drawable.Icon getStaticIcon();
- method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
- method public long getTimestamp();
- method public android.app.PendingIntent getTouchIntent();
+ method public static androidx.wear.ongoing.OngoingActivity? create(android.app.Notification);
+ method public static androidx.wear.ongoing.OngoingActivity? create(android.os.Bundle);
method public static boolean hasOngoingActivity(android.app.Notification);
}
- public class OngoingActivityStatus implements androidx.wear.ongoing.TimeDependentText androidx.versionedparcelable.VersionedParcelable {
- method public static androidx.wear.ongoing.OngoingActivityStatus forPart(androidx.wear.ongoing.StatusPart);
+ public final class Status implements androidx.wear.ongoing.TimeDependentText {
+ method public static androidx.wear.ongoing.Status forPart(androidx.wear.ongoing.Status.Part);
method public long getNextChangeTimeMillis(long);
- method public androidx.wear.ongoing.StatusPart? getPart(String);
+ method public androidx.wear.ongoing.Status.Part? getPart(String);
method public java.util.Set<java.lang.String!> getPartNames();
method public java.util.List<java.lang.CharSequence!> getTemplates();
method public CharSequence getText(android.content.Context, long);
}
- public static final class OngoingActivityStatus.Builder {
- ctor public OngoingActivityStatus.Builder();
- method public androidx.wear.ongoing.OngoingActivityStatus.Builder addPart(String, androidx.wear.ongoing.StatusPart);
- method public androidx.wear.ongoing.OngoingActivityStatus.Builder addTemplate(CharSequence);
- method public androidx.wear.ongoing.OngoingActivityStatus build();
+ public static final class Status.Builder {
+ ctor public Status.Builder();
+ method public androidx.wear.ongoing.Status.Builder addPart(String, androidx.wear.ongoing.Status.Part);
+ method public androidx.wear.ongoing.Status.Builder addTemplate(CharSequence);
+ method public androidx.wear.ongoing.Status build();
}
- public abstract class StatusPart implements androidx.wear.ongoing.TimeDependentText androidx.versionedparcelable.VersionedParcelable {
- ctor public StatusPart();
+ public abstract static class Status.Part implements androidx.wear.ongoing.TimeDependentText {
}
- public class TextStatusPart extends androidx.wear.ongoing.StatusPart {
- ctor public TextStatusPart(String);
+ public static final class Status.StopwatchPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+ ctor public Status.StopwatchPart(long, long, long);
+ ctor public Status.StopwatchPart(long, long);
+ ctor public Status.StopwatchPart(long);
+ }
+
+ public static final class Status.TextPart extends androidx.wear.ongoing.Status.Part {
+ ctor public Status.TextPart(String);
method public long getNextChangeTimeMillis(long);
method public CharSequence getText(android.content.Context, long);
}
- public interface TimeDependentText {
- method public long getNextChangeTimeMillis(long);
- method public CharSequence getText(android.content.Context, long);
- }
-
- public class TimerStatusPart extends androidx.wear.ongoing.StatusPart {
- ctor public TimerStatusPart(long, boolean, long, long);
- ctor public TimerStatusPart(long, boolean, long);
- ctor public TimerStatusPart(long, boolean);
- ctor public TimerStatusPart(long);
+ public abstract static class Status.TimerOrStopwatchPart extends androidx.wear.ongoing.Status.Part {
method public long getNextChangeTimeMillis(long);
method public long getPausedAtMillis();
method public CharSequence getText(android.content.Context, long);
@@ -95,5 +83,16 @@
method public boolean isPaused();
}
+ public static final class Status.TimerPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+ ctor public Status.TimerPart(long, long, long);
+ ctor public Status.TimerPart(long, long);
+ ctor public Status.TimerPart(long);
+ }
+
+ public interface TimeDependentText {
+ method public long getNextChangeTimeMillis(long);
+ method public CharSequence getText(android.content.Context, long);
+ }
+
}
diff --git a/wear/wear-ongoing/api/public_plus_experimental_current.txt b/wear/wear-ongoing/api/public_plus_experimental_current.txt
index 3c959c3..edd5ae4 100644
--- a/wear/wear-ongoing/api/public_plus_experimental_current.txt
+++ b/wear/wear-ongoing/api/public_plus_experimental_current.txt
@@ -9,14 +9,14 @@
method public int getNotificationId();
method public int getOngoingActivityId();
method public android.graphics.drawable.Icon getStaticIcon();
- method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
+ method public androidx.wear.ongoing.Status? getStatus();
method public String? getTag();
method public long getTimestamp();
method public android.app.PendingIntent getTouchIntent();
- method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivityData!>);
+ method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivity!>);
method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context);
method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, int);
- method public void update(android.content.Context, androidx.wear.ongoing.OngoingActivityStatus);
+ method public void update(android.content.Context, androidx.wear.ongoing.Status);
}
public static final class OngoingActivity.Builder {
@@ -30,61 +30,49 @@
method public androidx.wear.ongoing.OngoingActivity.Builder setOngoingActivityId(int);
method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(@DrawableRes int);
- method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.OngoingActivityStatus);
+ method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.Status);
method public androidx.wear.ongoing.OngoingActivity.Builder setTouchIntent(android.app.PendingIntent);
}
- @androidx.versionedparcelable.VersionedParcelize public class OngoingActivityData implements androidx.versionedparcelable.VersionedParcelable {
+ public class SerializationHelper {
method public static void copy(android.os.Bundle, android.os.Bundle);
- method public static androidx.wear.ongoing.OngoingActivityData? create(android.app.Notification);
- method public static androidx.wear.ongoing.OngoingActivityData? create(android.os.Bundle);
- method public android.graphics.drawable.Icon? getAnimatedIcon();
- method public String? getCategory();
- method public androidx.core.content.LocusIdCompat? getLocusId();
- method public int getOngoingActivityId();
- method public android.graphics.drawable.Icon getStaticIcon();
- method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
- method public long getTimestamp();
- method public android.app.PendingIntent getTouchIntent();
+ method public static androidx.wear.ongoing.OngoingActivity? create(android.app.Notification);
+ method public static androidx.wear.ongoing.OngoingActivity? create(android.os.Bundle);
method public static boolean hasOngoingActivity(android.app.Notification);
}
- @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class OngoingActivityStatus extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.wear.ongoing.TimeDependentText {
- method public static androidx.wear.ongoing.OngoingActivityStatus forPart(androidx.wear.ongoing.StatusPart);
+ public final class Status implements androidx.wear.ongoing.TimeDependentText {
+ method public static androidx.wear.ongoing.Status forPart(androidx.wear.ongoing.Status.Part);
method public long getNextChangeTimeMillis(long);
- method public androidx.wear.ongoing.StatusPart? getPart(String);
+ method public androidx.wear.ongoing.Status.Part? getPart(String);
method public java.util.Set<java.lang.String!> getPartNames();
method public java.util.List<java.lang.CharSequence!> getTemplates();
method public CharSequence getText(android.content.Context, long);
}
- public static final class OngoingActivityStatus.Builder {
- ctor public OngoingActivityStatus.Builder();
- method public androidx.wear.ongoing.OngoingActivityStatus.Builder addPart(String, androidx.wear.ongoing.StatusPart);
- method public androidx.wear.ongoing.OngoingActivityStatus.Builder addTemplate(CharSequence);
- method public androidx.wear.ongoing.OngoingActivityStatus build();
+ public static final class Status.Builder {
+ ctor public Status.Builder();
+ method public androidx.wear.ongoing.Status.Builder addPart(String, androidx.wear.ongoing.Status.Part);
+ method public androidx.wear.ongoing.Status.Builder addTemplate(CharSequence);
+ method public androidx.wear.ongoing.Status build();
}
- public abstract class StatusPart implements androidx.wear.ongoing.TimeDependentText androidx.versionedparcelable.VersionedParcelable {
- ctor public StatusPart();
+ public abstract static class Status.Part implements androidx.wear.ongoing.TimeDependentText {
}
- @androidx.versionedparcelable.VersionedParcelize public class TextStatusPart extends androidx.wear.ongoing.StatusPart {
- ctor public TextStatusPart(String);
+ public static final class Status.StopwatchPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+ ctor public Status.StopwatchPart(long, long, long);
+ ctor public Status.StopwatchPart(long, long);
+ ctor public Status.StopwatchPart(long);
+ }
+
+ public static final class Status.TextPart extends androidx.wear.ongoing.Status.Part {
+ ctor public Status.TextPart(String);
method public long getNextChangeTimeMillis(long);
method public CharSequence getText(android.content.Context, long);
}
- public interface TimeDependentText {
- method public long getNextChangeTimeMillis(long);
- method public CharSequence getText(android.content.Context, long);
- }
-
- @androidx.versionedparcelable.VersionedParcelize public class TimerStatusPart extends androidx.wear.ongoing.StatusPart {
- ctor public TimerStatusPart(long, boolean, long, long);
- ctor public TimerStatusPart(long, boolean, long);
- ctor public TimerStatusPart(long, boolean);
- ctor public TimerStatusPart(long);
+ public abstract static class Status.TimerOrStopwatchPart extends androidx.wear.ongoing.Status.Part {
method public long getNextChangeTimeMillis(long);
method public long getPausedAtMillis();
method public CharSequence getText(android.content.Context, long);
@@ -95,5 +83,16 @@
method public boolean isPaused();
}
+ public static final class Status.TimerPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+ ctor public Status.TimerPart(long, long, long);
+ ctor public Status.TimerPart(long, long);
+ ctor public Status.TimerPart(long);
+ }
+
+ public interface TimeDependentText {
+ method public long getNextChangeTimeMillis(long);
+ method public CharSequence getText(android.content.Context, long);
+ }
+
}
diff --git a/wear/wear-ongoing/api/restricted_current.txt b/wear/wear-ongoing/api/restricted_current.txt
index 3c959c3..edd5ae4 100644
--- a/wear/wear-ongoing/api/restricted_current.txt
+++ b/wear/wear-ongoing/api/restricted_current.txt
@@ -9,14 +9,14 @@
method public int getNotificationId();
method public int getOngoingActivityId();
method public android.graphics.drawable.Icon getStaticIcon();
- method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
+ method public androidx.wear.ongoing.Status? getStatus();
method public String? getTag();
method public long getTimestamp();
method public android.app.PendingIntent getTouchIntent();
- method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivityData!>);
+ method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivity!>);
method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context);
method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, int);
- method public void update(android.content.Context, androidx.wear.ongoing.OngoingActivityStatus);
+ method public void update(android.content.Context, androidx.wear.ongoing.Status);
}
public static final class OngoingActivity.Builder {
@@ -30,61 +30,49 @@
method public androidx.wear.ongoing.OngoingActivity.Builder setOngoingActivityId(int);
method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(@DrawableRes int);
- method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.OngoingActivityStatus);
+ method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.Status);
method public androidx.wear.ongoing.OngoingActivity.Builder setTouchIntent(android.app.PendingIntent);
}
- @androidx.versionedparcelable.VersionedParcelize public class OngoingActivityData implements androidx.versionedparcelable.VersionedParcelable {
+ public class SerializationHelper {
method public static void copy(android.os.Bundle, android.os.Bundle);
- method public static androidx.wear.ongoing.OngoingActivityData? create(android.app.Notification);
- method public static androidx.wear.ongoing.OngoingActivityData? create(android.os.Bundle);
- method public android.graphics.drawable.Icon? getAnimatedIcon();
- method public String? getCategory();
- method public androidx.core.content.LocusIdCompat? getLocusId();
- method public int getOngoingActivityId();
- method public android.graphics.drawable.Icon getStaticIcon();
- method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
- method public long getTimestamp();
- method public android.app.PendingIntent getTouchIntent();
+ method public static androidx.wear.ongoing.OngoingActivity? create(android.app.Notification);
+ method public static androidx.wear.ongoing.OngoingActivity? create(android.os.Bundle);
method public static boolean hasOngoingActivity(android.app.Notification);
}
- @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class OngoingActivityStatus extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.wear.ongoing.TimeDependentText {
- method public static androidx.wear.ongoing.OngoingActivityStatus forPart(androidx.wear.ongoing.StatusPart);
+ public final class Status implements androidx.wear.ongoing.TimeDependentText {
+ method public static androidx.wear.ongoing.Status forPart(androidx.wear.ongoing.Status.Part);
method public long getNextChangeTimeMillis(long);
- method public androidx.wear.ongoing.StatusPart? getPart(String);
+ method public androidx.wear.ongoing.Status.Part? getPart(String);
method public java.util.Set<java.lang.String!> getPartNames();
method public java.util.List<java.lang.CharSequence!> getTemplates();
method public CharSequence getText(android.content.Context, long);
}
- public static final class OngoingActivityStatus.Builder {
- ctor public OngoingActivityStatus.Builder();
- method public androidx.wear.ongoing.OngoingActivityStatus.Builder addPart(String, androidx.wear.ongoing.StatusPart);
- method public androidx.wear.ongoing.OngoingActivityStatus.Builder addTemplate(CharSequence);
- method public androidx.wear.ongoing.OngoingActivityStatus build();
+ public static final class Status.Builder {
+ ctor public Status.Builder();
+ method public androidx.wear.ongoing.Status.Builder addPart(String, androidx.wear.ongoing.Status.Part);
+ method public androidx.wear.ongoing.Status.Builder addTemplate(CharSequence);
+ method public androidx.wear.ongoing.Status build();
}
- public abstract class StatusPart implements androidx.wear.ongoing.TimeDependentText androidx.versionedparcelable.VersionedParcelable {
- ctor public StatusPart();
+ public abstract static class Status.Part implements androidx.wear.ongoing.TimeDependentText {
}
- @androidx.versionedparcelable.VersionedParcelize public class TextStatusPart extends androidx.wear.ongoing.StatusPart {
- ctor public TextStatusPart(String);
+ public static final class Status.StopwatchPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+ ctor public Status.StopwatchPart(long, long, long);
+ ctor public Status.StopwatchPart(long, long);
+ ctor public Status.StopwatchPart(long);
+ }
+
+ public static final class Status.TextPart extends androidx.wear.ongoing.Status.Part {
+ ctor public Status.TextPart(String);
method public long getNextChangeTimeMillis(long);
method public CharSequence getText(android.content.Context, long);
}
- public interface TimeDependentText {
- method public long getNextChangeTimeMillis(long);
- method public CharSequence getText(android.content.Context, long);
- }
-
- @androidx.versionedparcelable.VersionedParcelize public class TimerStatusPart extends androidx.wear.ongoing.StatusPart {
- ctor public TimerStatusPart(long, boolean, long, long);
- ctor public TimerStatusPart(long, boolean, long);
- ctor public TimerStatusPart(long, boolean);
- ctor public TimerStatusPart(long);
+ public abstract static class Status.TimerOrStopwatchPart extends androidx.wear.ongoing.Status.Part {
method public long getNextChangeTimeMillis(long);
method public long getPausedAtMillis();
method public CharSequence getText(android.content.Context, long);
@@ -95,5 +83,16 @@
method public boolean isPaused();
}
+ public static final class Status.TimerPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+ ctor public Status.TimerPart(long, long, long);
+ ctor public Status.TimerPart(long, long);
+ ctor public Status.TimerPart(long);
+ }
+
+ public interface TimeDependentText {
+ method public long getNextChangeTimeMillis(long);
+ method public CharSequence getText(android.content.Context, long);
+ }
+
}
diff --git a/wear/wear-ongoing/lint-baseline.xml b/wear/wear-ongoing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-ongoing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivity.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivity.java
index 6c53230..db86223 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivity.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivity.java
@@ -30,6 +30,7 @@
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.content.LocusIdCompat;
+import androidx.core.util.Preconditions;
import java.util.function.Predicate;
@@ -55,7 +56,7 @@
* them in both the {@link Builder#Builder(Context, String, int, NotificationCompat.Builder)} and
* {@link NotificationManager#notify(String, int, Notification)}
* <p>
- * Afterward, {@link OngoingActivity#update(Context, OngoingActivityStatus) update} can be used to
+ * Afterward, {@link OngoingActivity#update(Context, Status) update} can be used to
* update the status.
* <p>
* If saving the {@link OngoingActivity} instance is not convenient, it can be recovered (after the
@@ -66,6 +67,7 @@
@Nullable
private final String mTag;
private final int mNotificationId;
+ @Nullable
private final NotificationCompat.Builder mNotificationBuilder;
private final OngoingActivityData mData;
@@ -79,6 +81,14 @@
this.mData = data;
}
+ // Used when reconstructing an OngoingActivity form a bundle.
+ OngoingActivity(@NonNull OngoingActivityData data) {
+ this.mTag = null;
+ this.mNotificationId = 0;
+ this.mNotificationBuilder = null;
+ this.mData = data;
+ }
+
/**
* Builder used to build an {@link OngoingActivity}
*/
@@ -91,12 +101,14 @@
// Ongoing Activity Data
private Icon mAnimatedIcon;
private Icon mStaticIcon;
- private OngoingActivityStatus mStatus;
+ private Status mStatus;
private PendingIntent mTouchIntent;
private LocusIdCompat mLocusId;
- private int mOngoingActivityId = OngoingActivityData.DEFAULT_ID;
+ private int mOngoingActivityId = DEFAULT_ID;
private String mCategory;
+ static final int DEFAULT_ID = -1;
+
/**
* Construct a new empty {@link Builder}, associated with the given notification.
*
@@ -181,7 +193,7 @@
* show progress of the Ongoing Activity.
*/
@NonNull
- public Builder setStatus(@NonNull OngoingActivityStatus status) {
+ public Builder setStatus(@NonNull Status status) {
mStatus = status;
return this;
}
@@ -248,11 +260,12 @@
throw new IllegalArgumentException("Touch intent should be specified.");
}
- OngoingActivityStatus status = mStatus;
+ OngoingActivityStatus status = mStatus == null ? null : mStatus.toVersionedParcelable();
if (status == null) {
String text = notification.extras.getString(Notification.EXTRA_TEXT);
if (text != null) {
- status = OngoingActivityStatus.forPart(new TextStatusPart(text));
+ status = Status.forPart(new Status.TextPart(text))
+ .toVersionedParcelable();
}
}
@@ -318,8 +331,9 @@
* corresponding Notification.
*/
@Nullable
- public OngoingActivityStatus getStatus() {
- return mData.getStatus();
+ public Status getStatus() {
+ return mData.getStatus() == null ? null :
+ Status.fromVersionedParcelable(mData.getStatus());
}
/**
@@ -376,7 +390,8 @@
* this call returns.
*/
public void apply(@NonNull @SuppressWarnings("unused") Context context) {
- mData.extend(mNotificationBuilder);
+ Preconditions.checkNotNull(mNotificationBuilder);
+ SerializationHelper.extend(mNotificationBuilder, mData);
}
/**
@@ -388,9 +403,10 @@
* this call returns.
* @param status The new status of this Ongoing Activity.
*/
- public void update(@NonNull Context context, @NonNull OngoingActivityStatus status) {
- mData.setStatus(status);
- Notification notification = mData.extendAndBuild(mNotificationBuilder);
+ public void update(@NonNull Context context, @NonNull Status status) {
+ Preconditions.checkNotNull(mNotificationBuilder);
+ mData.setStatus(status.toVersionedParcelable());
+ Notification notification = SerializationHelper.extendAndBuild(mNotificationBuilder, mData);
NotificationManager manager = context.getSystemService(NotificationManager.class);
if (mTag == null) {
@@ -412,20 +428,23 @@
@Nullable
public static OngoingActivity recoverOngoingActivity(
@NonNull Context context,
- @NonNull Predicate<OngoingActivityData> filter
+ @NonNull Predicate<OngoingActivity> filter
) {
StatusBarNotification[] notifications =
context.getSystemService(NotificationManager.class).getActiveNotifications();
for (StatusBarNotification statusBarNotification : notifications) {
OngoingActivityData data =
- OngoingActivityData.create(statusBarNotification.getNotification());
- if (data != null && filter.test(data)) {
- return new OngoingActivity(
+ SerializationHelper.createInternal(statusBarNotification.getNotification());
+ if (data != null) {
+ OngoingActivity oa = new OngoingActivity(
statusBarNotification.getTag(),
statusBarNotification.getId(),
new NotificationCompat.Builder(context,
statusBarNotification.getNotification()),
data);
+ if (filter.test(oa)) {
+ return oa;
+ }
}
}
return null;
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityData.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityData.java
index 96831bb..9e06859 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityData.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityData.java
@@ -15,26 +15,26 @@
*/
package androidx.wear.ongoing;
-import android.app.Notification;
import android.app.PendingIntent;
import android.graphics.drawable.Icon;
-import android.os.Bundle;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.app.NotificationCompat;
+import androidx.annotation.RestrictTo;
import androidx.core.content.LocusIdCompat;
import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelable;
import androidx.versionedparcelable.VersionedParcelize;
/**
- * This class is used internally by the library to represent the data of an OngoingActivity.
+ * This class is used internally by the library to represent the data of an OngoingActivity and
+ * serialize/deserialize using VersionedParcelable.
+ * @hide
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY)
@VersionedParcelize
-public class OngoingActivityData implements VersionedParcelable {
+class OngoingActivityData implements VersionedParcelable {
@Nullable
@ParcelField(value = 1, defaultValue = "null")
Icon mAnimatedIcon;
@@ -89,67 +89,6 @@
mTimestamp = timestamp;
}
- @NonNull
- NotificationCompat.Builder extend(@NonNull NotificationCompat.Builder builder) {
- ParcelUtils.putVersionedParcelable(builder.getExtras(), EXTRA_ONGOING_ACTIVITY,
- this);
- return builder;
- }
-
- @NonNull Notification extendAndBuild(@NonNull NotificationCompat.Builder builder) {
- Notification notification = extend(builder).build();
- // TODO(http://b/169394642): Undo this if/when the bug is fixed.
- notification.extras.putBundle(
- EXTRA_ONGOING_ACTIVITY,
- builder.getExtras().getBundle(EXTRA_ONGOING_ACTIVITY)
- );
- return notification;
- }
-
- /**
- * Checks if the given notification represents an ongoing activity.
- */
- public static boolean hasOngoingActivity(@NonNull Notification notification) {
- return notification.extras.getBundle(EXTRA_ONGOING_ACTIVITY) != null;
- }
-
- /**
- * Deserializes the {@link OngoingActivityData} from a notification.
- *
- * @param notification the notification that may contain information about a Ongoing
- * Activity.
- * @return the data, or null of the notification doesn't contain Ongoing Activity data.
- */
- @Nullable
- public static OngoingActivityData create(@NonNull Notification notification) {
- return create(notification.extras);
- }
-
- /**
- * Deserializes the {@link OngoingActivityData} from a Bundle.
- *
- * @param bundle the bundle that may contain information about a Ongoing Activity.
- * @return the data, or null of the Bundle doesn't contain Ongoing Activity data.
- */
- @Nullable
- public static OngoingActivityData create(@NonNull Bundle bundle) {
- return ParcelUtils.getVersionedParcelable(bundle, EXTRA_ONGOING_ACTIVITY);
- }
-
-
- /**
- * Copies an Ongoing Activity information from a bundle to another, without deserializing
- * and serializing (Note that Bundle instance is shared, not copied and deserializing the
- * Ongoing activity information somewhere else negates the advantages of using this)
- *
- * @param sourceBundle The bundle to get the Ongoing Activity data from
- * @param destinationBundle The bundle to put the Ongoing Activity data into.
- */
- public static void copy(@NonNull Bundle sourceBundle, @NonNull Bundle destinationBundle) {
- destinationBundle.putBundle(EXTRA_ONGOING_ACTIVITY,
- sourceBundle.getBundle(EXTRA_ONGOING_ACTIVITY));
- }
-
/**
* Get the animated icon that can be used on some surfaces to represent this
* {@link OngoingActivity}. For example, in the WatchFace.
@@ -227,11 +166,5 @@
void setStatus(@NonNull OngoingActivityStatus status) {
mStatus = status;
}
-
- /** Notification action extra which contains ongoing activity extensions */
- private static final String EXTRA_ONGOING_ACTIVITY =
- "android.wearable.ongoingactivities.EXTENSIONS";
-
- static final int DEFAULT_ID = -1;
}
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityStatus.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityStatus.java
index dd07bd5..ffd3e7f 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityStatus.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityStatus.java
@@ -15,14 +15,11 @@
*/
package androidx.wear.ongoing;
-import android.content.Context;
import android.os.Bundle;
-import android.text.SpannableStringBuilder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
import androidx.versionedparcelable.CustomVersionedParcelable;
import androidx.versionedparcelable.NonParcelField;
import androidx.versionedparcelable.ParcelField;
@@ -30,55 +27,35 @@
import androidx.versionedparcelable.VersionedParcelize;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
- * Base class to serialize / deserialize {@link OngoingActivityStatus} into / from a Notification.
- * <p>
- * A status is composed of Parts, and they are joined together with a template.
- * <p>
- * Note that for backwards compatibility reasons the code rendering this status message may not
- * have all of the [StatusPart] classes that are available in later versions of the library.
- * Templates that do not have values for all of the named parts will not be used.
- * The template list will be iterated through looking for the first template with all matching named
- * parts available, this will be selected for rendering the status.
- * <p>
- * To provide for backwards compatibility, you should provide one (or more) fallback templates which
- * use status parts from earlier versions of the API. e.g. TextStatusPart & TimerStatusPart
- * <p>
- * The status and part classes here use timestamps for updating the displayed representation of the
- * status, in cases when this is needed (chronometers), as returned by
- * {@link android.os.SystemClock#elapsedRealtime()}
+ * Class used internally by the library to represent the status of and ongoing activity, and to
+ * serialize / deserialize.
+ * @hide
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY)
@VersionedParcelize(isCustom = true)
-public class OngoingActivityStatus extends CustomVersionedParcelable
- implements TimeDependentText {
+class OngoingActivityStatus extends CustomVersionedParcelable {
@NonNull
@ParcelField(value = 1)
List<CharSequence> mTemplates = new ArrayList<>();
@NonParcelField
@NonNull
- private Map<String, StatusPart> mParts = new HashMap<>();
+ Map<String, StatusPart> mParts = new HashMap<>();
// Used to serialize/deserialize mParts to avoid http://b/132619460
@ParcelField(value = 2)
Bundle mPartsAsBundle;
- // Name of the {@link StatusPart} created when using {@link OngoingActivityStatus.forPart()}
- private static final String DEFAULT_STATUS_PART_NAME = "defaultStatusPartName";
-
// Needed By VersionedParcelables.
OngoingActivityStatus() {
}
- // Basic constructor used by the Builder
- @VisibleForTesting
+ // Basic constructor used by OngoingActivityStatusApi
OngoingActivityStatus(
@Nullable List<CharSequence> templates,
@NonNull Map<String, StatusPart> parts
@@ -87,225 +64,13 @@
mParts = parts;
}
- /**
- * Convenience method for creating a Status with no template and a single Part.
- *
- * @param part The only Part that composes this status.
- * @return A new {@link OngoingActivityStatus} with just one Part.
- */
- @NonNull
- public static OngoingActivityStatus forPart(@NonNull StatusPart part) {
- // Create an OngoingActivityStatus using only this part and the default template.
- return new OngoingActivityStatus.Builder().addPart(DEFAULT_STATUS_PART_NAME, part).build();
- }
-
- /**
- * Helper to Build OngoingActivityStatus instances.
- *
- * Templates can be specified, to specify how to render the parts and any surrounding
- * text/format.
- * If no template is specified, a default template that concatenates all parts separated
- * by space is used.
- */
- public static final class Builder {
- private List<CharSequence> mTemplates = new ArrayList<>();
- private CharSequence mDefaultTemplate = "";
- private Map<String, StatusPart> mParts = new HashMap<>();
-
- public Builder() {
- }
-
- /**
- * Add a template to use for this status. Placeholders can be defined with #name#
- * To produce a '#', use '##' in the template.
- * If multiple templates are specified, the first one (in the order they where added by
- * calling this method) that has all required fields is used.
- * If no template is specified, a default template that concatenates all parts separated
- * by space is used.
- *
- * @param template the template to be added
- * @return this builder, to chain calls.
- */
- @NonNull
- public Builder addTemplate(@NonNull CharSequence template) {
- mTemplates.add(template);
- return this;
- }
-
- /**
- * Add a part to be inserted in the placeholders.
- *
- * @param name the name of this part. In the template, use this name surrounded by '#'
- * to reference it, e.g. here "track" and in the template "#track#"
- * @param part The part that will be rendered in the specified position/s in the template.
- * @return this builder, to chain calls.
- */
- @NonNull
- @SuppressWarnings("MissingGetterMatchingBuilder")
- // We don't want a getter getParts()
- public Builder addPart(@NonNull String name, @NonNull StatusPart part) {
- mParts.put(name, part);
- mDefaultTemplate += (mDefaultTemplate.length() > 0 ? " " : "") + "#" + name + "#";
- return this;
- }
-
- /**
- * Build an OngoingActivityStatus with the given parameters.
- * @return the built OngoingActivityStatus
- */
- @NonNull
- public OngoingActivityStatus build() {
- List<CharSequence> templates = mTemplates.isEmpty() ? Arrays.asList(mDefaultTemplate)
- : mTemplates;
-
- // Verify that the last template can be rendered by every SysUI.
- // Verify that all templates have all required parts.
- Map<String, CharSequence> base = new HashMap<>();
- Map<String, CharSequence> all = new HashMap<>();
- for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
- if (me.getValue() instanceof TextStatusPart
- || me.getValue() instanceof TimerStatusPart) {
- base.put(me.getKey(), "");
- }
- all.put(me.getKey(), "");
- }
- if (processTemplate(templates.get(templates.size() - 1), base) == null) {
- throw new IllegalStateException("For backwards compatibility reasons the last "
- + "templateThe should only use TextStatusPart & TimerStatusPart");
- }
- for (CharSequence template: templates) {
- if (processTemplate(template, all) == null) {
- throw new IllegalStateException("The template \"" + template + "\" is missing"
- + " some parts for rendering.");
- }
- }
-
- return new OngoingActivityStatus(templates, mParts);
- }
- }
-
- /**
- * @return the list of templates that this status has.
- */
- @NonNull
- public List<CharSequence> getTemplates() {
- return mTemplates;
- }
-
- /**
- * @return the names of the parts provide to this status.
- */
- @NonNull
- public Set<String> getPartNames() {
- return Collections.unmodifiableSet(mParts.keySet());
- }
-
- /**
- * Returns the value of the part with the given name.
- * @param name the name to lookup.
- * @return the part with the given name, can be null.
- */
- @Nullable
- public StatusPart getPart(@NonNull String name) {
- return mParts.get(name);
- }
-
- /**
- * Process a template and replace placeholders with the provided values.
- * Placeholders are named, delimited by '#'. For example: '#name#'
- * To produce a '#' in the output, use '##' in the template.
- *
- * @param template The template to use as base.
- * @param values The values to replace the placeholders in the template with.
- * @return The template with the placeholders replaced, or null if the template references a
- * value that it's not present (or null).
- */
- @Nullable
- static CharSequence processTemplate(@NonNull CharSequence template,
- @NonNull Map<String, CharSequence> values) {
- SpannableStringBuilder ssb = new SpannableStringBuilder(template);
-
- int opening = -1;
- for (int i = 0; i < ssb.length(); i++) {
- if (ssb.charAt(i) == '#') {
- if (opening >= 0) {
- // Replace '##' with '#'
- // Replace '#varName#' with the value from the map.
- CharSequence replaceWith =
- opening == i - 1 ? "#" :
- values.get(ssb.subSequence(opening + 1, i).toString());
- if (replaceWith == null) {
- return null;
- }
- ssb.replace(opening, i + 1, replaceWith);
- i = opening + replaceWith.length() - 1;
- opening = -1;
- } else {
- opening = i;
- }
- }
- }
-
- return ssb;
- }
-
- /**
- * Returns a textual representation of this status at the given time. The first template that
- * has all required information will be used, and each part will be used in their respective
- * placeholder/s.
- *
- * @param context may be used for internationalization. Only used while this method
- * executed.
- * @param timeNowMillis the timestamp of the time we want to display, usually now, as
- * @return the rendered text, for best compatibility, display using a TextView.
- */
- @NonNull
- @Override
- public CharSequence getText(@NonNull Context context, long timeNowMillis) {
- Map<String, CharSequence> texts = new HashMap<>();
- for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
- CharSequence text = me.getValue().getText(context, timeNowMillis);
- texts.put(me.getKey(), text);
- }
-
- for (CharSequence template : mTemplates) {
- CharSequence ret = processTemplate(template, texts);
- if (ret != null) {
- return ret;
- }
- }
-
- return "";
- }
-
- /**
- * Returns the next time this status could have a different rendering.
- * There is no guarantee that the rendering will change at the returned time (for example, if
- * some information in the status is not rendered).
- *
- * @param fromTimeMillis current time, usually now as returned by
- * {@link android.os.SystemClock#elapsedRealtime()}. In most cases
- * {@code getText} and {@code getNextChangeTimeMillis} should be called
- * with the exact same timestamp, so changes are not missed.
- * @return the next time (counting from fromTimeMillis) that this status may produce a
- * different result when calling getText().
- */
- @Override
- public long getNextChangeTimeMillis(long fromTimeMillis) {
- long ret = Long.MAX_VALUE;
- for (StatusPart part : mParts.values()) {
- ret = Math.min(ret, part.getNextChangeTimeMillis(fromTimeMillis));
- }
- return ret;
- }
-
// Implementation of CustomVersionedParcelable
/**
- * See {@link androidx.versionedparcelable.CustomVersionedParcelable.onPreParceling()}
+ * See {@link androidx.versionedparcelable.CustomVersionedParcelable#onPreParceling(boolean)}
* @hide
*/
@Override
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
public void onPreParceling(boolean isStream) {
mPartsAsBundle = new Bundle();
for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
@@ -314,11 +79,11 @@
}
/**
- * See {@link androidx.versionedparcelable.CustomVersionedParcelable.onPostParceling()}
+ * See {@link androidx.versionedparcelable.CustomVersionedParcelable#onPostParceling()}
* @hide
*/
@Override
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
public void onPostParceling() {
mParts = new HashMap<>();
for (String key : mPartsAsBundle.keySet()) {
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/SerializationHelper.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/SerializationHelper.java
new file mode 100644
index 0000000..5e6d10b
--- /dev/null
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/SerializationHelper.java
@@ -0,0 +1,134 @@
+/*
+ * 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.wear.ongoing;
+
+import android.app.Notification;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.core.app.NotificationCompat;
+import androidx.versionedparcelable.ParcelUtils;
+
+/**
+ * Class used to manage Ongoing Activity information as part of a Bundle or Notification.
+ */
+public class SerializationHelper {
+ // Utility class, avoid instantiation
+ private SerializationHelper() {
+ }
+
+ /**
+ * Checks if the given notification contains information of an ongoing activity.
+ */
+ public static boolean hasOngoingActivity(@NonNull Notification notification) {
+ return notification.extras.getBundle(EXTRA_ONGOING_ACTIVITY) != null;
+ }
+
+ /**
+ * Deserializes the {@link OngoingActivity} from a notification.
+ *
+ * @param notification the notification that may contain information about a Ongoing
+ * Activity.
+ * @return the data, or null of the notification doesn't contain Ongoing Activity data.
+ */
+ @Nullable
+ public static OngoingActivity create(@NonNull Notification notification) {
+ return create(notification.extras);
+ }
+
+ /**
+ * Deserializes the {@link OngoingActivity} from a Bundle.
+ *
+ * @param bundle the bundle that may contain information about a Ongoing Activity.
+ * @return the data, or null of the Bundle doesn't contain Ongoing Activity data.
+ */
+ @Nullable
+ public static OngoingActivity create(@NonNull Bundle bundle) {
+ OngoingActivityData data = createInternal(bundle);
+ return data == null ? null : new OngoingActivity(data);
+ }
+
+ /**
+ * Copies an Ongoing Activity information from a bundle to another, without deserializing
+ * and serializing (Note that Bundle instance is shared, not copied and deserializing the
+ * Ongoing activity information somewhere else negates the advantages of using this)
+ *
+ * @param sourceBundle The bundle to get the Ongoing Activity data from
+ * @param destinationBundle The bundle to put the Ongoing Activity data into.
+ */
+ public static void copy(@NonNull Bundle sourceBundle, @NonNull Bundle destinationBundle) {
+ destinationBundle.putBundle(EXTRA_ONGOING_ACTIVITY,
+ sourceBundle.getBundle(EXTRA_ONGOING_ACTIVITY));
+ }
+
+ /**
+ * Add the information from the given OngoingActivityData into the notification builder.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @NonNull
+ static NotificationCompat.Builder extend(@NonNull NotificationCompat.Builder builder,
+ @NonNull OngoingActivityData data) {
+ ParcelUtils.putVersionedParcelable(builder.getExtras(), EXTRA_ONGOING_ACTIVITY,
+ data);
+ return builder;
+ }
+
+ /**
+ * Add the information from the given OngoingActivityData into the notification builder
+ * and build the notification.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @NonNull
+ static Notification extendAndBuild(@NonNull NotificationCompat.Builder builder,
+ @NonNull OngoingActivityData data) {
+ Notification notification = extend(builder, data).build();
+ // TODO(http://b/169394642): Undo this if/when the bug is fixed.
+ notification.extras.putBundle(
+ EXTRA_ONGOING_ACTIVITY,
+ builder.getExtras().getBundle(EXTRA_ONGOING_ACTIVITY)
+ );
+ return notification;
+ }
+
+ /**
+ * Deserialize a OngoingActivityData from a notification.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Nullable
+ static OngoingActivityData createInternal(@NonNull Notification notification) {
+ return createInternal(notification.extras);
+ }
+
+ /**
+ * Deserialize a OngoingActivityData from a bundle.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Nullable
+ static OngoingActivityData createInternal(@NonNull Bundle bundle) {
+ return ParcelUtils.getVersionedParcelable(bundle, EXTRA_ONGOING_ACTIVITY);
+ }
+
+ /** Notification action extra which contains ongoing activity extensions */
+ private static final String EXTRA_ONGOING_ACTIVITY =
+ "android.wearable.ongoingactivities.EXTENSIONS";
+}
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/Status.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/Status.java
new file mode 100644
index 0000000..0100562
--- /dev/null
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/Status.java
@@ -0,0 +1,584 @@
+/*
+ * Copyright 2020 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.wear.ongoing;
+
+import android.content.Context;
+import android.text.SpannableStringBuilder;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base class to represent the status of an Ongoing Activity and render it.
+ * <p>
+ * A status is composed of Parts, and they are joined together with a template.
+ * <p>
+ * Note that for backwards compatibility reasons the code rendering this status message may not
+ * have all of the [Part] classes that are available in later versions of the library.
+ * Templates that do not have values for all of the named parts will not be used.
+ * The template list will be iterated through looking for the first template with all matching named
+ * parts available, this will be selected for rendering the status.
+ * <p>
+ * To provide for backwards compatibility, you should provide one (or more) fallback templates which
+ * use status parts from earlier versions of the API. e.g. TextPart, TimerPart & StopwatchPart
+ * <p>
+ * The status and part classes here use timestamps for updating the displayed representation of the
+ * status, in cases when this is needed (chronometers), as returned by
+ * {@link android.os.SystemClock#elapsedRealtime()}
+ */
+public final class Status implements TimeDependentText {
+ @NonNull
+ final List<CharSequence> mTemplates;
+
+ @NonNull
+ private final Map<String, StatusPart> mParts;
+
+ /**
+ * Abstract class to represent An Ongoing activity status or part of it.
+ * <p>
+ * Parts are used to create complex statuses, that may contain several timers, placeholders for
+ * text, etc. They may also be used to convey information to the system about this Ongoing
+ * Activity.
+ */
+ public abstract static class Part implements TimeDependentText {
+ // Hide constructor.
+ Part() {
+ }
+
+ @Nullable
+ StatusPart toVersionedParcelable() {
+ return null;
+ }
+
+ @Nullable
+ static Part fromVersionedParcelable(@Nullable StatusPart vp) {
+ if (vp == null) {
+ return null;
+ }
+ if (vp instanceof TextStatusPart) {
+ return new TextPart((TextStatusPart) vp);
+ } else if (vp instanceof TimerStatusPart) {
+ TimerStatusPart tsp = (TimerStatusPart) vp;
+ return tsp.mCountDown ? new TimerPart(tsp) : new StopwatchPart(tsp);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * An Ongoing activity status (or part of it) representing a plain, static text.
+ * <p>
+ * Available since wear-ongoing:1.0.0
+ */
+ public static final class TextPart extends Part {
+ @NonNull
+ private final TextStatusPart mPart;
+
+ TextPart(@NonNull TextStatusPart part) {
+ mPart = part;
+ }
+
+ /**
+ * Create a Part representing a static text.
+ */
+ public TextPart(@NonNull String str) {
+ mPart = new TextStatusPart(str);
+ }
+
+ @Override
+ @NonNull
+ StatusPart toVersionedParcelable() {
+ return mPart;
+ }
+
+ /**
+ * See {@link TimeDependentText#getText(Context, long)}
+ */
+ @NonNull
+ @Override
+ public CharSequence getText(@NonNull Context context, long timeNowMillis) {
+ return mPart.getText(context, timeNowMillis);
+ }
+
+ /**
+ * See {@link TimeDependentText#getNextChangeTimeMillis(long)}
+ */
+ @Override
+ public long getNextChangeTimeMillis(long fromTimeMillis) {
+ return mPart.getNextChangeTimeMillis(fromTimeMillis);
+ }
+
+ @Override
+ public int hashCode() {
+ return mPart.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TextPart)) return false;
+ return mPart.equals(((TextPart) obj).mPart);
+ }
+ }
+
+ /**
+ * Base class for {@link TimerPart} and {@link StopwatchPart}, defines the getters but can't
+ * be created directly, create one of those instead.
+ */
+ public abstract static class TimerOrStopwatchPart extends Part {
+ @NonNull
+ private final TimerStatusPart mPart;
+
+ TimerOrStopwatchPart(@NonNull TimerStatusPart part) {
+ mPart = part;
+ }
+
+ /**
+ * @return the time at which this Timer or Stopwatch will display 0, will usually be in the
+ * past for a stopwatch and in the future for timers.
+ */
+ public long getTimeZeroMillis() {
+ return mPart.mTimeZeroMillis;
+ }
+
+ /**
+ * @return {@code false} if this is a stopwatch or {@code true} if this is a timer.
+ */
+ public boolean isCountDown() {
+ return mPart.mCountDown;
+ }
+
+ /**
+ * Determines if this Timer or Stopwatch is paused. i.e. the display representation will
+ * not change over time.
+ *
+ * @return {@code true} if this is paused, {@code false} if it's running.
+ */
+ public boolean isPaused() {
+ return mPart.isPaused();
+ }
+
+ /**
+ * @return the timestamp of the time when this was paused. Use
+ * {@link #isPaused()} to determine if this is paused or not.
+ */
+ public long getPausedAtMillis() {
+ return mPart.mPausedAtMillis;
+ }
+
+ /**
+ * Determines if this has a total duration set.
+ *
+ * @return {@code true} if this the total duration was set, {@code false} if not.
+ */
+ public boolean hasTotalDuration() {
+ return mPart.mTotalDurationMillis >= 0L;
+ }
+
+ /**
+ * @return the total duration of this timer/stopwatch, if set. Use
+ * {@link #hasTotalDuration()} to determine if this has a duration set.
+ */
+ public long getTotalDurationMillis() {
+ return mPart.mTotalDurationMillis;
+ }
+
+ @Override
+ @NonNull
+ StatusPart toVersionedParcelable() {
+ return mPart;
+ }
+
+ @Override
+ public int hashCode() {
+ return mPart.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TimerOrStopwatchPart)) return false;
+ return mPart.equals(((TimerOrStopwatchPart) obj).mPart);
+ }
+
+ /**
+ * See {@link TimeDependentText#getText(Context, long)}
+ */
+ @NonNull
+ @Override
+ public CharSequence getText(@NonNull Context context, long timeNowMillis) {
+ return mPart.getText(context, timeNowMillis);
+ }
+
+ /**
+ * See {@link TimeDependentText#getNextChangeTimeMillis(long)}
+ */
+ @Override
+ public long getNextChangeTimeMillis(long fromTimeMillis) {
+ return mPart.getNextChangeTimeMillis(fromTimeMillis);
+ }
+ }
+
+ /**
+ * An Ongoing activity status (or part of it) representing a timer.
+ * <p>
+ * Available since wear-ongoing:1.0.0
+ */
+ public static final class TimerPart extends TimerOrStopwatchPart {
+ TimerPart(@NonNull TimerStatusPart part) {
+ super(part);
+ }
+
+ /**
+ * Create a Part representing a timer.
+ *
+ * @param timeZeroMillis timestamp of the time at the future in which this Timer
+ * should display 0.
+ * @param pausedAtMillis timestamp of the time when this timer was paused. Or
+ * {@code -1L} if this timer is running.
+ * @param totalDurationMillis total duration of this timer, useful to display as a
+ * progress bar or similar.
+ */
+ public TimerPart(long timeZeroMillis, long pausedAtMillis,
+ long totalDurationMillis) {
+ super(new TimerStatusPart(
+ timeZeroMillis,
+ /* countDown = */ true,
+ pausedAtMillis,
+ totalDurationMillis
+ ));
+ }
+
+ /**
+ * Create a Part representing a timer.
+ *
+ * @param timeZeroMillis timestamp of the time at the future in which this Timer
+ * should display 0.
+ * @param pausedAtMillis timestamp of the time when this timer was paused. Or
+ * {@code -1L} if this timer is running.
+ */
+ public TimerPart(long timeZeroMillis, long pausedAtMillis) {
+ this(timeZeroMillis, pausedAtMillis, TimerStatusPart.LONG_DEFAULT);
+ }
+
+ /**
+ * Create a Part representing a timer.
+ *
+ * @param timeZeroMillis timestamp of the time at the future in which this Timer
+ * should display 0.
+ */
+ public TimerPart(long timeZeroMillis) {
+ this(timeZeroMillis, TimerStatusPart.LONG_DEFAULT);
+ }
+ }
+
+ /**
+ * An Ongoing activity status (or part of it) representing a stopwatch
+ * <p>
+ * Available since wear-ongoing:1.0.0
+ */
+ public static final class StopwatchPart extends TimerOrStopwatchPart {
+ StopwatchPart(@NonNull TimerStatusPart part) {
+ super(part);
+ }
+
+ /**
+ * Create a Part representing a stopwatch.
+ *
+ * @param timeZeroMillis timestamp of the time at which this stopwatch started
+ * running.
+ * @param pausedAtMillis timestamp of the time when this stopwatch was paused. Or
+ * {@code -1L} if this stopwatch is running.
+ * @param totalDurationMillis total duration of this stopwatch, useful to display as a
+ * progress bar or similar.
+ */
+ public StopwatchPart(long timeZeroMillis, long pausedAtMillis, long totalDurationMillis) {
+ super(new TimerStatusPart(
+ timeZeroMillis,
+ /* countDown = */ false,
+ pausedAtMillis,
+ totalDurationMillis
+ ));
+ }
+
+ /**
+ * Create a Part representing a stopwatch.
+ *
+ * @param timeZeroMillis timestamp of the time at which this stopwatch started
+ * running.
+ * @param pausedAtMillis timestamp of the time when this stopwatch was paused. Or
+ * {@code -1L} if this stopwatch is running.
+ */
+ public StopwatchPart(long timeZeroMillis, long pausedAtMillis) {
+ this(timeZeroMillis, pausedAtMillis, TimerStatusPart.LONG_DEFAULT);
+ }
+
+
+ /**
+ * Create a Part representing a stopwatch.
+ *
+ * @param timeZeroMillis timestamp of the time at which this stopwatch started
+ * running.
+ */
+ public StopwatchPart(long timeZeroMillis) {
+ this(timeZeroMillis, TimerStatusPart.LONG_DEFAULT);
+ }
+ }
+
+ // Name of the {@link StatusPart} created when using {@link OngoingActivityStatus.forPart()}
+ private static final String DEFAULT_STATUS_PART_NAME = "defaultStatusPartName";
+
+ // Basic constructor used by the Builder
+ @VisibleForTesting
+ Status(
+ @Nullable List<CharSequence> templates,
+ @NonNull Map<String, StatusPart> parts
+ ) {
+ mTemplates = templates;
+ mParts = parts;
+ }
+
+ OngoingActivityStatus toVersionedParcelable() {
+ return new OngoingActivityStatus(mTemplates, mParts);
+ }
+
+ static Status fromVersionedParcelable(OngoingActivityStatus vp) {
+ return new Status(vp.mTemplates, vp.mParts);
+ }
+
+ /**
+ * Convenience method for creating a Status with no template and a single Part.
+ *
+ * @param part The only Part that composes this status.
+ * @return A new {@link Status} with just one Part.
+ */
+ @NonNull
+ public static Status forPart(@NonNull Part part) {
+ // Create an OngoingActivityStatus using only this part and the default template.
+ return new Status.Builder().addPart(DEFAULT_STATUS_PART_NAME, part).build();
+ }
+
+ /**
+ * Helper to Build OngoingActivityStatus instances.
+ *
+ * Templates can be specified, to specify how to render the parts and any surrounding
+ * text/format.
+ * If no template is specified, a default template that concatenates all parts separated
+ * by space is used.
+ */
+ public static final class Builder {
+ private List<CharSequence> mTemplates = new ArrayList<>();
+ private CharSequence mDefaultTemplate = "";
+ private Map<String, StatusPart> mParts = new HashMap<>();
+
+ public Builder() {
+ }
+
+ /**
+ * Add a template to use for this status. Placeholders can be defined with #name#
+ * To produce a '#', use '##' in the template.
+ * If multiple templates are specified, the first one (in the order they where added by
+ * calling this method) that has all required fields is used.
+ * If no template is specified, a default template that concatenates all parts separated
+ * by space is used.
+ *
+ * @param template the template to be added
+ * @return this builder, to chain calls.
+ */
+ @NonNull
+ public Builder addTemplate(@NonNull CharSequence template) {
+ mTemplates.add(template);
+ return this;
+ }
+
+ /**
+ * Add a part to be inserted in the placeholders.
+ *
+ * @param name the name of this part. In the template, use this name surrounded by '#'
+ * to reference it, e.g. here "track" and in the template "#track#"
+ * @param part The part that will be rendered in the specified position/s in the template.
+ * @return this builder, to chain calls.
+ */
+ @NonNull
+ @SuppressWarnings("MissingGetterMatchingBuilder")
+ // We don't want a getter getParts()
+ public Builder addPart(@NonNull String name, @NonNull Part part) {
+ mParts.put(name, part.toVersionedParcelable());
+ mDefaultTemplate += (mDefaultTemplate.length() > 0 ? " " : "") + "#" + name + "#";
+ return this;
+ }
+
+ /**
+ * Build an OngoingActivityStatus with the given parameters.
+ *
+ * @return the built OngoingActivityStatus
+ */
+ @NonNull
+ public Status build() {
+ List<CharSequence> templates = mTemplates.isEmpty() ? Arrays.asList(mDefaultTemplate)
+ : mTemplates;
+
+ // Verify that the last template can be rendered by every SysUI.
+ // Verify that all templates have all required parts.
+ Map<String, CharSequence> base = new HashMap<>();
+ Map<String, CharSequence> all = new HashMap<>();
+ for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
+ if (me.getValue() instanceof TextStatusPart
+ || me.getValue() instanceof TimerStatusPart) {
+ base.put(me.getKey(), "");
+ }
+ all.put(me.getKey(), "");
+ }
+ if (processTemplate(templates.get(templates.size() - 1), base) == null) {
+ throw new IllegalStateException("For backwards compatibility reasons the last "
+ + "templateThe should only use TextStatusPart & TimerStatusPart");
+ }
+ for (CharSequence template : templates) {
+ if (processTemplate(template, all) == null) {
+ throw new IllegalStateException("The template \"" + template + "\" is missing"
+ + " some parts for rendering.");
+ }
+ }
+
+ return new Status(templates, mParts);
+ }
+ }
+
+ /**
+ * @return the list of templates that this status has.
+ */
+ @NonNull
+ public List<CharSequence> getTemplates() {
+ return mTemplates;
+ }
+
+ /**
+ * @return the names of the parts provide to this status.
+ */
+ @NonNull
+ public Set<String> getPartNames() {
+ return Collections.unmodifiableSet(mParts.keySet());
+ }
+
+ /**
+ * Returns the value of the part with the given name.
+ *
+ * @param name the name to lookup.
+ * @return the part with the given name, can be null.
+ */
+ @Nullable
+ public Part getPart(@NonNull String name) {
+ return Part.fromVersionedParcelable(mParts.get(name));
+ }
+
+ /**
+ * Process a template and replace placeholders with the provided values.
+ * Placeholders are named, delimited by '#'. For example: '#name#'
+ * To produce a '#' in the output, use '##' in the template.
+ *
+ * @param template The template to use as base.
+ * @param values The values to replace the placeholders in the template with.
+ * @return The template with the placeholders replaced, or null if the template references a
+ * value that it's not present (or null).
+ */
+ @Nullable
+ static CharSequence processTemplate(@NonNull CharSequence template,
+ @NonNull Map<String, CharSequence> values) {
+ SpannableStringBuilder ssb = new SpannableStringBuilder(template);
+
+ int opening = -1;
+ for (int i = 0; i < ssb.length(); i++) {
+ if (ssb.charAt(i) == '#') {
+ if (opening >= 0) {
+ // Replace '##' with '#'
+ // Replace '#varName#' with the value from the map.
+ CharSequence replaceWith =
+ opening == i - 1 ? "#" :
+ values.get(ssb.subSequence(opening + 1, i).toString());
+ if (replaceWith == null) {
+ return null;
+ }
+ ssb.replace(opening, i + 1, replaceWith);
+ i = opening + replaceWith.length() - 1;
+ opening = -1;
+ } else {
+ opening = i;
+ }
+ }
+ }
+
+ return ssb;
+ }
+
+ /**
+ * Returns a textual representation of this status at the given time. The first template that
+ * has all required information will be used, and each part will be used in their respective
+ * placeholder/s.
+ *
+ * @param context may be used for internationalization. Only used while this method
+ * executed.
+ * @param timeNowMillis the timestamp of the time we want to display, usually now, as
+ * @return the rendered text, for best compatibility, display using a TextView.
+ */
+ @NonNull
+ @Override
+ public CharSequence getText(@NonNull Context context, long timeNowMillis) {
+ Map<String, CharSequence> texts = new HashMap<>();
+ for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
+ CharSequence text = me.getValue().getText(context, timeNowMillis);
+ texts.put(me.getKey(), text);
+ }
+
+ for (CharSequence template : mTemplates) {
+ CharSequence ret = processTemplate(template, texts);
+ if (ret != null) {
+ return ret;
+ }
+ }
+
+ return "";
+ }
+
+ /**
+ * Returns the next time this status could have a different rendering.
+ * There is no guarantee that the rendering will change at the returned time (for example, if
+ * some information in the status is not rendered).
+ *
+ * @param fromTimeMillis current time, usually now as returned by
+ * {@link android.os.SystemClock#elapsedRealtime()}. In most cases
+ * {@code getText} and {@code getNextChangeTimeMillis} should be called
+ * with the exact same timestamp, so changes are not missed.
+ * @return the next time (counting from fromTimeMillis) that this status may produce a
+ * different result when calling getText().
+ */
+ @Override
+ public long getNextChangeTimeMillis(long fromTimeMillis) {
+ long ret = Long.MAX_VALUE;
+ for (StatusPart part : mParts.values()) {
+ ret = Math.min(ret, part.getNextChangeTimeMillis(fromTimeMillis));
+ }
+ return ret;
+ }
+}
+
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/StatusPart.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/StatusPart.java
index bd2019c..ca0e81b 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/StatusPart.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/StatusPart.java
@@ -16,14 +16,13 @@
package androidx.wear.ongoing;
+import androidx.annotation.RestrictTo;
import androidx.versionedparcelable.VersionedParcelable;
/**
- * Abstract class to represent An Ongoing activity status or part of it.
- * <p>
- * Parts are used to create complex statuses, that may contain several timers, placeholders for
- * text, etc.
- * They may also be used to convey information to the system about this Ongoing Activity.
+ * Base class for objects that serialize/deserialize {@link Status.Part} objects.
+ * @hide
*/
-public abstract class StatusPart implements VersionedParcelable, TimeDependentText {
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+abstract class StatusPart implements VersionedParcelable, TimeDependentText {
}
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TextStatusPart.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TextStatusPart.java
index 4da03ca..e1379d98 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TextStatusPart.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TextStatusPart.java
@@ -19,18 +19,21 @@
import android.content.Context;
import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.versionedparcelable.ParcelField;
import androidx.versionedparcelable.VersionedParcelize;
import java.util.Objects;
/**
- * An Ongoing activity status (or part of it) representing a plain, static text.
+ * Implementation and internal representation of {@link Status.TextPart}.
* <p>
* Available since wear-ongoing:1.0.0
+ * @hide
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY)
@VersionedParcelize
-public class TextStatusPart extends StatusPart {
+class TextStatusPart extends StatusPart {
@NonNull
@ParcelField(value = 1, defaultValue = "")
String mStr = "";
@@ -39,7 +42,7 @@
TextStatusPart() {
}
- public TextStatusPart(@NonNull String str) {
+ TextStatusPart(@NonNull String str) {
this.mStr = str;
}
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TimerStatusPart.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TimerStatusPart.java
index 6336499..7d5b663 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TimerStatusPart.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TimerStatusPart.java
@@ -20,6 +20,7 @@
import android.text.format.DateUtils;
import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.versionedparcelable.NonParcelField;
import androidx.versionedparcelable.ParcelField;
import androidx.versionedparcelable.VersionedParcelize;
@@ -27,12 +28,15 @@
import java.util.Objects;
/**
- * An Ongoing activity status (or part of it) representing a timer or stopwatch.
+ * Implementation and internal representation of {@link Status.TimerPart} and
+ * {@link Status.StopwatchPart}
* <p>
* Available since wear-ongoing:1.0.0
+ * @hide
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY)
@VersionedParcelize
-public class TimerStatusPart extends StatusPart {
+class TimerStatusPart extends StatusPart {
@ParcelField(value = 1, defaultValue = "0")
long mTimeZeroMillis;
@@ -68,7 +72,7 @@
* @param totalDurationMillis total duration of this timer/stopwatch, useful to display as a
* progress bar or similar.
*/
- public TimerStatusPart(long timeZeroMillis, boolean countDown, long pausedAtMillis,
+ TimerStatusPart(long timeZeroMillis, boolean countDown, long pausedAtMillis,
long totalDurationMillis) {
this.mTimeZeroMillis = timeZeroMillis;
this.mCountDown = countDown;
@@ -77,43 +81,6 @@
}
/**
- * Create a Status representing a timer or stopwatch.
- *
- * @param timeZeroMillis timestamp of the time at which this Timer should display 0,
- * will be in the
- * past for a stopwatch and usually in the future for timers.
- * @param countDown indicates if this is a stopwatch (when {@code false}) or timer
- * (when {@code true}).
- * @param pausedAtMillis timestamp of the time when this timer was paused. Or
- * {@code -1L} if this timer is running.
- */
- public TimerStatusPart(long timeZeroMillis, boolean countDown, long pausedAtMillis) {
- this(timeZeroMillis, countDown, pausedAtMillis, LONG_DEFAULT);
- }
-
- /**
- * Create a Status representing a timer or stopwatch.
- *
- * @param timeZeroMillis timestamp of the time at which this Timer should display 0,
- * will be in the
- * past for a stopwatch and usually in the future for timers.
- * @param countDown indicates if this is a stopwatch (when {@code false}) or timer
- * (when {@code true}).
- */
- public TimerStatusPart(long timeZeroMillis, boolean countDown) {
- this(timeZeroMillis, countDown, LONG_DEFAULT);
- }
-
- /**
- * Create a Status representing stopwatch.
- *
- * @param timeZeroMillis timestamp of the time at which this Stopwatch started.
- */
- public TimerStatusPart(long timeZeroMillis) {
- this(timeZeroMillis, false);
- }
-
- /**
* See {@link TimeDependentText#getText(Context, long)}]
*/
@NonNull
@@ -152,57 +119,10 @@
fromTimeMillis + ((mTimeZeroMillis - fromTimeMillis) % 1000 + 1999) % 1000 + 1;
}
- /**
- * @return the time at which this Timer will display 0, will be in the past for a stopwatch
- * and usually in the future for timers.
- */
- public long getTimeZeroMillis() {
- return mTimeZeroMillis;
- }
-
- /**
- * @return {@code false} if this is a stopwatch or {@code true} if this is a timer.
- */
- public boolean isCountDown() {
- return mCountDown;
- }
-
- /**
- * Determines if this timer is paused. i.e. the display representation will not change over
- * time.
- *
- * @return {@code true} if this timer is paused, {@code false} if it's running.
- */
public boolean isPaused() {
return mPausedAtMillis >= 0L;
}
- /**
- * @return the timestamp of the time when this timer was paused. Use
- * {@link TimerStatusPart#isPaused()} to determine if this timer is paused or not.
- */
- public long getPausedAtMillis() {
- return mPausedAtMillis;
- }
-
- /**
- * Determines if this timer has a total duration set.
- *
- * @return {@code true} if this the total duration was set, {@code false} if not.
- */
- public boolean hasTotalDuration() {
- return mTotalDurationMillis >= 0L;
- }
-
- /**
- * @return the total duration of this timer/stopwatch, if set. Use
- * {@link TimerStatusPart#hasTotalDuration()} to determine if this timer has a
- * duration set.
- */
- public long getTotalDurationMillis() {
- return mTotalDurationMillis;
- }
-
@Override
public boolean equals(Object o) {
if (!(o instanceof TimerStatusPart)) return false;
diff --git a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityPartTest.kt b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityPartTest.kt
index 7c19cda..1110cee 100644
--- a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityPartTest.kt
+++ b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityPartTest.kt
@@ -30,86 +30,86 @@
@Test
fun testTextOngoingActivityStatusBasic() {
val text = "Text"
- val textStatus = TextStatusPart(text)
+ val textStatus = Status.TextPart(text)
assertEquals(Long.MAX_VALUE, textStatus.getNextChangeTimeMillis(0))
assertEquals(text, textStatus.getText(context, 0))
- assertEquals(TextStatusPart(text), textStatus)
- assertNotEquals(TextStatusPart("Other"), textStatus)
+ assertEquals(Status.TextPart(text), textStatus)
+ assertNotEquals(Status.TextPart("Other"), textStatus)
- assertEquals(TextStatusPart(text).hashCode(), textStatus.hashCode())
+ assertEquals(Status.TextPart(text).hashCode(), textStatus.hashCode())
assertEquals(
- TextStatusPart("Other").hashCode(),
- TextStatusPart("Other").hashCode()
+ Status.TextPart("Other").hashCode(),
+ Status.TextPart("Other").hashCode()
)
}
@Test
fun testTimerOngoingActivityStatusBasic() {
assertEquals(
- TimerStatusPart(1234L),
- TimerStatusPart(1234L)
+ Status.StopwatchPart(1234L),
+ Status.StopwatchPart(1234L)
)
assertEquals(
- TimerStatusPart(1234L).hashCode(),
- TimerStatusPart(1234L).hashCode()
+ Status.StopwatchPart(1234L).hashCode(),
+ Status.StopwatchPart(1234L).hashCode()
)
assertNotEquals(
- TimerStatusPart(1234L),
- TimerStatusPart(1235L)
+ Status.StopwatchPart(1234L),
+ Status.StopwatchPart(1235L)
)
assertEquals(
- TimerStatusPart(1234L, true),
- TimerStatusPart(1234L, true)
+ Status.TimerPart(1234L),
+ Status.TimerPart(1234L)
)
assertEquals(
- TimerStatusPart(1234L, true).hashCode(),
- TimerStatusPart(1234L, true).hashCode()
+ Status.TimerPart(1234L).hashCode(),
+ Status.TimerPart(1234L).hashCode()
)
assertNotEquals(
- TimerStatusPart(1234L, false),
- TimerStatusPart(1234L, true)
+ Status.StopwatchPart(1234L),
+ Status.TimerPart(1234L)
)
assertEquals(
- TimerStatusPart(1234L, true, 5678L),
- TimerStatusPart(1234L, true, 5678L)
+ Status.TimerPart(1234L, 5678L),
+ Status.TimerPart(1234L, 5678L)
)
assertEquals(
- TimerStatusPart(1234L, true, 5678L).hashCode(),
- TimerStatusPart(1234L, true, 5678L).hashCode()
+ Status.TimerPart(1234L, 5678L).hashCode(),
+ Status.TimerPart(1234L, 5678L).hashCode()
)
assertNotEquals(
- TimerStatusPart(1234L, true, 5678L),
- TimerStatusPart(1234L, true, 5679L)
+ Status.TimerPart(1234L, 5678L),
+ Status.TimerPart(1234L, 5679L)
)
assertNotEquals(
- TimerStatusPart(1234L, true, 5678L),
- TimerStatusPart(1234L, true)
+ Status.TimerPart(1234L, 5678L),
+ Status.TimerPart(1234L)
)
assertEquals(
- TimerStatusPart(1234L, true, 5678L, 100L),
- TimerStatusPart(1234L, true, 5678L, 100L)
+ Status.TimerPart(1234L, 5678L, 100L),
+ Status.TimerPart(1234L, 5678L, 100L)
)
assertEquals(
- TimerStatusPart(1234L, true, 5678L, 100L).hashCode(),
- TimerStatusPart(1234L, true, 5678L, 100L).hashCode()
+ Status.TimerPart(1234L, 5678L, 100L).hashCode(),
+ Status.TimerPart(1234L, 5678L, 100L).hashCode()
)
assertNotEquals(
- TimerStatusPart(1234L, true, 5678L, 100L),
- TimerStatusPart(1234L, true, 5678L, 101L)
+ Status.TimerPart(1234L, 5678L, 100L),
+ Status.TimerPart(1234L, 5678L, 101L)
)
assertNotEquals(
- TimerStatusPart(1234L, true, 5678L, 100L),
- TimerStatusPart(1234L, true, 5678L)
+ Status.TimerPart(1234L, 5678L, 100L),
+ Status.TimerPart(1234L, 5678L)
)
assertNotEquals(
- TimerStatusPart(1234L, true, 5678L, 100L),
- TimerStatusPart(1234L, true)
+ Status.TimerPart(1234L, 5678L, 100L),
+ Status.TimerPart(1234L)
)
}
@@ -117,16 +117,15 @@
fun testOngoingActivityStatusSerialization() {
val key = "KEY"
listOf(
- TimerStatusPart(1234L),
- TextStatusPart("Text1"),
- TimerStatusPart(1234L, false),
- TimerStatusPart(1234L, true),
- TimerStatusPart(1234L, true, 5678L),
- TextStatusPart("Text2"),
- TimerStatusPart(1234L, false, 5678L, 100L)
+ Status.StopwatchPart(1234L),
+ Status.TextPart("Text1"),
+ Status.TimerPart(1234L),
+ Status.TimerPart(1234L, 5678L),
+ Status.TextPart("Text2"),
+ Status.StopwatchPart(1234L, 5678L, 100L)
).forEach { original ->
val bundle = Bundle()
- ParcelUtils.putVersionedParcelable(bundle, key, original)
+ ParcelUtils.putVersionedParcelable(bundle, key, original.toVersionedParcelable())
val p = Parcel.obtain()
p.writeParcelable(bundle, 0)
@@ -134,8 +133,9 @@
val receivedBundle = p.readParcelable<Bundle>(Bundle::class.java.classLoader)!!
- val received = ParcelUtils.getVersionedParcelable<StatusPart>(receivedBundle, key)!!
- assertEquals(original::class, received::class)
+ val received = Status.Part.fromVersionedParcelable(
+ ParcelUtils.getVersionedParcelable<StatusPart>(receivedBundle, key)
+ )!!
assertEquals(original, received)
assertEquals(original.hashCode(), received.hashCode())
}
@@ -143,27 +143,21 @@
@Test
fun testTimerOngoingActivityStatusGetters() {
- TimerStatusPart(123L).also {
+ Status.StopwatchPart(123L).also {
assertEquals(123L, it.timeZeroMillis)
- assertFalse(it.isPaused)
- assertFalse(it.hasTotalDuration())
- }
-
- TimerStatusPart(1234L, false).also {
- assertEquals(1234L, it.timeZeroMillis)
assertFalse(it.isCountDown)
assertFalse(it.isPaused)
assertFalse(it.hasTotalDuration())
}
- TimerStatusPart(12345L, true).also {
+ Status.TimerPart(12345L).also {
assertEquals(12345L, it.timeZeroMillis)
assertTrue(it.isCountDown)
assertFalse(it.isPaused)
assertFalse(it.hasTotalDuration())
}
- TimerStatusPart(2345L, false, 3456L).also {
+ Status.StopwatchPart(2345L, 3456L).also {
assertEquals(2345L, it.timeZeroMillis)
assertFalse(it.isCountDown)
assertTrue(it.isPaused)
@@ -171,7 +165,7 @@
assertFalse(it.hasTotalDuration())
}
- TimerStatusPart(4567L, true, 7890L, 12000L).also {
+ Status.TimerPart(4567L, 7890L, 12000L).also {
assertEquals(4567L, it.timeZeroMillis)
assertTrue(it.isCountDown)
assertTrue(it.isPaused)
@@ -187,10 +181,10 @@
// timestamp 0).
val t0 = 123456L
val timerStatus =
- TimerStatusPart(/* timeZeroMillis = */ t0)
+ Status.StopwatchPart(/* timeZeroMillis = */ t0)
// The chronometer is not paused.
- assertFalse(timerStatus.isPaused())
+ assertFalse(timerStatus.isPaused)
// The chronometer will always change at timestamps ending in 456.
assertEquals(456L, timerStatus.getNextChangeTimeMillis(0L))
@@ -219,13 +213,10 @@
// Create a timer, set to expire at the given timestamp (around 2 minutes after
// timestamp 0).
val t0 = 123456L
- val timerStatus = TimerStatusPart(
- /* timeZeroMillis = */ t0,
- /* countDown = */ true
- )
+ val timerStatus = Status.TimerPart(t0)
// The Timer is not paused.
- assertFalse(timerStatus.isPaused())
+ assertFalse(timerStatus.isPaused)
// The timer will always change at timestamps ending in 456.
assertEquals(456L, timerStatus.getNextChangeTimeMillis(0L))
@@ -251,9 +242,8 @@
@Test
fun testTimerOngoingActivityStatusChronometerPaused() {
val t0 = 123456L
- var timerStatus = TimerStatusPart(
+ var timerStatus = Status.StopwatchPart(
/* timeZeroMillis = */ t0,
- /* countDown = */ false,
/* pausedAt = */ t0 + 1999
)
@@ -276,9 +266,8 @@
@Test
fun testTimerOngoingActivityStatusTimerPaused() {
val t0 = 123456L
- var timerStatus = TimerStatusPart(
+ var timerStatus = Status.TimerPart(
/* timeZeroMillis = */ t0,
- /* countDown = */ true,
/* pausedAt = */ t0 + 1999
)
diff --git a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityStatusTest.kt b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityStatusTest.kt
index 0ec5a9c..31c702f 100644
--- a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityStatusTest.kt
+++ b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityStatusTest.kt
@@ -25,9 +25,9 @@
@Test
fun testTextOngoingActivityStatusText() {
- val status = OngoingActivityStatus.Builder()
- .addPart("1", TextStatusPart("First Text"))
- .addPart("2", TextStatusPart("Second Text"))
+ val status = Status.Builder()
+ .addPart("1", Status.TextPart("First Text"))
+ .addPart("2", Status.TextPart("Second Text"))
.build()
assertEquals(Long.MAX_VALUE, status.getNextChangeTimeMillis(0))
@@ -37,9 +37,9 @@
@Test
fun testTextOngoingActivityStatusTextAndTemplate() {
- val status = OngoingActivityStatus.Builder()
- .addPart("one", TextStatusPart("First Text"))
- .addPart("two", TextStatusPart("Second Text"))
+ val status = Status.Builder()
+ .addPart("one", Status.TextPart("First Text"))
+ .addPart("two", Status.TextPart("Second Text"))
.addTemplate("#one# | #two#").build()
assertEquals(Long.MAX_VALUE, status.getNextChangeTimeMillis(0))
@@ -52,9 +52,9 @@
@Test
fun testTextOngoingActivityStatusTextAndTemplate2() {
- val status = OngoingActivityStatus.Builder()
- .addPart("a", TextStatusPart("A"))
- .addPart("b", TextStatusPart("B"))
+ val status = Status.Builder()
+ .addPart("a", Status.TextPart("A"))
+ .addPart("b", Status.TextPart("B"))
.addTemplate("#a##b#").build()
assertEquals(Long.MAX_VALUE, status.getNextChangeTimeMillis(0))
@@ -68,9 +68,9 @@
@Test
fun testTextOngoingActivityStatusMixed() {
val t0 = 123456L
- val status = OngoingActivityStatus.Builder()
- .addPart("type", TextStatusPart("Workout"))
- .addPart("time", TimerStatusPart(t0, /* countDown = */ false))
+ val status = Status.Builder()
+ .addPart("type", Status.TextPart("Workout"))
+ .addPart("time", Status.StopwatchPart(t0))
.addTemplate("The time on your #type# is #time#")
.build()
@@ -103,12 +103,12 @@
).forEach { (template, expected) ->
assertEquals(
expected,
- OngoingActivityStatus.processTemplate(template, values).toString()
+ Status.processTemplate(template, values).toString()
)
}
// Check that when undefined values are used, returns null
- assertNull(OngoingActivityStatus.processTemplate("#NOT#", values))
+ assertNull(Status.processTemplate("#NOT#", values))
}
@Test
@@ -127,7 +127,7 @@
listOf("#5#", "#4#", "#3#", "#2#") to "Three",
listOf("#1##11#", "2=#2#") to "2=Two",
).forEach { (templates, expected) ->
- val status = OngoingActivityStatus(
+ val status = Status(
templates,
values.map { (k, v) ->
k to TextStatusPart(v)
@@ -141,10 +141,10 @@
@Test(expected = IllegalStateException::class)
fun testVerifyBuildCheckBaseTemplate() {
// We verify on build() that the last template uses only base parts (Text & Timer)
- OngoingActivityStatus.Builder()
+ Status.Builder()
.addTemplate("#1#")
.addTemplate("#2#")
- .addPart("1", TextStatusPart("text"))
+ .addPart("1", Status.TextPart("text"))
.addPart("2", SampleStatusPart())
.build()
}
@@ -152,15 +152,25 @@
@Test(expected = IllegalStateException::class)
fun testVerifyBuildCheckTemplates() {
// We verify on build() that all parts used on templates are present
- OngoingActivityStatus.Builder()
+ Status.Builder()
.addTemplate("#1##2##3#")
- .addPart("1", TextStatusPart("text"))
- .addPart("2", TimerStatusPart(12345L))
+ .addPart("1", Status.TextPart("text"))
+ .addPart("2", Status.StopwatchPart(12345L))
.build()
}
- class SampleStatusPart : StatusPart() {
+ private class SampleStatusPartImpl : StatusPart() {
override fun getText(context: Context, timeNowMillis: Long) = "Sample"
override fun getNextChangeTimeMillis(fromTimeMillis: Long) = Long.MAX_VALUE
}
+
+ private class SampleStatusPart : Status.Part() {
+ val mPart = SampleStatusPartImpl()
+
+ override fun getText(context: Context, timeNowMillis: Long) =
+ mPart.getText(context, timeNowMillis)
+ override fun getNextChangeTimeMillis(fromTimeMillis: Long) =
+ mPart.getNextChangeTimeMillis(fromTimeMillis)
+ override fun toVersionedParcelable(): StatusPart = mPart
+ }
}
diff --git a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityTest.kt b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityTest.kt
index 4f7f098..1519bf6 100644
--- a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityTest.kt
+++ b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityTest.kt
@@ -27,7 +27,11 @@
private val StaticIconResourceId = 456
private val LocusIdValue = LocusIdCompat("TestLocusId")
private val OaId = 123456
- private val Status = OngoingActivityStatus.forPart(TextStatusPart("Basic Status"))
+ private val BasicStatus = androidx.wear.ongoing.Status.forPart(
+ Status.TextPart(
+ "Basic Status"
+ )
+ )
private val NotificationId = 4321
private val ChannelId = "ChannelId"
@@ -55,7 +59,7 @@
val notification = builder.build()
// check that the Notification contains the information needed.
- val received = OngoingActivityData.create(notification)!!
+ val received = SerializationHelper.create(notification)!!
assertEquals(StaticIconResourceId, received.staticIcon.resId)
assertEquals(PendingIntentValue, received.touchIntent)
// Also ensure that unset fields are null
@@ -72,7 +76,7 @@
.setStaticIcon(StaticIconResourceId)
.setLocusId(LocusIdValue)
.setOngoingActivityId(OaId)
- .setStatus(Status)
+ .setStatus(BasicStatus)
.setTouchIntent(PendingIntentValue)
.build()
oa.apply(context)
@@ -80,12 +84,12 @@
val notification = builder.build()
// check that the Notification contains the information needed.
- val received = OngoingActivityData.create(notification)!!
+ val received = SerializationHelper.create(notification)!!
assertEquals(AnimatedIconResourceId, received.animatedIcon!!.resId)
assertEquals(StaticIconResourceId, received.staticIcon.resId)
assertEquals(LocusIdValue, received.locusId)
assertEquals(OaId, received.ongoingActivityId)
- assertEquals(Status, received.status)
+ // TODO(ssancho): check status
assertEquals(PendingIntentValue, received.touchIntent)
}
@@ -97,26 +101,30 @@
.setStaticIcon(StaticIconResourceId)
.setLocusId(LocusIdValue)
.setOngoingActivityId(OaId)
- .setStatus(Status)
+ .setStatus(BasicStatus)
.setTouchIntent(PendingIntentValue)
.build()
oa.apply(context)
notificationManager.notify(NotificationId, builder.build())
// After posting, send an update.
- val newStatus = OngoingActivityStatus.forPart(TimerStatusPart(12345))
+ val newStatus = androidx.wear.ongoing.Status.forPart(
+ Status.StopwatchPart(
+ 12345
+ )
+ )
oa.update(context, newStatus)
// Get the notification and check that the status, and only the status has been updated.
val notifications = notificationManager.activeNotifications
assertEquals(1, notifications.size)
- val received = OngoingActivityData.create(notifications[0].notification)!!
+ val received = SerializationHelper.create(notifications[0].notification)!!
assertEquals(AnimatedIconResourceId, received.animatedIcon!!.resId)
assertEquals(StaticIconResourceId, received.staticIcon.resId)
assertEquals(LocusIdValue, received.locusId)
assertEquals(OaId, received.ongoingActivityId)
- assertEquals(newStatus, received.status)
+ // TODO(ssancho): check status
assertEquals(PendingIntentValue, received.touchIntent)
notificationManager.cancel(NotificationId)
@@ -143,7 +151,7 @@
for (i in 1..n) {
val builder = NotificationCompat.Builder(context, ChannelId)
OngoingActivity.Builder(context, NotificationId + i, builder)
- .setStatus(OngoingActivityStatus.forPart(TextStatusPart("Ongoing Activity")))
+ .setStatus(Status.forPart(Status.TextPart("Ongoing Activity")))
.setOngoingActivityId(i)
.setStaticIcon(StaticIconResourceId)
.setTouchIntent(PendingIntentValue)
@@ -159,13 +167,13 @@
val status = "New Status $i"
statuses.add(status)
OngoingActivity.recoverOngoingActivity(context, i)!!
- .update(context, OngoingActivityStatus.forPart(TextStatusPart(status)))
+ .update(context, Status.forPart(Status.TextPart(status)))
}
assertEquals(n, statuses.size) // Just in case.
// Get status from notifications.
val notificationStatuses = notificationManager.activeNotifications.mapNotNull { sbn ->
- OngoingActivityData.create(sbn.notification)?.status?.getText(context, 0).toString()
+ SerializationHelper.create(sbn.notification)?.status?.getText(context, 0).toString()
}.toSet()
// Check.
@@ -183,7 +191,7 @@
.setStaticIcon(StaticIconResourceId)
.setLocusId(LocusIdValue)
.setOngoingActivityId(OaId)
- .setStatus(Status)
+ .setStatus(BasicStatus)
.setTouchIntent(PendingIntentValue)
.build()
oa.apply(context)
@@ -191,15 +199,15 @@
// Copy the data.
val newBundle = Bundle()
- OngoingActivityData.copy(notification.extras, newBundle)
+ SerializationHelper.copy(notification.extras, newBundle)
// check that the information was copied.
- val received = OngoingActivityData.create(newBundle)!!
+ val received = SerializationHelper.create(newBundle)!!
assertEquals(AnimatedIconResourceId, received.animatedIcon!!.resId)
assertEquals(StaticIconResourceId, received.staticIcon.resId)
assertEquals(LocusIdValue, received.locusId)
assertEquals(OaId, received.ongoingActivityId)
- assertEquals(Status, received.status)
+ // TODO(ssancho): check status
assertEquals(PendingIntentValue, received.touchIntent)
}
@@ -211,26 +219,30 @@
.setStaticIcon(StaticIconResourceId)
.setLocusId(LocusIdValue)
.setOngoingActivityId(OaId)
- .setStatus(Status)
+ .setStatus(BasicStatus)
.setTouchIntent(PendingIntentValue)
.build()
oa.apply(context)
notificationManager.notify(NotificationId, builder.build())
// After posting, send an update.
- val newStatus = OngoingActivityStatus.forPart(TimerStatusPart(12345))
+ val newStatus = androidx.wear.ongoing.Status.forPart(
+ Status.StopwatchPart(
+ 12345
+ )
+ )
OngoingActivity.recoverOngoingActivity(context)!!.update(context, newStatus)
// Get the notification and check that the status, and only the status has been updated.
val notifications = notificationManager.activeNotifications
assertEquals(1, notifications.size)
- val received = OngoingActivityData.create(notifications[0].notification)!!
+ val received = SerializationHelper.create(notifications[0].notification)!!
assertEquals(AnimatedIconResourceId, received.animatedIcon!!.resId)
assertEquals(StaticIconResourceId, received.staticIcon.resId)
assertEquals(LocusIdValue, received.locusId)
assertEquals(OaId, received.ongoingActivityId)
- assertEquals(newStatus, received.status)
+ // TODO(ssancho): check status
assertEquals(PendingIntentValue, received.touchIntent)
// Clean up.
@@ -252,7 +264,7 @@
val notifications = notificationManager.activeNotifications
assertEquals(1, notifications.size)
- val received = OngoingActivityData.create(notifications[0].notification)!!
+ val received = SerializationHelper.create(notifications[0].notification)!!
assertNull(received.animatedIcon)
assertEquals(StaticIconResourceId, received.staticIcon.resId)
assertEquals(contentText, received.status!!.getText(context, 0).toString())
@@ -278,7 +290,7 @@
val oa = OngoingActivity.Builder(context, NotificationId, builder)
.setAnimatedIcon(newAnimatedIconResourceId)
.setStaticIcon(newStaticIconResourceId)
- .setStatus(Status)
+ .setStatus(BasicStatus)
.setTouchIntent(newPendingIntentValue)
.build()
oa.apply(context)
@@ -286,10 +298,10 @@
val notifications = notificationManager.activeNotifications
assertEquals(1, notifications.size)
- val received = OngoingActivityData.create(notifications[0].notification)!!
+ val received = SerializationHelper.create(notifications[0].notification)!!
assertEquals(newAnimatedIconResourceId, received.animatedIcon!!.resId)
assertEquals(newStaticIconResourceId, received.staticIcon.resId)
- assertEquals(Status, received.status)
+ // TODO(ssancho): check status
assertEquals(newPendingIntentValue, received.touchIntent)
// Clean up.
@@ -304,14 +316,14 @@
.setContentText("Text")
var notification = builder.build()
- assertFalse(OngoingActivityData.hasOngoingActivity(notification))
+ assertFalse(SerializationHelper.hasOngoingActivity(notification))
OngoingActivity.Builder(context, NotificationId, builder)
.build()
.apply(context)
notification = builder.build()
- assertTrue(OngoingActivityData.hasOngoingActivity(notification))
+ assertTrue(SerializationHelper.hasOngoingActivity(notification))
}
@Test
@@ -320,7 +332,7 @@
val builder1 = NotificationCompat.Builder(context, ChannelId)
val oa1 = OngoingActivity.Builder(context, NotificationId, builder1)
.setStaticIcon(StaticIconResourceId)
- .setStatus(OngoingActivityStatus.forPart(TextStatusPart("status1")))
+ .setStatus(Status.forPart(Status.TextPart("status1")))
.setOngoingActivityId(1)
.setTouchIntent(PendingIntentValue)
.build()
@@ -330,7 +342,7 @@
val builder2 = NotificationCompat.Builder(context, ChannelId)
val oa2 = OngoingActivity.Builder(context, tag, NotificationId, builder2)
.setStaticIcon(StaticIconResourceId)
- .setStatus(OngoingActivityStatus.forPart(TextStatusPart("status2")))
+ .setStatus(Status.forPart(Status.TextPart("status2")))
.setOngoingActivityId(2)
.setTouchIntent(PendingIntentValue)
.build()
@@ -340,13 +352,21 @@
assertEquals(2, notificationManager.activeNotifications.size)
// After posting, send an update to the second OA and check the statuses.
- val newStatus2 = OngoingActivityStatus.forPart(TextStatusPart("update2"))
+ val newStatus2 = androidx.wear.ongoing.Status.forPart(
+ Status.TextPart(
+ "update2"
+ )
+ )
OngoingActivity.recoverOngoingActivity(context, 2)?.update(context, newStatus2)
assertEquals("status1, update2", getStatuses())
// Update the first OA, and check the statuses.
- val newStatus1 = OngoingActivityStatus.forPart(TextStatusPart("updated-one"))
+ val newStatus1 = androidx.wear.ongoing.Status.forPart(
+ Status.TextPart(
+ "updated-one"
+ )
+ )
oa1.update(context, newStatus1)
assertEquals("updated-one, update2", getStatuses())
@@ -360,7 +380,7 @@
private fun getStatuses(): String =
notificationManager.activeNotifications
- .mapNotNull { OngoingActivityData.create(it.notification) }
- .sortedBy { it.mOngoingActivityId }
+ .mapNotNull { SerializationHelper.create(it.notification) }
+ .sortedBy { it.ongoingActivityId }
.joinToString { it.status?.getText(context, 0L).toString() }
}
diff --git a/wear/wear-phone-interactions/lint-baseline.xml b/wear/wear-phone-interactions/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-phone-interactions/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-remote-interactions/lint-baseline.xml b/wear/wear-remote-interactions/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-remote-interactions/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-client/guava/api/current.txt b/wear/wear-watchface-client/guava/api/current.txt
index eb19704..bf268ec 100644
--- a/wear/wear-watchface-client/guava/api/current.txt
+++ b/wear/wear-watchface-client/guava/api/current.txt
@@ -2,10 +2,10 @@
package androidx.wear.watchface.client {
public class ListenableWatchFaceControlClient implements androidx.wear.watchface.client.WatchFaceControlClient {
- ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient, java.util.concurrent.Executor executor);
+ ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient);
method public void close();
method public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight);
- method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+ method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient();
method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId);
method public suspend Object? getOrCreateInteractiveWatchFaceClient(String p, androidx.wear.watchface.client.DeviceConfig p1, androidx.wear.watchface.client.WatchUiState p2, androidx.wear.watchface.style.UserStyleData? p3, java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>? p4, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient> $completion);
@@ -14,7 +14,7 @@
}
public static final class ListenableWatchFaceControlClient.Companion {
- method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
}
}
diff --git a/wear/wear-watchface-client/guava/api/public_plus_experimental_current.txt b/wear/wear-watchface-client/guava/api/public_plus_experimental_current.txt
index eb19704..bf268ec 100644
--- a/wear/wear-watchface-client/guava/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-client/guava/api/public_plus_experimental_current.txt
@@ -2,10 +2,10 @@
package androidx.wear.watchface.client {
public class ListenableWatchFaceControlClient implements androidx.wear.watchface.client.WatchFaceControlClient {
- ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient, java.util.concurrent.Executor executor);
+ ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient);
method public void close();
method public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight);
- method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+ method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient();
method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId);
method public suspend Object? getOrCreateInteractiveWatchFaceClient(String p, androidx.wear.watchface.client.DeviceConfig p1, androidx.wear.watchface.client.WatchUiState p2, androidx.wear.watchface.style.UserStyleData? p3, java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>? p4, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient> $completion);
@@ -14,7 +14,7 @@
}
public static final class ListenableWatchFaceControlClient.Companion {
- method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
}
}
diff --git a/wear/wear-watchface-client/guava/api/restricted_current.txt b/wear/wear-watchface-client/guava/api/restricted_current.txt
index eb19704..bf268ec 100644
--- a/wear/wear-watchface-client/guava/api/restricted_current.txt
+++ b/wear/wear-watchface-client/guava/api/restricted_current.txt
@@ -2,10 +2,10 @@
package androidx.wear.watchface.client {
public class ListenableWatchFaceControlClient implements androidx.wear.watchface.client.WatchFaceControlClient {
- ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient, java.util.concurrent.Executor executor);
+ ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient);
method public void close();
method public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight);
- method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+ method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient();
method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId);
method public suspend Object? getOrCreateInteractiveWatchFaceClient(String p, androidx.wear.watchface.client.DeviceConfig p1, androidx.wear.watchface.client.WatchUiState p2, androidx.wear.watchface.style.UserStyleData? p3, java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>? p4, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient> $completion);
@@ -14,7 +14,7 @@
}
public static final class ListenableWatchFaceControlClient.Companion {
- method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
}
}
diff --git a/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt b/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt
index e42d7ad..1a39e34 100644
--- a/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt
+++ b/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt
@@ -42,8 +42,6 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
-import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
private const val TIMEOUT_MS = 500L
@@ -57,16 +55,12 @@
@Mock
private lateinit var surface: Surface
- private lateinit var executor: Executor
-
@Before
public fun setUp() {
MockitoAnnotations.initMocks(this)
Mockito.`when`(surfaceHolder.surfaceFrame)
.thenReturn(Rect(0, 0, 400, 400))
Mockito.`when`(surfaceHolder.surface).thenReturn(surface)
-
- executor = Executors.newSingleThreadExecutor()
}
@Test
@@ -74,8 +68,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
context,
- context.packageName,
- executor
+ context.packageName
).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
val headlessInstance = client.createHeadlessWatchFaceClient(
@@ -107,8 +100,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
context,
- context.packageName,
- executor
+ context.packageName
).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
assertNull(
@@ -132,8 +124,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
context,
- context.packageName,
- executor
+ context.packageName
).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
val interactiveInstanceFuture =
@@ -180,8 +171,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
context,
- context.packageName,
- executor
+ context.packageName
).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
val headlessInstance1 = client.createHeadlessWatchFaceClient(
@@ -231,8 +221,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
context,
- context.packageName,
- executor
+ context.packageName
).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
val interactiveInstanceFuture =
@@ -284,8 +273,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
context,
- context.packageName,
- executor
+ context.packageName
).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
assertNull(client.getInteractiveWatchFaceClientInstance("I do not exist"))
@@ -296,15 +284,13 @@
val context = ApplicationProvider.getApplicationContext<Context>()
ListenableWatchFaceControlClient.createWatchFaceControlClient(
context,
- context.packageName,
- executor
+ context.packageName
).cancel(true)
// Canceling should not prevent a subsequent createWatchFaceControlClient.
val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
context,
- context.packageName,
- executor
+ context.packageName
).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
assertThat(client).isNotNull()
client.close()
@@ -315,8 +301,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
context,
- context.packageName,
- executor
+ context.packageName
).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
client.listenableGetOrCreateInteractiveWatchFaceClient(
diff --git a/wear/wear-watchface-client/guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt b/wear/wear-watchface-client/guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt
index ea686a2..f93f87d 100644
--- a/wear/wear-watchface-client/guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt
+++ b/wear/wear-watchface-client/guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt
@@ -25,19 +25,19 @@
import androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException
import androidx.wear.watchface.style.UserStyleData
import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.Runnable
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
-import java.util.concurrent.Executor
+import kotlin.coroutines.CoroutineContext
/**
* [ListenableFuture]-based compatibility wrapper around [WatchFaceControlClient]'s suspending
* methods. This class is open to allow mocking.
*/
public open class ListenableWatchFaceControlClient(
- private val watchFaceControlClient: WatchFaceControlClient,
- private val executor: Executor
+ private val watchFaceControlClient: WatchFaceControlClient
) : WatchFaceControlClient {
override fun getInteractiveWatchFaceClientInstance(
instanceId: String
@@ -45,6 +45,13 @@
watchFaceControlClient.getInteractiveWatchFaceClientInstance(instanceId)
public companion object {
+ internal fun createImmediateCoroutineScope() = CoroutineScope(
+ object : CoroutineDispatcher() {
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ block.run()
+ }
+ }
+ )
/**
* Launches a coroutine with a new scope and returns a future that correctly handles
@@ -53,22 +60,21 @@
// TODO(flerda): Move this to a location where it can be shared.
internal fun <T> launchFutureCoroutine(
traceTag: String,
- scopeFactory: () -> CoroutineScope,
block: suspend CoroutineScope.() -> T
): ListenableFuture<T> {
val traceEvent = AsyncTraceEvent(traceTag)
val future = ResolvableFuture.create<T>()
- val coroutineScope = scopeFactory()
- // Propagate future cancellation.
- future.addListener(
- {
- if (future.isCancelled) {
- coroutineScope.cancel()
- }
- },
- { runner -> runner.run() }
- )
+ val coroutineScope = createImmediateCoroutineScope()
coroutineScope.launch {
+ // Propagate future cancellation.
+ future.addListener(
+ {
+ if (future.isCancelled) {
+ coroutineScope.cancel()
+ }
+ },
+ { runner -> runner.run() }
+ )
try {
future.set(block())
} catch (e: Exception) {
@@ -86,30 +92,30 @@
* Resolves as [ServiceNotBoundException] if the watch face control service can not
* be bound.
*
+ * Note the returned future may resolve immediately on the calling thread or it may resolve
+ * asynchronously when the service is connected on a background thread.
+ *
* @param context Calling application's [Context].
- * @param watchFacePackageName The name of the package containing the watch face control
+ * @param watchFacePackageName Name of the package containing the watch face control
* service to bind to.
- * @param executor The [Executor] on which any coroutine tasks are to run.
+ * @return [ListenableFuture]<[ListenableWatchFaceControlClient]> which on success resolves
+ * to a [ListenableWatchFaceControlClient] or throws a [ServiceNotBoundException] if the
+ * watch face control service can not be bound.
*/
@SuppressLint("NewApi") // For ACTION_WATCHFACE_CONTROL_SERVICE
@JvmStatic
public fun createWatchFaceControlClient(
context: Context,
- watchFacePackageName: String,
- executor: Executor
+ watchFacePackageName: String
): ListenableFuture<ListenableWatchFaceControlClient> =
launchFutureCoroutine(
"ListenableWatchFaceControlClient.createWatchFaceControlClient",
- {
- CoroutineScope(executor.asCoroutineDispatcher())
- },
) {
ListenableWatchFaceControlClient(
WatchFaceControlClient.createWatchFaceControlClient(
context,
watchFacePackageName
- ),
- executor
+ )
)
}
}
@@ -141,9 +147,6 @@
): ListenableFuture<InteractiveWatchFaceClient> =
launchFutureCoroutine(
"ListenableWatchFaceControlClient.listenableGetOrCreateInteractiveWatchFaceClient",
- {
- CoroutineScope(executor.asCoroutineDispatcher())
- },
) {
watchFaceControlClient.getOrCreateInteractiveWatchFaceClient(
id,
diff --git a/wear/wear-watchface-client/lint-baseline.xml b/wear/wear-watchface-client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt b/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt
index 0ce3355..d9f8180 100644
--- a/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt
+++ b/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt
@@ -56,7 +56,7 @@
private fun <T> loadGoldenResource(resourceId: Int, readFromParcel: (p: Parcel) -> T): T {
val resource = context.resources.openRawResource(resourceId)
- val bytes = Base64.getDecoder().decode(resource.readBytes())
+ val bytes = Base64.getDecoder().decode(String(resource.readBytes()))
val p = Parcel.obtain()
p.unmarshall(bytes, 0, bytes.size)
p.setDataPosition(0)
@@ -236,4 +236,11 @@
assertThat(complicationB.text.getTextAt(context.resources, 0)).isEqualTo("Example")
assertThat(complicationB.title!!.getTextAt(context.resources, 0)).isEqualTo("complication")
}
-}
\ No newline at end of file
+
+ @Test
+ public fun userStyleSchema() {
+ // TODO(b/187498135): Implement a golden test. This is harder than it sounds because the raw
+ // bytes of a serialized Parcel is not portable and can differ between architectures even
+ // on the same API level. The tests above possibly only pass on current bots by luck.
+ }
+}
diff --git a/wear/wear-watchface-complications-rendering/api/current.txt b/wear/wear-watchface-complications-rendering/api/current.txt
index 085dab4..2093fe8 100644
--- a/wear/wear-watchface-complications-rendering/api/current.txt
+++ b/wear/wear-watchface-complications-rendering/api/current.txt
@@ -20,15 +20,15 @@
public final class ComplicationDrawable extends android.graphics.drawable.Drawable {
ctor public ComplicationDrawable();
- ctor public ComplicationDrawable(android.content.Context);
- ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable);
- method public void draw(android.graphics.Canvas);
+ ctor public ComplicationDrawable(android.content.Context context);
+ ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable);
+ method public void draw(android.graphics.Canvas canvas);
method public androidx.wear.watchface.complications.rendering.ComplicationStyle getActiveStyle();
method public androidx.wear.watchface.complications.rendering.ComplicationStyle getAmbientStyle();
method public androidx.wear.complications.data.ComplicationData? getComplicationData();
method public android.content.Context? getContext();
method public long getCurrentTimeMillis();
- method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context, int);
+ method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
method public long getHighlightDuration();
method public CharSequence? getNoDataText();
method @Deprecated public int getOpacity();
@@ -37,19 +37,36 @@
method public boolean isInAmbientMode();
method public boolean isLowBitAmbient();
method public boolean isRangedValueProgressHidden();
- method public boolean onTap(@Px int, @Px int);
- method public void setAlpha(int);
- method public void setBurnInProtection(boolean);
- method public void setColorFilter(android.graphics.ColorFilter?);
- method public void setComplicationData(androidx.wear.complications.data.ComplicationData?, boolean);
- method public void setContext(android.content.Context);
- method public void setCurrentTimeMillis(long);
- method public void setHighlightDuration(@IntRange(from=0) long);
- method public void setHighlighted(boolean);
- method public void setInAmbientMode(boolean);
- method public void setLowBitAmbient(boolean);
- method public void setNoDataText(CharSequence?);
- method public void setRangedValueProgressHidden(boolean);
+ method public boolean onTap(@Px int x, @Px int y);
+ method public void setAlpha(@IntRange(from=0, to=255) int alpha);
+ method public void setBurnInProtectionOn(boolean p);
+ method public void setColorFilter(android.graphics.ColorFilter? colorFilter);
+ method public void setComplicationData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsync);
+ method public void setContext(android.content.Context context);
+ method public void setCurrentTimeMillis(long p);
+ method public void setHighlightDuration(@IntRange(from=0) long highlightDurationMillis);
+ method public void setHighlighted(boolean p);
+ method public void setInAmbientMode(boolean p);
+ method public void setLowBitAmbient(boolean p);
+ method public void setNoDataText(CharSequence? noDataText);
+ method public void setRangedValueProgressHidden(boolean rangedValueProgressHidden);
+ property public final androidx.wear.watchface.complications.rendering.ComplicationStyle activeStyle;
+ property public final androidx.wear.watchface.complications.rendering.ComplicationStyle ambientStyle;
+ property public final androidx.wear.complications.data.ComplicationData? complicationData;
+ property public final android.content.Context? context;
+ property public final long currentTimeMillis;
+ property public final long highlightDuration;
+ property public final boolean isBurnInProtectionOn;
+ property public final boolean isHighlighted;
+ property public final boolean isInAmbientMode;
+ property public final boolean isLowBitAmbient;
+ property public final boolean isRangedValueProgressHidden;
+ property public final CharSequence? noDataText;
+ field public static final androidx.wear.watchface.complications.rendering.ComplicationDrawable.Companion Companion;
+ }
+
+ public static final class ComplicationDrawable.Companion {
+ method public androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
}
public final class ComplicationHighlightRenderer {
diff --git a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
index 085dab4..2093fe8 100644
--- a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
@@ -20,15 +20,15 @@
public final class ComplicationDrawable extends android.graphics.drawable.Drawable {
ctor public ComplicationDrawable();
- ctor public ComplicationDrawable(android.content.Context);
- ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable);
- method public void draw(android.graphics.Canvas);
+ ctor public ComplicationDrawable(android.content.Context context);
+ ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable);
+ method public void draw(android.graphics.Canvas canvas);
method public androidx.wear.watchface.complications.rendering.ComplicationStyle getActiveStyle();
method public androidx.wear.watchface.complications.rendering.ComplicationStyle getAmbientStyle();
method public androidx.wear.complications.data.ComplicationData? getComplicationData();
method public android.content.Context? getContext();
method public long getCurrentTimeMillis();
- method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context, int);
+ method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
method public long getHighlightDuration();
method public CharSequence? getNoDataText();
method @Deprecated public int getOpacity();
@@ -37,19 +37,36 @@
method public boolean isInAmbientMode();
method public boolean isLowBitAmbient();
method public boolean isRangedValueProgressHidden();
- method public boolean onTap(@Px int, @Px int);
- method public void setAlpha(int);
- method public void setBurnInProtection(boolean);
- method public void setColorFilter(android.graphics.ColorFilter?);
- method public void setComplicationData(androidx.wear.complications.data.ComplicationData?, boolean);
- method public void setContext(android.content.Context);
- method public void setCurrentTimeMillis(long);
- method public void setHighlightDuration(@IntRange(from=0) long);
- method public void setHighlighted(boolean);
- method public void setInAmbientMode(boolean);
- method public void setLowBitAmbient(boolean);
- method public void setNoDataText(CharSequence?);
- method public void setRangedValueProgressHidden(boolean);
+ method public boolean onTap(@Px int x, @Px int y);
+ method public void setAlpha(@IntRange(from=0, to=255) int alpha);
+ method public void setBurnInProtectionOn(boolean p);
+ method public void setColorFilter(android.graphics.ColorFilter? colorFilter);
+ method public void setComplicationData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsync);
+ method public void setContext(android.content.Context context);
+ method public void setCurrentTimeMillis(long p);
+ method public void setHighlightDuration(@IntRange(from=0) long highlightDurationMillis);
+ method public void setHighlighted(boolean p);
+ method public void setInAmbientMode(boolean p);
+ method public void setLowBitAmbient(boolean p);
+ method public void setNoDataText(CharSequence? noDataText);
+ method public void setRangedValueProgressHidden(boolean rangedValueProgressHidden);
+ property public final androidx.wear.watchface.complications.rendering.ComplicationStyle activeStyle;
+ property public final androidx.wear.watchface.complications.rendering.ComplicationStyle ambientStyle;
+ property public final androidx.wear.complications.data.ComplicationData? complicationData;
+ property public final android.content.Context? context;
+ property public final long currentTimeMillis;
+ property public final long highlightDuration;
+ property public final boolean isBurnInProtectionOn;
+ property public final boolean isHighlighted;
+ property public final boolean isInAmbientMode;
+ property public final boolean isLowBitAmbient;
+ property public final boolean isRangedValueProgressHidden;
+ property public final CharSequence? noDataText;
+ field public static final androidx.wear.watchface.complications.rendering.ComplicationDrawable.Companion Companion;
+ }
+
+ public static final class ComplicationDrawable.Companion {
+ method public androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
}
public final class ComplicationHighlightRenderer {
diff --git a/wear/wear-watchface-complications-rendering/api/restricted_current.txt b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
index 110cc54..8b9e974 100644
--- a/wear/wear-watchface-complications-rendering/api/restricted_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
@@ -20,15 +20,15 @@
public final class ComplicationDrawable extends android.graphics.drawable.Drawable {
ctor public ComplicationDrawable();
- ctor public ComplicationDrawable(android.content.Context);
- ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable);
- method public void draw(android.graphics.Canvas);
+ ctor public ComplicationDrawable(android.content.Context context);
+ ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable);
+ method public void draw(android.graphics.Canvas canvas);
method public androidx.wear.watchface.complications.rendering.ComplicationStyle getActiveStyle();
method public androidx.wear.watchface.complications.rendering.ComplicationStyle getAmbientStyle();
method public androidx.wear.complications.data.ComplicationData? getComplicationData();
method public android.content.Context? getContext();
method public long getCurrentTimeMillis();
- method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context, int);
+ method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
method public long getHighlightDuration();
method public CharSequence? getNoDataText();
method @Deprecated public int getOpacity();
@@ -37,19 +37,36 @@
method public boolean isInAmbientMode();
method public boolean isLowBitAmbient();
method public boolean isRangedValueProgressHidden();
- method public boolean onTap(@Px int, @Px int);
- method public void setAlpha(int);
- method public void setBurnInProtection(boolean);
- method public void setColorFilter(android.graphics.ColorFilter?);
- method public void setComplicationData(androidx.wear.complications.data.ComplicationData?, boolean);
- method public void setContext(android.content.Context);
- method public void setCurrentTimeMillis(long);
- method public void setHighlightDuration(@IntRange(from=0) long);
- method public void setHighlighted(boolean);
- method public void setInAmbientMode(boolean);
- method public void setLowBitAmbient(boolean);
- method public void setNoDataText(CharSequence?);
- method public void setRangedValueProgressHidden(boolean);
+ method public boolean onTap(@Px int x, @Px int y);
+ method public void setAlpha(@IntRange(from=0, to=255) int alpha);
+ method public void setBurnInProtectionOn(boolean p);
+ method public void setColorFilter(android.graphics.ColorFilter? colorFilter);
+ method public void setComplicationData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsync);
+ method public void setContext(android.content.Context context);
+ method public void setCurrentTimeMillis(long p);
+ method public void setHighlightDuration(@IntRange(from=0) long highlightDurationMillis);
+ method public void setHighlighted(boolean p);
+ method public void setInAmbientMode(boolean p);
+ method public void setLowBitAmbient(boolean p);
+ method public void setNoDataText(CharSequence? noDataText);
+ method public void setRangedValueProgressHidden(boolean rangedValueProgressHidden);
+ property public final androidx.wear.watchface.complications.rendering.ComplicationStyle activeStyle;
+ property public final androidx.wear.watchface.complications.rendering.ComplicationStyle ambientStyle;
+ property public final androidx.wear.complications.data.ComplicationData? complicationData;
+ property public final android.content.Context? context;
+ property public final long currentTimeMillis;
+ property public final long highlightDuration;
+ property public final boolean isBurnInProtectionOn;
+ property public final boolean isHighlighted;
+ property public final boolean isInAmbientMode;
+ property public final boolean isLowBitAmbient;
+ property public final boolean isRangedValueProgressHidden;
+ property public final CharSequence? noDataText;
+ field public static final androidx.wear.watchface.complications.rendering.ComplicationDrawable.Companion Companion;
+ }
+
+ public static final class ComplicationDrawable.Companion {
+ method public androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
}
public final class ComplicationHighlightRenderer {
diff --git a/wear/wear-watchface-complications-rendering/lint-baseline.xml b/wear/wear-watchface-complications-rendering/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-complications-rendering/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt
index 05df52f..9222881 100644
--- a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt
+++ b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt
@@ -92,7 +92,7 @@
field = value
value.isInAmbientMode = watchState.isAmbient.value
value.isLowBitAmbient = watchState.hasLowBitAmbient
- value.setBurnInProtection(watchState.hasBurnInProtection)
+ value.isBurnInProtectionOn = watchState.hasBurnInProtection
attachedComplication?.scheduleUpdateComplications()
}
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
deleted file mode 100644
index 55c1e16..0000000
--- a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
+++ /dev/null
@@ -1,870 +0,0 @@
-/*
- * Copyright 2020 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.wear.watchface.complications.rendering;
-
-import android.app.PendingIntent.CanceledException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.Px;
-import androidx.annotation.VisibleForTesting;
-import androidx.wear.complications.ComplicationHelperActivity;
-import androidx.wear.complications.data.ComplicationData;
-import androidx.wear.complications.data.ComplicationType;
-import androidx.wear.complications.data.DataKt;
-import androidx.wear.watchface.complications.rendering.ComplicationRenderer.OnInvalidateListener;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * A styleable drawable object that draws complications. You can create a ComplicationDrawable from
- * XML inflation or by using one of the constructor methods.
- *
- * <h3>Constructing a ComplicationDrawable</h3>
- *
- * <p>To construct a ComplicationDrawable programmatically, use the {@link
- * #ComplicationDrawable(Context)} constructor. Afterwards, styling attributes you want to modify
- * can be set via set methods.
- *
- * <pre>
- * public void onCreate(SurfaceHolder holder) {
- * ...
- * ComplicationDrawable complicationDrawable = new ComplicationDrawable(WatchFaceService.this);
- * complicationDrawable.setBackgroundColorActive(backgroundColor);
- * complicationDrawable.setTextColorActive(textColor);
- * ...
- * }</pre>
- *
- * <h3>Constructing a ComplicationDrawable from XML</h3>
- *
- * <p>Constructing a ComplicationDrawable from an XML file makes it easier to modify multiple
- * styling attributes at once without calling any set methods. You may also use different XML files
- * to switch between different styles your watch face supports.
- *
- * <p>To construct a ComplicationDrawable from a drawable XML file, you may create an XML file in
- * your project's {@code res/drawable} folder. A ComplicationDrawable with red text and white title
- * in active mode, and white text and white title in ambient mode would look like this:
- *
- * <pre>
- * <?xml version="1.0" encoding="utf-8"?>
- * <android.support.wearable.complications.rendering.ComplicationDrawable
- * xmlns:app="http://schemas.android.com/apk/res-auto"
- * app:textColor="#FFFF0000"
- * app:titleColor="#FFFFFFFF">
- * <ambient
- * app:textColor="#FFFFFFFF" />
- * </android.support.wearable.complications.rendering.ComplicationDrawable>
- * </pre>
- *
- * <p>A top-level {@code drawable} tag with the {@code class} attribute may also be used to
- * construct a ComplicationDrawable from an XML file:
- *
- * <pre>
- * <?xml version="1.0" encoding="utf-8"?>
- * <drawable
- * class="android.support.wearable.complications.rendering.ComplicationDrawable"
- * xmlns:app="http://schemas.android.com/apk/res-auto"
- * app:textColor="#FFFF0000"
- * app:titleColor="#FFFFFFFF">
- * <ambient
- * app:textColor="#FFFFFFFF" />
- * </drawable></pre>
- *
- * <p>To inflate a ComplicationDrawable from XML file, use the {@link #getDrawable(Context, int)}
- * method. ComplicationDrawable needs access to the current context in order to style and draw
- * the complication.
- *
- * <pre>
- * public void onCreate(SurfaceHolder holder) {
- * ...
- * ComplicationDrawable complicationDrawable = (ComplicationDrawable)
- * getDrawable(R.drawable.complication);
- * complicationDrawable.setContext(WatchFaceService.this);
- * ...
- * }</pre>
- *
- * <h4>Syntax:</h4>
- *
- * <pre>
- * <?xml version="1.0" encoding="utf-8"?>
- * <android.support.wearable.complications.rendering.ComplicationDrawable
- * xmlns:app="http://schemas.android.com/apk/res-auto"
- * app:backgroundColor="color"
- * app:backgroundDrawable="drawable"
- * app:borderColor="color"
- * app:borderDashGap="dimension"
- * app:borderDashWidth="dimension"
- * app:borderRadius="dimension"
- * app:borderStyle="none|solid|dashed"
- * app:borderWidth="dimension"
- * app:highlightColor="color"
- * app:iconColor="color"
- * app:rangedValuePrimaryColor="color"
- * app:rangedValueProgressHidden="boolean"
- * app:rangedValueRingWidth="dimension"
- * app:rangedValueSecondaryColor="color"
- * app:textColor="color"
- * app:textSize="dimension"
- * app:textTypeface="string"
- * app:titleColor="color"
- * app:titleSize="dimension"
- * app:titleTypeface="string">
- * <ambient
- * app:backgroundColor="color"
- * app:backgroundDrawable="drawable"
- * app:borderColor="color"
- * app:borderDashGap="dimension"
- * app:borderDashWidth="dimension"
- * app:borderRadius="dimension"
- * app:borderStyle="none|solid|dashed"
- * app:borderWidth="dimension"
- * app:highlightColor="color"
- * app:iconColor="color"
- * app:rangedValuePrimaryColor="color"
- * app:rangedValueRingWidth="dimension"
- * app:rangedValueSecondaryColor="color"
- * app:textColor="color"
- * app:textSize="dimension"
- * app:textTypeface="string"
- * app:titleColor="color"
- * app:titleSize="dimension"
- * app:titleTypeface="string" />
- * </android.support.wearable.complications.rendering.ComplicationDrawable>
- * </pre>
- *
- * <p>Attributes of the top-level tag apply to both active and ambient modes while attributes of the
- * inner {@code ambient} tag only apply to ambient mode. As an exception, top-level only {@code
- * rangedValueProgressHidden} attribute applies to both modes, and cannot be overridden in ambient
- * mode. To hide ranged value in only one of the active or ambient modes, you may consider setting
- * {@code rangedValuePrimaryColor} and {@code rangedValueSecondaryColor} to {@link
- * android.graphics.Color#TRANSPARENT} instead.
- *
- * <h3>Drawing a ComplicationDrawable</h3>
- *
- * <p>Depending on the size and shape of the bounds, the layout of the complication may change. For
- * instance, a short text complication with an icon that is drawn on square bounds would draw the
- * icon above the short text, but a short text complication with an icon that is drawn on wide
- * rectangular bounds might draw the icon to the left of the short text instead.
- */
-public final class ComplicationDrawable extends Drawable {
-
- private Context mContext;
- private ComplicationRenderer mComplicationRenderer;
-
- private final ComplicationStyle mActiveStyle;
- private final ComplicationStyle mAmbientStyle;
-
- private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
-
- private final Runnable mUnhighlightRunnable =
- () -> {
- setHighlighted(false);
- invalidateSelf();
- };
-
- private final OnInvalidateListener mRendererInvalidateListener = () -> invalidateSelf();
-
- private CharSequence mNoDataText;
- private long mHighlightDuration;
- private long mCurrentTimeMillis;
- private boolean mInAmbientMode;
- private boolean mLowBitAmbient;
- private boolean mBurnInProtection;
- private boolean mHighlighted;
- private boolean mRangedValueProgressHidden;
-
- private boolean mIsInflatedFromXml;
- private boolean mAlreadyStyled;
-
- /** Default constructor. */
- public ComplicationDrawable() {
- mActiveStyle = new ComplicationStyle();
- mAmbientStyle = new ComplicationStyle();
- }
-
- /**
- * Creates a ComplicationDrawable using the given context. If this constructor is used, calling
- * {@link #setContext(Context)} may not be necessary.
- */
- public ComplicationDrawable(@NonNull Context context) {
- this();
- setContext(context);
- }
-
- public ComplicationDrawable(@NonNull ComplicationDrawable drawable) {
- mActiveStyle = new ComplicationStyle(drawable.mActiveStyle);
- mAmbientStyle = new ComplicationStyle(drawable.mAmbientStyle);
- mNoDataText = drawable.mNoDataText.subSequence(0, drawable.mNoDataText.length());
- mHighlightDuration = drawable.mHighlightDuration;
- mCurrentTimeMillis = drawable.mCurrentTimeMillis;
- setBounds(drawable.getBounds());
-
- mInAmbientMode = drawable.mInAmbientMode;
- mLowBitAmbient = drawable.mLowBitAmbient;
- mBurnInProtection = drawable.mBurnInProtection;
- mHighlighted = false;
- mRangedValueProgressHidden = drawable.mRangedValueProgressHidden;
- mIsInflatedFromXml = drawable.mIsInflatedFromXml;
- mAlreadyStyled = true;
- }
-
- /**
- * Creates a ComplicationDrawable from a resource.
- *
- * @param context The {@link Context} to load the resource from
- * @param id The id of the resource to load
- * @return The {@link ComplicationDrawable} loaded from the specified resource id or null if it
- * doesn't exist.
- */
- @Nullable
- public static ComplicationDrawable getDrawable(@NonNull Context context, int id) {
- if (context == null) {
- throw new IllegalArgumentException("Argument \"context\" should not be null.");
- }
- ComplicationDrawable drawable = (ComplicationDrawable) context.getDrawable(id);
- if (drawable == null) {
- return null;
- }
-
- drawable.setContext(context);
- return drawable;
- }
-
- /** Sets the style to default values using resources. */
- private static void setStyleToDefaultValues(ComplicationStyle style, Resources r) {
- style.setBackgroundColor(
- r.getColor(R.color.complicationDrawable_backgroundColor, null));
- style.setTextColor(r.getColor(R.color.complicationDrawable_textColor, null));
- style.setTitleColor(r.getColor(R.color.complicationDrawable_titleColor, null));
- style.setTextTypeface(
- Typeface.create(
- r.getString(R.string.complicationDrawable_textTypeface), Typeface.NORMAL));
- style.setTitleTypeface(
- Typeface.create(
- r.getString(R.string.complicationDrawable_titleTypeface), Typeface.NORMAL));
- style.setTextSize(r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize));
- style.setTitleSize(r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize));
- style.setIconColor(r.getColor(R.color.complicationDrawable_iconColor, null));
- style.setBorderColor(r.getColor(R.color.complicationDrawable_borderColor, null));
- style.setBorderWidth(r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth));
- style.setBorderRadius(r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius));
- style.setBorderStyle(r.getInteger(R.integer.complicationDrawable_borderStyle));
- style.setBorderDashWidth(
- r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth));
- style.setBorderDashGap(r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap));
- style.setRangedValueRingWidth(
- r.getDimensionPixelSize(R.dimen.complicationDrawable_rangedValueRingWidth));
- style.setRangedValuePrimaryColor(
- r.getColor(R.color.complicationDrawable_rangedValuePrimaryColor, null));
- style.setRangedValueSecondaryColor(
- r.getColor(R.color.complicationDrawable_rangedValueSecondaryColor, null));
- style.setHighlightColor(r.getColor(R.color.complicationDrawable_highlightColor, null));
- }
-
- /**
- * Sets the context used to render the complication. If a context is not set,
- * ComplicationDrawable will throw an {@link IllegalStateException} if one of
- * {@link #draw(Canvas)}, {@link #setBounds(Rect)}, or {@link
- * #setComplicationData(ComplicationData, boolean)} is called.
- *
- * <p>While this can be called from any context, ideally, a
- * androidx.wear.watchface.WatchFaceService object should be passed here to allow creating
- * permission dialogs by the {@link #onTap(int, int)} method, in case current watch face
- * doesn't have the permission to receive complication data.
- *
- * <p>If this ComplicationDrawable is retrieved using {@link Resources#getDrawable(int, Theme)},
- * this method must be called before calling any of the methods mentioned above.
- *
- * <p>If this ComplicationDrawable is not inflated from an XML file, this method will reset the
- * style to match the default values, so if {@link #ComplicationDrawable()} is used to construct
- * a ComplicationDrawable, this method should be called right after.
- */
- public void setContext(@NonNull Context context) {
- if (context == null) {
- throw new IllegalArgumentException("Argument \"context\" should not be null.");
- }
- if (Objects.equals(context, mContext)) {
- return;
- }
- mContext = context;
-
- if (!mIsInflatedFromXml && !mAlreadyStyled) {
- setStyleToDefaultValues(mActiveStyle, context.getResources());
- setStyleToDefaultValues(mAmbientStyle, context.getResources());
- }
-
- if (!mAlreadyStyled) {
- mHighlightDuration = context.getResources()
- .getInteger(R.integer.complicationDrawable_highlightDurationMs);
- }
-
- mComplicationRenderer = new ComplicationRenderer(mContext, mActiveStyle, mAmbientStyle);
- mComplicationRenderer.setOnInvalidateListener(mRendererInvalidateListener);
- if (mNoDataText == null) {
- setNoDataText(context.getString(R.string.complicationDrawable_noDataText));
- } else {
- mComplicationRenderer.setNoDataText(mNoDataText);
- }
- mComplicationRenderer.setRangedValueProgressHidden(mRangedValueProgressHidden);
- mComplicationRenderer.setBounds(getBounds());
- }
-
- /**
- * Returns the {@link Context} used to render the complication.
- */
- @Nullable public Context getContext() {
- return mContext;
- }
-
- private void inflateAttributes(Resources r, XmlPullParser parser) {
- TypedArray a =
- r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable);
- setRangedValueProgressHidden(
- a.getBoolean(R.styleable.ComplicationDrawable_rangedValueProgressHidden, false));
- a.recycle();
- }
-
- private void inflateStyle(boolean isAmbient, Resources r, XmlPullParser parser) {
- TypedArray a =
- r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable);
- ComplicationStyle complicationStyle = isAmbient ? mAmbientStyle : mActiveStyle;
- if (a.hasValue(R.styleable.ComplicationDrawable_backgroundColor)) {
- complicationStyle.setBackgroundColor(
- a.getColor(
- R.styleable.ComplicationDrawable_backgroundColor,
- r.getColor(R.color.complicationDrawable_backgroundColor, null)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_backgroundDrawable)) {
- complicationStyle.setBackgroundDrawable(
- a.getDrawable(R.styleable.ComplicationDrawable_backgroundDrawable));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_textColor)) {
- complicationStyle.setTextColor(
- a.getColor(
- R.styleable.ComplicationDrawable_textColor,
- r.getColor(R.color.complicationDrawable_textColor, null)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_titleColor)) {
- complicationStyle.setTitleColor(
- a.getColor(
- R.styleable.ComplicationDrawable_titleColor,
- r.getColor(R.color.complicationDrawable_titleColor, null)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_textTypeface)) {
- complicationStyle.setTextTypeface(
- Typeface.create(
- a.getString(R.styleable.ComplicationDrawable_textTypeface),
- Typeface.NORMAL));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_titleTypeface)) {
- complicationStyle.setTitleTypeface(
- Typeface.create(
- a.getString(R.styleable.ComplicationDrawable_titleTypeface),
- Typeface.NORMAL));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_textSize)) {
- complicationStyle.setTextSize(
- a.getDimensionPixelSize(
- R.styleable.ComplicationDrawable_textSize,
- r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_titleSize)) {
- complicationStyle.setTitleSize(
- a.getDimensionPixelSize(
- R.styleable.ComplicationDrawable_titleSize,
- r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_iconColor)) {
- complicationStyle.setIconColor(
- a.getColor(
- R.styleable.ComplicationDrawable_iconColor,
- r.getColor(R.color.complicationDrawable_iconColor, null)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_borderColor)) {
- complicationStyle.setBorderColor(
- a.getColor(
- R.styleable.ComplicationDrawable_borderColor,
- r.getColor(R.color.complicationDrawable_borderColor, null)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_borderRadius)) {
- complicationStyle.setBorderRadius(
- a.getDimensionPixelSize(
- R.styleable.ComplicationDrawable_borderRadius,
- r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_borderStyle)) {
- complicationStyle.setBorderStyle(
- a.getInt(
- R.styleable.ComplicationDrawable_borderStyle,
- r.getInteger(R.integer.complicationDrawable_borderStyle)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_borderDashWidth)) {
- complicationStyle.setBorderDashWidth(
- a.getDimensionPixelSize(
- R.styleable.ComplicationDrawable_borderDashWidth,
- r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_borderDashGap)) {
- complicationStyle.setBorderDashGap(
- a.getDimensionPixelSize(
- R.styleable.ComplicationDrawable_borderDashGap,
- r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_borderWidth)) {
- complicationStyle.setBorderWidth(
- a.getDimensionPixelSize(
- R.styleable.ComplicationDrawable_borderWidth,
- r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueRingWidth)) {
- complicationStyle.setRangedValueRingWidth(
- a.getDimensionPixelSize(
- R.styleable.ComplicationDrawable_rangedValueRingWidth,
- r.getDimensionPixelSize(
- R.dimen.complicationDrawable_rangedValueRingWidth)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_rangedValuePrimaryColor)) {
- complicationStyle.setRangedValuePrimaryColor(
- a.getColor(
- R.styleable.ComplicationDrawable_rangedValuePrimaryColor,
- r.getColor(
- R.color.complicationDrawable_rangedValuePrimaryColor, null)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueSecondaryColor)) {
- complicationStyle.setRangedValueSecondaryColor(
- a.getColor(
- R.styleable.ComplicationDrawable_rangedValueSecondaryColor,
- r.getColor(
- R.color.complicationDrawable_rangedValueSecondaryColor, null)));
- }
- if (a.hasValue(R.styleable.ComplicationDrawable_highlightColor)) {
- complicationStyle.setHighlightColor(
- a.getColor(
- R.styleable.ComplicationDrawable_highlightColor,
- r.getColor(R.color.complicationDrawable_highlightColor, null)));
- }
- a.recycle();
- }
-
- /**
- * Inflates this ComplicationDrawable from an XML resource. This can't be called more than once
- * for each ComplicationDrawable. Note that framework may have called this once to create the
- * ComplicationDrawable instance from an XML resource.
- *
- * @param r Resources used to resolve attribute values
- * @param parser XML parser from which to inflate this ComplicationDrawable
- * @param attrs Base set of attribute values
- * @param theme Ignored by ComplicationDrawable
- */
- @Override
- @SuppressWarnings("ObjectToString")
- public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
- @NonNull AttributeSet attrs, @Nullable Theme theme)
- throws XmlPullParserException, IOException {
- if (mIsInflatedFromXml) {
- throw new IllegalStateException("inflate may be called once only.");
- }
- mIsInflatedFromXml = true;
- int type;
- final int outerDepth = parser.getDepth();
- // Inflate attributes always shared between active and ambient mode
- inflateAttributes(r, parser);
- // Reset both style builders to default values
- setStyleToDefaultValues(mActiveStyle, r);
- setStyleToDefaultValues(mAmbientStyle, r);
- // Attributes of the outer tag applies to both active and ambient styles
- inflateStyle(false, r, parser);
- inflateStyle(true, r, parser);
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
-
- // Attributes of inner <ambient> tag applies to ambient style only
- final String name = parser.getName();
- if (TextUtils.equals(name, "ambient")) {
- inflateStyle(true, r, parser);
- } else {
- Log.w(
- "ComplicationDrawable",
- "Unknown element: " + name + " for ComplicationDrawable " + this);
- }
- }
- }
-
- /**
- * Draws the complication for the last known time. Last known time is derived from
- * ComplicationDrawable#setCurrentTimeMillis(long)}.
- *
- * @param canvas Canvas for the complication to be drawn onto
- */
- @Override
- public void draw(@NonNull Canvas canvas) {
- assertInitialized();
- updateStyleIfRequired();
- mComplicationRenderer.draw(
- canvas,
- mCurrentTimeMillis,
- mInAmbientMode,
- mLowBitAmbient,
- mBurnInProtection,
- mHighlighted);
- }
-
- /** Does nothing. */
- @Override
- public void setAlpha(int alpha) {
- // No op.
- }
-
- // TODO(b/186499115): In follow up patch, when this class is converted to Kotlin, make link
- // working.
- /**
- * Does nothing. Use {link ComplicationStyle#setImageColorFilter(ColorFilter)} instead to apply
- * color filter to small and large images.
- */
- @Override
- public void setColorFilter(@Nullable ColorFilter colorFilter) {
- // No op.
- }
-
- /**
- * {@inheritDoc}
- *
- * @deprecated This method is no longer used in graphics optimizations
- */
- @Override
- @Deprecated
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- protected void onBoundsChange(@NonNull Rect bounds) {
- if (mComplicationRenderer != null) {
- mComplicationRenderer.setBounds(bounds);
- }
- }
-
- /**
- * Sets the text to be rendered when {@link ComplicationData} is of type {@link
- * ComplicationType#NO_DATA}. If {@code noDataText} is null, an empty text will be
- * rendered.
- */
- public void setNoDataText(@Nullable CharSequence noDataText) {
- if (noDataText == null) {
- mNoDataText = "";
- } else {
- mNoDataText = noDataText.subSequence(0, noDataText.length());
- }
- if (mComplicationRenderer != null) {
- mComplicationRenderer.setNoDataText(mNoDataText);
- }
- }
-
- /**
- * Sets if the ranged value progress should be hidden when {@link ComplicationData} is of type
- * {@link ComplicationType#RANGED_VALUE}.
- *
- * @param rangedValueProgressHidden {@code true} if progress should be hidden, {@code false}
- * otherwise
- * @attr ref androidx.wear.watchface.complications.rendering.R
- * .styleable#ComplicationDrawable_rangedValueProgressHidden
- */
- public void setRangedValueProgressHidden(boolean rangedValueProgressHidden) {
- mRangedValueProgressHidden = rangedValueProgressHidden;
- if (mComplicationRenderer != null) {
- mComplicationRenderer.setRangedValueProgressHidden(rangedValueProgressHidden);
- }
- }
-
- /** Returns {@code true} if the ranged value progress is hidden, {@code false} otherwise. */
- public boolean isRangedValueProgressHidden() {
- return mRangedValueProgressHidden;
- }
-
- /**
- * Sets the complication data to be drawn. If {@code complicationData} is {@code null}, nothing
- * will be drawn when {@link #draw(Canvas)} is called.
- *
- * @param complicationData The [ComplicationData] to set
- * @param loadDrawablesAsync If true any drawables should be loaded asynchronously,
- * otherwise they will be loaded synchronously.
- */
- public void setComplicationData(
- @Nullable ComplicationData complicationData,
- boolean loadDrawablesAsync
- ) {
- assertInitialized();
- mComplicationRenderer.setComplicationData(
- complicationData != null ? complicationData.asWireComplicationData() : null,
- loadDrawablesAsync
- );
- }
-
- /**
- * Returns the {@link ComplicationData} to be drawn by this ComplicationDrawable.
- */
- @Nullable
- public ComplicationData getComplicationData() {
- return (mComplicationRenderer.getComplicationData() != null)
- ? DataKt.toApiComplicationData(mComplicationRenderer.getComplicationData()) : null;
- }
-
- /** Sets whether the complication should be rendered in ambient mode. */
- public void setInAmbientMode(boolean inAmbientMode) {
- mInAmbientMode = inAmbientMode;
- }
-
- /** Returns whether the complication is rendered in ambient mode. */
- public boolean isInAmbientMode() {
- return mInAmbientMode;
- }
-
- /**
- * Sets whether the complication, when rendering in ambient mode, should apply a style suitable
- * for low bit ambient mode.
- */
- public void setLowBitAmbient(boolean lowBitAmbient) {
- mLowBitAmbient = lowBitAmbient;
- }
-
- /**
- * Returns whether the complication, when rendering in ambient mode, should apply a style
- * suitable for low bit ambient mode.
- */
- public boolean isLowBitAmbient() {
- return mLowBitAmbient;
- }
-
- /**
- * Sets whether the complication, when rendering in ambient mode, should apply a style suitable
- * for display on devices with burn in protection.
- */
- public void setBurnInProtection(boolean burnInProtection) {
- mBurnInProtection = burnInProtection;
- }
-
- /**
- * Whether the complication, when rendering in ambient mode, should apply a style suitable for
- * display on devices with burn in protection.
- */
- public boolean isBurnInProtectionOn() {
- return mBurnInProtection;
- }
-
- /**
- * Sets the current time in mulliseconds since the epoch. This will be used to render
- * {@link ComplicationData} with time dependent text.
- *
- * @param currentTimeMillis time in milliseconds since the epoch
- */
- public void setCurrentTimeMillis(long currentTimeMillis) {
- mCurrentTimeMillis = currentTimeMillis;
- }
-
- /**
- * Returns the time in milliseconds since the epoch used for rendering {@link ComplicationData}
- * with time dependent text.
- */
- public long getCurrentTimeMillis() {
- return mCurrentTimeMillis;
- }
-
- /**
- * Sets whether the complication is currently highlighted. This may be called by a watch face
- * when a complication is tapped.
- *
- * <p>If watch face is in ambient mode, highlight will not be visible even if this is set to
- * {@code true}, because it may cause burn-in or power inefficiency.
- */
- public void setHighlighted(boolean isHighlighted) {
- mHighlighted = isHighlighted;
- }
-
- /**
- * Returns whether the complication is currently highlighted.
- */
- public boolean isHighlighted() {
- return mHighlighted;
- }
-
- /**
- * Sends the tap action for the complication if tap coordinates are inside the complication
- * bounds.
- *
- * <p>This method will also highlight the complication. The highlight duration is 300
- * milliseconds by default but can be modified using the {@link #setHighlightDuration(long)}
- * method.
- *
- * <p>If {@link ComplicationData} has the type {@link ComplicationType#NO_PERMISSION}, this
- * method will launch an intent to request complication permission for the watch face. This will
- * only work if the context set by {@link #getDrawable} or the constructor is an
- * instance of WatchFaceService.
- *
- * @param x X coordinate of the tap relative to screen origin
- * @param y Y coordinate of the tap relative to screen origin
- * @return {@code true} if the action was successful, {@code false} if complication data is not
- * set, the complication has no tap action, the tap action (i.e. {@link
- * android.app.PendingIntent}) is cancelled, or the given x and y are not inside the
- * complication bounds.
- */
- public boolean onTap(@Px int x, @Px int y) {
- if (mComplicationRenderer == null) {
- return false;
- }
- android.support.wearable.complications.ComplicationData data =
- mComplicationRenderer.getComplicationData();
- if (data == null) {
- return false;
- }
- if (!data.hasTapAction() && data.getType()
- != android.support.wearable.complications.ComplicationData.TYPE_NO_PERMISSION) {
- return false;
- }
- if (!getBounds().contains(x, y)) {
- return false;
- }
- if (data.getType()
- == android.support.wearable.complications.ComplicationData.TYPE_NO_PERMISSION) {
- // Check if mContext is an instance of WatchFaceService. We can't use the standard
- // instanceof operator because WatchFaceService is defined in library which depends on
- // this one, hence the reflection hack.
- try {
- if (Class.forName("androidx.wear.watchface.WatchFaceService")
- .isInstance(mContext)) {
- mContext.startActivity(
- ComplicationHelperActivity.createPermissionRequestHelperIntent(
- mContext,
- new ComponentName(mContext, mContext.getClass()))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- } else {
- return false;
- }
- } catch (ClassNotFoundException e) {
- // If watchFaceServiceClass class isn't found we know mContext can't be an instance
- // of WatchFaceService.
- return false;
- }
- } else {
- try {
- data.getTapAction().send();
- } catch (CanceledException e) {
- return false;
- }
- }
- if (getHighlightDuration() > 0) {
- setHighlighted(true);
- invalidateSelf();
- mMainThreadHandler.removeCallbacks(mUnhighlightRunnable);
- mMainThreadHandler.postDelayed(mUnhighlightRunnable, getHighlightDuration());
- }
- return true;
- }
-
- /**
- * Sets the duration for the complication to stay highlighted after calling the {@link
- * #onTap(int, int)} method. Default value is 300 milliseconds. Setting highlight duration to 0
- * disables highlighting.
- *
- * @param highlightDurationMillis highlight duration in milliseconds
- */
- public void setHighlightDuration(@IntRange(from = 0) long highlightDurationMillis) {
- if (highlightDurationMillis < 0) {
- throw new IllegalArgumentException("Highlight duration should be non-negative.");
- }
- mHighlightDuration = highlightDurationMillis;
- }
-
- /** Returns the highlight duration. */
- public long getHighlightDuration() {
- return mHighlightDuration;
- }
-
- /** Builds styles and syncs them with the complication renderer. */
- void updateStyleIfRequired() {
- if (mActiveStyle.isDirty() || mAmbientStyle.isDirty()) {
- mComplicationRenderer.updateStyle(mActiveStyle, mAmbientStyle);
- mActiveStyle.clearDirtyFlag();
- mAmbientStyle.clearDirtyFlag();
- }
- }
-
- /**
- * Throws an exception if the context is not set. This method should be called if any of the
- * member methods do a context-dependent job.
- */
- private void assertInitialized() {
- if (mContext == null) {
- throw new IllegalStateException(
- "ComplicationDrawable does not have a context. Use setContext(Context) to set"
- + " it first.");
- }
- }
-
- /** Returns complication style for active mode. */
- @NonNull
- public ComplicationStyle getActiveStyle() {
- return mActiveStyle;
- }
-
- /** Returns complication style for ambient mode. */
- @NonNull
- public ComplicationStyle getAmbientStyle() {
- return mAmbientStyle;
- }
-
- /** Returns complication renderer. */
- @Nullable
- @VisibleForTesting(otherwise = VisibleForTesting.NONE)
- public ComplicationRenderer getComplicationRenderer() {
- return mComplicationRenderer;
- }
-
- /**
- * Returns the text to be rendered when {@link ComplicationData} is of type {@link
- * ComplicationType#NO_DATA}.
- */
- @Nullable
- public CharSequence getNoDataText() {
- return mNoDataText;
- }
-}
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.kt b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.kt
new file mode 100644
index 0000000..0639d79
--- /dev/null
+++ b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.kt
@@ -0,0 +1,751 @@
+/*
+ * Copyright 2020 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.wear.watchface.complications.rendering
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.Looper
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.util.Log
+import android.util.Xml
+import androidx.annotation.IntRange
+import androidx.annotation.Px
+import androidx.annotation.VisibleForTesting
+import androidx.wear.complications.ComplicationHelperActivity
+import androidx.wear.complications.data.ComplicationData
+import androidx.wear.complications.data.ComplicationType.NO_DATA
+import androidx.wear.complications.data.ComplicationType.NO_PERMISSION
+import androidx.wear.complications.data.ComplicationType.RANGED_VALUE
+import androidx.wear.complications.data.toApiComplicationData
+import androidx.wear.watchface.complications.rendering.ComplicationRenderer.OnInvalidateListener
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import java.io.IOException
+
+/**
+ * A styleable drawable object that draws complications. You can create a ComplicationDrawable from
+ * XML inflation or by using one of the constructor methods.
+ *
+ * <h3>Constructing a ComplicationDrawable</h3>
+ *
+ * To construct a ComplicationDrawable programmatically, use the [ComplicationDrawable]
+ * constructor. Afterwards, styling attributes you want to modify
+ * can be set via set methods.
+ *
+ * ```
+ * public void onCreate(SurfaceHolder holder) {
+ * ...
+ * ComplicationDrawable complicationDrawable = new ComplicationDrawable(WatchFaceService.this);
+ * complicationDrawable.setBackgroundColorActive(backgroundColor);
+ * complicationDrawable.setTextColorActive(textColor);
+ * ...
+ * }
+ * ```
+ *
+ * <h3>Constructing a ComplicationDrawable from XML</h3>
+ *
+ * Constructing a ComplicationDrawable from an XML file makes it easier to modify multiple
+ * styling attributes at once without calling any set methods. You may also use different XML files
+ * to switch between different styles your watch face supports.
+ *
+ *
+ * To construct a ComplicationDrawable from a drawable XML file, you may create an XML file in
+ * your project's `res/drawable` folder. A ComplicationDrawable with red text and white title
+ * in active mode, and white text and white title in ambient mode would look like this:
+ *
+ * ```
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <android.support.wearable.complications.rendering.ComplicationDrawable
+ * xmlns:app="http://schemas.android.com/apk/res-auto"
+ * app:textColor="#FFFF0000"
+ * app:titleColor="#FFFFFFFF">
+ * <ambient
+ * app:textColor="#FFFFFFFF" />
+ * </android.support.wearable.complications.rendering.ComplicationDrawable>
+ * ```
+ *
+ *
+ * A top-level `drawable` tag with the `class` attribute may also be used to
+ * construct a ComplicationDrawable from an XML file:
+ *
+ * ```
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <drawable
+ * class="android.support.wearable.complications.rendering.ComplicationDrawable"
+ * xmlns:app="http://schemas.android.com/apk/res-auto"
+ * app:textColor="#FFFF0000"
+ * app:titleColor="#FFFFFFFF">
+ * <ambient
+ * app:textColor="#FFFFFFFF" />
+ * </drawable>
+ * ```
+ *
+ * To inflate a ComplicationDrawable from XML file, use the [.getDrawable]
+ * method. ComplicationDrawable needs access to the current context in order to style and draw
+ * the complication.
+ *
+ * ```
+ * public void onCreate(SurfaceHolder holder) {
+ * ...
+ * ComplicationDrawable complicationDrawable = (ComplicationDrawable)
+ * getDrawable(R.drawable.complication);
+ * complicationDrawable.setContext(WatchFaceService.this);
+ * ...
+ * }
+ * ```
+ *
+ * <h4>Syntax:</h4>
+ * ```
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <android.support.wearable.complications.rendering.ComplicationDrawable
+ * xmlns:app="http://schemas.android.com/apk/res-auto"
+ * app:backgroundColor="color"
+ * app:backgroundDrawable="drawable"
+ * app:borderColor="color"
+ * app:borderDashGap="dimension"
+ * app:borderDashWidth="dimension"
+ * app:borderRadius="dimension"
+ * app:borderStyle="none|solid|dashed"
+ * app:borderWidth="dimension"
+ * app:highlightColor="color"
+ * app:iconColor="color"
+ * app:rangedValuePrimaryColor="color"
+ * app:rangedValueProgressHidden="boolean"
+ * app:rangedValueRingWidth="dimension"
+ * app:rangedValueSecondaryColor="color"
+ * app:textColor="color"
+ * app:textSize="dimension"
+ * app:textTypeface="string"
+ * app:titleColor="color"
+ * app:titleSize="dimension"
+ * app:titleTypeface="string">
+ * <ambient
+ * app:backgroundColor="color"
+ * app:backgroundDrawable="drawable"
+ * app:borderColor="color"
+ * app:borderDashGap="dimension"
+ * app:borderDashWidth="dimension"
+ * app:borderRadius="dimension"
+ * app:borderStyle="none|solid|dashed"
+ * app:borderWidth="dimension"
+ * app:highlightColor="color"
+ * app:iconColor="color"
+ * app:rangedValuePrimaryColor="color"
+ * app:rangedValueRingWidth="dimension"
+ * app:rangedValueSecondaryColor="color"
+ * app:textColor="color"
+ * app:textSize="dimension"
+ * app:textTypeface="string"
+ * app:titleColor="color"
+ * app:titleSize="dimension"
+ * app:titleTypeface="string" />
+ * </android.support.wearable.complications.rendering.ComplicationDrawable>
+ * ```
+ *
+ * Attributes of the top-level tag apply to both active and ambient modes while attributes of the
+ * inner `ambient` tag only apply to ambient mode. As an exception, top-level only
+ * `rangedValueProgressHidden` attribute applies to both modes, and cannot be overridden in ambient
+ * mode. To hide ranged value in only one of the active or ambient modes, you may consider setting
+ * `rangedValuePrimaryColor` and `rangedValueSecondaryColor` to [android.graphics.Color.TRANSPARENT]
+ * instead.
+ *
+ * <h3>Drawing a ComplicationDrawable</h3>
+ *
+ * Depending on the size and shape of the bounds, the layout of the complication may change. For
+ * instance, a short text complication with an icon that is drawn on square bounds would draw the
+ * icon above the short text, but a short text complication with an icon that is drawn on wide
+ * rectangular bounds might draw the icon to the left of the short text instead.
+ */
+public class ComplicationDrawable : Drawable {
+ /**
+ * Returns the [Context] used to render the complication.
+ */
+ public var context: Context? = null
+ private set
+
+ /** Returns complication renderer. */
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ @get:JvmName("getComplicationRenderer")
+ internal var complicationRenderer: ComplicationRenderer? = null
+ private set
+
+ /** Returns complication style for active mode. */
+ public val activeStyle: ComplicationStyle
+
+ /** Returns complication style for ambient mode. */
+ public val ambientStyle: ComplicationStyle
+
+ private val mainThreadHandler = Handler(Looper.getMainLooper())
+ private val unhighlightRunnable = Runnable {
+ isHighlighted = false
+ invalidateSelf()
+ }
+ private val rendererInvalidateListener = OnInvalidateListener { invalidateSelf() }
+
+ /**
+ * The time in milliseconds since the epoch used for rendering [ComplicationData]
+ * with time dependent text.
+ */
+ public var currentTimeMillis: Long = 0
+
+ /** Whether the complication is rendered in ambient mode. */
+ public var isInAmbientMode: Boolean = false
+
+ /**
+ * Whether the complication, when rendering in ambient mode, should apply a style
+ * suitable for low bit ambient mode.
+ */
+ public var isLowBitAmbient: Boolean = false
+
+ /**
+ * Whether the complication, when rendering in ambient mode, should apply a style suitable for
+ * display on devices with burn in protection.
+ */
+ public var isBurnInProtectionOn: Boolean = false
+
+ /**
+ * Whether the complication is currently highlighted. This may be called by a watch face
+ * when a complication is tapped.
+ *
+ * If watch face is in ambient mode, highlight will not be visible even if this is set to
+ * `true`, because it may cause burn-in or power inefficiency.
+ */
+ public var isHighlighted: Boolean = false
+
+ private var isInflatedFromXml = false
+ private var alreadyStyled = false
+
+ /** Default constructor. */
+ public constructor() {
+ activeStyle = ComplicationStyle()
+ ambientStyle = ComplicationStyle()
+ }
+
+ /**
+ * Creates a ComplicationDrawable using the given context. If this constructor is used, calling
+ * [.setContext] may not be necessary.
+ */
+ public constructor(context: Context) : this() {
+ setContext(context)
+ }
+
+ public constructor(drawable: ComplicationDrawable) {
+ activeStyle = ComplicationStyle(drawable.activeStyle)
+ ambientStyle = ComplicationStyle(drawable.ambientStyle)
+ noDataText = drawable.noDataText!!.subSequence(0, drawable.noDataText!!.length)
+ highlightDuration = drawable.highlightDuration
+ currentTimeMillis = drawable.currentTimeMillis
+ bounds = drawable.bounds
+ isInAmbientMode = drawable.isInAmbientMode
+ isLowBitAmbient = drawable.isLowBitAmbient
+ isBurnInProtectionOn = drawable.isBurnInProtectionOn
+ isHighlighted = false
+ isRangedValueProgressHidden = drawable.isRangedValueProgressHidden
+ isInflatedFromXml = drawable.isInflatedFromXml
+ alreadyStyled = true
+ }
+
+ /**
+ * Sets the context used to render the complication. If a context is not set,
+ * ComplicationDrawable will throw an [IllegalStateException] if one of [draw], [setBounds],
+ * or [setComplicationData] is called.
+ *
+ * While this can be called from any context, ideally, a
+ * androidx.wear.watchface.WatchFaceService object should be passed here to allow creating
+ * permission dialogs by the [onTap] method, in case current watch face doesn't have the
+ * permission to receive complication data.
+ *
+ * If this ComplicationDrawable is retrieved using [Resources.getDrawable], this method must
+ * be called before calling any of the methods mentioned above.
+ *
+ * If this ComplicationDrawable is not inflated from an XML file, this method will reset the
+ * style to match the default values, so if [ComplicationDrawable()] is used to construct a
+ * ComplicationDrawable, this method should be called right after.
+ */
+ public fun setContext(context: Context) {
+ if (context == this.context) {
+ return
+ }
+ this.context = context
+ if (!isInflatedFromXml && !alreadyStyled) {
+ setStyleToDefaultValues(activeStyle, context.resources)
+ setStyleToDefaultValues(ambientStyle, context.resources)
+ }
+ if (!alreadyStyled) {
+ highlightDuration = context.resources
+ .getInteger(R.integer.complicationDrawable_highlightDurationMs).toLong()
+ }
+ complicationRenderer = ComplicationRenderer(this.context, activeStyle, ambientStyle)
+ val nonNullComplicationRenderer = complicationRenderer!!
+ nonNullComplicationRenderer.setOnInvalidateListener(rendererInvalidateListener)
+ if (noDataText == null) {
+ noDataText = context.getString(R.string.complicationDrawable_noDataText)
+ } else {
+ nonNullComplicationRenderer.setNoDataText(noDataText)
+ }
+ nonNullComplicationRenderer.isRangedValueProgressHidden = isRangedValueProgressHidden
+ nonNullComplicationRenderer.bounds = bounds
+ }
+
+ private fun inflateAttributes(r: Resources, parser: XmlPullParser) {
+ val a = r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable)
+ isRangedValueProgressHidden =
+ a.getBoolean(R.styleable.ComplicationDrawable_rangedValueProgressHidden, false)
+ a.recycle()
+ }
+
+ private fun inflateStyle(isAmbient: Boolean, r: Resources, parser: XmlPullParser) {
+ val a = r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable)
+ val complicationStyle = if (isAmbient) ambientStyle else activeStyle
+ if (a.hasValue(R.styleable.ComplicationDrawable_backgroundColor)) {
+ complicationStyle.backgroundColor = a.getColor(
+ R.styleable.ComplicationDrawable_backgroundColor,
+ r.getColor(R.color.complicationDrawable_backgroundColor, null)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_backgroundDrawable)) {
+ complicationStyle.backgroundDrawable =
+ a.getDrawable(R.styleable.ComplicationDrawable_backgroundDrawable)
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_textColor)) {
+ complicationStyle.textColor = a.getColor(
+ R.styleable.ComplicationDrawable_textColor,
+ r.getColor(R.color.complicationDrawable_textColor, null)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_titleColor)) {
+ complicationStyle.titleColor = a.getColor(
+ R.styleable.ComplicationDrawable_titleColor,
+ r.getColor(R.color.complicationDrawable_titleColor, null)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_textTypeface)) {
+ complicationStyle.setTextTypeface(
+ Typeface.create(
+ a.getString(R.styleable.ComplicationDrawable_textTypeface),
+ Typeface.NORMAL
+ )
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_titleTypeface)) {
+ complicationStyle.setTitleTypeface(
+ Typeface.create(
+ a.getString(R.styleable.ComplicationDrawable_titleTypeface),
+ Typeface.NORMAL
+ )
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_textSize)) {
+ complicationStyle.textSize = a.getDimensionPixelSize(
+ R.styleable.ComplicationDrawable_textSize,
+ r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_titleSize)) {
+ complicationStyle.titleSize = a.getDimensionPixelSize(
+ R.styleable.ComplicationDrawable_titleSize,
+ r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_iconColor)) {
+ complicationStyle.iconColor = a.getColor(
+ R.styleable.ComplicationDrawable_iconColor,
+ r.getColor(R.color.complicationDrawable_iconColor, null)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_borderColor)) {
+ complicationStyle.borderColor = a.getColor(
+ R.styleable.ComplicationDrawable_borderColor,
+ r.getColor(R.color.complicationDrawable_borderColor, null)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_borderRadius)) {
+ complicationStyle.borderRadius = a.getDimensionPixelSize(
+ R.styleable.ComplicationDrawable_borderRadius,
+ r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_borderStyle)) {
+ complicationStyle.borderStyle = a.getInt(
+ R.styleable.ComplicationDrawable_borderStyle,
+ r.getInteger(R.integer.complicationDrawable_borderStyle)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_borderDashWidth)) {
+ complicationStyle.borderDashWidth = a.getDimensionPixelSize(
+ R.styleable.ComplicationDrawable_borderDashWidth,
+ r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_borderDashGap)) {
+ complicationStyle.borderDashGap = a.getDimensionPixelSize(
+ R.styleable.ComplicationDrawable_borderDashGap,
+ r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_borderWidth)) {
+ complicationStyle.borderWidth = a.getDimensionPixelSize(
+ R.styleable.ComplicationDrawable_borderWidth,
+ r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth)
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueRingWidth)) {
+ complicationStyle.rangedValueRingWidth = a.getDimensionPixelSize(
+ R.styleable.ComplicationDrawable_rangedValueRingWidth,
+ r.getDimensionPixelSize(
+ R.dimen.complicationDrawable_rangedValueRingWidth
+ )
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_rangedValuePrimaryColor)) {
+ complicationStyle.rangedValuePrimaryColor = a.getColor(
+ R.styleable.ComplicationDrawable_rangedValuePrimaryColor,
+ r.getColor(
+ R.color.complicationDrawable_rangedValuePrimaryColor, null
+ )
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueSecondaryColor)) {
+ complicationStyle.rangedValueSecondaryColor = a.getColor(
+ R.styleable.ComplicationDrawable_rangedValueSecondaryColor,
+ r.getColor(
+ R.color.complicationDrawable_rangedValueSecondaryColor, null
+ )
+ )
+ }
+ if (a.hasValue(R.styleable.ComplicationDrawable_highlightColor)) {
+ complicationStyle.highlightColor = a.getColor(
+ R.styleable.ComplicationDrawable_highlightColor,
+ r.getColor(R.color.complicationDrawable_highlightColor, null)
+ )
+ }
+ a.recycle()
+ }
+
+ /**
+ * Inflates this ComplicationDrawable from an XML resource. This can't be called more than once
+ * for each ComplicationDrawable. Note that framework may have called this once to create the
+ * ComplicationDrawable instance from an XML resource.
+ *
+ * @param r Resources used to resolve attribute values
+ * @param parser XML parser from which to inflate this ComplicationDrawable
+ * @param attrs Base set of attribute values
+ * @param theme Ignored by ComplicationDrawable
+ */
+ @Throws(XmlPullParserException::class, IOException::class)
+ public override fun inflate(
+ r: Resources,
+ parser: XmlPullParser,
+ attrs: AttributeSet,
+ theme: Resources.Theme?
+ ) {
+ check(!isInflatedFromXml) { "inflate may be called once only." }
+ isInflatedFromXml = true
+ var type: Int
+ val outerDepth = parser.depth
+ // Inflate attributes always shared between active and ambient mode
+ inflateAttributes(r, parser)
+ // Reset both style builders to default values
+ setStyleToDefaultValues(activeStyle, r)
+ setStyleToDefaultValues(ambientStyle, r)
+ // Attributes of the outer tag applies to both active and ambient styles
+ inflateStyle(false, r, parser)
+ inflateStyle(true, r, parser)
+ while (
+ parser.next().also { type = it } != XmlPullParser.END_DOCUMENT &&
+ (type != XmlPullParser.END_TAG || parser.depth > outerDepth)
+ ) {
+ if (type != XmlPullParser.START_TAG) {
+ continue
+ }
+
+ // Attributes of inner <ambient> tag applies to ambient style only
+ val name = parser.name
+ if (TextUtils.equals(name, "ambient")) {
+ inflateStyle(true, r, parser)
+ } else {
+ Log.w(
+ "ComplicationDrawable",
+ "Unknown element: $name for ComplicationDrawable $this"
+ )
+ }
+ }
+ }
+
+ /**
+ * Draws the complication for the last known time. Last known time is derived from
+ * ComplicationDrawable#setCurrentTimeMillis(long)}.
+ *
+ * @param canvas Canvas for the complication to be drawn onto
+ */
+ public override fun draw(canvas: Canvas) {
+ assertInitialized()
+ updateStyleIfRequired()
+ complicationRenderer?.draw(
+ canvas,
+ currentTimeMillis,
+ isInAmbientMode,
+ isLowBitAmbient,
+ isBurnInProtectionOn,
+ isHighlighted
+ )
+ }
+
+ /**
+ * This function is not supported in [ComplicationDrawable].
+ *
+ * @throws [UnsupportedOperationException] when called.
+ */
+ public override fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) {
+ throw UnsupportedOperationException("setAlpha is not supported in ComplicationDrawable.")
+ }
+
+ /**
+ * This function is not supported in [ComplicationDrawable]. Use
+ * [ComplicationStyle.imageColorFilter] instead to apply color filter to small and large images.
+ *
+ * @throws [UnsupportedOperationException] when called.
+ */
+ public override fun setColorFilter(colorFilter: ColorFilter?) {
+ throw UnsupportedOperationException(
+ "setColorFilter is not supported in ComplicationDrawable."
+ )
+ }
+
+ /** @throws [UnsupportedOperationException] when called. */
+ @Deprecated("This method is no longer used in graphics optimizations")
+ override fun getOpacity(): Int =
+ throw UnsupportedOperationException("getOpacity is not supported in ComplicationDrawable.")
+
+ protected override fun onBoundsChange(bounds: Rect) {
+ if (complicationRenderer != null) {
+ complicationRenderer!!.bounds = bounds
+ }
+ }
+
+ /** If the ranged value progress should be hidden when [ComplicationData] is of type
+ * [RANGED_VALUE].
+ *
+ * @attr ref androidx.wear.watchface.complications.rendering.R
+ * .styleable#ComplicationDrawable_rangedValueProgressHidden
+ */
+ public var isRangedValueProgressHidden: Boolean = false
+ set(rangedValueProgressHidden) {
+ field = rangedValueProgressHidden
+ complicationRenderer?.isRangedValueProgressHidden = rangedValueProgressHidden
+ }
+
+ /**
+ * Sets the complication data to be drawn. If `complicationData` is `null`, nothing
+ * will be drawn when [draw] is called.
+ *
+ * @param complicationData The [ComplicationData] to set
+ * @param loadDrawablesAsync If true any drawables should be loaded asynchronously,
+ * otherwise they will be loaded synchronously.
+ */
+ public fun setComplicationData(
+ complicationData: ComplicationData?,
+ loadDrawablesAsync: Boolean
+ ) {
+ assertInitialized()
+ complicationRenderer?.setComplicationData(
+ complicationData?.asWireComplicationData(),
+ loadDrawablesAsync
+ )
+ }
+
+ /**
+ * Returns the [ComplicationData] to be drawn by this ComplicationDrawable.
+ */
+ public val complicationData: ComplicationData?
+ get() = if (complicationRenderer?.complicationData != null)
+ complicationRenderer!!.complicationData.toApiComplicationData()
+ else null
+
+ /**
+ * Sends the tap action for the complication if tap coordinates are inside the complication
+ * bounds.
+ *
+ * This method will also highlight the complication. The highlight duration is 300
+ * milliseconds by default but can be modified using the [.setHighlightDuration]
+ * method.
+ *
+ * If [ComplicationData] has the type [NO_PERMISSION], this method will launch an intent to
+ * request complication permission for the watch face. This will only work if the context set
+ * by [getDrawable] or the constructor is an instance of WatchFaceService.
+ *
+ * @param x X coordinate of the tap relative to screen origin
+ * @param y Y coordinate of the tap relative to screen origin
+ * @return `true` if the action was successful, `false` if complication data is not set, the
+ * complication has no tap action, the tap action (i.e. [android.app.PendingIntent]) is
+ * cancelled, or the given x and y are not inside the complication bounds.
+ */
+ public fun onTap(@Px x: Int, @Px y: Int): Boolean {
+ if (complicationRenderer == null) {
+ return false
+ }
+ val data = complicationRenderer!!.complicationData ?: return false
+ if (!data.hasTapAction() && data.type
+ != android.support.wearable.complications.ComplicationData.TYPE_NO_PERMISSION
+ ) {
+ return false
+ }
+ if (!bounds.contains(x, y)) {
+ return false
+ }
+ if (data.type
+ == android.support.wearable.complications.ComplicationData.TYPE_NO_PERMISSION
+ ) {
+ // Check if context is an instance of WatchFaceService. We can't use the standard
+ // instanceof operator because WatchFaceService is defined in library which depends on
+ // this one, hence the reflection hack.
+ try {
+ if (context!!::class.java.name == "androidx.wear.watchface.WatchFaceService") {
+ context!!.startActivity(
+ ComplicationHelperActivity.createPermissionRequestHelperIntent(
+ context!!,
+ ComponentName(context!!, context!!.javaClass)
+ )
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+ } else {
+ return false
+ }
+ } catch (e: ClassNotFoundException) {
+ // If watchFaceServiceClass class isn't found we know context can't be an instance
+ // of WatchFaceService.
+ return false
+ }
+ } else {
+ try {
+ data.tapAction!!.send()
+ } catch (e: PendingIntent.CanceledException) {
+ return false
+ }
+ }
+ if (highlightDuration > 0) {
+ isHighlighted = true
+ invalidateSelf()
+ mainThreadHandler.removeCallbacks(unhighlightRunnable)
+ mainThreadHandler.postDelayed(unhighlightRunnable, highlightDuration)
+ }
+ return true
+ }
+
+ /** The duration for the complication to stay highlighted after calling the [onTap] method.
+ * Default value is 300 milliseconds. Setting highlight duration to 0 disables highlighting.
+ */
+ public var highlightDuration: Long = 0
+ set(@IntRange(from = 0) highlightDurationMillis: Long) {
+ require(highlightDurationMillis >= 0) { "Highlight duration should be non-negative." }
+ field = highlightDurationMillis
+ }
+
+ /** Builds styles and syncs them with the complication renderer. */
+ @JvmName(name = "updateStyleIfRequired")
+ internal fun updateStyleIfRequired() {
+ if (activeStyle.isDirty || ambientStyle.isDirty) {
+ complicationRenderer!!.updateStyle(activeStyle, ambientStyle)
+ activeStyle.clearDirtyFlag()
+ ambientStyle.clearDirtyFlag()
+ }
+ }
+
+ /**
+ * Throws an exception if the context is not set. This method should be called if any of the
+ * member methods do a context-dependent job.
+ */
+ private fun assertInitialized() {
+ checkNotNull(context) {
+ "ComplicationDrawable does not have a context. Use setContext(Context) to set it first."
+ }
+ }
+ /**
+ * The text to be rendered when [ComplicationData] is of type [NO_DATA]. If `noDataText` is
+ * null, an empty text will be
+ * rendered.
+ */
+ public var noDataText: CharSequence? = null
+ set(noDataText) {
+ field = noDataText?.subSequence(0, noDataText.length) ?: ""
+ if (complicationRenderer != null) {
+ complicationRenderer!!.setNoDataText(field)
+ }
+ }
+
+ public companion object {
+ /**
+ * Creates a ComplicationDrawable from a resource.
+ *
+ * @param context The [Context] to load the resource from
+ * @param id The id of the resource to load
+ * @return The [ComplicationDrawable] loaded from the specified resource id or null if it
+ * doesn't exist.
+ */
+ @JvmStatic
+ public fun getDrawable(context: Context, id: Int): ComplicationDrawable? {
+ val drawable = context.getDrawable(id) as ComplicationDrawable? ?: return null
+ drawable.setContext(context)
+ return drawable
+ }
+
+ /** Sets the style to default values using resources. */
+ @JvmStatic
+ internal fun setStyleToDefaultValues(style: ComplicationStyle, r: Resources) {
+ style.backgroundColor = r.getColor(R.color.complicationDrawable_backgroundColor, null)
+ style.textColor = r.getColor(R.color.complicationDrawable_textColor, null)
+ style.titleColor = r.getColor(R.color.complicationDrawable_titleColor, null)
+ style.setTextTypeface(
+ Typeface.create(
+ r.getString(R.string.complicationDrawable_textTypeface), Typeface.NORMAL
+ )
+ )
+ style.setTitleTypeface(
+ Typeface.create(
+ r.getString(R.string.complicationDrawable_titleTypeface), Typeface.NORMAL
+ )
+ )
+ style.textSize = r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize)
+ style.titleSize = r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize)
+ style.iconColor = r.getColor(R.color.complicationDrawable_iconColor, null)
+ style.borderColor = r.getColor(R.color.complicationDrawable_borderColor, null)
+ style.borderWidth = r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth)
+ style.borderRadius = r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius)
+ style.borderStyle = r.getInteger(R.integer.complicationDrawable_borderStyle)
+ style.borderDashWidth =
+ r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth)
+ style.borderDashGap =
+ r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap)
+ style.rangedValueRingWidth =
+ r.getDimensionPixelSize(R.dimen.complicationDrawable_rangedValueRingWidth)
+ style.rangedValuePrimaryColor =
+ r.getColor(R.color.complicationDrawable_rangedValuePrimaryColor, null)
+ style.rangedValueSecondaryColor =
+ r.getColor(R.color.complicationDrawable_rangedValueSecondaryColor, null)
+ style.highlightColor = r.getColor(R.color.complicationDrawable_highlightColor, null)
+ }
+ }
+}
\ No newline at end of file
diff --git a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
index 223c731..ed6f874c 100644
--- a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
+++ b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
@@ -120,11 +120,6 @@
}
@Test
- public void callingSetContextWithNullThrowsIllegalArgumentException() {
- assertThrows(IllegalArgumentException.class, () -> mComplicationDrawable.setContext(null));
- }
-
- @Test
public void callingDrawOnCanvasBeforeSetContextThrowsAnException() {
assertThrows(IllegalStateException.class, () -> mComplicationDrawable.draw(mMockCanvas));
}
diff --git a/wear/wear-watchface-data/api/restricted_current.txt b/wear/wear-watchface-data/api/restricted_current.txt
index 98430d9..16f5ad0 100644
--- a/wear/wear-watchface-data/api/restricted_current.txt
+++ b/wear/wear-watchface-data/api/restricted_current.txt
@@ -308,7 +308,7 @@
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public class ComplicationOverlayWireFormat implements android.os.Parcelable androidx.versionedparcelable.VersionedParcelable {
- ctor public ComplicationOverlayWireFormat(int, Boolean?, java.util.Map<androidx.wear.complications.data.ComplicationType!,android.graphics.RectF!>?, Integer?);
+ ctor public ComplicationOverlayWireFormat(int, Boolean?, java.util.Map<java.lang.Integer!,android.graphics.RectF!>?, Integer?);
method public int describeContents();
method public Integer? getAccessibilityTraversalIndex();
method public void writeToParcel(android.os.Parcel, int);
@@ -319,7 +319,7 @@
field public static final long NULL_ACCESSIBILITY_TRAVERSAL_INDEX = 4294967296L; // 0x100000000L
field @androidx.versionedparcelable.ParcelField(1) public int mComplicationId;
field @androidx.versionedparcelable.ParcelField(2) public int mEnabled;
- field @androidx.versionedparcelable.ParcelField(3) public java.util.Map<androidx.wear.complications.data.ComplicationType!,android.graphics.RectF!>? mPerComplicationTypeBounds;
+ field @androidx.versionedparcelable.ParcelField(3) public java.util.Map<java.lang.Integer!,android.graphics.RectF!>? mPerComplicationTypeBounds;
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public class ComplicationsOptionWireFormat extends androidx.wear.watchface.style.data.OptionWireFormat {
diff --git a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationOverlayWireFormat.java b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationOverlayWireFormat.java
index 71ebc33..e63e83b 100644
--- a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationOverlayWireFormat.java
+++ b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationOverlayWireFormat.java
@@ -28,7 +28,6 @@
import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelable;
import androidx.versionedparcelable.VersionedParcelize;
-import androidx.wear.complications.data.ComplicationType;
import java.util.Map;
@@ -54,7 +53,7 @@
@ParcelField(3)
@Nullable
- public Map<ComplicationType, RectF> mPerComplicationTypeBounds;
+ public Map<Integer, RectF> mPerComplicationTypeBounds;
/** Ideally this would be Integer but VersionedParcelable doesn't support that. */
@ParcelField(4)
@@ -66,7 +65,7 @@
public ComplicationOverlayWireFormat(
int complicationId,
@Nullable Boolean enabled,
- @Nullable Map<ComplicationType, RectF> perComplicationTypeBounds,
+ @Nullable Map<Integer, RectF> perComplicationTypeBounds,
@Nullable Integer accessibilityTraversalIndex
) {
mComplicationId = complicationId;
diff --git a/wear/wear-watchface-editor/api/current.txt b/wear/wear-watchface-editor/api/current.txt
index f1dddbb..56b5343 100644
--- a/wear/wear-watchface-editor/api/current.txt
+++ b/wear/wear-watchface-editor/api/current.txt
@@ -37,6 +37,7 @@
method public abstract Integer? getBackgroundComplicationId();
method @UiThread public abstract Integer? getComplicationIdAt(@Px int x, @Px int y);
method @UiThread public abstract suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+ method @UiThread public abstract suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
method public abstract java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
method public abstract long getPreviewReferenceTimeMillis();
method public abstract androidx.wear.watchface.style.UserStyle getUserStyle();
diff --git a/wear/wear-watchface-editor/api/public_plus_experimental_current.txt b/wear/wear-watchface-editor/api/public_plus_experimental_current.txt
index f1dddbb..56b5343 100644
--- a/wear/wear-watchface-editor/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-editor/api/public_plus_experimental_current.txt
@@ -37,6 +37,7 @@
method public abstract Integer? getBackgroundComplicationId();
method @UiThread public abstract Integer? getComplicationIdAt(@Px int x, @Px int y);
method @UiThread public abstract suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+ method @UiThread public abstract suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
method public abstract java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
method public abstract long getPreviewReferenceTimeMillis();
method public abstract androidx.wear.watchface.style.UserStyle getUserStyle();
diff --git a/wear/wear-watchface-editor/api/restricted_current.txt b/wear/wear-watchface-editor/api/restricted_current.txt
index 5ab6ecc..9cee27d 100644
--- a/wear/wear-watchface-editor/api/restricted_current.txt
+++ b/wear/wear-watchface-editor/api/restricted_current.txt
@@ -3,11 +3,12 @@
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract class BaseEditorSession extends androidx.wear.watchface.editor.EditorSession {
method public void close();
- method protected final void fetchComplicationPreviewData();
+ method protected final void fetchComplicationsData();
method public Integer? getBackgroundComplicationId();
method protected final boolean getClosed();
method public Integer? getComplicationIdAt(@Px int x, @Px int y);
method public suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> $completion);
+ method public suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> $completion);
method public final kotlinx.coroutines.CoroutineScope getCoroutineScope();
method protected final boolean getForceClosed();
method public suspend Object? openComplicationProviderChooser(int p, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationProvider> $completion);
@@ -57,6 +58,7 @@
method public abstract Integer? getBackgroundComplicationId();
method @UiThread public abstract Integer? getComplicationIdAt(@Px int x, @Px int y);
method @UiThread public abstract suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+ method @UiThread public abstract suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
method public abstract java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
method public abstract long getPreviewReferenceTimeMillis();
method public abstract androidx.wear.watchface.style.UserStyle getUserStyle();
diff --git a/wear/wear-watchface-editor/guava/api/current.txt b/wear/wear-watchface-editor/guava/api/current.txt
index 35b16e6..716598b 100644
--- a/wear/wear-watchface-editor/guava/api/current.txt
+++ b/wear/wear-watchface-editor/guava/api/current.txt
@@ -8,8 +8,10 @@
method public Integer? getBackgroundComplicationId();
method public Integer? getComplicationIdAt(int x, int y);
method public suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+ method public suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
method public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.data.ComplicationData>> getListenableComplicationPreviewData();
+ method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> getListenableComplicationsProviderInfo();
method public long getPreviewReferenceTimeMillis();
method public androidx.wear.watchface.style.UserStyle getUserStyle();
method public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
diff --git a/wear/wear-watchface-editor/guava/api/public_plus_experimental_current.txt b/wear/wear-watchface-editor/guava/api/public_plus_experimental_current.txt
index 35b16e6..716598b 100644
--- a/wear/wear-watchface-editor/guava/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-editor/guava/api/public_plus_experimental_current.txt
@@ -8,8 +8,10 @@
method public Integer? getBackgroundComplicationId();
method public Integer? getComplicationIdAt(int x, int y);
method public suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+ method public suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
method public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.data.ComplicationData>> getListenableComplicationPreviewData();
+ method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> getListenableComplicationsProviderInfo();
method public long getPreviewReferenceTimeMillis();
method public androidx.wear.watchface.style.UserStyle getUserStyle();
method public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
diff --git a/wear/wear-watchface-editor/guava/api/restricted_current.txt b/wear/wear-watchface-editor/guava/api/restricted_current.txt
index 35b16e6..716598b 100644
--- a/wear/wear-watchface-editor/guava/api/restricted_current.txt
+++ b/wear/wear-watchface-editor/guava/api/restricted_current.txt
@@ -8,8 +8,10 @@
method public Integer? getBackgroundComplicationId();
method public Integer? getComplicationIdAt(int x, int y);
method public suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+ method public suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
method public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.data.ComplicationData>> getListenableComplicationPreviewData();
+ method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> getListenableComplicationsProviderInfo();
method public long getPreviewReferenceTimeMillis();
method public androidx.wear.watchface.style.UserStyle getUserStyle();
method public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
diff --git a/wear/wear-watchface-editor/guava/src/main/java/androidx/wear/watchface/editor/ListenableEditorSession.kt b/wear/wear-watchface-editor/guava/src/main/java/androidx/wear/watchface/editor/ListenableEditorSession.kt
index f6c92d1..d9f57d1 100644
--- a/wear/wear-watchface-editor/guava/src/main/java/androidx/wear/watchface/editor/ListenableEditorSession.kt
+++ b/wear/wear-watchface-editor/guava/src/main/java/androidx/wear/watchface/editor/ListenableEditorSession.kt
@@ -24,6 +24,7 @@
import androidx.annotation.RequiresApi
import androidx.annotation.UiThread
import androidx.concurrent.futures.ResolvableFuture
+import androidx.wear.complications.ComplicationProviderInfo
import androidx.wear.complications.data.ComplicationData
import androidx.wear.watchface.RenderParameters
import androidx.wear.watchface.client.ComplicationState
@@ -138,9 +139,26 @@
return future
}
+ /** [ListenableFuture] wrapper around [EditorSession.getComplicationsProviderInfo]. */
+ public fun getListenableComplicationsProviderInfo():
+ ListenableFuture<Map<Int, ComplicationProviderInfo?>> {
+ val future = ResolvableFuture.create<Map<Int, ComplicationProviderInfo?>>()
+ getCoroutineScope().launch {
+ try {
+ future.set(wrappedEditorSession.getComplicationsProviderInfo())
+ } catch (e: Exception) {
+ future.setException(e)
+ }
+ }
+ return future
+ }
+
override suspend fun getComplicationsPreviewData(): Map<Int, ComplicationData> =
wrappedEditorSession.getComplicationsPreviewData()
+ override suspend fun getComplicationsProviderInfo(): Map<Int, ComplicationProviderInfo?> =
+ wrappedEditorSession.getComplicationsProviderInfo()
+
@get:SuppressWarnings("AutoBoxing")
override val backgroundComplicationId: Int?
get() = wrappedEditorSession.backgroundComplicationId
diff --git a/wear/wear-watchface-editor/lint-baseline.xml b/wear/wear-watchface-editor/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-editor/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-editor/samples/lint-baseline.xml b/wear/wear-watchface-editor/samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-editor/samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/ComplicationConfigFragment.kt b/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/ComplicationConfigFragment.kt
index 12d569a..53f57ea 100644
--- a/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/ComplicationConfigFragment.kt
+++ b/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/ComplicationConfigFragment.kt
@@ -20,6 +20,7 @@
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Rect
+import android.os.Build
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
@@ -27,8 +28,10 @@
import android.view.View
import android.view.ViewGroup
import android.widget.Button
+import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.fragment.app.Fragment
+import androidx.wear.complications.ComplicationProviderInfo
import androidx.wear.complications.data.ComplicationData
import androidx.wear.watchface.DrawMode
import androidx.wear.watchface.RenderParameters
@@ -110,6 +113,14 @@
}.resourceId
)
setOnClickListener { onComplicationButtonClicked(stateEntry.key) }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ setOnLongClickListener {
+ TooltipApi26.updateTooltip(it, watchFaceConfigActivity, stateEntry.key)
+ // Do not consume the long click so that the tooltip is shown by the
+ // default handler.
+ false
+ }
+ }
addView(this)
}
}
@@ -146,6 +157,25 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.O)
+ private object TooltipApi26 {
+ fun updateTooltip(
+ button: View,
+ watchFaceConfigActivity: WatchFaceConfigActivity,
+ complicationId: Int
+ ) {
+ watchFaceConfigActivity.coroutineScope.launch {
+ val providerInfo =
+ watchFaceConfigActivity.editorSession
+ .getComplicationsProviderInfo()[complicationId]
+ button.tooltipText = getProviderInfoToast(providerInfo)
+ }
+ }
+
+ private fun getProviderInfoToast(providerInfo: ComplicationProviderInfo?): String =
+ providerInfo?.name ?: "Empty complication provider"
+ }
+
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
drawRect.set(0, 0, width, height)
}
diff --git a/wear/wear-watchface-editor/src/androidTest/AndroidManifest.xml b/wear/wear-watchface-editor/src/androidTest/AndroidManifest.xml
index e1d17da..8698f0b 100644
--- a/wear/wear-watchface-editor/src/androidTest/AndroidManifest.xml
+++ b/wear/wear-watchface-editor/src/androidTest/AndroidManifest.xml
@@ -25,6 +25,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
<activity android:name="androidx.wear.watchface.editor.TestComplicationHelperActivity"
android:exported="false">
<intent-filter>
@@ -33,5 +34,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <activity
+ android:name="androidx.wear.watchface.editor.TestComplicationProviderChooserActivity" />
+
+ <activity android:name="androidx.wear.complications.ComplicationHelperActivity" />
</application>
</manifest>
diff --git a/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt b/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
index 72b79d5..38007635 100644
--- a/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
+++ b/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
@@ -41,6 +41,7 @@
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
import androidx.wear.complications.ComplicationBounds
+import androidx.wear.complications.ComplicationHelperActivity
import androidx.wear.complications.ComplicationProviderInfo
import androidx.wear.complications.DefaultComplicationProviderPolicy
import androidx.wear.complications.ProviderChooserIntent
@@ -110,6 +111,8 @@
private const val PROVIDER_CHOOSER_EXTRA_KEY = "PROVIDER_CHOOSER_EXTRA_KEY"
private const val PROVIDER_CHOOSER_EXTRA_VALUE = "PROVIDER_CHOOSER_EXTRA_VALUE"
+private const val PROVIDER_CHOOSER_RESULT_EXTRA_KEY = "PROVIDER_CHOOSER_RESULT_EXTRA_KEY"
+private const val PROVIDER_CHOOSER_RESULT_EXTRA_VALUE = "PROVIDER_CHOOSER_RESULT_EXTRA_VALUE"
private typealias WireComplicationProviderInfo =
android.support.wearable.complications.ComplicationProviderInfo
@@ -235,6 +238,24 @@
}
}
+/** Fake complication provider choooser for testing. */
+public class TestComplicationProviderChooserActivity : Activity() {
+
+ public companion object {
+ public var lastIntent: Intent? = null
+ public var resultIntent: Intent? = null
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ lastIntent = intent
+
+ setResult(123, resultIntent)
+ finish()
+ }
+}
+
@RunWith(AndroidJUnit4::class)
@MediumTest
public class EditorSessionTest {
@@ -409,6 +430,9 @@
@After
public fun tearDown() {
+ ComplicationProviderChooserContract.useTestComplicationHelperActivity = false
+ ComplicationHelperActivity.useTestComplicationProviderChooserActivity = false
+ ComplicationHelperActivity.skipPermissionCheck = false
WatchFace.clearAllEditorDelegates()
}
@@ -897,7 +921,7 @@
}
@Test
- public fun launchComplicationProviderChooser_ComplicationConfigExtras() {
+ public fun launchComplicationProviderChooser_ComplicationConfigExtrasToHelper() {
ComplicationProviderChooserContract.useTestComplicationHelperActivity = true
val chosenComplicationProviderInfo = ComplicationProviderInfo(
"TestProvider3App",
@@ -913,6 +937,7 @@
"android.support.wearable.complications.EXTRA_PROVIDER_INFO",
chosenComplicationProviderInfo.toWireComplicationProviderInfo()
)
+ putExtra(PROVIDER_CHOOSER_RESULT_EXTRA_KEY, PROVIDER_CHOOSER_RESULT_EXTRA_VALUE)
}
val scenario = createOnWatchFaceEditingTestActivity(
@@ -935,6 +960,9 @@
chosenComplicationProviderInfo,
chosenComplicationProvider.complicationProviderInfo
)
+ assertThat(
+ chosenComplicationProvider.extras[PROVIDER_CHOOSER_RESULT_EXTRA_KEY]
+ ).isEqualTo(PROVIDER_CHOOSER_RESULT_EXTRA_VALUE)
assertThat(
TestComplicationHelperActivity.lastIntent?.extras?.getString(
@@ -945,6 +973,62 @@
}
@Test
+ public fun launchComplicationProviderChooser_ComplicationConfigExtrasToChooser() {
+ // Invoke the test provider chooser to record the result.
+ ComplicationHelperActivity.useTestComplicationProviderChooserActivity = true
+ // Invoke the provider chooser without checking for permissions first.
+ ComplicationHelperActivity.skipPermissionCheck = true
+
+ val chosenComplicationProviderInfo = ComplicationProviderInfo(
+ "TestProvider3App",
+ "TestProvider3",
+ Icon.createWithBitmap(
+ Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ ),
+ ComplicationType.LONG_TEXT,
+ provider3
+ )
+ TestComplicationProviderChooserActivity.resultIntent = Intent().apply {
+ putExtra(
+ "android.support.wearable.complications.EXTRA_PROVIDER_INFO",
+ chosenComplicationProviderInfo.toWireComplicationProviderInfo()
+ )
+ putExtra(PROVIDER_CHOOSER_RESULT_EXTRA_KEY, PROVIDER_CHOOSER_RESULT_EXTRA_VALUE)
+ }
+
+ val scenario = createOnWatchFaceEditingTestActivity(
+ emptyList(),
+ listOf(leftComplication, rightComplication)
+ )
+
+ lateinit var editorSession: EditorSession
+ scenario.onActivity { activity ->
+ editorSession = activity.editorSession
+ }
+
+ runBlocking {
+ val chosenComplicationProvider =
+ editorSession.openComplicationProviderChooser(RIGHT_COMPLICATION_ID)
+ assertThat(chosenComplicationProvider).isNotNull()
+ checkNotNull(chosenComplicationProvider)
+ assertThat(chosenComplicationProvider.complicationId).isEqualTo(RIGHT_COMPLICATION_ID)
+ assertEquals(
+ chosenComplicationProviderInfo,
+ chosenComplicationProvider.complicationProviderInfo
+ )
+ assertThat(
+ chosenComplicationProvider.extras[PROVIDER_CHOOSER_RESULT_EXTRA_KEY]
+ ).isEqualTo(PROVIDER_CHOOSER_RESULT_EXTRA_VALUE)
+
+ assertThat(
+ TestComplicationProviderChooserActivity.lastIntent?.extras?.getString(
+ PROVIDER_CHOOSER_EXTRA_KEY
+ )
+ ).isEqualTo(PROVIDER_CHOOSER_EXTRA_VALUE)
+ }
+ }
+
+ @Test
public fun getComplicationIdAt() {
val scenario = createOnWatchFaceEditingTestActivity(
emptyList(),
@@ -1244,7 +1328,7 @@
scenario.onActivity {
baseEditorSession = it.editorSession as BaseEditorSession
baseEditorSession.pendingComplicationProviderChooserResult = CompletableDeferred()
- baseEditorSession.updatePreviewData(
+ baseEditorSession.onComplicationProviderChooserResult(
ComplicationProviderChooserResult(
ComplicationProviderInfo(
"provider.app",
diff --git a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index d08f9f6..6935eed 100644
--- a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -132,6 +132,16 @@
@UiThread
public abstract suspend fun getComplicationsPreviewData(): Map<Int, ComplicationData>
+ /**
+ * Returns a map of complication ids to [ComplicationProviderInfo] that represent the
+ * information available about the provider for each complication.
+ *
+ * A `null` [ComplicationProviderInfo] will be associated with a complication id if the
+ * complication is configured to show the empty complication provider.
+ */
+ @UiThread
+ public abstract suspend fun getComplicationsProviderInfo(): Map<Int, ComplicationProviderInfo?>
+
/** The ID of the background complication or `null` if there isn't one. */
@get:SuppressWarnings("AutoBoxing")
public abstract val backgroundComplicationId: Int?
@@ -319,8 +329,10 @@
EditorService.globalEditorService.addCloseCallback(closeCallback)
}
- // This is completed when [fetchComplicationPreviewData] has called [getPreviewData] for
- // each complication and each of those have been completed.
+ /**
+ * This is completed when [fetchComplicationsData] has called [getPreviewData] for each
+ * complication and each of those have been completed.
+ */
private val deferredComplicationPreviewDataMap =
CompletableDeferred<MutableMap<Int, ComplicationData>>()
@@ -328,6 +340,14 @@
return deferredComplicationPreviewDataMap.await()
}
+ // This is completed when [fetchProviderInfo] has called [getProviderInfo] for each
+ // complication and each of those have been completed.
+ private val deferredComplicationsProviderInfoMap =
+ CompletableDeferred<MutableMap<Int, ComplicationProviderInfo?>>()
+
+ override suspend fun getComplicationsProviderInfo(): Map<Int, ComplicationProviderInfo?> =
+ deferredComplicationsProviderInfoMap.await()
+
/** Pending result for [openComplicationProviderChooser]. */
internal var pendingComplicationProviderChooserResult:
CompletableDeferred<ChosenComplicationProvider?>? = null
@@ -337,10 +357,10 @@
private val chooseComplicationProvider =
activity.registerForActivityResult(ComplicationProviderChooserContract()) {
- updatePreviewData(it)
+ onComplicationProviderChooserResult(it)
}
- internal fun updatePreviewData(
+ internal fun onComplicationProviderChooserResult(
complicationProviderChooserResult: ComplicationProviderChooserResult?
) {
// Check if the user cancelled the provider chooser.
@@ -351,8 +371,13 @@
}
val providerInfoRetriever =
providerInfoRetrieverProvider.getProviderInfoRetriever()
- coroutineScope.launchWithTracing("BaseEditorSession.updatePreviewData") {
+ coroutineScope.launchWithTracing(
+ "BaseEditorSession.onComplicationProviderChooserResult"
+ ) {
try {
+ val complicationsProviderInfoMap = deferredComplicationsProviderInfoMap.await()
+ complicationsProviderInfoMap[pendingComplicationProviderId] =
+ complicationProviderChooserResult.providerInfo
val previewData = getPreviewData(
providerInfoRetriever,
complicationProviderChooserResult.providerInfo
@@ -463,9 +488,9 @@
MonochromaticImage.Builder(providerInfo.icon).build()
).build()
- protected fun fetchComplicationPreviewData() {
+ protected fun fetchComplicationsData() {
val providerInfoRetriever = providerInfoRetrieverProvider.getProviderInfoRetriever()
- coroutineScope.launchWithTracing("BaseEditorSession.fetchComplicationPreviewData") {
+ coroutineScope.launchWithTracing("BaseEditorSession.fetchComplicationsData") {
try {
// Unlikely but WCS could conceivably crash during this call. We could retry but it's
// not obvious if that'd succeed or if WCS session state is recoverable, it's probably
@@ -474,6 +499,10 @@
watchFaceComponentName,
complicationsState.keys.toIntArray()
)
+ deferredComplicationsProviderInfoMap.complete(
+ extractComplicationsProviderInfoMap(providerInfoArray)?.toMutableMap()
+ ?: mutableMapOf()
+ )
deferredComplicationPreviewDataMap.complete(
// Parallel fetch preview ComplicationData.
providerInfoArray?.associateBy(
@@ -645,7 +674,7 @@
UserStyle(initialEditorUserStyle, editorDelegate.userStyleSchema)
}
- fetchComplicationPreviewData()
+ fetchComplicationsData()
}
}
@@ -686,7 +715,7 @@
}
init {
- fetchComplicationPreviewData()
+ fetchComplicationsData()
}
}
@@ -712,6 +741,12 @@
internal companion object {
const val EXTRA_PROVIDER_INFO = "android.support.wearable.complications.EXTRA_PROVIDER_INFO"
+
+ /**
+ * Whether to invoke a test activity instead of the [ComplicationHelperActivity].
+ *
+ * To be used in tests.
+ */
internal var useTestComplicationHelperActivity = false
}
@@ -749,4 +784,16 @@
}
}
+/**
+ * Extracts a map from complication ID to the corresponding [ComplicationProviderInfo] from the
+ * given array of [ProviderInfoRetriever.ProviderInfo].
+ */
+internal fun extractComplicationsProviderInfoMap(
+ providerInfoArray: Array<ProviderInfoRetriever.ProviderInfo>?
+): Map<Int, ComplicationProviderInfo?>? =
+ providerInfoArray?.associateBy(
+ { it.watchFaceComplicationId },
+ { it.info }
+ )
+
internal fun Bundle.asString() = keySet().map { "$it: ${get(it)}" }
diff --git a/wear/wear-watchface-style/lint-baseline.xml b/wear/wear-watchface-style/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-style/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
index 55a881d..d3169ac 100644
--- a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
+++ b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
@@ -19,6 +19,7 @@
import android.graphics.drawable.Icon
import androidx.annotation.RestrictTo
import androidx.wear.complications.ComplicationBounds
+import androidx.wear.complications.data.ComplicationType
import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay
import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationsOption
import androidx.wear.watchface.style.UserStyleSetting.Id.Companion.MAX_LENGTH
@@ -438,7 +439,11 @@
"Unrecognised wireFormat.mEnabled " + wireFormat.mEnabled
)
},
- wireFormat.mPerComplicationTypeBounds?.let { ComplicationBounds(it) },
+ wireFormat.mPerComplicationTypeBounds?.let {
+ ComplicationBounds(
+ it.mapKeys { ComplicationType.fromWireType(it.key) }
+ )
+ },
wireFormat.accessibilityTraversalIndex
)
@@ -446,7 +451,9 @@
ComplicationOverlayWireFormat(
complicationId,
enabled,
- complicationBounds?.perComplicationTypeBounds,
+ complicationBounds?.perComplicationTypeBounds?.mapKeys {
+ it.key.toWireComplicationType()
+ },
accessibilityTraversalIndex
)
}
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index eaa4494..1aa5089 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -359,7 +359,7 @@
method @UiThread public void invalidate();
method public void onUserStyleChanged();
method public void setActiveComplications(int[] watchFaceComplicationIds);
- method public void setDefaultComplicationProviderWithFallbacks(int watchFaceComplicationId, java.util.List<android.content.ComponentName>? providers, @androidx.wear.complications.SystemProviders.Companion.ProviderId int fallbackSystemProvider, int type);
+ method public void setDefaultComplicationProviderWithFallbacks(int watchFaceComplicationId, java.util.List<android.content.ComponentName>? providers, @androidx.wear.complications.SystemProviders.ProviderId int fallbackSystemProvider, int type);
method public void updateContentDescriptionLabels();
}
diff --git a/wear/wear-watchface/samples/lint-baseline.xml b/wear/wear-watchface/samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface/samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastReceivers.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastReceivers.kt
deleted file mode 100644
index 8f9b1b9..0000000
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastReceivers.kt
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright 2020 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.wear.watchface
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import androidx.annotation.UiThread
-import androidx.annotation.VisibleForTesting
-
-/**
- * All watchface instances share the same [Context] which is a problem for broadcast receivers
- * because the OS will mistakenly believe we're leaking them if there's more than one instance. So
- * we need to use this class to share them.
- */
-internal class BroadcastReceivers private constructor(private val context: Context) {
-
- interface BroadcastEventObserver {
- /** Called when we receive [Intent.ACTION_TIME_TICK]. */
- @UiThread
- fun onActionTimeTick()
-
- /** Called when we receive [Intent.ACTION_TIMEZONE_CHANGED]. */
- @UiThread
- fun onActionTimeZoneChanged()
-
- /** Called when we receive [Intent.ACTION_TIME_CHANGED]. */
- @UiThread
- fun onActionTimeChanged()
-
- /** Called when we receive [Intent.ACTION_BATTERY_LOW]. */
- @UiThread
- fun onActionBatteryLow()
-
- /** Called when we receive [Intent.ACTION_BATTERY_OKAY]. */
- @UiThread
- fun onActionBatteryOkay()
-
- /** Called when we receive [Intent.ACTION_POWER_CONNECTED]. */
- @UiThread
- fun onActionPowerConnected()
-
- /** Called when we receive [WatchFaceImpl.MOCK_TIME_INTENT]. */
- @UiThread
- fun onMockTime(intent: Intent)
- }
-
- companion object {
- val broadcastEventObservers = HashSet<BroadcastEventObserver>()
-
- /**
- * We don't leak due to balanced calls to [addBroadcastEventObserver] and
- * [removeBroadcastEventObserver] which sets this back to null.
- */
- @SuppressWarnings("StaticFieldLeak")
- var broadcastReceivers: BroadcastReceivers? = null
-
- @UiThread
- fun addBroadcastEventObserver(context: Context, observer: BroadcastEventObserver) {
- broadcastEventObservers.add(observer)
- if (broadcastReceivers == null) {
- broadcastReceivers = BroadcastReceivers(context)
- }
- }
-
- @UiThread
- fun removeBroadcastEventObserver(observer: BroadcastEventObserver) {
- broadcastEventObservers.remove(observer)
- if (broadcastEventObservers.isEmpty()) {
- broadcastReceivers!!.onDestroy()
- broadcastReceivers = null
- }
- }
-
- @VisibleForTesting
- fun sendOnActionBatteryLowForTesting(intent: Intent) {
- require(intent.action == Intent.ACTION_BATTERY_LOW)
- require(broadcastEventObservers.isNotEmpty())
- for (observer in broadcastEventObservers) {
- observer.onActionBatteryLow()
- }
- }
-
- @VisibleForTesting
- fun sendOnActionBatteryOkayForTesting(intent: Intent) {
- require(intent.action == Intent.ACTION_BATTERY_OKAY)
- require(broadcastEventObservers.isNotEmpty())
- for (observer in broadcastEventObservers) {
- observer.onActionBatteryOkay()
- }
- }
-
- @VisibleForTesting
- fun sendOnActionPowerConnectedForTesting(intent: Intent) {
- require(intent.action == Intent.ACTION_POWER_CONNECTED)
- require(broadcastEventObservers.isNotEmpty())
- for (observer in broadcastEventObservers) {
- observer.onActionPowerConnected()
- }
- }
-
- @VisibleForTesting
- fun sendOnMockTimeForTesting(intent: Intent) {
- require(intent.action == WatchFaceImpl.MOCK_TIME_INTENT)
- require(broadcastEventObservers.isNotEmpty())
- for (observer in broadcastEventObservers) {
- observer.onMockTime(intent)
- }
- }
- }
-
- private val actionTimeTickReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- @SuppressWarnings("SyntheticAccessor")
- override fun onReceive(context: Context, intent: Intent) {
- for (observer in broadcastEventObservers) {
- observer.onActionTimeTick()
- }
- }
- }
-
- private val actionTimeZoneReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- for (observer in broadcastEventObservers) {
- observer.onActionTimeZoneChanged()
- }
- }
- }
-
- private val actionTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- for (observer in broadcastEventObservers) {
- observer.onActionTimeChanged()
- }
- }
- }
-
- private val actionBatteryLowReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- @SuppressWarnings("SyntheticAccessor")
- override fun onReceive(context: Context, intent: Intent) {
- for (observer in broadcastEventObservers) {
- observer.onActionBatteryLow()
- }
- }
- }
-
- private val actionBatteryOkayReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- @SuppressWarnings("SyntheticAccessor")
- override fun onReceive(context: Context, intent: Intent) {
- for (observer in broadcastEventObservers) {
- observer.onActionBatteryOkay()
- }
- }
- }
-
- private val actionPowerConnectedReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- @SuppressWarnings("SyntheticAccessor")
- override fun onReceive(context: Context, intent: Intent) {
- for (observer in broadcastEventObservers) {
- observer.onActionPowerConnected()
- }
- }
- }
-
- private val mockTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- @SuppressWarnings("SyntheticAccessor")
- override fun onReceive(context: Context, intent: Intent) {
- for (observer in broadcastEventObservers) {
- observer.onMockTime(intent)
- }
- }
- }
-
- init {
- context.registerReceiver(actionTimeTickReceiver, IntentFilter(Intent.ACTION_TIME_TICK))
- context.registerReceiver(
- actionTimeZoneReceiver,
- IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)
- )
- context.registerReceiver(actionTimeReceiver, IntentFilter(Intent.ACTION_TIME_CHANGED))
- context.registerReceiver(actionBatteryLowReceiver, IntentFilter(Intent.ACTION_BATTERY_LOW))
- context.registerReceiver(
- actionBatteryOkayReceiver,
- IntentFilter(Intent.ACTION_BATTERY_OKAY)
- )
- context.registerReceiver(
- actionPowerConnectedReceiver,
- IntentFilter(Intent.ACTION_POWER_CONNECTED)
- )
- context.registerReceiver(mockTimeReceiver, IntentFilter(WatchFaceImpl.MOCK_TIME_INTENT))
- }
-
- fun onDestroy() {
- context.unregisterReceiver(actionTimeTickReceiver)
- context.unregisterReceiver(actionTimeZoneReceiver)
- context.unregisterReceiver(actionTimeReceiver)
- context.unregisterReceiver(actionBatteryLowReceiver)
- context.unregisterReceiver(actionBatteryOkayReceiver)
- context.unregisterReceiver(actionPowerConnectedReceiver)
- context.unregisterReceiver(mockTimeReceiver)
- }
-}
\ No newline at end of file
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
new file mode 100644
index 0000000..cc6675a
--- /dev/null
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2020 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.wear.watchface
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.annotation.UiThread
+
+/**
+ * This class decouples [BroadcastEventObserver]s from the actual broadcast event receivers to make
+ * testing easier.
+ */
+internal class BroadcastsReceiver constructor(
+ private val context: Context,
+ private val observer: BroadcastEventObserver
+) {
+
+ interface BroadcastEventObserver {
+ /** Called when we receive [Intent.ACTION_TIME_TICK]. */
+ @UiThread
+ fun onActionTimeTick()
+
+ /** Called when we receive [Intent.ACTION_TIMEZONE_CHANGED]. */
+ @UiThread
+ fun onActionTimeZoneChanged()
+
+ /** Called when we receive [Intent.ACTION_TIME_CHANGED]. */
+ @UiThread
+ fun onActionTimeChanged()
+
+ /** Called when we receive [Intent.ACTION_BATTERY_LOW]. */
+ @UiThread
+ fun onActionBatteryLow()
+
+ /** Called when we receive [Intent.ACTION_BATTERY_OKAY]. */
+ @UiThread
+ fun onActionBatteryOkay()
+
+ /** Called when we receive [Intent.ACTION_POWER_CONNECTED]. */
+ @UiThread
+ fun onActionPowerConnected()
+
+ /** Called when we receive [WatchFaceImpl.MOCK_TIME_INTENT]. */
+ @UiThread
+ fun onMockTime(intent: Intent)
+ }
+
+ internal val actionTimeTickReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ @SuppressWarnings("SyntheticAccessor")
+ override fun onReceive(context: Context, intent: Intent) {
+ observer.onActionTimeTick()
+ }
+ }
+
+ internal val actionTimeZoneReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ observer.onActionTimeZoneChanged()
+ }
+ }
+
+ internal val actionTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ observer.onActionTimeChanged()
+ }
+ }
+
+ internal val actionBatteryLowReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ @SuppressWarnings("SyntheticAccessor")
+ override fun onReceive(context: Context, intent: Intent) {
+ observer.onActionBatteryLow()
+ }
+ }
+
+ internal val actionBatteryOkayReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ @SuppressWarnings("SyntheticAccessor")
+ override fun onReceive(context: Context, intent: Intent) {
+ observer.onActionBatteryOkay()
+ }
+ }
+
+ internal val actionPowerConnectedReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ @SuppressWarnings("SyntheticAccessor")
+ override fun onReceive(context: Context, intent: Intent) {
+ observer.onActionPowerConnected()
+ }
+ }
+
+ internal val mockTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ @SuppressWarnings("SyntheticAccessor")
+ override fun onReceive(context: Context, intent: Intent) {
+ observer.onMockTime(intent)
+ }
+ }
+
+ init {
+ context.registerReceiver(actionTimeTickReceiver, IntentFilter(Intent.ACTION_TIME_TICK))
+ context.registerReceiver(
+ actionTimeZoneReceiver,
+ IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)
+ )
+ context.registerReceiver(actionTimeReceiver, IntentFilter(Intent.ACTION_TIME_CHANGED))
+ context.registerReceiver(actionBatteryLowReceiver, IntentFilter(Intent.ACTION_BATTERY_LOW))
+ context.registerReceiver(
+ actionBatteryOkayReceiver,
+ IntentFilter(Intent.ACTION_BATTERY_OKAY)
+ )
+ context.registerReceiver(
+ actionPowerConnectedReceiver,
+ IntentFilter(Intent.ACTION_POWER_CONNECTED)
+ )
+ context.registerReceiver(mockTimeReceiver, IntentFilter(WatchFaceImpl.MOCK_TIME_INTENT))
+ }
+
+ fun onDestroy() {
+ context.unregisterReceiver(actionTimeTickReceiver)
+ context.unregisterReceiver(actionTimeZoneReceiver)
+ context.unregisterReceiver(actionTimeReceiver)
+ context.unregisterReceiver(actionBatteryLowReceiver)
+ context.unregisterReceiver(actionBatteryOkayReceiver)
+ context.unregisterReceiver(actionPowerConnectedReceiver)
+ context.unregisterReceiver(mockTimeReceiver)
+ }
+}
\ No newline at end of file
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
index 27a58d2..09eca33 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
@@ -445,7 +445,9 @@
// The caller might modify a number of complications. For efficiency we need to coalesce
// these into one update task.
- complicationsManager.scheduleUpdate()
+ if (this::complicationsManager.isInitialized) {
+ complicationsManager.scheduleUpdate()
+ }
}
internal var enabledDirty = true
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index 2153570..8dbc10c 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -435,7 +435,7 @@
private var mockTime = MockTime(1.0, 0, Long.MAX_VALUE)
private var lastTappedComplicationId: Int? = null
- private var registeredReceivers = false
+ internal var broadcastsReceiver: BroadcastsReceiver? = null
// True if 'Do Not Disturb' mode is on.
private var muteMode = false
@@ -465,7 +465,7 @@
legacyWatchFaceStyle.tapEventsAccepted
)
- private val broadcastEventObserver = object : BroadcastReceivers.BroadcastEventObserver {
+ private val broadcastEventObserver = object : BroadcastsReceiver.BroadcastEventObserver {
override fun onActionTimeTick() {
if (!watchState.isAmbient.value) {
renderer.invalidate()
@@ -759,14 +759,12 @@
require(watchFaceHostApi.getHandler().looper.isCurrentThread) {
"registerReceivers must be called the UiThread"
}
- if (registeredReceivers) {
- return
+
+ // There's no point registering BroadcastsReceiver for headless instances.
+ if (broadcastsReceiver == null && !watchState.isHeadless) {
+ broadcastsReceiver =
+ BroadcastsReceiver(watchFaceHostApi.getContext(), broadcastEventObserver)
}
- registeredReceivers = true
- BroadcastReceivers.addBroadcastEventObserver(
- watchFaceHostApi.getContext(),
- broadcastEventObserver
- )
}
@UiThread
@@ -774,11 +772,8 @@
require(watchFaceHostApi.getHandler().looper.isCurrentThread) {
"unregisterReceivers must be called the UiThread"
}
- if (!registeredReceivers) {
- return
- }
- registeredReceivers = false
- BroadcastReceivers.removeBroadcastEventObserver(broadcastEventObserver)
+ broadcastsReceiver?.onDestroy()
+ broadcastsReceiver = null
}
private fun scheduleDraw() {
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
index 9f074cd..55f0263 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
@@ -22,7 +22,7 @@
import android.support.wearable.complications.ComplicationData
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
-import androidx.wear.complications.SystemProviders.Companion.ProviderId
+import androidx.wear.complications.SystemProviders.ProviderId
import androidx.wear.watchface.style.data.UserStyleWireFormat
/**
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 2cb6fc3..b9a7ee8 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -50,7 +50,7 @@
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
import androidx.versionedparcelable.ParcelUtils
-import androidx.wear.complications.SystemProviders.Companion.ProviderId
+import androidx.wear.complications.SystemProviders.ProviderId
import androidx.wear.complications.data.ComplicationData
import androidx.wear.complications.data.ComplicationType
import androidx.wear.complications.data.toApiComplicationData
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index b2668c2..118cb31 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -624,7 +624,8 @@
watchState.isAmbient.value = false
testWatchFaceService.mockSystemTimeMillis = 1000L
- BroadcastReceivers.sendOnMockTimeForTesting(
+ watchFaceImpl.broadcastsReceiver!!.mockTimeReceiver.onReceive(
+ context,
Intent(WatchFaceImpl.MOCK_TIME_INTENT).apply {
putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_SPEED_MULTIPLIER, 2.0f)
putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_WRAPPING_MIN_TIME, -1L)
@@ -651,7 +652,8 @@
watchState.isAmbient.value = false
testWatchFaceService.mockSystemTimeMillis = 1000L
- BroadcastReceivers.sendOnMockTimeForTesting(
+ watchFaceImpl.broadcastsReceiver!!.mockTimeReceiver.onReceive(
+ context,
Intent(WatchFaceImpl.MOCK_TIME_INTENT).apply {
putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_SPEED_MULTIPLIER, 2.0f)
putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_WRAPPING_MIN_TIME, 1000L)
@@ -857,13 +859,19 @@
)
// The delay should change when battery is low.
- BroadcastReceivers.sendOnActionBatteryLowForTesting(Intent(Intent.ACTION_BATTERY_LOW))
+ watchFaceImpl.broadcastsReceiver!!.actionBatteryLowReceiver.onReceive(
+ context,
+ Intent(Intent.ACTION_BATTERY_LOW)
+ )
assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
WatchFaceImpl.MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS
)
// And go back to normal when battery is OK.
- BroadcastReceivers.sendOnActionBatteryOkayForTesting(Intent(Intent.ACTION_BATTERY_OKAY))
+ watchFaceImpl.broadcastsReceiver!!.actionBatteryOkayReceiver.onReceive(
+ context,
+ Intent(Intent.ACTION_BATTERY_OKAY)
+ )
assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
INTERACTIVE_UPDATE_RATE_MS
)
@@ -882,13 +890,17 @@
)
// The delay should change when battery is low.
- BroadcastReceivers.sendOnActionBatteryLowForTesting(Intent(Intent.ACTION_BATTERY_LOW))
+ watchFaceImpl.broadcastsReceiver!!.actionBatteryLowReceiver.onReceive(
+ context,
+ Intent(Intent.ACTION_BATTERY_LOW)
+ )
assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
WatchFaceImpl.MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS
)
// And go back to normal when power is connected.
- BroadcastReceivers.sendOnActionPowerConnectedForTesting(
+ watchFaceImpl.broadcastsReceiver!!.actionPowerConnectedReceiver.onReceive(
+ context,
Intent(Intent.ACTION_POWER_CONNECTED)
)
assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
@@ -2535,6 +2547,38 @@
verify(observer).onChanged(true)
}
+ @Test
+ public fun complicationsUserStyleSetting_with_setComplicationBounds() {
+ val complicationsStyleSetting = ComplicationsUserStyleSetting(
+ UserStyleSetting.Id("complications_style_setting"),
+ "AllComplications",
+ "Number and position",
+ icon = null,
+ complicationConfig = listOf(
+ ComplicationsOption(
+ Option.Id(RIGHT_COMPLICATION),
+ "Right",
+ null,
+ listOf(
+ ComplicationOverlay.Builder(LEFT_COMPLICATION_ID)
+ .setComplicationBounds(
+ ComplicationBounds(RectF(10f, 10f, 20f, 20f))
+ ).build()
+ )
+ )
+ ),
+ affectsWatchFaceLayers = listOf(WatchFaceLayer.COMPLICATIONS)
+ )
+
+ // This should not crash.
+ initEngine(
+ WatchFaceType.DIGITAL,
+ listOf(leftComplication, rightComplication),
+ UserStyleSchema(listOf(complicationsStyleSetting)),
+ apiVersion = 4
+ )
+ }
+
@Suppress("DEPRECATION")
private fun getChinWindowInsetsApi25(@Px chinHeight: Int): WindowInsets =
WindowInsets.Builder().setSystemWindowInsets(
diff --git a/wear/wear/api/current.txt b/wear/wear/api/current.txt
index eb93233..1b11fb1 100644
--- a/wear/wear/api/current.txt
+++ b/wear/wear/api/current.txt
@@ -263,6 +263,7 @@
method public void setText(String?);
method public void setTextColor(@ColorInt int);
method public void setTextSize(float);
+ method public void setTypeface(android.graphics.Typeface?, int);
method public void setTypeface(android.graphics.Typeface?);
}
diff --git a/wear/wear/api/public_plus_experimental_current.txt b/wear/wear/api/public_plus_experimental_current.txt
index eb93233..1b11fb1 100644
--- a/wear/wear/api/public_plus_experimental_current.txt
+++ b/wear/wear/api/public_plus_experimental_current.txt
@@ -263,6 +263,7 @@
method public void setText(String?);
method public void setTextColor(@ColorInt int);
method public void setTextSize(float);
+ method public void setTypeface(android.graphics.Typeface?, int);
method public void setTypeface(android.graphics.Typeface?);
}
diff --git a/wear/wear/api/restricted_current.txt b/wear/wear/api/restricted_current.txt
index aca9475..6e3ffdf 100644
--- a/wear/wear/api/restricted_current.txt
+++ b/wear/wear/api/restricted_current.txt
@@ -270,6 +270,7 @@
method public void setText(String?);
method public void setTextColor(@ColorInt int);
method public void setTextSize(float);
+ method public void setTypeface(android.graphics.Typeface?, int);
method public void setTypeface(android.graphics.Typeface?);
}
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/CurvedTextViewTest.kt b/wear/wear/src/androidTest/java/androidx/wear/widget/CurvedTextViewTest.kt
index 0d81777..34cec3d 100644
--- a/wear/wear/src/androidTest/java/androidx/wear/widget/CurvedTextViewTest.kt
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/CurvedTextViewTest.kt
@@ -19,6 +19,7 @@
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
+import android.graphics.Typeface
import android.text.TextUtils
import android.view.View
import android.view.View.MeasureSpec
@@ -277,6 +278,36 @@
)
}
+ @Test
+ fun testStyles() {
+ doOneTest(
+ "styles_test",
+ listOf(
+ CurvedTextView(ApplicationProvider.getApplicationContext())
+ .apply {
+ text = "Bold"
+ anchorAngleDegrees = 0.0f
+ anchorType = ArcLayout.ANCHOR_START
+ setTypeface(null, Typeface.BOLD)
+ },
+ CurvedTextView(ApplicationProvider.getApplicationContext())
+ .apply {
+ text = "Italic"
+ anchorAngleDegrees = 60.0f
+ anchorType = ArcLayout.ANCHOR_START
+ setTypeface(null, Typeface.ITALIC)
+ },
+ CurvedTextView(ApplicationProvider.getApplicationContext())
+ .apply {
+ text = "ItalicBold"
+ anchorAngleDegrees = 120.0f
+ anchorType = ArcLayout.ANCHOR_START
+ setTypeface(null, Typeface.BOLD_ITALIC)
+ },
+ )
+ )
+ }
+
companion object {
private const val SCREEN_WIDTH = 390
private const val SCREEN_HEIGHT = 390
diff --git a/wear/wear/src/main/java/androidx/wear/widget/CurvedTextView.java b/wear/wear/src/main/java/androidx/wear/widget/CurvedTextView.java
index 9cd55a9..949098a 100644
--- a/wear/wear/src/main/java/androidx/wear/widget/CurvedTextView.java
+++ b/wear/wear/src/main/java/androidx/wear/widget/CurvedTextView.java
@@ -553,7 +553,7 @@
* bold and italic bits in the Paint if the Typeface that you provided does not have all the
* bits in the style that you specified.
*/
- private void setTypeface(@Nullable Typeface tf, int style) {
+ public void setTypeface(@Nullable Typeface tf, int style) {
if (style > 0) {
if (tf == null) {
tf = Typeface.defaultFromStyle(style);
@@ -578,6 +578,7 @@
mTypeface = tf;
}
}
+ doUpdate();
}
/**
diff --git a/webkit/integration-tests/testapp/build.gradle b/webkit/integration-tests/testapp/build.gradle
index 4caa18a..3442df0 100644
--- a/webkit/integration-tests/testapp/build.gradle
+++ b/webkit/integration-tests/testapp/build.gradle
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("AndroidXPlugin")
@@ -24,21 +23,21 @@
implementation("androidx.appcompat:appcompat:1.1.0")
implementation("androidx.core:core:1.1.0")
implementation(project(":webkit:webkit"))
- implementation(GUAVA_ANDROID)
- implementation(ESPRESSO_IDLING_NET)
- implementation(ESPRESSO_IDLING_RESOURCE)
+ implementation(libs.guavaAndroid)
+ implementation(libs.espressoIdlingNet)
+ implementation(libs.espressoIdlingResource)
- androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
- androidTestImplementation(ANDROIDX_TEST_CORE)
- androidTestImplementation(ANDROIDX_TEST_RUNNER)
- androidTestImplementation(ANDROIDX_TEST_RULES)
- androidTestImplementation(ESPRESSO_CORE, excludes.espresso)
- androidTestImplementation(ESPRESSO_CONTRIB, excludes.espresso)
- androidTestImplementation(ESPRESSO_IDLING_RESOURCE)
- androidTestImplementation(ESPRESSO_WEB, excludes.espresso)
- androidTestImplementation(MOCKITO_CORE, excludes.bytebuddy)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.testRules)
+ androidTestImplementation(libs.espressoCore, excludes.espresso)
+ androidTestImplementation(libs.espressoContrib, excludes.espresso)
+ androidTestImplementation(libs.espressoIdlingResource)
+ androidTestImplementation(libs.espressoWeb, excludes.espresso)
+ androidTestImplementation(libs.mockitoCore, excludes.bytebuddy)
// DexMaker has it"s own MockMaker
- androidTestImplementation(DEXMAKER_MOCKITO, excludes.bytebuddy)
+ androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
}
rootProject.tasks.getByName("buildOnServer").dependsOn(project.path + ":assembleRelease")
diff --git a/webkit/webkit/build.gradle b/webkit/webkit/build.gradle
index a52b165..ff5de9e 100644
--- a/webkit/webkit/build.gradle
+++ b/webkit/webkit/build.gradle
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import static androidx.build.dependencies.DependenciesKt.*
import androidx.build.LibraryGroups
import androidx.build.Publish
import androidx.build.SupportConfigKt
@@ -28,15 +27,15 @@
api("androidx.annotation:annotation:1.1.0")
api("androidx.core:core:1.1.0")
- androidTestImplementation(OKHTTP_MOCKWEBSERVER)
- androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
- androidTestImplementation(ANDROIDX_TEST_CORE)
- androidTestImplementation(ANDROIDX_TEST_RUNNER)
- androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(libs.okhttpMockwebserver)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.testRules)
androidTestImplementation("androidx.concurrent:concurrent-futures:1.0.0")
// Hamcrest matchers:
- androidTestImplementation(ESPRESSO_CONTRIB, excludes.espresso)
+ androidTestImplementation(libs.espressoContrib, excludes.espresso)
}
ext {
diff --git a/window/window-extensions/lint-baseline.xml b/window/window-extensions/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/window/window-extensions/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/window/window-samples/lint-baseline.xml b/window/window-samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/window/window-samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/window/window-sidecar/lint-baseline.xml b/window/window-sidecar/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/window/window-sidecar/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index 5c30487..c82eaa4 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -40,6 +40,12 @@
method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo> callback);
}
+ public interface WindowInfoRepo {
+ method public androidx.window.WindowMetrics currentWindowMetrics();
+ method public androidx.window.WindowMetrics maximumWindowMetrics();
+ method public kotlinx.coroutines.flow.Flow<androidx.window.WindowLayoutInfo> windowLayoutInfo();
+ }
+
public final class WindowLayoutInfo {
method public java.util.List<androidx.window.DisplayFeature> getDisplayFeatures();
property public final java.util.List<androidx.window.DisplayFeature> displayFeatures;
@@ -66,5 +72,8 @@
property public final android.graphics.Rect bounds;
}
+ public final class WindowServices {
+ }
+
}
diff --git a/window/window/api/public_plus_experimental_current.txt b/window/window/api/public_plus_experimental_current.txt
index 5c30487..c6788d2 100644
--- a/window/window/api/public_plus_experimental_current.txt
+++ b/window/window/api/public_plus_experimental_current.txt
@@ -40,6 +40,12 @@
method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo> callback);
}
+ public interface WindowInfoRepo {
+ method public androidx.window.WindowMetrics currentWindowMetrics();
+ method public androidx.window.WindowMetrics maximumWindowMetrics();
+ method public kotlinx.coroutines.flow.Flow<androidx.window.WindowLayoutInfo> windowLayoutInfo();
+ }
+
public final class WindowLayoutInfo {
method public java.util.List<androidx.window.DisplayFeature> getDisplayFeatures();
property public final java.util.List<androidx.window.DisplayFeature> displayFeatures;
@@ -66,5 +72,9 @@
property public final android.graphics.Rect bounds;
}
+ public final class WindowServices {
+ method @kotlinx.coroutines.ExperimentalCoroutinesApi public static androidx.window.WindowInfoRepo windowInfoRepository(android.app.Activity);
+ }
+
}
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index 5c30487..c82eaa4 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -40,6 +40,12 @@
method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo> callback);
}
+ public interface WindowInfoRepo {
+ method public androidx.window.WindowMetrics currentWindowMetrics();
+ method public androidx.window.WindowMetrics maximumWindowMetrics();
+ method public kotlinx.coroutines.flow.Flow<androidx.window.WindowLayoutInfo> windowLayoutInfo();
+ }
+
public final class WindowLayoutInfo {
method public java.util.List<androidx.window.DisplayFeature> getDisplayFeatures();
property public final java.util.List<androidx.window.DisplayFeature> displayFeatures;
@@ -66,5 +72,8 @@
property public final android.graphics.Rect bounds;
}
+ public final class WindowServices {
+ }
+
}
diff --git a/window/window/build.gradle b/window/window/build.gradle
index 2fe4f79..cfb74ad 100644
--- a/window/window/build.gradle
+++ b/window/window/build.gradle
@@ -17,19 +17,9 @@
import androidx.build.LibraryGroups
import androidx.build.LibraryVersions
import androidx.build.Publish
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_CORE
-import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_EXT_JUNIT
-import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_RULES
-import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_RUNNER
-import static androidx.build.dependencies.DependenciesKt.DEXMAKER_MOCKITO
-import static androidx.build.dependencies.DependenciesKt.JUNIT
-import static androidx.build.dependencies.DependenciesKt.MOCKITO_CORE
-import static androidx.build.dependencies.DependenciesKt.MOCKITO_KOTLIN
-import static androidx.build.dependencies.DependenciesKt.ROBOLECTRIC
-import static androidx.build.dependencies.DependenciesKt.TRUTH
-import static androidx.build.dependencies.DependenciesKt.getKOTLIN_COROUTINES_ANDROID
-import static androidx.build.dependencies.DependenciesKt.getKOTLIN_STDLIB
+import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("AndroidXPlugin")
@@ -38,6 +28,9 @@
}
android {
+ defaultConfig {
+ multiDexEnabled = true
+ }
buildTypes.all {
consumerProguardFiles "proguard-rules.pro"
}
@@ -69,6 +62,7 @@
testImplementation(ROBOLECTRIC)
testImplementation(MOCKITO_CORE)
testImplementation(MOCKITO_KOTLIN)
+ testImplementation(KOTLIN_COROUTINES_TEST)
testImplementation(compileOnly(project(":window:window-extensions")))
testImplementation(compileOnly(project(":window:window-sidecar")))
@@ -78,6 +72,8 @@
androidTestImplementation(DEXMAKER_MOCKITO, excludes.bytebuddy)
androidTestImplementation(MOCKITO_CORE, excludes.bytebuddy)
androidTestImplementation(MOCKITO_KOTLIN, excludes.bytebuddy)
+ androidTestImplementation(KOTLIN_COROUTINES_TEST)
+ androidTestImplementation(MULTIDEX)
androidTestImplementation(TRUTH)
androidTestImplementation(compileOnly(project(":window:window-extensions")))
androidTestImplementation(compileOnly(project(":window:window-sidecar")))
@@ -95,3 +91,10 @@
// Sidecar interface.
failOnDeprecationWarnings = false
}
+
+// Allow usage of Kotlin's @OptIn.
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
+ }
+}
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.kt b/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.kt
index 1847ea0..6eb3985 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.kt
@@ -16,7 +16,6 @@
package androidx.window
import android.content.Context
-import android.content.Intent
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -48,14 +47,14 @@
@Test
override fun testWindowLayoutCallback() {
- val activity = activityTestRule.launchActivity(Intent())
- val windowToken = getActivityWindowToken(activity)
- assertNotNull(windowToken)
- val callbackInterface = mock<ExtensionCallbackInterface>()
- extensionCompat.setExtensionCallback(callbackInterface)
- extensionCompat.onWindowLayoutChangeListenerAdded(activity)
- verify(callbackInterface)
- .onWindowLayoutChanged(any(), any())
+ activityTestRule.scenario.onActivity { activity ->
+ val windowToken = getActivityWindowToken(activity)
+ assertNotNull(windowToken)
+ val callbackInterface = mock<ExtensionCallbackInterface>()
+ extensionCompat.setExtensionCallback(callbackInterface)
+ extensionCompat.onWindowLayoutChangeListenerAdded(activity)
+ verify(callbackInterface).onWindowLayoutChanged(any(), any())
+ }
}
private fun assumeExtensionV1_0() {
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionTest.kt b/window/window/src/androidTest/java/androidx/window/ExtensionTest.kt
index b9fd0fe..2a8267c 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionTest.kt
@@ -13,19 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:Suppress("DEPRECATION") // TODO(b/185594766) Migrate from ActivityTestRule
package androidx.window
import android.content.Context
-import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Rect
+import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.rule.ActivityTestRule
import androidx.window.ExtensionInterfaceCompat.ExtensionCallbackInterface
import androidx.window.TestActivity.Companion.resetResumeCounter
import androidx.window.TestActivity.Companion.waitForOnResume
@@ -51,9 +50,8 @@
private lateinit var context: Context
- private val configHandlingActivityTestRule = ActivityTestRule(
- TestConfigChangeHandlingActivity::class.java, false, true
- )
+ private val configHandlingActivityTestRule =
+ ActivityScenarioRule(TestConfigChangeHandlingActivity::class.java)
@Before
public fun setUp() {
@@ -83,22 +81,24 @@
val extension = ExtensionWindowBackend.initAndVerifyExtension(context)
val callbackInterface = mock<ExtensionCallbackInterface>()
extension!!.setExtensionCallback(callbackInterface)
- val activity = activityTestRule.launchActivity(Intent())
- extension.onWindowLayoutChangeListenerAdded(activity)
- assertTrue("Layout must happen after launch", activity.waitForLayout())
- verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(
- any(),
- argThat(WindowLayoutInfoValidator(activity))
- )
+ activityTestRule.scenario.onActivity { activity ->
+ extension.onWindowLayoutChangeListenerAdded(activity)
+ assertTrue("Layout must happen after launch", activity.waitForLayout())
+ verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(
+ any(),
+ argThat(WindowLayoutInfoValidator(activity))
+ )
+ }
}
@Test
public fun testRegisterWindowLayoutChangeListener() {
assumeExtensionV10_V01()
val extension = ExtensionWindowBackend.initAndVerifyExtension(context)
- val activity = activityTestRule.launchActivity(Intent())
- extension!!.onWindowLayoutChangeListenerAdded(activity)
- extension.onWindowLayoutChangeListenerRemoved(activity)
+ activityTestRule.scenario.onActivity { activity ->
+ extension!!.onWindowLayoutChangeListenerAdded(activity)
+ extension.onWindowLayoutChangeListenerRemoved(activity)
+ }
}
@Test
@@ -108,36 +108,37 @@
val callbackInterface = mock<ExtensionCallbackInterface>()
extension!!.setExtensionCallback(callbackInterface)
- val activity = configHandlingActivityTestRule.launchActivity(Intent())
- extension.onWindowLayoutChangeListenerAdded(activity)
- activity.resetLayoutCounter()
- activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- activity.waitForLayout()
- if (activity.resources.configuration.orientation
- != Configuration.ORIENTATION_PORTRAIT
- ) {
- // Orientation change did not occur on this device config. Skipping the test.
- return
- }
- activity.resetLayoutCounter()
- activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
- val layoutHappened = activity.waitForLayout()
- if (activity.resources.configuration.orientation
- != Configuration.ORIENTATION_LANDSCAPE
- ) {
- // Orientation change did not occur on this device config. Skipping the test.
- return
- }
- assertTrue("Layout must happen after orientation change", layoutHappened)
- if (!isSidecar) {
- verify(callbackInterface, atLeastOnce())
- .onWindowLayoutChanged(
- any(),
- argThat(DistinctWindowLayoutInfoMatcher())
- )
- } else {
- verify(callbackInterface, atLeastOnce())
- .onWindowLayoutChanged(any(), any())
+ val scenario = ActivityScenario.launch(TestConfigChangeHandlingActivity::class.java)
+ scenario.onActivity { activity ->
+ extension.onWindowLayoutChangeListenerAdded(activity)
+ activity.resetLayoutCounter()
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ activity.waitForLayout()
+ val activityOrientation = activity.resources.configuration.orientation
+ if (activityOrientation != Configuration.ORIENTATION_PORTRAIT) {
+ // Orientation change did not occur on this device config. Skipping the test.
+ return@onActivity
+ }
+ activity.resetLayoutCounter()
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ val layoutHappened = activity.waitForLayout()
+ if (activity.resources.configuration.orientation
+ != Configuration.ORIENTATION_LANDSCAPE
+ ) {
+ // Orientation change did not occur on this device config. Skipping the test.
+ return@onActivity
+ }
+ assertTrue("Layout must happen after orientation change", layoutHappened)
+ if (!isSidecar) {
+ verify(callbackInterface, atLeastOnce())
+ .onWindowLayoutChanged(
+ any(),
+ argThat(DistinctWindowLayoutInfoMatcher())
+ )
+ } else {
+ verify(callbackInterface, atLeastOnce())
+ .onWindowLayoutChanged(any(), any())
+ }
}
}
@@ -147,38 +148,37 @@
val extension = ExtensionWindowBackend.initAndVerifyExtension(context)
val callbackInterface = mock<ExtensionCallbackInterface>()
extension!!.setExtensionCallback(callbackInterface)
- var activity = activityTestRule.launchActivity(Intent())
- extension.onWindowLayoutChangeListenerAdded(activity)
- activity.resetLayoutCounter()
- activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- activity = activityTestRule.activity
- activity.waitForLayout()
- if (activity.resources.configuration.orientation
- != Configuration.ORIENTATION_PORTRAIT
- ) {
- // Orientation change did not occur on this device config. Skipping the test.
- return
- }
- resetResumeCounter()
- activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
- waitForOnResume()
- activity = activityTestRule.activity
- activity.waitForLayout()
- if (activity.resources.configuration.orientation
- != Configuration.ORIENTATION_LANDSCAPE
- ) {
- // Orientation change did not occur on this device config. Skipping the test.
- return
- }
- if (!isSidecar) {
- verify(callbackInterface, atLeastOnce())
- .onWindowLayoutChanged(
- any(),
- argThat(DistinctWindowLayoutInfoMatcher())
- )
- } else {
- verify(callbackInterface, atLeastOnce())
- .onWindowLayoutChanged(any(), any())
+ activityTestRule.scenario.onActivity { activity ->
+ extension.onWindowLayoutChangeListenerAdded(activity)
+ activity.resetLayoutCounter()
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ activity.waitForLayout()
+ if (activity.resources.configuration.orientation
+ != Configuration.ORIENTATION_PORTRAIT
+ ) {
+ // Orientation change did not occur on this device config. Skipping the test.
+ return@onActivity
+ }
+ resetResumeCounter()
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ waitForOnResume()
+ activity.waitForLayout()
+ if (activity.resources.configuration.orientation
+ != Configuration.ORIENTATION_LANDSCAPE
+ ) {
+ // Orientation change did not occur on this device config. Skipping the test.
+ return@onActivity
+ }
+ if (!isSidecar) {
+ verify(callbackInterface, atLeastOnce())
+ .onWindowLayoutChanged(
+ any(),
+ argThat(DistinctWindowLayoutInfoMatcher())
+ )
+ } else {
+ verify(callbackInterface, atLeastOnce())
+ .onWindowLayoutChanged(any(), any())
+ }
}
}
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.kt b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.kt
index 0154f5c..51d5e6b 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.kt
@@ -17,7 +17,6 @@
import android.app.Activity
import android.content.Context
-import android.content.Intent
import android.graphics.Rect
import androidx.core.util.Consumer
import androidx.test.core.app.ApplicationProvider
@@ -82,84 +81,91 @@
@Test
public fun testRegisterLayoutChangeCallback() {
- val backend = ExtensionWindowBackend.getInstance(context)
- backend.windowExtension = mock<ExtensionInterfaceCompat>()
+ activityTestRule.scenario.onActivity { activity ->
+ val backend = ExtensionWindowBackend.getInstance(context)
+ backend.windowExtension = mock()
+ // Check registering the layout change callback
+ val consumer = mock<Consumer<WindowLayoutInfo>>()
- // Check registering the layout change callback
- val consumer = mock<Consumer<WindowLayoutInfo>>()
- val activity = activityTestRule.launchActivity(Intent())
- backend.registerLayoutChangeCallback(activity, { obj: Runnable -> obj.run() }, consumer)
- assertEquals(1, backend.windowLayoutChangeCallbacks.size.toLong())
- verify(backend.windowExtension!!).onWindowLayoutChangeListenerAdded(activity)
+ backend.registerLayoutChangeCallback(activity, { obj: Runnable -> obj.run() }, consumer)
+ assertEquals(1, backend.windowLayoutChangeCallbacks.size.toLong())
+ verify(backend.windowExtension!!).onWindowLayoutChangeListenerAdded(activity)
- // Check unregistering the layout change callback
- backend.unregisterLayoutChangeCallback(consumer)
- assertTrue(backend.windowLayoutChangeCallbacks.isEmpty())
- verify(backend.windowExtension!!).onWindowLayoutChangeListenerRemoved(
- eq(activity)
- )
+ // Check unregistering the layout change callback
+ backend.unregisterLayoutChangeCallback(consumer)
+ assertTrue(backend.windowLayoutChangeCallbacks.isEmpty())
+ verify(backend.windowExtension!!).onWindowLayoutChangeListenerRemoved(
+ eq(activity)
+ )
+ }
}
@Test
public fun testRegisterLayoutChangeCallback_callsExtensionOnce() {
- val backend = ExtensionWindowBackend.getInstance(context)
- backend.windowExtension = mock()
+ activityTestRule.scenario.onActivity { activity ->
+ val backend = ExtensionWindowBackend.getInstance(context)
+ backend.windowExtension = mock()
- // Check registering the layout change callback
- val consumer = mock<Consumer<WindowLayoutInfo>>()
- val activity = activityTestRule.launchActivity(Intent())
- backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
- backend.registerLayoutChangeCallback(activity, Runnable::run, mock())
- assertEquals(2, backend.windowLayoutChangeCallbacks.size.toLong())
- verify(backend.windowExtension!!).onWindowLayoutChangeListenerAdded(activity)
+ // Check registering the layout change callback
+ val consumer = mock<Consumer<WindowLayoutInfo>>()
- // Check unregistering the layout change callback
- backend.unregisterLayoutChangeCallback(consumer)
- assertEquals(1, backend.windowLayoutChangeCallbacks.size.toLong())
- verify(backend.windowExtension!!, times(0))
- .onWindowLayoutChangeListenerRemoved(eq(activity))
+ backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+ backend.registerLayoutChangeCallback(activity, Runnable::run, mock())
+ assertEquals(2, backend.windowLayoutChangeCallbacks.size.toLong())
+ verify(backend.windowExtension!!).onWindowLayoutChangeListenerAdded(activity)
+
+ // Check unregistering the layout change callback
+ backend.unregisterLayoutChangeCallback(consumer)
+ assertEquals(1, backend.windowLayoutChangeCallbacks.size.toLong())
+ verify(backend.windowExtension!!, times(0))
+ .onWindowLayoutChangeListenerRemoved(eq(activity))
+ }
}
@Test
public fun testRegisterLayoutChangeCallback_clearListeners() {
- val backend = ExtensionWindowBackend.getInstance(context)
- backend.windowExtension = mock()
+ activityTestRule.scenario.onActivity { activity ->
+ val backend = ExtensionWindowBackend.getInstance(context)
+ backend.windowExtension = mock()
- // Check registering the layout change callback
- val firstConsumer = mock<Consumer<WindowLayoutInfo>>()
- val secondConsumer = mock<Consumer<WindowLayoutInfo>>()
- val activity = activityTestRule.launchActivity(Intent())
- backend.registerLayoutChangeCallback(
- activity,
- { obj: Runnable -> obj.run() },
- firstConsumer
- )
- backend.registerLayoutChangeCallback(
- activity,
- { obj: Runnable -> obj.run() },
- secondConsumer
- )
+ // Check registering the layout change callback
+ val firstConsumer = mock<Consumer<WindowLayoutInfo>>()
+ val secondConsumer = mock<Consumer<WindowLayoutInfo>>()
- // Check unregistering the layout change callback
- backend.unregisterLayoutChangeCallback(firstConsumer)
- backend.unregisterLayoutChangeCallback(secondConsumer)
- assertTrue(backend.windowLayoutChangeCallbacks.isEmpty())
- verify(backend.windowExtension!!).onWindowLayoutChangeListenerRemoved(activity)
+ backend.registerLayoutChangeCallback(
+ activity,
+ { obj: Runnable -> obj.run() },
+ firstConsumer
+ )
+ backend.registerLayoutChangeCallback(
+ activity,
+ { obj: Runnable -> obj.run() },
+ secondConsumer
+ )
+
+ // Check unregistering the layout change callback
+ backend.unregisterLayoutChangeCallback(firstConsumer)
+ backend.unregisterLayoutChangeCallback(secondConsumer)
+ assertTrue(backend.windowLayoutChangeCallbacks.isEmpty())
+ verify(backend.windowExtension!!).onWindowLayoutChangeListenerRemoved(activity)
+ }
}
@Test
public fun testLayoutChangeCallback_emitNewValue() {
- val backend = ExtensionWindowBackend.getInstance(context)
- backend.windowExtension = mock<ExtensionInterfaceCompat>()
+ activityTestRule.scenario.onActivity { activity ->
+ val backend = ExtensionWindowBackend.getInstance(context)
+ backend.windowExtension = mock()
- // Check that callbacks from the extension are propagated correctly
- val consumer = mock<Consumer<WindowLayoutInfo>>()
- val activity = activityTestRule.launchActivity(Intent())
- backend.registerLayoutChangeCallback(activity, { obj: Runnable -> obj.run() }, consumer)
- val windowLayoutInfo = newTestWindowLayoutInfo()
- val backendListener = backend.ExtensionListenerImpl()
- backendListener.onWindowLayoutChanged(activity, windowLayoutInfo)
- verify(consumer).accept(eq(windowLayoutInfo))
+ // Check that callbacks from the extension are propagated correctly
+ val consumer = mock<Consumer<WindowLayoutInfo>>()
+
+ backend.registerLayoutChangeCallback(activity, { obj: Runnable -> obj.run() }, consumer)
+ val windowLayoutInfo = newTestWindowLayoutInfo()
+ val backendListener = backend.ExtensionListenerImpl()
+ backendListener.onWindowLayoutChanged(activity, windowLayoutInfo)
+ verify(consumer).accept(eq(windowLayoutInfo))
+ }
}
@Test
@@ -185,14 +191,6 @@
override fun accept(t: T) {
values.add(t)
}
-
- fun allValues(): List<T> {
- return values
- }
-
- fun lastValue(): T {
- return values.last()
- }
}
internal companion object {
diff --git a/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.kt b/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.kt
index 2f5e3fc..a3a0a0e 100644
--- a/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.kt
@@ -19,7 +19,6 @@
package androidx.window
import android.content.Context
-import android.content.Intent
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -46,27 +45,27 @@
public class SidecarCompatDeviceTest : WindowTestBase(), CompatDeviceTestInterface {
private lateinit var sidecarCompat: SidecarCompat
- private lateinit var testActivity: TestActivity
@Before
public fun setUp() {
assumeExtensionV01()
sidecarCompat = SidecarCompat(ApplicationProvider.getApplicationContext() as Context)
- testActivity = activityTestRule.launchActivity(Intent())
}
@Test
override fun testWindowLayoutCallback() {
- val windowToken = getActivityWindowToken(testActivity)
- assertNotNull(windowToken)
- val callbackInterface = mock<ExtensionCallbackInterface>()
- sidecarCompat.setExtensionCallback(callbackInterface)
- sidecarCompat.onWindowLayoutChangeListenerAdded(testActivity)
- val sidecarWindowLayoutInfo = sidecarCompat.sidecar!!.getWindowLayoutInfo(windowToken)
- verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(
- any(),
- argThat(SidecarMatcher(sidecarWindowLayoutInfo))
- )
+ activityTestRule.scenario.onActivity { testActivity ->
+ val windowToken = getActivityWindowToken(testActivity)
+ assertNotNull(windowToken)
+ val callbackInterface = mock<ExtensionCallbackInterface>()
+ sidecarCompat.setExtensionCallback(callbackInterface)
+ sidecarCompat.onWindowLayoutChangeListenerAdded(testActivity)
+ val sidecarWindowLayoutInfo = sidecarCompat.sidecar!!.getWindowLayoutInfo(windowToken)
+ verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(
+ any(),
+ argThat(SidecarMatcher(sidecarWindowLayoutInfo))
+ )
+ }
}
private fun assumeExtensionV01() {
diff --git a/window/window/src/androidTest/java/androidx/window/WindowBackendTest.kt b/window/window/src/androidTest/java/androidx/window/WindowBackendTest.kt
index adc63a5..fce69cd 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowBackendTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/WindowBackendTest.kt
@@ -16,7 +16,6 @@
package androidx.window
import android.app.Activity
-import android.content.Intent
import android.graphics.Rect
import androidx.core.util.Consumer
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -41,13 +40,14 @@
public fun testFakeWindowBackend() {
val windowLayoutInfo = newTestWindowLayout()
val windowBackend: WindowBackend = FakeWindowBackend(windowLayoutInfo)
- val activity = activityTestRule.launchActivity(Intent())
- val wm = WindowManager(activity, windowBackend)
- val layoutInfoConsumer: Consumer<WindowLayoutInfo> = mock(
- WindowLayoutInfoConsumer::class.java
- )
- wm.registerLayoutChangeCallback(MoreExecutors.directExecutor(), layoutInfoConsumer)
- verify(layoutInfoConsumer).accept(windowLayoutInfo)
+ activityTestRule.scenario.onActivity { activity ->
+ val wm = WindowManager(activity, windowBackend)
+ val layoutInfoConsumer: Consumer<WindowLayoutInfo> = mock(
+ WindowLayoutInfoConsumer::class.java
+ )
+ wm.registerLayoutChangeCallback(MoreExecutors.directExecutor(), layoutInfoConsumer)
+ verify(layoutInfoConsumer).accept(windowLayoutInfo)
+ }
}
private fun newTestWindowLayout(): WindowLayoutInfo {
diff --git a/window/window/src/androidTest/java/androidx/window/WindowInfoRepoImpTest.kt b/window/window/src/androidTest/java/androidx/window/WindowInfoRepoImpTest.kt
new file mode 100644
index 0000000..724e6e3
--- /dev/null
+++ b/window/window/src/androidTest/java/androidx/window/WindowInfoRepoImpTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.window
+
+import android.app.Activity
+import androidx.core.util.Consumer
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import java.util.concurrent.Executor
+
+@OptIn(ExperimentalCoroutinesApi::class)
+public class WindowInfoRepoImpTest {
+
+ @get:Rule
+ public val activityScenario: ActivityScenarioRule<TestActivity> =
+ ActivityScenarioRule(TestActivity::class.java)
+
+ private val testScope = TestCoroutineScope()
+
+ @Test
+ public fun testGetCurrentWindowMetrics() {
+ activityScenario.scenario.onActivity { testActivity ->
+ val windowBoundsHelper = WindowBoundsHelper.instance
+ val repo = WindowInfoRepoImp(
+ testActivity,
+ windowBoundsHelper,
+ FakeWindowBackend()
+ )
+ val expectedBounds = windowBoundsHelper.computeCurrentWindowBounds(testActivity)
+ val expected = WindowMetrics(expectedBounds)
+ val actual = repo.currentWindowMetrics()
+ assertEquals(expected, actual)
+ }
+ }
+
+ @Test
+ public fun testGetMaximumWindowMetrics() {
+ activityScenario.scenario.onActivity { testActivity ->
+ val windowBoundsHelper = WindowBoundsHelper.instance
+ val repo = WindowInfoRepoImp(
+ testActivity,
+ windowBoundsHelper,
+ FakeWindowBackend()
+ )
+ val expectedBounds = windowBoundsHelper.computeMaximumWindowBounds(testActivity)
+ val expected = WindowMetrics(expectedBounds)
+ val actual = repo.maximumWindowMetrics()
+ assertEquals(expected, actual)
+ }
+ }
+
+ @Test
+ public fun testWindowLayoutFeatures(): Unit = testScope.runBlockingTest {
+ activityScenario.scenario.onActivity { testActivity ->
+ val windowBoundsHelper = WindowBoundsHelper.instance
+ val fakeBackend = FakeWindowBackend()
+ val repo = WindowInfoRepoImp(
+ testActivity,
+ windowBoundsHelper,
+ fakeBackend
+ )
+ val collector = TestConsumer<WindowLayoutInfo>()
+ testScope.launch {
+ repo.windowLayoutInfo().collect(collector::accept)
+ }
+ fakeBackend.triggerSignal(WindowLayoutInfo(emptyList()))
+ collector.assertValue(WindowLayoutInfo(emptyList()))
+ }
+ }
+
+ private class FakeWindowBackend : WindowBackend {
+
+ private class CallbackHolder(
+ val executor: Executor,
+ val callback: Consumer<WindowLayoutInfo>
+ ) : Consumer<WindowLayoutInfo> {
+
+ override fun accept(t: WindowLayoutInfo?) {
+ executor.execute { callback.accept(t) }
+ }
+ }
+
+ private val consumers = mutableListOf<CallbackHolder>()
+
+ fun triggerSignal(info: WindowLayoutInfo) {
+ consumers.forEach { consumer -> consumer.accept(info) }
+ }
+
+ override fun registerLayoutChangeCallback(
+ activity: Activity,
+ executor: Executor,
+ callback: Consumer<WindowLayoutInfo>
+ ) {
+ consumers.add(CallbackHolder(executor, callback))
+ }
+
+ override fun unregisterLayoutChangeCallback(callback: Consumer<WindowLayoutInfo>) {
+ consumers.removeIf { it.callback == callback }
+ }
+ }
+}
\ No newline at end of file
diff --git a/window/window/src/androidTest/java/androidx/window/WindowTestBase.kt b/window/window/src/androidTest/java/androidx/window/WindowTestBase.kt
index 1f5ef28..ac49fa1 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowTestBase.kt
+++ b/window/window/src/androidTest/java/androidx/window/WindowTestBase.kt
@@ -13,21 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:Suppress("DEPRECATION") // TODO(b/185594766) Need to migrate to ActivityScenario
package androidx.window
import android.app.Activity
import android.os.IBinder
-import androidx.test.rule.ActivityTestRule
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import org.junit.Rule
/**
* Base class for all tests in the module.
*/
public open class WindowTestBase {
- @JvmField
- public val activityTestRule: ActivityTestRule<TestActivity> =
- ActivityTestRule(TestActivity::class.java, false, true)
+ @get:Rule
+ public val activityTestRule: ActivityScenarioRule<TestActivity> =
+ ActivityScenarioRule(TestActivity::class.java)
public companion object {
@JvmStatic
diff --git a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt
index 24abe4e..477c79d 100644
--- a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt
+++ b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt
@@ -141,7 +141,7 @@
}
/**
- * Wrapper around [<] that also includes the [Executor]
+ * Wrapper around [Consumer<WindowLayoutInfo>] that also includes the [Executor]
* on which the callback should run and the [Activity].
*/
internal class WindowLayoutChangeCallbackWrapper(
diff --git a/window/window/src/main/java/androidx/window/WindowInfoRepo.kt b/window/window/src/main/java/androidx/window/WindowInfoRepo.kt
new file mode 100644
index 0000000..32ea8e3
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/WindowInfoRepo.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.window
+
+import android.content.Context
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * An interface to provide all the relevant info about a [android.view.Window].
+ */
+public interface WindowInfoRepo {
+
+ /**
+ * Returns the [WindowMetrics] according to the current system state.
+ *
+ *
+ * The metrics describe the size of the area the window would occupy with
+ * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+ * and any combination of flags that would allow the window to extend behind display cutouts.
+ *
+ *
+ * The value of this is based on the **current** windowing state of the system. For
+ * example, for activities in multi-window mode, the metrics returned are based on the
+ * current bounds that the user has selected for the [Activity][android.app.Activity]'s
+ * window.
+ *
+ * @see maximumWindowMetrics
+ * @see android.view.WindowManager.getCurrentWindowMetrics
+ */
+ public fun currentWindowMetrics(): WindowMetrics
+
+ /**
+ * Returns the largest [WindowMetrics] an app may expect in the current system state.
+ *
+ *
+ * The metrics describe the size of the largest potential area the window might occupy with
+ * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+ * and any combination of flags that would allow the window to extend behind display cutouts.
+ *
+ *
+ * The value of this is based on the largest **potential** windowing state of the system.
+ * For example, for activities in multi-window mode the metrics returned are based on what the
+ * bounds would be if the user expanded the window to cover the entire screen.
+ *
+ *
+ * Note that this might still be smaller than the size of the physical display if certain
+ * areas of the display are not available to windows created for the associated [Context].
+ * For example, devices with foldable displays that wrap around the enclosure may split the
+ * physical display into different regions, one for the front and one for the back, each acting
+ * as different logical displays. In this case [.getMaximumWindowMetrics] would return
+ * the region describing the side of the device the associated [context's][Context]
+ * window is placed.
+ *
+ * @see currentWindowMetrics
+ * @see android.view.WindowManager.getMaximumWindowMetrics
+ */
+ public fun maximumWindowMetrics(): WindowMetrics
+
+ /**
+ * A [Flow] of [WindowLayoutInfo] that contains all the available features.
+ */
+ public fun windowLayoutInfo(): Flow<WindowLayoutInfo>
+}
diff --git a/window/window/src/main/java/androidx/window/WindowInfoRepoImp.kt b/window/window/src/main/java/androidx/window/WindowInfoRepoImp.kt
new file mode 100644
index 0000000..817459f
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/WindowInfoRepoImp.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.window
+
+import android.app.Activity
+import android.content.Context
+import androidx.core.util.Consumer
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+/**
+ * An implementation of [WindowInfoRepo] that provides the [WindowLayoutInfo] and
+ * [WindowMetrics] for the given [Activity].
+ *
+ * @param activity that the provided window is based on.
+ * @param windowBoundsHelper a helper to calculate the [WindowMetrics] for the [Activity].
+ * @param windowBackend a helper to provide the [WindowLayoutInfo].
+ */
+@ExperimentalCoroutinesApi
+internal class WindowInfoRepoImp(
+ private val activity: Activity,
+ private val windowBoundsHelper: WindowBoundsHelper,
+ private val windowBackend: WindowBackend
+) : WindowInfoRepo {
+
+ /**
+ * Returns the [WindowMetrics] according to the current system state.
+ *
+ *
+ * The metrics describe the size of the area the window would occupy with
+ * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+ * and any combination of flags that would allow the window to extend behind display cutouts.
+ *
+ *
+ * The value of this is based on the **current** windowing state of the system. For
+ * example, for activities in multi-window mode, the metrics returned are based on the
+ * current bounds that the user has selected for the [Activity][android.app.Activity]'s
+ * window.
+ *
+ * @see maximumWindowMetrics
+ * @see android.view.WindowManager.getCurrentWindowMetrics
+ */
+ override fun currentWindowMetrics(): WindowMetrics {
+ return WindowMetrics(windowBoundsHelper.computeCurrentWindowBounds(activity))
+ }
+
+ /**
+ * Returns the largest [WindowMetrics] an app may expect in the current system state.
+ *
+ *
+ * The metrics describe the size of the largest potential area the window might occupy with
+ * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+ * and any combination of flags that would allow the window to extend behind display cutouts.
+ *
+ *
+ * The value of this is based on the largest **potential** windowing state of the system.
+ * For example, for activities in multi-window mode the metrics returned are based on what the
+ * bounds would be if the user expanded the window to cover the entire screen.
+ *
+ *
+ * Note that this might still be smaller than the size of the physical display if certain
+ * areas of the display are not available to windows created for the associated [Context].
+ * For example, devices with foldable displays that wrap around the enclosure may split the
+ * physical display into different regions, one for the front and one for the back, each acting
+ * as different logical displays. In this case [.getMaximumWindowMetrics] would return
+ * the region describing the side of the device the associated [context's][Context]
+ * window is placed.
+ *
+ * @see currentWindowMetrics
+ * @see android.view.WindowManager.getMaximumWindowMetrics
+ */
+ override fun maximumWindowMetrics(): WindowMetrics {
+ return WindowMetrics(windowBoundsHelper.computeMaximumWindowBounds(activity))
+ }
+
+ /**
+ * A [Flow] of window layout changes in the current visual [Context].
+ *
+ * @see Activity.onAttachedToWindow
+ */
+ override fun windowLayoutInfo(): Flow<WindowLayoutInfo> = callbackFlow {
+ val callback = Consumer<WindowLayoutInfo> { info -> offer(info) }
+ windowBackend.registerLayoutChangeCallback(activity, Runnable::run, callback)
+ awaitClose { windowBackend.unregisterLayoutChangeCallback(callback) }
+ }.buffer(capacity = UNLIMITED)
+}
diff --git a/window/window/src/main/java/androidx/window/WindowManager.kt b/window/window/src/main/java/androidx/window/WindowManager.kt
index 3c29415..5b6deed 100644
--- a/window/window/src/main/java/androidx/window/WindowManager.kt
+++ b/window/window/src/main/java/androidx/window/WindowManager.kt
@@ -90,7 +90,7 @@
* current bounds that the user has selected for the [Activity][android.app.Activity]'s
* window.
*
- * @see .getMaximumWindowMetrics
+ * @see getMaximumWindowMetrics
* @see android.view.WindowManager.getCurrentWindowMetrics
*/
public fun getCurrentWindowMetrics(): WindowMetrics {
@@ -121,7 +121,7 @@
* the region describing the side of the device the associated [context's][Context]
* window is placed.
*
- * @see .getCurrentWindowMetrics
+ * @see getCurrentWindowMetrics
* @see android.view.WindowManager.getMaximumWindowMetrics
*/
public fun getMaximumWindowMetrics(): WindowMetrics {
diff --git a/window/window/src/main/java/androidx/window/WindowServices.kt b/window/window/src/main/java/androidx/window/WindowServices.kt
new file mode 100644
index 0000000..fd3881d
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/WindowServices.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+@file:JvmName("WindowServices")
+
+package androidx.window
+
+import android.app.Activity
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Provide an instance of [WindowInfoRepo] that is associated to the given [Activity]
+ */
+@ExperimentalCoroutinesApi
+public fun Activity.windowInfoRepository(): WindowInfoRepo {
+ return WindowInfoRepoImp(
+ this,
+ WindowBoundsHelper.instance,
+ ExtensionWindowBackend.getInstance(this)
+ )
+}
diff --git a/window/window/src/testUtil/java/androidx/window/TestConsumer.kt b/window/window/src/testUtil/java/androidx/window/TestConsumer.kt
new file mode 100644
index 0000000..6976014
--- /dev/null
+++ b/window/window/src/testUtil/java/androidx/window/TestConsumer.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.window
+
+import androidx.core.util.Consumer
+import org.junit.Assert.assertEquals
+
+/**
+ * A test [Consumer] to hold all the values and make assertions based on the values.
+ */
+public class TestConsumer<T> : Consumer<T> {
+
+ private val values: MutableList<T> = mutableListOf()
+
+ /**
+ * Add the value to the list of seen values.
+ */
+ override fun accept(t: T) {
+ values.add(t)
+ }
+
+ /**
+ * Assert that there have been a fixed number of values
+ */
+ public fun assertValueCount(count: Int) {
+ assertEquals(count, values.size)
+ }
+
+ /**
+ * Assert that there has been exactly one value.
+ */
+ public fun assertValue(t: T) {
+ assertValueCount(1)
+ assertEquals(t, values[0])
+ }
+}
diff --git a/work/workmanager-benchmark/lint-baseline.xml b/work/workmanager-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-inspection/lint-baseline.xml b/work/workmanager-inspection/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-inspection/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-ktx/lint-baseline.xml b/work/workmanager-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-lint/lint-baseline.xml b/work/workmanager-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/work/workmanager-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-multiprocess/lint-baseline.xml b/work/workmanager-multiprocess/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-multiprocess/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-rxjava2/lint-baseline.xml b/work/workmanager-rxjava2/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-rxjava2/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-rxjava3/lint-baseline.xml b/work/workmanager-rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>